277 lines
9.2 KiB
Go
277 lines
9.2 KiB
Go
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, 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, 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, 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, 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, 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, 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, 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
|
|
}, 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")
|
|
}
|
|
}
|