Smail/backend/config/partitioned_compatibility_test.go
smail d193324e6d
Some checks are pending
Security Checks / security (push) Waiting to run
整改基线:封存公网端口分离与远程后端回归候选
2026-09-18 01:05:50 +08:00

105 lines
3.9 KiB
Go

package config
import (
"bytes"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"time"
"git.pcux.cn/xuans/Smail/utils/credential"
"github.com/go-sql-driver/mysql"
)
func TestPartitionedDatabaseConnectionPreservesMigrationTimezone(t *testing.T) {
vault, err := credential.NewVault(t.TempDir())
if err != nil {
t.Fatal(err)
}
secret, err := ApplySecretChange(vault, StoredSecret{}, SecretChange{Operation: SecretReplace, Value: &SecretInput{Plaintext: "test-password"}}, SecretPolicy{Required: true, MaxLength: 4096}, time.Now().UTC())
if err != nil {
t.Fatal(err)
}
for _, kind := range []DatabaseType{DatabaseTypeMySQL, DatabaseTypePostgreSQL} {
_, dsn, err := compatibilityDatabase(DatabaseConfig{Type: kind, Host: "::1", Port: 3306, Username: "test", Name: "test", Password: secret}, vault)
if err != nil {
t.Fatal("runtime connection failed")
}
if kind == DatabaseTypeMySQL {
settings, err := mysql.ParseDSN(dsn)
_, rawQuery, _ := strings.Cut(dsn, "?")
query, queryErr := url.ParseQuery(rawQuery)
// charset 会被驱动解析为内部选项,不再保留在 Params 中。
if err != nil || queryErr != nil || !settings.ParseTime || settings.Params["time_zone"] != "'+00:00'" || query.Get("charset") != "utf8mb4" {
t.Fatal("MySQL runtime timezone differs from migration")
}
} else {
uri, err := url.Parse(dsn)
if err != nil || uri.Query().Get("timezone") != "UTC" || uri.Query().Get("search_path") != "public" || uri.Query().Get("sslmode") != "require" {
t.Fatal("PostgreSQL runtime options differ from migration")
}
}
}
}
func TestPartitionedCompatibilityStartupAndWriteProtection(t *testing.T) {
root := t.TempDir()
oldRoot, oldArgs, oldDevelopment := ROOT_PATH, os.Args, DevelopmentMode
oldInstance, oldSnapshot := Instance, CurrentConfig()
ROOT_PATH, os.Args = root, []string{"smail"}
t.Cleanup(func() {
ROOT_PATH, os.Args, DevelopmentMode = oldRoot, oldArgs, oldDevelopment
Instance = oldInstance
PublishConfigSnapshot(oldSnapshot)
})
writeLegacyMigrationFixture(t, root, map[string]any{
"logLevel": "info", "domain": "mail.example.com", "webDomain": "mail.example.com", "adminRecoveryDomain": "recovery.mail.example.com",
"dbType": "sqlite", "dbDSN": "./config/existing.db", "isInit": true,
"sslType": "1", "SSLPublicKeyPath": "./config/ssl/site.crt", "SSLPrivateKeyPath": "./config/ssl/site.key",
"httpPort": 80, "httpsPort": 443, "httpsEnabled": 1,
"publicHttpsPort": 9443,
})
if _, err := MigrateLegacyConfig(root, RuntimeValidationOptions{HasSuperAdmin: true, Production: true}); err != nil {
t.Fatal(err)
}
path := filepath.Join(root, "config", "config.json")
before, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
for i := 0; i < 2; i++ {
if err := Init(); err != nil {
t.Fatal(err)
}
cfg := CurrentConfig()
if cfg.PublicHTTPSPort != 9443 || cfg.HttpsPort != 443 {
t.Fatal("分区启动兼容快照丢失公网或内部端口")
}
if !cfg.IsSetupReady() || cfg.DbType != DBTypeSQLite || cfg.DbDSN != filepath.Join(root, "data", "existing.db") {
t.Fatal("partitioned startup lost initialization or database identity")
}
if cfg.WebDomain != "mail.example.com" || cfg.AdminRecoveryDomain != "recovery.mail.example.com" || cfg.SSL.CertificateRef != "data/ssl/site.crt" {
t.Fatal("partitioned startup lost domain or certificate path")
}
}
cfg, err := ReadConfig()
if err != nil {
t.Fatal(err)
}
if err := WriteConfig(cfg); err == nil {
t.Fatal("legacy writer must not overwrite partitioned config")
}
after, err := os.ReadFile(path)
if err != nil || !bytes.Equal(before, after) {
t.Fatal("loading or legacy writing changed partitioned root")
}
if err := os.WriteFile(filepath.Join(root, "config", "smtp.json"), []byte("{}"), 0600); err != nil {
t.Fatal(err)
}
if err := Init(); err == nil {
t.Fatal("corrupted partition must not silently fall back to legacy startup")
}
}