package main import ( "bytes" "context" "errors" "strings" "testing" "sub2api-cn-relay-manager/internal/config" "sub2api-cn-relay-manager/internal/provision" "sub2api-cn-relay-manager/internal/store/sqlite" ) type errWriter struct { err error } func (w errWriter) Write([]byte) (int, error) { return 0, w.err } func TestExecuteWritesConfigSummaryAfterBootstrap(t *testing.T) { var output bytes.Buffer loadCalled := false err := execute(context.Background(), &output, nil, func(context.Context) (config.StartupConfig, error) { loadCalled = true return config.StartupConfig{ Server: config.ServerConfig{ListenAddr: ":9191"}, Database: config.DatabaseConfig{ SQLiteDSN: "file:test.db?_foreign_keys=on", }, }, nil }, nil, nil, nil, nil, nil) if err != nil { t.Fatalf("execute() returned error: %v", err) } if !loadCalled { t.Fatal("execute() did not load config") } got := output.String() if !strings.Contains(got, "sub2api-cn-relay-manager cli ready") { t.Fatalf("execute() output = %q, want readiness line", got) } if !strings.Contains(got, "listen_addr=:9191") { t.Fatalf("execute() output = %q, want listen addr summary", got) } if !strings.Contains(got, "sqlite_dsn=file:test.db?_foreign_keys=on") { t.Fatalf("execute() output = %q, want sqlite dsn summary", got) } } func TestExecuteReturnsBootstrapError(t *testing.T) { wantErr := errors.New("load config failed") err := execute(context.Background(), &bytes.Buffer{}, nil, func(context.Context) (config.StartupConfig, error) { return config.StartupConfig{}, wantErr }, nil, nil, nil, nil, nil) if !errors.Is(err, wantErr) { t.Fatalf("execute() error = %v, want %v", err, wantErr) } } func TestExecuteReturnsWriteError(t *testing.T) { wantErr := errors.New("write failed") err := execute(context.Background(), errWriter{err: wantErr}, nil, func(context.Context) (config.StartupConfig, error) { return config.StartupConfig{ Server: config.ServerConfig{ListenAddr: ":9292"}, Database: config.DatabaseConfig{SQLiteDSN: "file:test.db"}, }, nil }, nil, nil, nil, nil, nil) if !errors.Is(err, wantErr) { t.Fatalf("execute() error = %v, want %v", err, wantErr) } } func TestExecuteInstallPackWritesSummary(t *testing.T) { var output bytes.Buffer installCalled := false err := execute(context.Background(), &output, []string{ "install-pack", "--host-base-url", "https://sub2api.example.com", "--pack-path", "/tmp/openai-pack.zip", }, nil, func(_ context.Context, req installPackCLIRequest) (provision.PackInstallResult, error) { installCalled = true if req.PackPath != "/tmp/openai-pack.zip" { t.Fatalf("unexpected install request: %+v", req) } return provision.PackInstallResult{ Pack: sqlite.Pack{PackID: "openai-cn-pack", Version: "1.0.0"}, HostVersion: "0.1.126", Providers: []sqlite.Provider{{ProviderID: "deepseek"}}, }, nil }, nil, nil, nil, nil) if err != nil { t.Fatalf("execute() install-pack error = %v", err) } if !installCalled { t.Fatal("execute() did not invoke installPack") } got := output.String() if !strings.Contains(got, "pack_id=openai-cn-pack") || !strings.Contains(got, "providers=1") || !strings.Contains(got, "host_version=0.1.126") { t.Fatalf("execute() install-pack output = %q, want summary", got) } } func TestExecuteImportProviderWritesSummary(t *testing.T) { var output bytes.Buffer importCalled := false err := execute(context.Background(), &output, []string{ "import-provider", "--host-base-url", "https://sub2api.example.com", "--pack-dir", "/tmp/pack", "--provider-id", "deepseek", "--keys", "k1,k2", "--access-api-key", "user-key", }, nil, nil, func(_ context.Context, req importCLIRequest) (provision.ImportReport, error) { importCalled = true if req.ProviderID != "deepseek" || len(req.Keys) != 2 { t.Fatalf("unexpected import request: %+v", req) } return provision.ImportReport{ BatchStatus: provision.BatchStatusSucceeded, ProviderStatus: provision.ProviderStatusActive, AccessStatus: provision.AccessStatusSelfServiceReady, Accounts: []provision.AccountImportResult{{}, {}}, }, nil }, nil, nil, nil) if err != nil { t.Fatalf("execute() import error = %v", err) } if !importCalled { t.Fatal("execute() did not invoke importProvider") } got := output.String() if !strings.Contains(got, "batch_status=succeeded") || !strings.Contains(got, "accounts=2") { t.Fatalf("execute() import output = %q, want summary", got) } } func TestExecutePreviewProviderWritesSummary(t *testing.T) { var output bytes.Buffer previewCalled := false err := execute(context.Background(), &output, []string{ "preview-provider", "--host-base-url", "https://sub2api.example.com", "--pack-dir", "/tmp/pack", "--provider-id", "deepseek", "--keys", "k1,k2", }, nil, nil, nil, func(_ context.Context, req previewCLIRequest) (provision.PreviewReport, error) { previewCalled = true if req.ProviderID != "deepseek" || len(req.Keys) != 2 { t.Fatalf("unexpected preview request: %+v", req) } return provision.PreviewReport{ AcceptedKeys: []string{"k1", "k2"}, Names: provision.ResourceNames{Group: "crm-deepseek-group", Channel: "crm-deepseek-channel", Plan: "crm-deepseek-plan"}, Decisions: map[string]provision.PreviewDecision{ "group": {Action: provision.PreviewActionCreate}, "channel": {Action: provision.PreviewActionReuse, ExistingID: "channel_1"}, "plan": {Action: provision.PreviewActionConflict}, }, }, nil }, nil, nil) if err != nil { t.Fatalf("execute() preview error = %v", err) } if !previewCalled { t.Fatal("execute() did not invoke previewProvider") } got := output.String() if !strings.Contains(got, "accepted_keys=2") || !strings.Contains(got, "group=create") || !strings.Contains(got, "channel=reuse") || !strings.Contains(got, "plan=conflict") { t.Fatalf("execute() preview output = %q, want summary", got) } } func TestExecuteRollbackProviderWritesSummary(t *testing.T) { var output bytes.Buffer rollbackCalled := false err := execute(context.Background(), &output, []string{ "rollback-provider", "--host-base-url", "https://sub2api.example.com", "--pack-dir", "/tmp/pack", "--provider-id", "deepseek", }, nil, nil, nil, nil, func(_ context.Context, req rollbackCLIRequest) (rollbackSummary, error) { rollbackCalled = true if req.ProviderID != "deepseek" { t.Fatalf("unexpected rollback request: %+v", req) } return rollbackSummary{Accounts: 2, Plans: 1, Channels: 1, Groups: 1}, nil }, nil) if err != nil { t.Fatalf("execute() rollback error = %v", err) } if !rollbackCalled { t.Fatal("execute() did not invoke rollbackProvider") } got := output.String() if !strings.Contains(got, "deleted_accounts=2") || !strings.Contains(got, "deleted_groups=1") { t.Fatalf("execute() rollback output = %q, want summary", got) } } func TestExecuteReconcileProviderWritesSummary(t *testing.T) { var output bytes.Buffer reconcileCalled := false err := execute(context.Background(), &output, []string{ "reconcile-provider", "--host-base-url", "https://sub2api.example.com", "--pack-dir", "/tmp/pack", "--provider-id", "deepseek", "--access-api-key", "user-key", }, nil, nil, nil, nil, nil, func(_ context.Context, req reconcileCLIRequest) (provision.ReconcileResult, error) { reconcileCalled = true if req.ProviderID != "deepseek" || req.AccessAPIKey != "user-key" { t.Fatalf("unexpected reconcile request: %+v", req) } return provision.ReconcileResult{Status: "drifted", MissingCount: 1, ExtraCount: 2, ProbeFailureCount: 1, AccessStatus: provision.AccessStatusBroken}, nil }) if err != nil { t.Fatalf("execute() reconcile error = %v", err) } if !reconcileCalled { t.Fatal("execute() did not invoke reconcileProvider") } got := output.String() if !strings.Contains(got, "status=drifted") || !strings.Contains(got, "missing_count=1") || !strings.Contains(got, "access_status=broken") { t.Fatalf("execute() reconcile output = %q, want summary", got) } } func TestParseInstallPackCLIArgsRequiresHostBaseURL(t *testing.T) { _, err := parseInstallPackCLIArgs([]string{"--pack-path", "/tmp/openai-pack.zip"}) if err == nil { t.Fatal("parseInstallPackCLIArgs() error = nil, want validation error") } } func TestParseImportCLIArgsRequiresHostBaseURL(t *testing.T) { _, err := parseImportCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "deepseek", "--keys", "k1", "--access-api-key", "user-key"}) if err == nil { t.Fatal("parseImportCLIArgs() error = nil, want validation error") } } func TestParsePreviewCLIArgsRequiresHostBaseURL(t *testing.T) { _, err := parsePreviewCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "deepseek", "--keys", "k1"}) if err == nil { t.Fatal("parsePreviewCLIArgs() error = nil, want validation error") } } func TestParseRollbackCLIArgsRequiresHostBaseURL(t *testing.T) { _, err := parseRollbackCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "deepseek"}) if err == nil { t.Fatal("parseRollbackCLIArgs() error = nil, want validation error") } } func TestParseReconcileCLIArgsRequiresHostBaseURL(t *testing.T) { _, err := parseReconcileCLIArgs([]string{"--pack-dir", "/tmp/pack", "--provider-id", "deepseek"}) if err == nil { t.Fatal("parseReconcileCLIArgs() error = nil, want validation error") } }