package settings import ( "context" "encoding/json" "errors" "os" "path/filepath" "reflect" "sort" "strings" "testing" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/providers" ) func TestFixtureIsConsumedByGo(t *testing.T) { data, err := os.ReadFile("../../../contracts/settings/runtime-v1.json") if err != nil { t.Fatal(err) } var fixture struct { Version int `json:"version"` FileName string `json:"fileName"` AllowedKeys []string `json:"allowedKeys"` SecretKeys []string `json:"secretKeys"` DatabaseBackedKeys []string `json:"databaseBackedKeys"` } if err := json.Unmarshal(data, &fixture); err != nil { t.Fatal(err) } if fixture.Version != 1 || fixture.FileName != ".env.local" || len(fixture.AllowedKeys) != 27 || len(fixture.SecretKeys) != 9 { t.Fatalf("fixture = %#v", fixture) } wantDatabaseKeys := RuntimeSettingKeys() gotDatabaseKeys := append([]string(nil), fixture.DatabaseBackedKeys...) sort.Strings(gotDatabaseKeys) if !reflect.DeepEqual(gotDatabaseKeys, wantDatabaseKeys) { t.Fatalf("databaseBackedKeys=%#v want %#v", gotDatabaseKeys, wantDatabaseKeys) } } func TestMinimaxSettingsAreDatabaseBackedAndReportedConnected(t *testing.T) { repository := &runtimeSettingsRepositoryStub{values: map[string]string{ "MINIMAX_API_KEY": "secret", "MINIMAX_BASE_URL": "https://api.minimax.test", "VIDEO_GENERATE_ENGINE": "minimax", }} service := NewWithRuntimeRepository(filepath.Join(t.TempDir(), ".env.local"), nil, repository, nil) value, err := service.Get(context.Background()) if err != nil { t.Fatal(err) } payload := value.(Payload) if !payload.Services.Minimax || payload.EngineAssignments[1].Engine != "minimax" || payload.EngineAssignments[1].ReqKey != providers.MinimaxH3Model { t.Fatalf("payload=%#v", payload) } } func TestServiceSavePreservesSecretsAndUnrelatedEnvLines(t *testing.T) { path := filepath.Join(t.TempDir(), ".env.local") original := "# operator note\nDATABASE_URL=postgres://untouched\nZHINIAN_AUTH_SESSION_SECRET=keep-me\nZHINIAN_BILLING_CONTACT=old # prior\nCUSTOM_QUOTED=\"a b\"\n" if err := os.WriteFile(path, []byte(original), 0o600); err != nil { t.Fatal(err) } environment := map[string]string{"EVOLINK_BASE_URL": "https://runtime.example"} var applied map[string]string service := New(path, environment, func(_ context.Context, updates map[string]string) error { applied = clone(updates); return nil }) value, err := service.Save(context.Background(), map[string]any{ "ZHINIAN_AUTH_SESSION_SECRET": " ", "ZHINIAN_BILLING_CONTACT": " ", "EVOLINK_BASE_URL": " https://new.example/v1 ", "DATABASE_URL": "postgres://attack", "ALI_OSS_BUCKET": 42, }) if err != nil { t.Fatal(err) } text, err := os.ReadFile(path) if err != nil { t.Fatal(err) } got := string(text) for _, expected := range []string{"# operator note", "DATABASE_URL=postgres://untouched", "ZHINIAN_AUTH_SESSION_SECRET=keep-me", "ZHINIAN_BILLING_CONTACT=", "CUSTOM_QUOTED=\"a b\"", "EVOLINK_BASE_URL=https://new.example/v1"} { if !strings.Contains(got, expected) { t.Fatalf("file missing %q:\n%s", expected, got) } } if strings.Contains(got, "postgres://attack") || strings.Contains(got, "ALI_OSS_BUCKET=42") { t.Fatalf("unexpected unapproved update:\n%s", got) } if !reflect.DeepEqual(applied, map[string]string{"ZHINIAN_BILLING_CONTACT": "", "EVOLINK_BASE_URL": "https://new.example/v1"}) { t.Fatalf("applied = %#v", applied) } if payload := value.(Payload); payload.RestartRequired { t.Fatal("provider setting update must be applied without restart") } assertSecretProjection(t, value, "ZHINIAN_AUTH_SESSION_SECRET", true) } func TestRuntimeRepositoryOverridesEnvironmentAndProjectsSecrets(t *testing.T) { repository := &runtimeSettingsRepositoryStub{values: map[string]string{ "EVOLINK_API_KEY": "database-secret", "EVOLINK_BASE_URL": "https://database.example", "IMAGE_GENERATE_ENGINE": "evolink", "ZHINIAN_AUTH_SESSION_SECRET": "database-session-secret", "ZHINIAN_BILLING_CONTACT": "database-billing-contact", "ALI_OSS_BUCKET": "database-bucket", }} service := NewWithRuntimeRepository( filepath.Join(t.TempDir(), ".env.local"), map[string]string{ "EVOLINK_API_KEY": "environment-secret", "EVOLINK_BASE_URL": "https://environment.example", "ZHINIAN_AUTH_SESSION_SECRET": "environment-session-secret", "ZHINIAN_BILLING_CONTACT": "environment-billing-contact", "ALI_OSS_BUCKET": "environment-bucket", }, repository, nil, ) value, err := service.Get(context.Background()) if err != nil { t.Fatal(err) } if got := fieldValue(t, value, "EVOLINK_BASE_URL"); got != "https://database.example" { t.Fatalf("base URL = %q", got) } assertSecretProjection(t, value, "EVOLINK_API_KEY", true) runtime, err := service.RuntimeValues(context.Background()) if err != nil || runtime["EVOLINK_API_KEY"] != "database-secret" || runtime["ZHINIAN_AUTH_SESSION_SECRET"] != "database-session-secret" || runtime["ZHINIAN_BILLING_CONTACT"] != "database-billing-contact" || runtime["ALI_OSS_BUCKET"] != "database-bucket" { t.Fatalf("runtime=%#v err=%v", runtime, err) } } func TestRuntimeRepositoryPersistsEveryEditableSettingWithoutCreatingLocalFile(t *testing.T) { path := filepath.Join(t.TempDir(), "nested", ".env.local") repository := &runtimeSettingsRepositoryStub{} service := NewWithRuntimeRepository(path, nil, repository, nil) updates := map[string]any{} want := map[string]string{} for _, key := range RuntimeSettingKeys() { value := "database-" + strings.ToLower(key) updates[key] = value want[key] = value } value, err := service.Save(context.Background(), updates) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(repository.saved, want) { t.Fatalf("saved=%#v want %#v", repository.saved, want) } if _, err := os.Stat(path); !os.IsNotExist(err) { t.Fatalf("database-backed save unexpectedly created local settings file: %v", err) } if !value.(Payload).RestartRequired { t.Fatal("startup-only database settings should require restart") } } func TestRuntimeRepositorySaveIsPartialImmediateAndDoesNotCreateSettingsFile(t *testing.T) { path := filepath.Join(t.TempDir(), "nested", ".env.local") repository := &runtimeSettingsRepositoryStub{values: map[string]string{"EVOLINK_API_KEY": "keep-secret"}} service := NewWithRuntimeRepository(path, nil, repository, nil) value, err := service.Save(context.Background(), map[string]any{ "EVOLINK_API_KEY": " ", "EVOLINK_BASE_URL": " https://saved.example ", }) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(repository.saved, map[string]string{"EVOLINK_BASE_URL": "https://saved.example"}) { t.Fatalf("saved=%#v", repository.saved) } if _, err := os.Stat(path); !os.IsNotExist(err) { t.Fatalf("provider save unexpectedly created local settings file: %v", err) } if value.(Payload).RestartRequired { t.Fatal("provider database update should apply immediately") } assertSecretProjection(t, value, "EVOLINK_API_KEY", true) } func TestRuntimeRepositoryFailureDoesNotFallBackToLocalFile(t *testing.T) { path := filepath.Join(t.TempDir(), "nested", ".env.local") repository := &runtimeSettingsRepositoryStub{saveErr: errors.New("database unavailable")} service := NewWithRuntimeRepository(path, nil, repository, nil) _, err := service.Save(context.Background(), map[string]any{"ALI_OSS_BUCKET": "bucket"}) if err == nil || !strings.Contains(err.Error(), "persist runtime settings") { t.Fatalf("Save() error=%v", err) } if _, statErr := os.Stat(path); !os.IsNotExist(statErr) { t.Fatalf("failed database save unexpectedly created local settings file: %v", statErr) } } func TestServiceGetUsesInjectedEnvironmentWithoutMutatingProcess(t *testing.T) { path := filepath.Join(t.TempDir(), ".env.local") if err := os.WriteFile(path, []byte("EVOLINK_BASE_URL=file-value\nSEEDANCE_API_KEY=file-secret\n"), 0o600); err != nil { t.Fatal(err) } const sentinel = "settings-service-must-not-write-this" old, present := os.LookupEnv(sentinel) t.Cleanup(func() { if present { _ = os.Setenv(sentinel, old) } else { _ = os.Unsetenv(sentinel) } }) _ = os.Unsetenv(sentinel) service := New(path, map[string]string{"EVOLINK_BASE_URL": "runtime-value", "SEEDANCE_API_KEY": "runtime-secret"}, nil) value, err := service.Get(context.Background()) if err != nil { t.Fatal(err) } if _, exists := os.LookupEnv(sentinel); exists { t.Fatal("process environment mutated") } if got := fieldValue(t, value, "EVOLINK_BASE_URL"); got != "runtime-value" { t.Fatalf("value = %q", got) } assertSecretProjection(t, value, "SEEDANCE_API_KEY", true) } func TestLoadEnvironmentIgnoresUnknownRuntimeFlags(t *testing.T) { path := filepath.Join(t.TempDir(), ".env.local") if err := os.WriteFile(path, []byte("ZHINIAN_ALLOW_UNCONFIGURED_PROVIDERS=true\nUNKNOWN_RUNTIME_FLAG=true\n"), 0o600); err != nil { t.Fatal(err) } values, err := LoadEnvironment(path, nil) if err != nil { t.Fatal(err) } if _, exists := values["ZHINIAN_ALLOW_UNCONFIGURED_PROVIDERS"]; exists { t.Fatal("obsolete provider bootstrap flag was loaded") } if _, exists := values["UNKNOWN_RUNTIME_FLAG"]; exists { t.Fatal("unknown runtime flag was loaded") } } func TestServiceSynchronizesBillingAccountWithoutRequiringRestart(t *testing.T) { writer := &billingWriterStub{} service := New(filepath.Join(t.TempDir(), ".env.local"), nil, nil).WithBillingAccountWriter(writer) value, err := service.Save(context.Background(), map[string]any{ "ZHINIAN_BILLING_ACCOUNT_NAME": " Acme ", "ZHINIAN_BILLING_ACCOUNT_BANK": " Bank ", "ZHINIAN_BILLING_ACCOUNT_NUMBER": " 123 ", "ZHINIAN_BILLING_CONTACT": " Ops ", }) if err != nil { t.Fatal(err) } if writer.value != (BillingAccount{AccountName: "Acme", BankName: "Bank", AccountNumber: "123", Contact: "Ops"}) { t.Fatalf("billing account=%#v", writer.value) } if value.(Payload).RestartRequired { t.Fatal("billing account update should be applied immediately") } } func TestServicePartialBillingUpdatePreservesPersistedSiblingFields(t *testing.T) { path := filepath.Join(t.TempDir(), ".env.local") if err := os.WriteFile(path, []byte("ZHINIAN_BILLING_ACCOUNT_NAME=Acme\nZHINIAN_BILLING_ACCOUNT_BANK=Legacy Bank\nZHINIAN_BILLING_ACCOUNT_NUMBER=123\nZHINIAN_BILLING_CONTACT=Old Ops\n"), 0o600); err != nil { t.Fatal(err) } writer := &billingWriterStub{} service := New(path, nil, nil).WithBillingAccountWriter(writer) if _, err := service.Save(context.Background(), map[string]any{"ZHINIAN_BILLING_CONTACT": "New Ops"}); err != nil { t.Fatal(err) } want := BillingAccount{AccountName: "Acme", BankName: "Legacy Bank", AccountNumber: "123", Contact: "New Ops"} if writer.value != want { t.Fatalf("billing account=%#v want %#v", writer.value, want) } } func TestRuntimeRepositoryPartialBillingUpdatePreservesDatabaseSiblings(t *testing.T) { path := filepath.Join(t.TempDir(), "nested", ".env.local") repository := &runtimeSettingsRepositoryStub{values: map[string]string{ "ZHINIAN_BILLING_ACCOUNT_NAME": "Acme", "ZHINIAN_BILLING_ACCOUNT_BANK": "Legacy Bank", "ZHINIAN_BILLING_ACCOUNT_NUMBER": "123", "ZHINIAN_BILLING_CONTACT": "Old Ops", }} writer := &billingWriterStub{} service := NewWithRuntimeRepository(path, nil, repository, nil).WithBillingAccountWriter(writer) value, err := service.Save(context.Background(), map[string]any{"ZHINIAN_BILLING_CONTACT": "New Ops"}) if err != nil { t.Fatal(err) } want := BillingAccount{AccountName: "Acme", BankName: "Legacy Bank", AccountNumber: "123", Contact: "New Ops"} if writer.value != want { t.Fatalf("billing account=%#v want %#v", writer.value, want) } if value.(Payload).RestartRequired { t.Fatal("billing account database update should apply immediately") } if _, err := os.Stat(path); !os.IsNotExist(err) { t.Fatalf("billing database update unexpectedly created local settings file: %v", err) } } func TestRestartRequirementIsIndependentFromDatabasePersistence(t *testing.T) { tests := []struct { key string want bool }{ {key: "EVOLINK_API_KEY", want: false}, {key: "IMAGE_GENERATE_ENGINE", want: false}, {key: "ZHINIAN_BILLING_ACCOUNT_NAME", want: false}, {key: "ZHINIAN_AUTH_REQUIRED", want: true}, {key: "ZHINIAN_AUTH_SESSION_SECRET", want: true}, {key: "ZHINIAN_BILLING_REQUIRED", want: true}, {key: "ALI_OSS_ENDPOINT", want: true}, {key: "ALI_OSS_BUCKET", want: true}, {key: "ALI_OSS_ACCESS_KEY_ID", want: true}, {key: "ALI_OSS_ACCESS_KEY_SECRET", want: true}, {key: "ALI_OSS_PUBLIC_BASE_URL", want: true}, } for _, test := range tests { t.Run(test.key, func(t *testing.T) { if got := requiresRestart(map[string]string{test.key: "value"}); got != test.want { t.Fatalf("requiresRestart=%v want %v", got, test.want) } }) } } type billingWriterStub struct{ value BillingAccount } func (writer *billingWriterStub) SaveBillingAccount(_ context.Context, value BillingAccount) error { writer.value = value return nil } type runtimeSettingsRepositoryStub struct { values map[string]string saved map[string]string saveErr error } func (repository *runtimeSettingsRepositoryStub) LoadRuntimeSettings(_ context.Context, _ []string) (map[string]string, error) { return clone(repository.values), nil } func (repository *runtimeSettingsRepositoryStub) SaveRuntimeSettings(_ context.Context, values map[string]string) error { if repository.saveErr != nil { return repository.saveErr } repository.saved = clone(values) if repository.values == nil { repository.values = map[string]string{} } for key, value := range values { repository.values[key] = value } return nil } func fieldValue(t *testing.T, value any, key string) string { t.Helper() payload := value.(Payload) for _, group := range payload.Groups { for _, field := range group.Fields { if field.Key == key { return field.Value } } } t.Fatalf("field %s not found", key) return "" } func assertSecretProjection(t *testing.T, value any, key string, configured bool) { t.Helper() payload := value.(Payload) for _, group := range payload.Groups { for _, field := range group.Fields { if field.Key == key { if field.Value != "" || field.Configured != configured { t.Fatalf("field = %#v", field) } return } } } t.Fatalf("field %s not found", key) } func clone(input map[string]string) map[string]string { output := map[string]string{} for key, value := range input { output[key] = value } return output }