Smail/test/backend/json_format_test.go
smail 560605a8e1
Some checks are pending
Security Checks / security (push) Waiting to run
统一配置JSON缩进排版并保持摘要与历史兼容
2026-09-08 21:08:36 +08:00

162 lines
4.7 KiB
Go

package config
import (
"bytes"
"encoding/json"
"os"
"path/filepath"
"testing"
"time"
)
func requireIndentedJSON(t *testing.T, data []byte) {
t.Helper()
var expected bytes.Buffer
if err := json.Indent(&expected, data, "", " "); err != nil {
t.Fatal("配置不是有效 JSON")
}
if !bytes.Equal(data, expected.Bytes()) || !bytes.Contains(data, []byte("\n \"")) {
t.Fatal("配置未按两空格缩进分行保存")
}
}
func TestRuntimeJSONFormattingPreservesValuesAndDigests(t *testing.T) {
runtime := validRuntimeConfig()
runtime.Plugins.Plugins["telegram_push"] = PluginInstance{
Settings: json.RawMessage(`{"label":"中文配置","nested":{"enabled":true},"items":["邮件",9007199254740993]}`),
}
files, err := marshalRuntimeFiles(runtime)
if err != nil {
t.Fatal(err)
}
for _, data := range files {
requireIndentedJSON(t, data)
}
var root RootConfig
if err := json.Unmarshal(files["config.json"], &root); err != nil {
t.Fatal(err)
}
for section, reference := range root.Sections {
name, _ := FixedSectionFile(section)
if reference.SHA256 != SectionDigest(files[name]) {
t.Fatal("根配置未使用格式化后的摘要")
}
}
var plugins PluginsSectionConfig
if err := json.Unmarshal(files["plugins.json"], &plugins); err != nil {
t.Fatal(err)
}
var compact bytes.Buffer
if err := json.Compact(&compact, plugins.Plugins["telegram_push"].Settings); err != nil {
t.Fatal(err)
}
if !bytes.Equal(compact.Bytes(), runtime.Plugins.Plugins["telegram_push"].Settings) {
t.Fatal("格式化改变了中文、嵌套字段或大整数")
}
}
func TestRuntimeJSONFormattingLoadsCompactHistoryAndPersistsIndented(t *testing.T) {
for _, deployment := range []bool{false, true} {
name := "legacy"
if deployment {
name = "deployment"
}
t.Run(name, func(t *testing.T) {
root := t.TempDir()
if deployment {
if err := InitializeDeployment(root); err != nil {
t.Fatal(err)
}
}
current := validRuntimeConfig()
files, err := marshalRuntimeFiles(current)
if err != nil {
t.Fatal(err)
}
var oldRoot RootConfig
if err := json.Unmarshal(files["config.json"], &oldRoot); err != nil {
t.Fatal(err)
}
// 模拟真实的旧版单行文件,仍使用原字节的摘要。
for section, reference := range oldRoot.Sections {
var compact bytes.Buffer
if err := json.Compact(&compact, files[reference.File]); err != nil {
t.Fatal(err)
}
files[reference.File] = compact.Bytes()
reference.SHA256 = SectionDigest(compact.Bytes())
oldRoot.Sections[section] = reference
}
files["config.json"], err = json.Marshal(oldRoot)
if err != nil {
t.Fatal(err)
}
directory, err := secureManagerConfigDirectory(root)
if err != nil {
t.Fatal(err)
}
for filename, data := range files {
if err := os.WriteFile(filepath.Join(directory, filename), data, 0600); err != nil {
t.Fatal(err)
}
}
validation := RuntimeValidationOptions{HasSuperAdmin: true}
loader := NewConfigLoader(root, ConfigLoaderOptions{Validation: validation})
loaded, err := loader.Load()
if err != nil || loaded.Generation != current.Generation {
t.Fatalf("旧版单行配置不能读取:%v", err)
}
store := NewTransactionStore(root, validation)
if _, err := store.CreateBackup(loaded); err != nil {
t.Fatal(err)
}
if _, err := store.LoadBackup(loaded.Generation); err != nil {
t.Fatal(err)
}
candidate := cloneRuntimeConfig(loaded)
setCandidateGeneration(&candidate, loaded.Generation+1)
if err := store.Commit(loaded, candidate); err != nil {
t.Fatal(err)
}
if _, err := loader.Load(); err != nil {
t.Fatal(err)
}
for filename := range files {
data, err := os.ReadFile(filepath.Join(directory, filename))
if err != nil {
t.Fatal(err)
}
requireIndentedJSON(t, data)
}
entries, err := os.ReadDir(directory)
if err != nil || len(entries) != 5 {
t.Fatal("格式化不应生成额外配置文件")
}
})
}
}
func TestAuditRemainsJSONLAfterConfigFormatting(t *testing.T) {
writer, err := NewFileAuditWriter(t.TempDir())
if err != nil {
t.Fatal(err)
}
for index := 0; index < 2; index++ {
if err := writer.WriteConfigAudit(nil, ConfigAuditEvent{
Operation: ConfigOperationUpdate, Section: SectionRoot,
ActorID: "1", ActorRole: "super_admin", FromGeneration: 1,
ToGeneration: 2, Result: "success", CreatedAt: time.Now().UTC(),
}); err != nil {
t.Fatal(err)
}
}
data, err := os.ReadFile(writer.path)
if err != nil {
t.Fatal(err)
}
lines := bytes.Split(bytes.TrimSpace(data), []byte("\n"))
if len(lines) != 2 || !json.Valid(lines[0]) || !json.Valid(lines[1]) {
t.Fatal("本次排版不能改变逐行审计格式")
}
}