package bootstrap import ( "context" "errors" "testing" "time" "proxy-pool/internal/config" controllerRuntime "proxy-pool/internal/controller/runtime" "proxy-pool/internal/domain/activitypool" "proxy-pool/internal/domain/adminstate" extractionDomain "proxy-pool/internal/domain/extraction" ) func TestRunLoadsOneSnapshotCommitsItAndClosesInfrastructure(t *testing.T) { t.Parallel() resolver := &memoryResolver{files: map[string][]byte{"controller.yaml": []byte(bootstrapTestConfig)}} state := adminstate.NewMemoryStore() activity := &stubActivityStore{} closeErr := errors.New("close failed") infrastructure := &stubInfrastructure{ports: ports{ state: state, activity: activity, readiness: readyStub{}, close: func() error { return closeErr }, }} runErr := errors.New("runtime failed") factory := &recordingRuntimeFactory{runner: runnerStub{err: runErr}} now := time.Date(2026, 7, 30, 11, 0, 0, 0, time.UTC) err := run(context.Background(), Options{ ConfigPath: "controller.yaml", Resolver: resolver, Now: func() time.Time { return now }, }, infrastructure, factory) if !errors.Is(err, runErr) || !errors.Is(err, closeErr) { t.Fatalf("run() error = %v, want runtime and close errors", err) } if resolver.reads != 1 { t.Fatalf("configuration reads = %d, want 1", resolver.reads) } if infrastructure.opens != 1 || infrastructure.configuration == nil { t.Fatalf("infrastructure opens = %d, config = %p", infrastructure.opens, infrastructure.configuration) } snapshot, snapshotErr := state.Snapshot(context.Background()) if snapshotErr != nil || snapshot.Config == nil || snapshot.Config.Source != "controller.yaml" || snapshot.Revision != 1 { t.Fatalf("management snapshot = %+v, %v", snapshot, snapshotErr) } if factory.configuration == nil || factory.dependencies.Extractor == nil || factory.dependencies.Readiness == nil || factory.dependencies.AdminService == nil { t.Fatalf("runtime assembly = config:%p dependencies:%+v", factory.configuration, factory.dependencies) } } func TestRunRejectsInvalidOptionsBeforeIO(t *testing.T) { t.Parallel() valid := Options{ConfigPath: "controller.yaml", Resolver: &memoryResolver{}, Now: time.Now} tests := []struct { name string ctx context.Context options Options }{ {name: "nil context", options: valid}, {name: "missing path", ctx: context.Background(), options: Options{Resolver: valid.Resolver, Now: time.Now}}, {name: "unclean path", ctx: context.Background(), options: Options{ConfigPath: " controller.yaml", Resolver: valid.Resolver, Now: time.Now}}, {name: "missing resolver", ctx: context.Background(), options: Options{ConfigPath: "controller.yaml", Now: time.Now}}, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { t.Parallel() if err := run(test.ctx, test.options, &stubInfrastructure{}, &recordingRuntimeFactory{}); !errors.Is(err, ErrInvalidOptions) { t.Fatalf("run() error = %v", err) } }) } } type memoryResolver struct { files map[string][]byte reads int } func (*memoryResolver) LookupEnv(string) (string, bool) { return "", false } func (resolver *memoryResolver) ReadFile(path string) ([]byte, error) { resolver.reads++ content, ok := resolver.files[path] if !ok { return nil, errors.New("file missing") } return append([]byte(nil), content...), nil } type stubInfrastructure struct { ports ports err error opens int configuration *config.Config } func (infrastructure *stubInfrastructure) Open( _ context.Context, configuration *config.Config, ) (ports, error) { infrastructure.opens++ infrastructure.configuration = configuration return infrastructure.ports, infrastructure.err } type recordingRuntimeFactory struct { configuration *config.Config dependencies controllerRuntime.Dependencies runner controllerRunner err error } func (factory *recordingRuntimeFactory) New( configuration *config.Config, dependencies controllerRuntime.Dependencies, options controllerRuntime.Options, ) (controllerRunner, error) { factory.configuration = configuration factory.dependencies = dependencies return factory.runner, factory.err } type runnerStub struct{ err error } func (runner runnerStub) Run(context.Context) error { return runner.err } type readyStub struct{} func (readyStub) Ready(context.Context) error { return nil } type stubActivityStore struct{} func (*stubActivityStore) Extract(_ context.Context, command extractionDomain.Command) (extractionDomain.Result, error) { return extractionDomain.Result{Requested: command.Requested}, nil } func (*stubActivityStore) ReadStateInventory( _ context.Context, upstreamIDs []string, _ time.Time, ) ([]activitypool.StateInventory, error) { result := make([]activitypool.StateInventory, len(upstreamIDs)) for index, upstreamID := range upstreamIDs { result[index].UpstreamID = upstreamID } return result, nil } const bootstrapTestConfig = ` version: 1 security: requireProtectionOnPublicListen: true gateway: enabled: false distribution: enabled: true listen: 127.0.0.1:0 auth: {mode: none} extraction: fulfillment: partial maxCountPerRequest: 20 minRemainingTTL: 5s maxHealthCheckAge: 15s reserveForGateway: 5 idempotencyTTL: 5m admin: enabled: true listen: 127.0.0.1:0 auth: {mode: none} storage: postgresURL: postgres://fixture redisURL: redis://fixture routing: - name: extract enabled: true purpose: extract upstreams: [provider-a, provider-b] strategy: {type: sequential, switchAfterEmptyFetch: 5, endBehavior: stayLast} onUnavailable: {action: reject} upstreams: provider-a: &upstream enabled: true exposure: [extract] provider: {billingMode: fetch, protocols: [http]} api: url: https://provider.invalid/proxies method: GET template: '{{.}}' auth: {type: none} proxyAuth: {type: response} pool: {maxSize: 100} capacity: {maxConcurrencyPerProxy: 10} lifecycle: {ttl: 2m, allocationSafetyMargin: 10s} fetch: {requestInterval: 1s, timeout: 3s, maxAttempts: 3, maxInFlight: 1, maxTotal: 1000} check: interval: 30s jitter: 20 maxInFlight: 100 timeout: 2s maxAttempts: 2 maxConsecutiveFailures: 3 urls: [https://example.invalid/health] provider-b: *upstream `