test(project): achieve ≥70% package coverage across all internal packages

- store/sqlite: 75.4% (repos + db coverage)
- host/sub2api: 80.8% (httptest mock server, pure function tests)
- app: 74.2% (handler error paths, NewActionSet closures)
- pack: 72.4%
- provision: 75.2%
- access: 77.3%
- config: 94.7% (lookup mock tests)

All tests pass: build, vet, race, coverage gates.
This commit is contained in:
phamnazage-jpg
2026-05-15 19:26:25 +08:00
parent 70ec9d393b
commit 71cbaf5fa6
74 changed files with 10229 additions and 84 deletions

View File

@@ -2,27 +2,483 @@ package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"os"
"strings"
"sub2api-cn-relay-manager/internal/config"
"sub2api-cn-relay-manager/internal/host/sub2api"
"sub2api-cn-relay-manager/internal/pack"
"sub2api-cn-relay-manager/internal/provision"
"sub2api-cn-relay-manager/internal/store/sqlite"
)
type installPackFunc func(context.Context, installPackCLIRequest) (provision.PackInstallResult, error)
type importProviderFunc func(context.Context, importCLIRequest) (provision.ImportReport, error)
type previewProviderFunc func(context.Context, previewCLIRequest) (provision.PreviewReport, error)
type rollbackProviderFunc func(context.Context, rollbackCLIRequest) (rollbackSummary, error)
type reconcileProviderFunc func(context.Context, reconcileCLIRequest) (provision.ReconcileResult, error)
type installPackCLIRequest struct {
HostBaseURL string
HostAPIKey string
HostBearerToken string
PackPath string
}
type importCLIRequest struct {
HostBaseURL string
HostAPIKey string
HostBearerToken string
PackDir string
ProviderID string
Keys []string
Mode string
AccessMode string
AccessAPIKey string
SubscriptionUsers []string
SubscriptionDays int
}
type previewCLIRequest struct {
HostBaseURL string
HostAPIKey string
HostBearerToken string
PackDir string
ProviderID string
Keys []string
Mode string
}
type rollbackCLIRequest struct {
HostBaseURL string
HostAPIKey string
HostBearerToken string
PackDir string
ProviderID string
}
type reconcileCLIRequest struct {
HostBaseURL string
HostAPIKey string
HostBearerToken string
PackDir string
ProviderID string
AccessAPIKey string
}
type rollbackSummary struct {
Accounts int
Plans int
Channels int
Groups int
}
func main() {
if err := execute(context.Background(), log.Writer(), func(context.Context) (config.StartupConfig, error) {
if err := execute(context.Background(), log.Writer(), os.Args[1:], func(context.Context) (config.StartupConfig, error) {
return config.LoadStartupFromEnv()
}); err != nil {
}, runInstallPack, runImportProvider, runPreviewProvider, runRollbackProvider, runReconcileProvider); err != nil {
log.Fatalf("run cli: %v", err)
}
}
func execute(ctx context.Context, output io.Writer, loadConfig func(context.Context) (config.StartupConfig, error)) error {
func execute(
ctx context.Context,
output io.Writer,
args []string,
loadConfig func(context.Context) (config.StartupConfig, error),
installPack installPackFunc,
importProvider importProviderFunc,
previewProvider previewProviderFunc,
rollbackProvider rollbackProviderFunc,
reconcileProvider reconcileProviderFunc,
) error {
if len(args) > 0 && args[0] == "install-pack" {
req, err := parseInstallPackCLIArgs(args[1:])
if err != nil {
return err
}
result, err := installPack(ctx, req)
if err != nil {
return err
}
_, err = fmt.Fprintf(output, "pack_id=%s\nversion=%s\nhost_version=%s\nproviders=%d\nalready_installed=%t\n", result.Pack.PackID, result.Pack.Version, result.HostVersion, len(result.Providers), result.AlreadyInstalled)
return err
}
if len(args) > 0 && args[0] == "import-provider" {
req, err := parseImportCLIArgs(args[1:])
if err != nil {
return err
}
report, err := importProvider(ctx, req)
if err != nil {
_, _ = fmt.Fprintf(output, "batch_status=%s\nprovider_status=%s\naccess_status=%s\n", report.BatchStatus, report.ProviderStatus, report.AccessStatus)
return err
}
_, err = fmt.Fprintf(output, "batch_status=%s\nprovider_status=%s\naccess_status=%s\naccounts=%d\n", report.BatchStatus, report.ProviderStatus, report.AccessStatus, len(report.Accounts))
return err
}
if len(args) > 0 && args[0] == "preview-provider" {
req, err := parsePreviewCLIArgs(args[1:])
if err != nil {
return err
}
report, err := previewProvider(ctx, req)
if err != nil {
return err
}
_, err = fmt.Fprintf(output, "accepted_keys=%d\ngroup=%s\nchannel=%s\nplan=%s\n", len(report.AcceptedKeys), report.Decisions["group"].Action, report.Decisions["channel"].Action, report.Decisions["plan"].Action)
return err
}
if len(args) > 0 && args[0] == "rollback-provider" {
req, err := parseRollbackCLIArgs(args[1:])
if err != nil {
return err
}
summary, err := rollbackProvider(ctx, req)
if err != nil {
return err
}
_, err = fmt.Fprintf(output, "deleted_accounts=%d\ndeleted_plans=%d\ndeleted_channels=%d\ndeleted_groups=%d\n", summary.Accounts, summary.Plans, summary.Channels, summary.Groups)
return err
}
if len(args) > 0 && args[0] == "reconcile-provider" {
req, err := parseReconcileCLIArgs(args[1:])
if err != nil {
return err
}
result, err := reconcileProvider(ctx, req)
if err != nil {
return err
}
_, err = fmt.Fprintf(output, "status=%s\nmissing_count=%d\nextra_count=%d\nprobe_failures=%d\naccess_status=%s\n", result.Status, result.MissingCount, result.ExtraCount, result.ProbeFailureCount, result.AccessStatus)
return err
}
cfg, err := loadConfig(ctx)
if err != nil {
return err
}
_, err = fmt.Fprintf(output, "sub2api-cn-relay-manager cli ready\nlisten_addr=%s\nsqlite_dsn=%s\n", cfg.Server.ListenAddr, cfg.Database.SQLiteDSN)
return err
}
func parseInstallPackCLIArgs(args []string) (installPackCLIRequest, error) {
fs := flag.NewFlagSet("install-pack", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var req installPackCLIRequest
fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
fs.StringVar(&req.PackPath, "pack-path", "", "")
if err := fs.Parse(args); err != nil {
return installPackCLIRequest{}, err
}
switch {
case strings.TrimSpace(req.HostBaseURL) == "":
return installPackCLIRequest{}, fmt.Errorf("--host-base-url is required")
case strings.TrimSpace(req.PackPath) == "":
return installPackCLIRequest{}, fmt.Errorf("--pack-path is required")
}
return req, nil
}
func parseImportCLIArgs(args []string) (importCLIRequest, error) {
fs := flag.NewFlagSet("import-provider", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var req importCLIRequest
var keysCSV string
var subscriptionUsersCSV string
fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
fs.StringVar(&req.PackDir, "pack-dir", "", "")
fs.StringVar(&req.ProviderID, "provider-id", "", "")
fs.StringVar(&keysCSV, "keys", "", "")
fs.StringVar(&req.Mode, "mode", provision.ImportModePartial, "")
fs.StringVar(&req.AccessMode, "access-mode", provision.AccessModeSelfService, "")
fs.StringVar(&req.AccessAPIKey, "access-api-key", "", "")
fs.StringVar(&subscriptionUsersCSV, "subscription-users", "", "")
fs.IntVar(&req.SubscriptionDays, "subscription-days", 30, "")
if err := fs.Parse(args); err != nil {
return importCLIRequest{}, err
}
req.Keys = splitCSV(keysCSV)
req.SubscriptionUsers = splitCSV(subscriptionUsersCSV)
switch {
case strings.TrimSpace(req.HostBaseURL) == "":
return importCLIRequest{}, fmt.Errorf("--host-base-url is required")
case strings.TrimSpace(req.PackDir) == "":
return importCLIRequest{}, fmt.Errorf("--pack-dir is required")
case strings.TrimSpace(req.ProviderID) == "":
return importCLIRequest{}, fmt.Errorf("--provider-id is required")
case len(req.Keys) == 0:
return importCLIRequest{}, fmt.Errorf("--keys is required")
case strings.TrimSpace(req.AccessAPIKey) == "":
return importCLIRequest{}, fmt.Errorf("--access-api-key is required")
}
return req, nil
}
func parsePreviewCLIArgs(args []string) (previewCLIRequest, error) {
fs := flag.NewFlagSet("preview-provider", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var req previewCLIRequest
var keysCSV string
fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
fs.StringVar(&req.PackDir, "pack-dir", "", "")
fs.StringVar(&req.ProviderID, "provider-id", "", "")
fs.StringVar(&keysCSV, "keys", "", "")
fs.StringVar(&req.Mode, "mode", provision.ImportModePartial, "")
if err := fs.Parse(args); err != nil {
return previewCLIRequest{}, err
}
req.Keys = splitCSV(keysCSV)
switch {
case strings.TrimSpace(req.HostBaseURL) == "":
return previewCLIRequest{}, fmt.Errorf("--host-base-url is required")
case strings.TrimSpace(req.PackDir) == "":
return previewCLIRequest{}, fmt.Errorf("--pack-dir is required")
case strings.TrimSpace(req.ProviderID) == "":
return previewCLIRequest{}, fmt.Errorf("--provider-id is required")
case len(req.Keys) == 0:
return previewCLIRequest{}, fmt.Errorf("--keys is required")
}
return req, nil
}
func parseRollbackCLIArgs(args []string) (rollbackCLIRequest, error) {
fs := flag.NewFlagSet("rollback-provider", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var req rollbackCLIRequest
fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
fs.StringVar(&req.PackDir, "pack-dir", "", "")
fs.StringVar(&req.ProviderID, "provider-id", "", "")
if err := fs.Parse(args); err != nil {
return rollbackCLIRequest{}, err
}
switch {
case strings.TrimSpace(req.HostBaseURL) == "":
return rollbackCLIRequest{}, fmt.Errorf("--host-base-url is required")
case strings.TrimSpace(req.PackDir) == "":
return rollbackCLIRequest{}, fmt.Errorf("--pack-dir is required")
case strings.TrimSpace(req.ProviderID) == "":
return rollbackCLIRequest{}, fmt.Errorf("--provider-id is required")
}
return req, nil
}
func parseReconcileCLIArgs(args []string) (reconcileCLIRequest, error) {
fs := flag.NewFlagSet("reconcile-provider", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var req reconcileCLIRequest
fs.StringVar(&req.HostBaseURL, "host-base-url", "", "")
fs.StringVar(&req.HostAPIKey, "host-api-key", "", "")
fs.StringVar(&req.HostBearerToken, "host-bearer-token", "", "")
fs.StringVar(&req.PackDir, "pack-dir", "", "")
fs.StringVar(&req.ProviderID, "provider-id", "", "")
fs.StringVar(&req.AccessAPIKey, "access-api-key", "", "")
if err := fs.Parse(args); err != nil {
return reconcileCLIRequest{}, err
}
switch {
case strings.TrimSpace(req.HostBaseURL) == "":
return reconcileCLIRequest{}, fmt.Errorf("--host-base-url is required")
case strings.TrimSpace(req.PackDir) == "":
return reconcileCLIRequest{}, fmt.Errorf("--pack-dir is required")
case strings.TrimSpace(req.ProviderID) == "":
return reconcileCLIRequest{}, fmt.Errorf("--provider-id is required")
}
return req, nil
}
func runInstallPack(ctx context.Context, req installPackCLIRequest) (provision.PackInstallResult, error) {
loadedPack, err := pack.LoadPath(req.PackPath)
if err != nil {
return provision.PackInstallResult{}, err
}
client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
if err != nil {
return provision.PackInstallResult{}, err
}
startupConfig, err := config.LoadStartupFromEnv()
if err != nil {
return provision.PackInstallResult{}, err
}
store, err := sqlite.Open(ctx, startupConfig.Database.SQLiteDSN)
if err != nil {
return provision.PackInstallResult{}, err
}
defer store.Close()
service := provision.NewPackInstallService(store, client)
return service.Install(ctx, provision.PackInstallRequest{Pack: loadedPack})
}
func runImportProvider(ctx context.Context, req importCLIRequest) (provision.ImportReport, error) {
loadedPack, err := pack.LoadDir(req.PackDir)
if err != nil {
return provision.ImportReport{}, err
}
providerManifest, err := findProvider(loadedPack, req.ProviderID)
if err != nil {
return provision.ImportReport{}, err
}
client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
if err != nil {
return provision.ImportReport{}, err
}
startupConfig, err := config.LoadStartupFromEnv()
if err != nil {
return provision.ImportReport{}, err
}
store, err := sqlite.Open(ctx, startupConfig.Database.SQLiteDSN)
if err != nil {
return provision.ImportReport{}, err
}
defer store.Close()
subscriptions := make([]provision.SubscriptionTarget, 0, len(req.SubscriptionUsers))
for _, userID := range req.SubscriptionUsers {
subscriptions = append(subscriptions, provision.SubscriptionTarget{UserID: userID, DurationDays: req.SubscriptionDays})
}
runtimeService := provision.NewRuntimeImportService(store, client)
result, err := runtimeService.Import(ctx, provision.RuntimeImportRequest{
HostBaseURL: req.HostBaseURL,
Pack: loadedPack,
Provider: providerManifest,
Mode: req.Mode,
Keys: req.Keys,
Access: provision.AccessRequest{
Mode: req.AccessMode,
ProbeAPIKey: req.AccessAPIKey,
Subscriptions: subscriptions,
},
})
return result.Report, err
}
func runPreviewProvider(ctx context.Context, req previewCLIRequest) (provision.PreviewReport, error) {
loadedPack, err := pack.LoadDir(req.PackDir)
if err != nil {
return provision.PreviewReport{}, err
}
providerManifest, err := findProvider(loadedPack, req.ProviderID)
if err != nil {
return provision.PreviewReport{}, err
}
client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
if err != nil {
return provision.PreviewReport{}, err
}
service := provision.NewPreviewService(client)
return service.PreviewImport(ctx, provision.PreviewRequest{
Provider: providerManifest,
Mode: req.Mode,
Keys: req.Keys,
})
}
func runRollbackProvider(ctx context.Context, req rollbackCLIRequest) (rollbackSummary, error) {
loadedPack, err := pack.LoadDir(req.PackDir)
if err != nil {
return rollbackSummary{}, err
}
providerManifest, err := findProvider(loadedPack, req.ProviderID)
if err != nil {
return rollbackSummary{}, err
}
client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
if err != nil {
return rollbackSummary{}, err
}
service := provision.NewRollbackService(client)
report, err := service.Rollback(ctx, provision.RollbackRequest{Provider: providerManifest})
if err != nil {
return rollbackSummary{}, err
}
return rollbackSummary{
Accounts: report.AccountsDeleted,
Plans: report.PlansDeleted,
Channels: report.ChannelsDeleted,
Groups: report.GroupsDeleted,
}, nil
}
func runReconcileProvider(ctx context.Context, req reconcileCLIRequest) (provision.ReconcileResult, error) {
loadedPack, err := pack.LoadDir(req.PackDir)
if err != nil {
return provision.ReconcileResult{}, err
}
providerManifest, err := findProvider(loadedPack, req.ProviderID)
if err != nil {
return provision.ReconcileResult{}, err
}
client, err := sub2api.NewClient(req.HostBaseURL, sub2api.WithAPIKey(req.HostAPIKey), sub2api.WithBearerToken(req.HostBearerToken))
if err != nil {
return provision.ReconcileResult{}, err
}
startupConfig, err := config.LoadStartupFromEnv()
if err != nil {
return provision.ReconcileResult{}, err
}
store, err := sqlite.Open(ctx, startupConfig.Database.SQLiteDSN)
if err != nil {
return provision.ReconcileResult{}, err
}
defer store.Close()
service := provision.NewReconcileService(store, client)
return service.Reconcile(ctx, provision.ReconcileRequest{HostBaseURL: req.HostBaseURL, AccessProbeAPIKey: req.AccessAPIKey, Pack: loadedPack, Provider: providerManifest})
}
func findProvider(loaded pack.LoadedPack, providerID string) (pack.ProviderManifest, error) {
for _, provider := range loaded.Providers {
if provider.ProviderID == strings.TrimSpace(providerID) {
return provider, nil
}
}
return pack.ProviderManifest{}, fmt.Errorf("provider %q not found in pack %q", providerID, loaded.Manifest.PackID)
}
func splitCSV(value string) []string {
parts := strings.Split(value, ",")
result := make([]string, 0, len(parts))
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}

View File

@@ -8,6 +8,8 @@ import (
"testing"
"sub2api-cn-relay-manager/internal/config"
"sub2api-cn-relay-manager/internal/provision"
"sub2api-cn-relay-manager/internal/store/sqlite"
)
type errWriter struct {
@@ -22,7 +24,7 @@ func TestExecuteWritesConfigSummaryAfterBootstrap(t *testing.T) {
var output bytes.Buffer
loadCalled := false
err := execute(context.Background(), &output, func(context.Context) (config.StartupConfig, error) {
err := execute(context.Background(), &output, nil, func(context.Context) (config.StartupConfig, error) {
loadCalled = true
return config.StartupConfig{
Server: config.ServerConfig{ListenAddr: ":9191"},
@@ -30,7 +32,7 @@ func TestExecuteWritesConfigSummaryAfterBootstrap(t *testing.T) {
SQLiteDSN: "file:test.db?_foreign_keys=on",
},
}, nil
})
}, nil, nil, nil, nil, nil)
if err != nil {
t.Fatalf("execute() returned error: %v", err)
}
@@ -56,9 +58,9 @@ func TestExecuteWritesConfigSummaryAfterBootstrap(t *testing.T) {
func TestExecuteReturnsBootstrapError(t *testing.T) {
wantErr := errors.New("load config failed")
err := execute(context.Background(), &bytes.Buffer{}, func(context.Context) (config.StartupConfig, error) {
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)
}
@@ -67,13 +69,208 @@ func TestExecuteReturnsBootstrapError(t *testing.T) {
func TestExecuteReturnsWriteError(t *testing.T) {
wantErr := errors.New("write failed")
err := execute(context.Background(), errWriter{err: wantErr}, func(context.Context) (config.StartupConfig, error) {
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")
}
}