gitcat
1package server
2
3import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "gitcat/internal/gitx"
9)
10
11// TestRenameRepoKeepsDiskInSync 是 P0-2 的回归测试:改名后数据库与磁盘目录
12// 必须同时指向新名字。修复前 UpdateRepo 会改 DB 名却不改目录,会把仓库
13// 变成「数据缺失」。
14func TestRenameRepoKeepsDiskInSync(t *testing.T) {
15 srv, st, alice, _ := newTestServer(t)
16 oldDir := makeRepo(t, srv, st, "oldname", alice)
17
18 repo, err := st.RepoByName("oldname")
19 if err != nil {
20 t.Fatalf("读仓库失败: %v", err)
21 }
22 if err := srv.renameRepo(repo, "newname"); err != nil {
23 t.Fatalf("改名失败: %v", err)
24 }
25 if _, err := os.Stat(oldDir); !os.IsNotExist(err) {
26 t.Error("旧目录仍在,磁盘未被同步重命名")
27 }
28 newDir := srv.repoPath("newname")
29 if info, err := os.Stat(filepath.Join(newDir, "HEAD")); err != nil || info.IsDir() {
30 t.Errorf("新目录不是有效的裸仓库: %v", err)
31 }
32 if !gitx.IsBareRepo(newDir) {
33 t.Error("改名后无法被识别为裸仓库")
34 }
35 got, err := st.RepoByName("newname")
36 if err != nil {
37 t.Fatalf("按新名查不到仓库: %v", err)
38 }
39 if got.ID != repo.ID {
40 t.Errorf("改名后 ID 变了: %d -> %d", repo.ID, got.ID)
41 }
42 if _, err := os.Stat(srv.repoPath(got.Name) + "/HEAD"); err != nil {
43 t.Errorf("改名后 DB 与磁盘脱钩: %v", err)
44 }
45}
46
47func TestRenameToExistingNameFails(t *testing.T) {
48 srv, st, alice, _ := newTestServer(t)
49 dirA := makeRepo(t, srv, st, "alpha", alice)
50 dirB := makeRepo(t, srv, st, "beta", alice)
51
52 repo, err := st.RepoByName("alpha")
53 if err != nil {
54 t.Fatalf("读仓库失败: %v", err)
55 }
56 if err := srv.renameRepo(repo, "beta"); err == nil {
57 t.Fatal("改名到已存在名称应失败")
58 }
59 // 失败后两侧都必须保持原样
60 if _, err := os.Stat(filepath.Join(dirA, "HEAD")); err != nil {
61 t.Errorf("失败的改名破坏了源目录: %v", err)
62 }
63 if _, err := os.Stat(filepath.Join(dirB, "HEAD")); err != nil {
64 t.Errorf("失败的改名破坏了目标目录: %v", err)
65 }
66 if got, _ := st.RepoByName("alpha"); got == nil {
67 t.Error("失败的改名却改了数据库")
68 }
69}
70
71func TestRenameRejectsIllegalNames(t *testing.T) {
72 srv, st, alice, _ := newTestServer(t)
73 makeRepo(t, srv, st, "keep", alice)
74
75 bad := []string{"", " ", "../escape", "a/b", `a\b`, "has space",
76 "admin", "-lead", ".hidden", "a..b", ".git", "a.git.git", "x" + string(rune(0))}
77 for _, name := range bad {
78 if got, err := validateNewRepoName(name); err == nil {
79 t.Errorf("非法名 %q 通过了校验,得到 %q", name, got)
80 }
81 }
82 // 与「新建项目」一致:输入带 .git 后缀时会被剥掉,而不是报错
83 if got, err := validateNewRepoName("proj.git"); err != nil || got != "proj" {
84 t.Errorf("带 .git 后缀应被接受并剥离,得到 %q(err=%v)", got, err)
85 }
86 if got, err := validateNewRepoName("good-name.v2"); err != nil || got != "good-name.v2" {
87 t.Errorf("合法名被误拒: got=%q err=%v", got, err)
88 }
89 if _, err := os.Stat(srv.repoPath("keep") + "/HEAD"); err != nil {
90 t.Errorf("非法改名破坏了目录: %v", err)
91 }
92 if got, _ := st.RepoByName("keep"); got == nil {
93 t.Error("非法改名却改了数据库")
94 }
95}
96
97// TestUpdateRepoCannotRename 锁死根因:普通更新不得改名字。
98func TestUpdateRepoCannotRename(t *testing.T) {
99 srv, st, _, _ := newTestServer(t)
100 makeRepo(t, srv, st, "solid", nil)
101 repo, _ := st.RepoByName("solid")
102 repo.Name = "hijacked"
103 repo.Description = "改描述"
104 if err := st.UpdateRepo(repo); err != nil {
105 t.Fatalf("UpdateRepo 失败: %v", err)
106 }
107 got, err := st.RepoByName("solid")
108 if err != nil {
109 t.Fatalf("原名查不到仓库(说明 UpdateRepo 改了名字): %v", err)
110 }
111 if got.Description != "改描述" {
112 t.Errorf("描述未更新: %q", got.Description)
113 }
114 if _, err := st.RepoByName("hijacked"); err == nil {
115 t.Error("UpdateRepo 竟然把仓库改名了")
116 }
117 if _, err := os.Stat(srv.repoPath("solid") + "/HEAD"); err != nil {
118 t.Errorf("磁盘目录被动过: %v", err)
119 }
120}
121
122// TestRenameKeepsPushHistory 改名不应影响已有的推送留痕。
123func TestRenameKeepsPushHistory(t *testing.T) {
124 srv, st, alice, _ := newTestServer(t)
125 makeRepo(t, srv, st, "proj", alice)
126 repo, _ := st.RepoByName("proj")
127 if _, err := st.CreatePush(repo.ID, &alice.ID, alice.Username, "Alice",
128 "main", "0000", "1111", nil); err != nil {
129 t.Fatalf("造推送记录失败: %v", err)
130 }
131 if err := srv.renameRepo(repo, "proj-renamed"); err != nil {
132 t.Fatalf("改名失败: %v", err)
133 }
134 pushes, err := st.PushesByRepo(repo.ID, 10)
135 if err != nil {
136 t.Fatalf("读推送记录失败: %v", err)
137 }
138 if len(pushes) != 1 {
139 t.Fatalf("改名后推送记录丢失,剩 %d 条", len(pushes))
140 }
141 if pushes[0].RepoName != "proj-renamed" {
142 t.Errorf("推送记录里的仓库名未跟着更新: %q", pushes[0].RepoName)
143 }
144}