package main import ( "encoding/json" "fmt" "os" "path/filepath" "strings" "sync" "testing" "time" "qiweimanager/logger" ) func TestSaveLogEntryConcurrentWritesValidJSON(t *testing.T) { dir := t.TempDir() const total = 50 var wg sync.WaitGroup for i := 0; i < total; i++ { wg.Add(1) go func(i int) { defer wg.Done() err := SaveLogEntry(dir, logger.LogEntry{ ID: int64(i + 1), Time: "12:00:00", Source: "test", Type: "info", Content: fmt.Sprintf("entry-%d", i), Duration: int64(i), }) if err != nil { t.Errorf("SaveLogEntry failed: %v", err) } }(i) } wg.Wait() path := filepath.Join(dir, "operations", fmt.Sprintf("%s_operations.json", time.Now().Format("2006-01-02"))) data, err := os.ReadFile(path) if err != nil { t.Fatalf("read operation log failed: %v", err) } var entries []logger.LogEntry if err := json.Unmarshal(data, &entries); err != nil { t.Fatalf("operation log is not valid JSON: %v\n%s", err, string(data)) } if len(entries) != total { t.Fatalf("expected %d entries, got %d", total, len(entries)) } } func TestLoadOperationLogEntriesQuarantinesCorruptJSON(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "broken_operations.json") if err := os.WriteFile(path, []byte(`{"id":1}`), 0644); err != nil { t.Fatalf("write corrupt log failed: %v", err) } if _, err := loadOperationLogEntriesFromFile(path); err == nil { t.Fatal("expected corrupt JSON read to fail") } if _, err := os.Stat(path); !os.IsNotExist(err) { t.Fatalf("expected corrupt source file to be moved, stat err=%v", err) } matches, err := filepath.Glob(filepath.Join(dir, "broken_operations.corrupt.*.json")) if err != nil { t.Fatalf("glob corrupt backup failed: %v", err) } if len(matches) != 1 || !strings.Contains(filepath.Base(matches[0]), ".corrupt.") { t.Fatalf("expected one corrupt backup file, got %#v", matches) } } func TestLoadOperationLogEntriesRepairsTrailingGarbage(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "recover_operations.json") raw := `[{"id":1,"time":"12:00:00","source":"App","type":"info","content":"ok","duration":1}],{"duration":50}` if err := os.WriteFile(path, []byte(raw), 0644); err != nil { t.Fatalf("write recoverable log failed: %v", err) } entries, err := loadOperationLogEntriesFromFile(path) if err != nil { t.Fatalf("expected recoverable log, got %v", err) } if len(entries) != 1 || entries[0].Content != "ok" { t.Fatalf("unexpected entries: %#v", entries) } data, err := os.ReadFile(path) if err != nil { t.Fatalf("read repaired log failed: %v", err) } if !json.Valid(data) || strings.Contains(string(data), `{"duration":50}`) { t.Fatalf("expected clean repaired json, got %s", string(data)) } matches, err := filepath.Glob(filepath.Join(dir, "recover_operations.repaired.*.json")) if err != nil { t.Fatalf("glob repaired backup failed: %v", err) } if len(matches) != 1 { t.Fatalf("expected repaired backup, got %#v", matches) } } func TestOperationLogMojibakeRepair(t *testing.T) { got := repairMojibakeText("绋嬪簭鍒濆鎴愬姛") if got != "程序初始成功" { t.Fatalf("expected repaired Chinese, got %q", got) } entry := normalizeOperationLogEntry(logger.LogEntry{Content: "HTTP瀹㈡埛绔垵濮嬪寲瀹屾垚锛岀鍙? 10001"}) if !strings.Contains(entry.Content, "HTTP客户端初始化完成") { t.Fatalf("expected normalized content, got %q", entry.Content) } }