162 lines
4.7 KiB
Go
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("本次排版不能改变逐行审计格式")
|
|
}
|
|
}
|