package bootstrap import ( "context" "errors" "strings" "testing" "time" "proxy-pool/internal/config" "proxy-pool/internal/controller/admin" "proxy-pool/internal/controller/pool" "proxy-pool/internal/controller/provider" controllerRuntime "proxy-pool/internal/controller/runtime" "proxy-pool/internal/domain/activitypool" "proxy-pool/internal/domain/adminstate" extractionDomain "proxy-pool/internal/domain/extraction" "proxy-pool/internal/domain/upstream" "proxy-pool/internal/platform/admission" "proxy-pool/internal/platform/credentials" ) var bootstrapTestFingerprintKey = []byte("0123456789abcdef0123456789abcdef") func TestRunLoadsOneSnapshotCommitsItAndClosesInfrastructure(t *testing.T) { t.Parallel() resolver := &memoryResolver{files: map[string][]byte{"controller.yaml": []byte(bootstrapTestConfig)}} state := adminstate.NewMemoryStore() activity := &stubActivityStore{} credentialStore, err := credentials.NewMemoryStore(10) if err != nil { t.Fatalf("NewMemoryStore(): %v", err) } closeErr := errors.New("close failed") infrastructure := &stubInfrastructure{ports: ports{ state: state, activity: activity, readiness: readyStub{}, metricsReadiness: readyStub{}, admission: admission.AllowAll{}, coordinator: coordinatorStub{}, credentials: credentialStore, 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) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() err = run(ctx, Options{ ConfigPath: "controller.yaml", Resolver: resolver, Now: func() time.Time { return now }, FingerprintKey: bootstrapTestFingerprintKey, }, 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 || factory.dependencies.MetricsHandler == 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) } }) } } func TestRunRejectsMissingAdminFingerprintKeyBeforeOpeningInfrastructure(t *testing.T) { for name, key := range map[string][]byte{ "missing": nil, "short": []byte("too-short"), } { t.Run(name, func(t *testing.T) { infrastructure := &stubInfrastructure{} err := run(context.Background(), Options{ ConfigPath: "controller.yaml", Resolver: &memoryResolver{files: map[string][]byte{"controller.yaml": []byte(bootstrapTestConfig)}}, Now: time.Now, FingerprintKey: key, }, infrastructure, &recordingRuntimeFactory{}) if !errors.Is(err, ErrInvalidOptions) || !errors.Is(err, config.ErrInvalidFingerprint) { t.Fatalf("run() error = %v", err) } if infrastructure.opens != 0 { t.Fatalf("infrastructure opens = %d, want 0", infrastructure.opens) } }) } } func TestRunRejectsMissingDistributionAdmissionDependency(t *testing.T) { t.Parallel() credentialStore, err := credentials.NewMemoryStore(10) if err != nil { t.Fatalf("NewMemoryStore(): %v", err) } infrastructure := &stubInfrastructure{ports: ports{ state: adminstate.NewMemoryStore(), activity: &stubActivityStore{}, readiness: readyStub{}, metricsReadiness: readyStub{}, coordinator: coordinatorStub{}, credentials: credentialStore, close: func() error { return nil }, }} factory := &recordingRuntimeFactory{runner: runnerStub{err: errors.New("runtime should not start")}} err = run(context.Background(), Options{ ConfigPath: "controller.yaml", Resolver: &memoryResolver{files: map[string][]byte{"controller.yaml": []byte(bootstrapTestConfig)}}, Now: time.Now, FingerprintKey: bootstrapTestFingerprintKey, }, infrastructure, factory) if !errors.Is(err, ErrStartup) || !errors.Is(err, ErrInvalidOptions) { t.Fatalf("run() error = %v, want ErrStartup and ErrInvalidOptions", err) } if factory.configuration != nil { t.Fatal("runtime factory called without admission dependency") } } func TestRunSupportsProviderOnlyConfigurationWithoutHTTPRuntime(t *testing.T) { source := strings.ReplaceAll(bootstrapTestConfig, "distribution:\n enabled: true", "distribution:\n enabled: false") source = strings.ReplaceAll(source, "admin:\n enabled: true", "admin:\n enabled: false") source = strings.ReplaceAll(source, "metrics:\n enabled: true", "metrics:\n enabled: false") credentialStore, err := credentials.NewMemoryStore(10) if err != nil { t.Fatalf("NewMemoryStore(): %v", err) } infrastructure := &stubInfrastructure{ports: ports{ activity: &stubActivityStore{}, coordinator: coordinatorStub{}, credentials: credentialStore, close: func() error { return nil }, }} factory := &recordingRuntimeFactory{} ctx, cancel := context.WithCancel(context.Background()) time.AfterFunc(20*time.Millisecond, cancel) err = run(ctx, Options{ ConfigPath: "controller.yaml", Resolver: &memoryResolver{files: map[string][]byte{"controller.yaml": []byte(source)}}, Now: time.Now, }, infrastructure, factory) if !errors.Is(err, context.Canceled) { t.Fatalf("run(provider only) error = %v, want context cancellation", err) } if factory.configuration != nil { t.Fatal("HTTP runtime factory was called for Provider-only configuration") } } func TestRunAdminDisableStopsActiveProviderRuntime(t *testing.T) { state := adminstate.NewMemoryStore() credentialStore, err := credentials.NewMemoryStore(200) if err != nil { t.Fatalf("NewMemoryStore(): %v", err) } started := make(chan string, 2) stopped := make(chan string, 2) infrastructure := &stubInfrastructure{ports: ports{ state: state, activity: &stubActivityStore{}, readiness: readyStub{}, metricsReadiness: readyStub{}, admission: admission.AllowAll{}, coordinator: coordinatorFunc(func(ctx context.Context, upstreamID string) error { started <- upstreamID <-ctx.Done() stopped <- upstreamID return ctx.Err() }), credentials: credentialStore, close: func() error { return nil }, }} wantErr := errors.New("test HTTP runtime stopped") factory := runtimeFactoryFunc(func( _ *config.Config, dependencies controllerRuntime.Dependencies, _ controllerRuntime.Options, ) (controllerRunner, error) { return runnerFunc(func(ctx context.Context) error { for { select { case upstreamID := <-started: if upstreamID != "provider-a" { continue } if _, err := dependencies.AdminService.SetUpstreamEnabled(ctx, admin.SetUpstreamCommand{ RequestID: "req-disable", ActorID: "admin:test", Name: "provider-a", Enabled: false, }); err != nil { return err } for { select { case stoppedID := <-stopped: if stoppedID == "provider-a" { return wantErr } case <-ctx.Done(): return ctx.Err() } } case <-ctx.Done(): return ctx.Err() } } }), nil }) ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() err = run(ctx, Options{ ConfigPath: "controller.yaml", Resolver: &memoryResolver{files: map[string][]byte{"controller.yaml": []byte(bootstrapTestConfig)}}, Now: time.Now, FingerprintKey: bootstrapTestFingerprintKey, }, infrastructure, factory) if !errors.Is(err, wantErr) { t.Fatalf("run() error = %v, want %v", err, wantErr) } } func TestRetainProviderStatsKeepsAllConfiguredProviders(t *testing.T) { configuration, err := config.Load(strings.NewReader(bootstrapTestConfig)) if err != nil { t.Fatalf("config.Load(): %v", err) } disabled := configuration.Upstreams["provider-b"] disabled.Enabled = false configuration.Upstreams["provider-b"] = disabled stats, err := provider.NewStatsRecorder(2) if err != nil { t.Fatalf("provider.NewStatsRecorder(): %v", err) } stats.Record(provider.Result{UpstreamID: "removed", Class: upstream.FetchError}) stats.Record(provider.Result{UpstreamID: "provider-b", Class: upstream.FetchError}) retainProviderStats(configuration, stats) stats.Record(provider.Result{UpstreamID: "provider-a", Class: upstream.FetchError}) got := stats.ReadProviderStats([]string{"removed", "provider-a", "provider-b"}) if got[0].FetchErrorCount != 0 || got[1].FetchErrorCount != 1 || got[2].FetchErrorCount != 1 { t.Fatalf("Provider stats after retention = %+v", got) } } 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 runnerFunc func(context.Context) error func (run runnerFunc) Run(ctx context.Context) error { return run(ctx) } type runtimeFactoryFunc func( *config.Config, controllerRuntime.Dependencies, controllerRuntime.Options, ) (controllerRunner, error) func (factory runtimeFactoryFunc) New( configuration *config.Config, dependencies controllerRuntime.Dependencies, options controllerRuntime.Options, ) (controllerRunner, error) { return factory(configuration, dependencies, options) } 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 } func (*stubActivityStore) UpsertFetched( _ context.Context, _ string, batch activitypool.FetchedBatch, ) (activitypool.UpsertResult, error) { return activitypool.UpsertResult{Accepted: len(batch.Proxies), Inserted: len(batch.Proxies)}, nil } func (*stubActivityStore) ReadInventory( context.Context, string, time.Duration, ) (pool.InventorySnapshot, error) { return pool.InventorySnapshot{Managed: 100, AvailableSlots: 1_000}, nil } type coordinatorStub struct{} func (coordinatorStub) RunLeader( ctx context.Context, _ string, _ provider.CoordinationLimits, _ func(context.Context, provider.LeaderSession) error, ) error { <-ctx.Done() return ctx.Err() } type coordinatorFunc func(context.Context, string) error func (run coordinatorFunc) RunLeader( ctx context.Context, upstreamID string, _ provider.CoordinationLimits, _ func(context.Context, provider.LeaderSession) error, ) error { return run(ctx, upstreamID) } 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} metrics: enabled: true listen: 127.0.0.1:0 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} refill: {reconcileInterval: 1s, minimumAvailableSlots: 100, targetAvailableSlots: 200} lifecycle: {ttl: 2m, allocationSafetyMargin: 10s} fetch: {estimatedIPsPerCall: 10, 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 `