package httpapi import ( "os" "path/filepath" "testing" ) // 记忆文件是「下次启动打开哪个赛事」的唯一依据:必须能原子写入、能被读回, // 且绝不能因为半截或非法内容把服务端指到一个奇怪的目录(P0:导入后页面空白)。 func TestActiveProjectFileRoundTrip(t *testing.T) { dir := t.TempDir() if got := ReadActiveProject(dir); got != "" { t.Fatalf("没有记忆文件时应返回空串,得到 %q", got) } if got := ReadActiveProject(""); got != "" { t.Fatalf("数据目录为空时应返回空串,得到 %q", got) } if err := WriteActiveProject(dir, "秋季赛"); err != nil { t.Fatalf("写记忆文件: %v", err) } if got := ReadActiveProject(dir); got != "秋季赛" { t.Fatalf("读回的项目名应为 秋季赛,得到 %q", got) } raw, err := os.ReadFile(filepath.Join(dir, ActiveProjectFile)) if err != nil { t.Fatalf("读记忆文件: %v", err) } if string(raw) != "秋季赛\n" { t.Fatalf("记忆文件应是单行纯文本,得到 %q", raw) } // 临时文件必须已经改名掉,不能在数据目录里堆积。 entries, err := os.ReadDir(dir) if err != nil { t.Fatalf("读取数据目录: %v", err) } if len(entries) != 1 || entries[0].Name() != ActiveProjectFile { t.Fatalf("数据目录里应只剩记忆文件,得到 %v", entries) } // 覆盖写:旧项目名必须被整体替换,不能留下半截。 if err := WriteActiveProject(dir, "冬季赛"); err != nil { t.Fatalf("覆盖写记忆文件: %v", err) } if got := ReadActiveProject(dir); got != "冬季赛" { t.Fatalf("覆盖后应读到 冬季赛,得到 %q", got) } } func TestActiveProjectFileRejectsUnsafeContent(t *testing.T) { for _, tc := range []struct{ name, content string }{ {"路径穿越", "../逃逸\n"}, {"多行", "好名字\n坏名字\n"}, {"只有空白", " \n"}, {"保留名", "CON\n"}, {"分隔符", "a/b\n"}, {"含控制字符", "好\x00名字\n"}, } { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, ActiveProjectFile), []byte(tc.content), 0o644); err != nil { t.Fatalf("写记忆文件: %v", err) } if got := ReadActiveProject(dir); got != "" { t.Errorf("%s:非法内容应回空串交由调用方回落 default,得到 %q", tc.name, got) } } // 两侧空白要 trim:手工编辑或 Windows 记事本都会留下换行与空格。 dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, ActiveProjectFile), []byte(" 秋季赛 \r\n"), 0o644); err != nil { t.Fatalf("写记忆文件: %v", err) } if got := ReadActiveProject(dir); got != "秋季赛" { t.Fatalf("应 trim 掉空白后读回 秋季赛,得到 %q", got) } } func TestWriteActiveProjectRejectsBadInput(t *testing.T) { dir := t.TempDir() if err := WriteActiveProject(dir, "../逃逸"); err == nil { t.Fatal("非法项目名不该被写进记忆文件") } if err := WriteActiveProject("", "秋季赛"); err == nil { t.Fatal("数据目录为空时不该静默成功") } if _, err := os.Stat(filepath.Join(dir, ActiveProjectFile)); !os.IsNotExist(err) { t.Fatalf("非法输入不该留下记忆文件: %v", err) } }