package data import ( "context" "os" "path/filepath" "strings" "testing" "kra/app/system/integration/storage" "kra/internal/conf" "google.golang.org/protobuf/encoding/protojson" "gopkg.in/yaml.v3" "gorm.io/gorm" ) func openIntegrationConfigTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared") if err != nil { t.Fatal(err) } sqlDB, err := db.DB() if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = sqlDB.Close() }) if err = db.AutoMigrate(&integrationConfigPO{}); err != nil { t.Fatal(err) } return db } func TestMigrateAllCreatesIntegrationConfigTable(t *testing.T) { db, err := openWithDriver("sqlite", "file:"+t.Name()+"?mode=memory&cache=shared") if err != nil { t.Fatal(err) } sqlDB, err := db.DB() if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = sqlDB.Close() }) if err = migrateAll(db); err != nil { t.Fatal(err) } if !db.Migrator().HasTable(&integrationConfigPO{}) { t.Fatal("migrateAll did not create sys_integration_configs") } } func TestStorageIntegrationConfigRoundTrip(t *testing.T) { db := openIntegrationConfigTestDB(t) storage := &conf.AdminBackend_Storage{ Type: "aliyun-oss", Qiniu: &conf.AdminBackend_Qiniu{ Zone: "ZoneHuadong", Bucket: "qiniu-bucket", AccessKey: "qiniu-key", SecretKey: "qiniu-secret", }, AliyunOss: &conf.AdminBackend_ObjectStore{ Endpoint: "oss-cn-hangzhou.aliyuncs.com", Region: "cn-hangzhou", Bucket: "assets", AccessKey: "aliyun-key", SecretKey: "aliyun-secret", BaseUrl: "https://cdn.example.com", PathPrefix: "uploads", }, Minio: &conf.AdminBackend_ObjectStore{Endpoint: "127.0.0.1:9000", Bucket: "local", ForcePathStyle: true}, } if err := saveStorageIntegrationConfig(db, storage); err != nil { t.Fatal(err) } email := &conf.AdminBackend_Email{ To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com", Secret: "smtp-secret", Nickname: "Kra", Port: 465, IsSsl: true, } if err := saveEmailIntegrationConfig(db, email); err != nil { t.Fatal(err) } if err := db.Create(&integrationConfigPO{Kind: integrationKindPayment, Provider: "wechat-pay", Config: `{"merchant_id":"123"}`}).Error; err != nil { t.Fatal(err) } loaded, found, err := loadStorageIntegrationConfig(db) if err != nil { t.Fatal(err) } if !found { t.Fatal("storage integration configuration was not found") } if loaded.Type != "aliyun-oss" { t.Fatalf("storage type = %q, want aliyun-oss", loaded.Type) } if loaded.AliyunOss == nil || loaded.AliyunOss.SecretKey != "aliyun-secret" || loaded.AliyunOss.PathPrefix != "uploads" { t.Fatalf("aliyun configuration = %#v", loaded.AliyunOss) } if loaded.Qiniu == nil || loaded.Qiniu.SecretKey != "qiniu-secret" { t.Fatalf("qiniu configuration = %#v", loaded.Qiniu) } loadedEmail, found, err := loadEmailIntegrationConfig(db) if err != nil || !found { t.Fatalf("email configuration found=%v, err=%v", found, err) } if loadedEmail.Host != "smtp.example.com" || loadedEmail.Secret != "smtp-secret" || loadedEmail.Port != 465 { t.Fatalf("email configuration = %#v", loadedEmail) } var storageCount, emailCount, paymentCount int64 if err = db.Model(&integrationConfigPO{}).Where("kind = ?", integrationKindStorage).Count(&storageCount).Error; err != nil { t.Fatal(err) } if err = db.Model(&integrationConfigPO{}).Where("kind = ?", integrationKindPayment).Count(&paymentCount).Error; err != nil { t.Fatal(err) } if err = db.Model(&integrationConfigPO{}).Where("kind = ?", integrationKindEmail).Count(&emailCount).Error; err != nil { t.Fatal(err) } if storageCount != int64(len(storageProviderNames)) { t.Fatalf("storage row count = %d, want %d", storageCount, len(storageProviderNames)) } if paymentCount != 1 { t.Fatalf("payment row count = %d, want 1", paymentCount) } if emailCount != 1 { t.Fatalf("email row count = %d, want 1", emailCount) } } func TestResolveStorageIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) { db := openIntegrationConfigTestDB(t) legacy := &conf.AdminBackend_Storage{ Type: "qiniu", Qiniu: &conf.AdminBackend_Qiniu{Bucket: "legacy", SecretKey: "legacy-secret"}, } loaded, err := resolveStorageIntegrationConfig(db, legacy) if err != nil { t.Fatal(err) } if loaded.Type != "qiniu" || loaded.Qiniu.GetBucket() != "legacy" { t.Fatalf("migrated storage = %#v", loaded) } other := &conf.AdminBackend_Storage{ Type: "minio", Minio: &conf.AdminBackend_ObjectStore{Bucket: "must-not-replace-database"}, } loaded, err = resolveStorageIntegrationConfig(db, other) if err != nil { t.Fatal(err) } if loaded.Type != "qiniu" || loaded.Qiniu.GetSecretKey() != "legacy-secret" { t.Fatalf("database configuration was replaced by legacy config: %#v", loaded) } } func TestMaskStorageSecretsLeavesUnconfiguredProvidersEmpty(t *testing.T) { storage := &conf.AdminBackend_Storage{ Qiniu: &conf.AdminBackend_Qiniu{}, AliyunOss: &conf.AdminBackend_ObjectStore{SecretKey: "configured-secret"}, Minio: &conf.AdminBackend_ObjectStore{}, } maskStorageSecrets(storage) if storage.Qiniu.SecretKey != "" || storage.Minio.SecretKey != "" { t.Fatalf("empty provider secrets were masked: %#v", storage) } if storage.AliyunOss.SecretKey != "******" { t.Fatalf("configured secret was not masked: %q", storage.AliyunOss.SecretKey) } } func TestResolveEmailIntegrationConfigMigratesLegacyOnlyOnce(t *testing.T) { db := openIntegrationConfigTestDB(t) legacy := &conf.AdminBackend_Email{To: "ops@example.com", From: "old@example.com", Host: "smtp.old.example.com", Secret: "old-secret", Port: 465, IsSsl: true} loaded, err := resolveEmailIntegrationConfig(db, legacy) if err != nil { t.Fatal(err) } if loaded.Host != legacy.Host || loaded.Secret != legacy.Secret { t.Fatalf("migrated email = %#v", loaded) } loaded, err = resolveEmailIntegrationConfig(db, &conf.AdminBackend_Email{Host: "must-not-replace.example.com"}) if err != nil { t.Fatal(err) } if loaded.Host != legacy.Host || loaded.Secret != legacy.Secret { t.Fatalf("database email was replaced by legacy config: %#v", loaded) } } func TestPersistConfigValuesRemovesStorageFromYAML(t *testing.T) { path := filepath.Join(t.TempDir(), "config.yaml") input := []byte("data: {}\nadmin:\n router_prefix: /old\n storage:\n type: qiniu\n qiniu:\n secret_key: legacy-secret\n email:\n host: smtp.legacy.example.com\n secret: legacy-email-secret\n extension_key: retained\n") if err := os.WriteFile(path, input, 0o600); err != nil { t.Fatal(err) } d := &Data{} admin := &conf.AdminBackend{ ConfigPath: path, RouterPrefix: "/api", Storage: &conf.AdminBackend_Storage{ Type: "qiniu", Qiniu: &conf.AdminBackend_Qiniu{SecretKey: "database-only-secret"}, }, Email: &conf.AdminBackend_Email{Host: "smtp.database.example.com", Secret: "database-only-email-secret"}, } if err := d.persistConfigValues(&conf.Data{}, admin); err != nil { t.Fatal(err) } raw, err := os.ReadFile(path) if err != nil { t.Fatal(err) } var document map[string]any if err = yaml.Unmarshal(raw, &document); err != nil { t.Fatal(err) } adminValue, ok := document["admin"].(map[string]any) if !ok { t.Fatalf("admin config = %#v", document["admin"]) } if _, exists := adminValue["storage"]; exists { t.Fatalf("storage remained in YAML: %s", raw) } if _, exists := adminValue["email"]; exists { t.Fatalf("email remained in YAML: %s", raw) } if adminValue["extension_key"] != "retained" { t.Fatalf("extension key was not retained: %#v", adminValue) } } func TestPersistRuntimeConfigReplacesActiveStorage(t *testing.T) { db := openIntegrationConfigTestDB(t) configPath := filepath.Join(t.TempDir(), "config.yaml") if err := os.WriteFile(configPath, []byte("data: {}\nadmin: {}\n"), 0o600); err != nil { t.Fatal(err) } oldRoot := filepath.Join(t.TempDir(), "old") newRoot := filepath.Join(t.TempDir(), "new") currentAdmin := &conf.AdminBackend{ ConfigPath: configPath, Local: &conf.AdminBackend_Local{StorePath: oldRoot, PathPrefix: "old-files"}, Storage: &conf.AdminBackend_Storage{Type: "local"}, } currentStorage, err := storage.NewReloadable(currentAdmin) if err != nil { t.Fatal(err) } reloadableDB := &reloadableDB{} reloadableDB.current.Store(db) d := &Data{ runtime: conf.NewRuntime(&conf.Data{}, currentAdmin), gormDB: reloadableDB, storage: currentStorage, } d.databaseReady.Store(true) nextAdmin := cloneAdminConfig(currentAdmin) nextAdmin.Local = &conf.AdminBackend_Local{StorePath: newRoot, PathPrefix: "new-files"} nextAdmin.Email = &conf.AdminBackend_Email{ To: "ops@example.com", From: "mailer@example.com", Host: "smtp.example.com", Secret: "runtime-secret", Port: 465, IsSsl: true, } dataRaw, err := protojson.Marshal(&conf.Data{}) if err != nil { t.Fatal(err) } adminRaw, err := protojson.Marshal(nextAdmin) if err != nil { t.Fatal(err) } if err = d.PersistRuntimeConfig(context.Background(), dataRaw, adminRaw); err != nil { t.Fatal(err) } stored, err := d.storage.Put(context.Background(), "active.txt", strings.NewReader("active")) if err != nil { t.Fatal(err) } if stored.URL != "/new-files/active.txt" { t.Fatalf("active storage URL = %q, want /new-files/active.txt", stored.URL) } if _, err = os.Stat(filepath.Join(newRoot, "active.txt")); err != nil { t.Fatalf("active storage did not write to the new root: %v", err) } loaded, found, err := loadStorageIntegrationConfig(db) if err != nil || !found || loaded.Type != "local" { t.Fatalf("database storage config = %#v, found=%v, err=%v", loaded, found, err) } loadedEmail, found, err := loadEmailIntegrationConfig(db) if err != nil || !found || loadedEmail.Secret != "runtime-secret" { t.Fatalf("database email config = %#v, found=%v, err=%v", loadedEmail, found, err) } if runtimeEmail := d.runtime.Admin().Email; runtimeEmail == nil || runtimeEmail.Host != "smtp.example.com" { t.Fatalf("runtime email config = %#v", runtimeEmail) } }