Files
NianAIGC/backend/internal/settings/service_test.go

181 lines
6.1 KiB
Go

package settings
import (
"context"
"encoding/json"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
)
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"`
}
if err := json.Unmarshal(data, &fixture); err != nil {
t.Fatal(err)
}
if fixture.Version != 1 || fixture.FileName != ".env.local" || len(fixture.AllowedKeys) != 25 || len(fixture.SecretKeys) != 8 {
t.Fatalf("fixture = %#v", fixture)
}
}
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 declare restartRequired")
}
assertSecretProjection(t, value, "ZHINIAN_AUTH_SESSION_SECRET", true)
}
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 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)
}
}
type billingWriterStub struct{ value BillingAccount }
func (writer *billingWriterStub) SaveBillingAccount(_ context.Context, value BillingAccount) error {
writer.value = 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
}