refactor(supply-api): split runtime assembly helpers
This commit is contained in:
129
docs/plans/2026-04-15-supply-api-runtime-helper-split-plan.md
Normal file
129
docs/plans/2026-04-15-supply-api-runtime-helper-split-plan.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Supply API Runtime Helper Split Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 把 `supply-api/internal/app/runtime.go` 中偏大的 `buildRuntimeWithFactory` 拆成更小的装配 helper,降低单函数复杂度,同时保持现有行为不变。
|
||||
|
||||
**Architecture:** 新增存储装配、鉴权装配和 API 装配的内部 helper struct/func,由 `buildRuntimeWithFactory` 只负责串联。继续保留现有外部 API、日志口径和运行时语义,测试先锁 helper 级行为,再复用现有高层回归。
|
||||
|
||||
**Tech Stack:** Go, Go test
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 提取存储装配 helper
|
||||
|
||||
**Files:**
|
||||
- Modify: `supply-api/internal/app/runtime.go`
|
||||
- Modify: `supply-api/internal/app/runtime_test.go`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```go
|
||||
func TestBuildStoreBundle_UsesInMemoryStoresWithoutDatabase(t *testing.T) {
|
||||
bundle := buildStoreBundle(nil, testLogger{})
|
||||
if bundle.accountStore == nil {
|
||||
t.Fatal("expected account store")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd "supply-api" && go test ./internal/app -run 'TestBuildStoreBundle_(UsesInMemoryStoresWithoutDatabase|UsesDatabaseBackedStoresWithDatabase)' -v`
|
||||
Expected: FAIL,因为 helper 尚不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
```go
|
||||
type runtimeStoreBundle struct { ... }
|
||||
func buildStoreBundle(db *repository.DB, logger logging.Logger) runtimeStoreBundle { ... }
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cd "supply-api" && go test ./internal/app -run 'TestBuildStoreBundle_(UsesInMemoryStoresWithoutDatabase|UsesDatabaseBackedStoresWithDatabase)' -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add supply-api/internal/app/runtime.go supply-api/internal/app/runtime_test.go
|
||||
git commit -m "refactor(supply-api): extract runtime store bundle"
|
||||
```
|
||||
|
||||
### Task 2: 提取安全与 API 装配 helper
|
||||
|
||||
**Files:**
|
||||
- Modify: `supply-api/internal/app/runtime.go`
|
||||
- Modify: `supply-api/internal/app/runtime_test.go`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
```go
|
||||
func TestBuildSecurityBundle_UsesMemoryTokenBackendWithoutRepository(t *testing.T) {
|
||||
bundle := buildSecurityBundle("dev", cfg, testLogger{}, auditStore, nil, nil)
|
||||
if bundle.authMiddleware == nil {
|
||||
t.Fatal("expected auth middleware")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `cd "supply-api" && go test ./internal/app -run 'TestBuildSecurityBundle_UsesMemoryTokenBackendWithoutRepository' -v`
|
||||
Expected: FAIL,因为 helper 尚不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
```go
|
||||
type runtimeSecurityBundle struct { ... }
|
||||
type runtimeAPIBundle struct { ... }
|
||||
func buildSecurityBundle(...) runtimeSecurityBundle { ... }
|
||||
func buildAPIBundle(...) (runtimeAPIBundle, error) { ... }
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `cd "supply-api" && go test ./internal/app -run 'TestBuildSecurityBundle_UsesMemoryTokenBackendWithoutRepository' -v`
|
||||
Expected: PASS
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add supply-api/internal/app/runtime.go supply-api/internal/app/runtime_test.go
|
||||
git commit -m "refactor(supply-api): extract runtime security and api bundles"
|
||||
```
|
||||
|
||||
### Task 3: 验证与收尾
|
||||
|
||||
**Files:**
|
||||
- Verify: `supply-api/internal/app/runtime.go`
|
||||
- Verify: `supply-api/internal/app/bootstrap.go`
|
||||
- Verify: `supply-api/cmd/supply-api/main.go`
|
||||
|
||||
**Step 1: Run focused tests**
|
||||
|
||||
Run: `cd "supply-api" && go test ./internal/app ./cmd/supply-api ./internal/httpapi`
|
||||
Expected: PASS
|
||||
|
||||
**Step 2: Run e2e build-tag tests**
|
||||
|
||||
Run: `cd "supply-api" && go test -tags=e2e ./e2e`
|
||||
Expected: PASS
|
||||
|
||||
**Step 3: Run repo exit verification**
|
||||
|
||||
Run: `bash "scripts/ci/repo_integrity_check.sh"`
|
||||
Expected: PASS
|
||||
|
||||
**Step 4: Check formatting**
|
||||
|
||||
Run: `git diff --check`
|
||||
Expected: no output
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/plans/2026-04-15-supply-api-runtime-helper-split-plan.md supply-api/internal/app/runtime.go supply-api/internal/app/runtime_test.go
|
||||
git commit -m "refactor(supply-api): split runtime assembly helpers"
|
||||
```
|
||||
@@ -64,6 +64,29 @@ type runtimeFactory struct {
|
||||
newRedisCache func(cfg config.RedisConfig) (*cache.RedisCache, error)
|
||||
}
|
||||
|
||||
type runtimeStoreBundle struct {
|
||||
accountStore domain.AccountStore
|
||||
packageStore domain.PackageStore
|
||||
settlementStore domain.SettlementStore
|
||||
earningStore domain.EarningStore
|
||||
auditStore audit.AuditStore
|
||||
alertService *auditservice.AlertService
|
||||
fkValidator *repository.ForeignKeyValidator
|
||||
tokenStatusRepo *repository.TokenStatusRepository
|
||||
idempotencyRepo *repository.IdempotencyRepository
|
||||
}
|
||||
|
||||
type runtimeSecurityBundle struct {
|
||||
authMiddleware *middleware.AuthMiddleware
|
||||
revocationSubscriber revocationSubscriber
|
||||
}
|
||||
|
||||
type runtimeAPIBundle struct {
|
||||
supplyAPI *httpapi.SupplyAPI
|
||||
alertAPI *httpapi.AlertAPI
|
||||
rateLimitConfig *middleware.RateLimitConfig
|
||||
}
|
||||
|
||||
// BuildRuntime 构建 supply-api 运行时依赖。
|
||||
func BuildRuntime(opts RuntimeOptions) (*Runtime, error) {
|
||||
return buildRuntimeWithFactory(opts, runtimeFactory{
|
||||
@@ -126,129 +149,11 @@ func buildRuntimeWithFactory(opts RuntimeOptions, factory runtimeFactory) (*Runt
|
||||
infof(opts.Logger, "connected to redis at %s:%d", opts.Config.Redis.Host, opts.Config.Redis.Port)
|
||||
}
|
||||
|
||||
var accountStore domain.AccountStore
|
||||
var packageStore domain.PackageStore
|
||||
var settlementStore domain.SettlementStore
|
||||
var earningStore domain.EarningStore
|
||||
var auditRepository *auditrepo.PostgresAuditRepository
|
||||
var tokenStatusRepo *repository.TokenStatusRepository
|
||||
var idempotencyRepo *repository.IdempotencyRepository
|
||||
|
||||
if db != nil {
|
||||
accountRepo := repository.NewAccountRepository(db.Pool)
|
||||
packageRepo := repository.NewPackageRepository(db.Pool)
|
||||
settlementRepo := repository.NewSettlementRepository(db.Pool)
|
||||
usageRepo := repository.NewUsageRepository(db.Pool)
|
||||
idempotencyRepo = repository.NewIdempotencyRepository(db.Pool)
|
||||
auditRepository = auditrepo.NewPostgresAuditRepository(db.Pool)
|
||||
tokenStatusRepo = repository.NewTokenStatusRepository(db.Pool)
|
||||
|
||||
accountStore = adapter.NewDBAccountStore(accountRepo)
|
||||
packageStore = adapter.NewDBPackageStore(packageRepo)
|
||||
settlementStore = adapter.NewDBSettlementStore(settlementRepo, accountRepo, db.Pool)
|
||||
earningStore = adapter.NewDBEarningStore(usageRepo)
|
||||
} else {
|
||||
accountStore = adapter.NewInMemoryAccountStoreAdapter()
|
||||
packageStore = adapter.NewInMemoryPackageStoreAdapter()
|
||||
settlementStore = adapter.NewInMemorySettlementStoreAdapter()
|
||||
earningStore = adapter.NewInMemoryEarningStoreAdapter()
|
||||
}
|
||||
|
||||
var auditStore audit.AuditStore
|
||||
if auditRepository != nil {
|
||||
auditStore = audit.NewPostgresAuditStore(auditRepository)
|
||||
opts.Logger.Info("审计存储: 使用PostgreSQL (DB-backed)", nil)
|
||||
} else {
|
||||
auditStore = audit.NewMemoryAuditStore()
|
||||
opts.Logger.Warn("审计存储使用内存实现 (生产环境不应使用)", nil)
|
||||
}
|
||||
|
||||
var alertStore auditservice.AlertStoreInterface
|
||||
if db != nil {
|
||||
alertStore = auditrepo.NewPostgresAlertRepository(db.Pool)
|
||||
opts.Logger.Info("告警存储: 使用PostgreSQL (DB-backed)", nil)
|
||||
} else {
|
||||
alertStore = auditservice.NewInMemoryAlertStore()
|
||||
opts.Logger.Warn("告警存储使用内存实现 (仅开发环境允许)", nil)
|
||||
}
|
||||
alertService := auditservice.NewAlertService(alertStore)
|
||||
|
||||
var fkValidator *repository.ForeignKeyValidator
|
||||
if db != nil {
|
||||
fkValidator = repository.NewForeignKeyValidator(db.Pool)
|
||||
opts.Logger.Info("外键校验器: 已初始化 (PostgreSQL-backed)", nil)
|
||||
} else {
|
||||
opts.Logger.Warn("外键校验器未启用 (db不可用)", nil)
|
||||
}
|
||||
|
||||
_ = domain.NewInvariantChecker(accountStore, packageStore, settlementStore)
|
||||
|
||||
accountService := domain.NewAccountService(accountStore, auditStore)
|
||||
packageService := domain.NewPackageService(packageStore, accountStore, auditStore)
|
||||
settlementService := domain.NewSettlementService(settlementStore, earningStore, auditStore)
|
||||
earningService := domain.NewEarningService(earningStore)
|
||||
|
||||
tokenCache := middleware.NewTokenCache()
|
||||
var tokenBackend middleware.TokenStatusBackend
|
||||
var revocationSubscriber revocationSubscriber
|
||||
if tokenStatusRepo != nil {
|
||||
dbTokenBackend := middleware.NewDBTokenStatusBackend(tokenStatusRepo, redisCache, opts.Config.Token.RevocationCacheTTL)
|
||||
tokenBackend = dbTokenBackend
|
||||
revocationSubscriber = dbTokenBackend
|
||||
opts.Logger.Info("Token状态后端: 使用PostgreSQL (DB-backed)", nil)
|
||||
} else {
|
||||
tokenBackend = adapter.NewMemoryTokenBackend()
|
||||
opts.Logger.Warn("Token状态后端使用内存实现 (生产环境不应使用)", nil)
|
||||
}
|
||||
|
||||
auditEmitter := adapter.NewAuditEmitterAdapter(auditStore)
|
||||
authMiddleware := middleware.NewAuthMiddleware(middleware.AuthConfig{
|
||||
SecretKey: opts.Config.Token.SecretKey,
|
||||
PublicKey: opts.Config.Token.PublicKey,
|
||||
Algorithm: opts.Config.Token.Algorithm,
|
||||
Issuer: opts.Config.Token.Issuer,
|
||||
CacheTTL: opts.Config.Token.RevocationCacheTTL,
|
||||
Enabled: env != "dev",
|
||||
}, tokenCache, tokenBackend, auditEmitter)
|
||||
|
||||
var idempotencyMiddleware *middleware.IdempotencyMiddleware
|
||||
if db != nil && idempotencyRepo != nil {
|
||||
idempotencyMiddleware = middleware.NewIdempotencyMiddleware(idempotencyRepo, middleware.IdempotencyConfig{
|
||||
TTL: tuning.idempotencyTTL,
|
||||
Enabled: env != "dev",
|
||||
})
|
||||
opts.Logger.Info("幂等中间件已启用(DB-backed)", nil)
|
||||
} else {
|
||||
if isProd {
|
||||
return nil, errors.New("idempotency repository unavailable")
|
||||
}
|
||||
opts.Logger.Warn("幂等中间件未启用(db或repo不可用)- 需要幂等的写接口将返回 503", nil)
|
||||
}
|
||||
|
||||
rateLimitConfig := middleware.DefaultRateLimitConfig()
|
||||
rateLimitConfig.Enabled = env != "dev"
|
||||
opts.Logger.Info("限流中间件已初始化", nil)
|
||||
|
||||
supplyAPI, err := httpapi.NewSupplyAPI(
|
||||
accountService,
|
||||
packageService,
|
||||
settlementService,
|
||||
earningService,
|
||||
idempotencyMiddleware,
|
||||
auditStore,
|
||||
fkValidator,
|
||||
opts.Config.Server.DefaultSupplierID,
|
||||
opts.Config.Server.StatementBaseURL,
|
||||
now,
|
||||
)
|
||||
storeBundle := buildStoreBundle(db, opts.Logger)
|
||||
securityBundle := buildSecurityBundle(env, opts.Config, opts.Logger, storeBundle.auditStore, redisCache, storeBundle.tokenStatusRepo)
|
||||
apiBundle, err := buildAPIBundle(env, opts.Config, now, tuning, opts.Logger, isProd, storeBundle)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize supply api: %w", err)
|
||||
}
|
||||
supplyAPI.SetWithdrawEnabled(opts.Config.Settlement.WithdrawEnabled)
|
||||
|
||||
alertAPI, err := httpapi.NewAlertAPI(alertService)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize alert api: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Runtime{
|
||||
@@ -259,11 +164,147 @@ func buildRuntimeWithFactory(opts RuntimeOptions, factory runtimeFactory) (*Runt
|
||||
serverConfig: normalizeServerConfig(opts.Config.Server),
|
||||
db: db,
|
||||
redisCache: redisCache,
|
||||
supplyAPI: supplyAPI,
|
||||
alertAPI: alertAPI,
|
||||
authMiddleware: authMiddleware,
|
||||
rateLimitConfig: rateLimitConfig,
|
||||
supplyAPI: apiBundle.supplyAPI,
|
||||
alertAPI: apiBundle.alertAPI,
|
||||
authMiddleware: securityBundle.authMiddleware,
|
||||
rateLimitConfig: apiBundle.rateLimitConfig,
|
||||
revocationSubscriber: securityBundle.revocationSubscriber,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildStoreBundle(db *repository.DB, logger logging.Logger) runtimeStoreBundle {
|
||||
var bundle runtimeStoreBundle
|
||||
|
||||
if db != nil {
|
||||
accountRepo := repository.NewAccountRepository(db.Pool)
|
||||
packageRepo := repository.NewPackageRepository(db.Pool)
|
||||
settlementRepo := repository.NewSettlementRepository(db.Pool)
|
||||
usageRepo := repository.NewUsageRepository(db.Pool)
|
||||
|
||||
bundle.idempotencyRepo = repository.NewIdempotencyRepository(db.Pool)
|
||||
bundle.tokenStatusRepo = repository.NewTokenStatusRepository(db.Pool)
|
||||
bundle.accountStore = adapter.NewDBAccountStore(accountRepo)
|
||||
bundle.packageStore = adapter.NewDBPackageStore(packageRepo)
|
||||
bundle.settlementStore = adapter.NewDBSettlementStore(settlementRepo, accountRepo, db.Pool)
|
||||
bundle.earningStore = adapter.NewDBEarningStore(usageRepo)
|
||||
bundle.auditStore = audit.NewPostgresAuditStore(auditrepo.NewPostgresAuditRepository(db.Pool))
|
||||
bundle.alertService = auditservice.NewAlertService(auditrepo.NewPostgresAlertRepository(db.Pool))
|
||||
bundle.fkValidator = repository.NewForeignKeyValidator(db.Pool)
|
||||
|
||||
logger.Info("审计存储: 使用PostgreSQL (DB-backed)", nil)
|
||||
logger.Info("告警存储: 使用PostgreSQL (DB-backed)", nil)
|
||||
logger.Info("外键校验器: 已初始化 (PostgreSQL-backed)", nil)
|
||||
return bundle
|
||||
}
|
||||
|
||||
bundle.accountStore = adapter.NewInMemoryAccountStoreAdapter()
|
||||
bundle.packageStore = adapter.NewInMemoryPackageStoreAdapter()
|
||||
bundle.settlementStore = adapter.NewInMemorySettlementStoreAdapter()
|
||||
bundle.earningStore = adapter.NewInMemoryEarningStoreAdapter()
|
||||
bundle.auditStore = audit.NewMemoryAuditStore()
|
||||
bundle.alertService = auditservice.NewAlertService(auditservice.NewInMemoryAlertStore())
|
||||
|
||||
logger.Warn("审计存储使用内存实现 (生产环境不应使用)", nil)
|
||||
logger.Warn("告警存储使用内存实现 (仅开发环境允许)", nil)
|
||||
logger.Warn("外键校验器未启用 (db不可用)", nil)
|
||||
return bundle
|
||||
}
|
||||
|
||||
func buildSecurityBundle(
|
||||
env string,
|
||||
cfg *config.Config,
|
||||
logger logging.Logger,
|
||||
auditStore audit.AuditStore,
|
||||
redisCache *cache.RedisCache,
|
||||
tokenStatusRepo *repository.TokenStatusRepository,
|
||||
) runtimeSecurityBundle {
|
||||
tokenCache := middleware.NewTokenCache()
|
||||
var tokenBackend middleware.TokenStatusBackend
|
||||
var revocationSubscriber revocationSubscriber
|
||||
|
||||
if tokenStatusRepo != nil {
|
||||
dbTokenBackend := middleware.NewDBTokenStatusBackend(tokenStatusRepo, redisCache, cfg.Token.RevocationCacheTTL)
|
||||
tokenBackend = dbTokenBackend
|
||||
revocationSubscriber = dbTokenBackend
|
||||
logger.Info("Token状态后端: 使用PostgreSQL (DB-backed)", nil)
|
||||
} else {
|
||||
tokenBackend = adapter.NewMemoryTokenBackend()
|
||||
logger.Warn("Token状态后端使用内存实现 (生产环境不应使用)", nil)
|
||||
}
|
||||
|
||||
return runtimeSecurityBundle{
|
||||
authMiddleware: middleware.NewAuthMiddleware(middleware.AuthConfig{
|
||||
SecretKey: cfg.Token.SecretKey,
|
||||
PublicKey: cfg.Token.PublicKey,
|
||||
Algorithm: cfg.Token.Algorithm,
|
||||
Issuer: cfg.Token.Issuer,
|
||||
CacheTTL: cfg.Token.RevocationCacheTTL,
|
||||
Enabled: env != "dev",
|
||||
}, tokenCache, tokenBackend, adapter.NewAuditEmitterAdapter(auditStore)),
|
||||
revocationSubscriber: revocationSubscriber,
|
||||
}
|
||||
}
|
||||
|
||||
func buildAPIBundle(
|
||||
env string,
|
||||
cfg *config.Config,
|
||||
now func() time.Time,
|
||||
tuning runtimeTuning,
|
||||
logger logging.Logger,
|
||||
isProd bool,
|
||||
storeBundle runtimeStoreBundle,
|
||||
) (runtimeAPIBundle, error) {
|
||||
_ = domain.NewInvariantChecker(storeBundle.accountStore, storeBundle.packageStore, storeBundle.settlementStore)
|
||||
|
||||
accountService := domain.NewAccountService(storeBundle.accountStore, storeBundle.auditStore)
|
||||
packageService := domain.NewPackageService(storeBundle.packageStore, storeBundle.accountStore, storeBundle.auditStore)
|
||||
settlementService := domain.NewSettlementService(storeBundle.settlementStore, storeBundle.earningStore, storeBundle.auditStore)
|
||||
earningService := domain.NewEarningService(storeBundle.earningStore)
|
||||
|
||||
var idempotencyMiddleware *middleware.IdempotencyMiddleware
|
||||
if storeBundle.idempotencyRepo != nil {
|
||||
idempotencyMiddleware = middleware.NewIdempotencyMiddleware(storeBundle.idempotencyRepo, middleware.IdempotencyConfig{
|
||||
TTL: tuning.idempotencyTTL,
|
||||
Enabled: env != "dev",
|
||||
})
|
||||
logger.Info("幂等中间件已启用(DB-backed)", nil)
|
||||
} else {
|
||||
if isProd {
|
||||
return runtimeAPIBundle{}, errors.New("idempotency repository unavailable")
|
||||
}
|
||||
logger.Warn("幂等中间件未启用(db或repo不可用)- 需要幂等的写接口将返回 503", nil)
|
||||
}
|
||||
|
||||
rateLimitConfig := middleware.DefaultRateLimitConfig()
|
||||
rateLimitConfig.Enabled = env != "dev"
|
||||
logger.Info("限流中间件已初始化", nil)
|
||||
|
||||
supplyAPI, err := httpapi.NewSupplyAPI(
|
||||
accountService,
|
||||
packageService,
|
||||
settlementService,
|
||||
earningService,
|
||||
idempotencyMiddleware,
|
||||
storeBundle.auditStore,
|
||||
storeBundle.fkValidator,
|
||||
cfg.Server.DefaultSupplierID,
|
||||
cfg.Server.StatementBaseURL,
|
||||
now,
|
||||
)
|
||||
if err != nil {
|
||||
return runtimeAPIBundle{}, fmt.Errorf("failed to initialize supply api: %w", err)
|
||||
}
|
||||
supplyAPI.SetWithdrawEnabled(cfg.Settlement.WithdrawEnabled)
|
||||
|
||||
alertAPI, err := httpapi.NewAlertAPI(storeBundle.alertService)
|
||||
if err != nil {
|
||||
return runtimeAPIBundle{}, fmt.Errorf("failed to initialize alert api: %w", err)
|
||||
}
|
||||
|
||||
return runtimeAPIBundle{
|
||||
supplyAPI: supplyAPI,
|
||||
alertAPI: alertAPI,
|
||||
rateLimitConfig: rateLimitConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"lijiaoqiao/supply-api/internal/audit"
|
||||
"lijiaoqiao/supply-api/internal/cache"
|
||||
"lijiaoqiao/supply-api/internal/config"
|
||||
"lijiaoqiao/supply-api/internal/domain"
|
||||
@@ -64,6 +65,72 @@ func TestBuildRuntime_ProdRequiresDatabase(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildStoreBundle_UsesInMemoryStoresWithoutDatabase(t *testing.T) {
|
||||
bundle := buildStoreBundle(nil, testLogger{})
|
||||
if bundle.accountStore == nil {
|
||||
t.Fatal("expected account store")
|
||||
}
|
||||
if bundle.packageStore == nil {
|
||||
t.Fatal("expected package store")
|
||||
}
|
||||
if bundle.settlementStore == nil {
|
||||
t.Fatal("expected settlement store")
|
||||
}
|
||||
if bundle.earningStore == nil {
|
||||
t.Fatal("expected earning store")
|
||||
}
|
||||
if bundle.auditStore == nil {
|
||||
t.Fatal("expected audit store")
|
||||
}
|
||||
if bundle.alertService == nil {
|
||||
t.Fatal("expected alert service")
|
||||
}
|
||||
if bundle.tokenStatusRepo != nil {
|
||||
t.Fatal("expected nil token status repo without database")
|
||||
}
|
||||
if bundle.idempotencyRepo != nil {
|
||||
t.Fatal("expected nil idempotency repo without database")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildStoreBundle_UsesDatabaseBackedStoresWithDatabase(t *testing.T) {
|
||||
bundle := buildStoreBundle(&repository.DB{}, testLogger{})
|
||||
if bundle.accountStore == nil {
|
||||
t.Fatal("expected account store")
|
||||
}
|
||||
if bundle.packageStore == nil {
|
||||
t.Fatal("expected package store")
|
||||
}
|
||||
if bundle.settlementStore == nil {
|
||||
t.Fatal("expected settlement store")
|
||||
}
|
||||
if bundle.earningStore == nil {
|
||||
t.Fatal("expected earning store")
|
||||
}
|
||||
if bundle.auditStore == nil {
|
||||
t.Fatal("expected audit store")
|
||||
}
|
||||
if bundle.alertService == nil {
|
||||
t.Fatal("expected alert service")
|
||||
}
|
||||
if bundle.tokenStatusRepo == nil {
|
||||
t.Fatal("expected token status repo with database")
|
||||
}
|
||||
if bundle.idempotencyRepo == nil {
|
||||
t.Fatal("expected idempotency repo with database")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSecurityBundle_UsesMemoryTokenBackendWithoutRepository(t *testing.T) {
|
||||
security := buildSecurityBundle("dev", testRuntimeConfig(), testLogger{}, audit.NewMemoryAuditStore(), nil, nil)
|
||||
if security.authMiddleware == nil {
|
||||
t.Fatal("expected auth middleware")
|
||||
}
|
||||
if security.revocationSubscriber != nil {
|
||||
t.Fatal("expected nil revocation subscriber without token repository")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveEnv_RejectsUnsupportedValue(t *testing.T) {
|
||||
_, err := ResolveEnv("qa")
|
||||
if err == nil {
|
||||
|
||||
Reference in New Issue
Block a user