1package archesrv
2
3import (
4 "io"
5 "net/http"
6 "strings"
7 "testing"
8)
9
10func TestForgeServer_Issues_CreateAndList(t *testing.T) {
11 s, ts := newTestServer(t)
12 _, client := loginAsAdmin(t, s, ts)
13 setupRepoWithDisk(t, s, "myrepo", "private")
14
15 resp, err := client.PostForm(ts.URL+"/myrepo/issues", map[string][]string{
16 "title": {"First issue"},
17 "body": {"some body text"},
18 })
19 if err != nil {
20 t.Fatalf("POST /myrepo/issues: %v", err)
21 }
22 resp.Body.Close()
23 if resp.StatusCode >= 400 {
24 t.Fatalf("create issue: got %d", resp.StatusCode)
25 }
26
27 resp2, err := client.Get(ts.URL + "/myrepo/issues")
28 if err != nil {
29 t.Fatalf("GET /myrepo/issues: %v", err)
30 }
31 defer resp2.Body.Close()
32 if resp2.StatusCode != http.StatusOK {
33 t.Errorf("GET /myrepo/issues: want 200, got %d", resp2.StatusCode)
34 }
35 body, _ := io.ReadAll(resp2.Body)
36 if !strings.Contains(string(body), "First issue") {
37 t.Error("issue list page should contain the issue title")
38 }
39}
40
41func TestForgeServer_Issues_UnauthenticatedCannotCreate(t *testing.T) {
42 s, ts := newTestServer(t)
43 setupRepoWithDisk(t, s, "myrepo", "public")
44
45 resp, err := http.PostForm(ts.URL+"/myrepo/issues", map[string][]string{
46 "title": {"sneaky issue"},
47 })
48 if err != nil {
49 t.Fatalf("POST /myrepo/issues: %v", err)
50 }
51 resp.Body.Close()
52 if resp.StatusCode != http.StatusUnauthorized {
53 t.Errorf("expected 401, got %d", resp.StatusCode)
54 }
55}
56
57func TestForgeServer_Issues_MissingTitleIsError(t *testing.T) {
58 s, ts := newTestServer(t)
59 _, client := loginAsAdmin(t, s, ts)
60 setupRepoWithDisk(t, s, "myrepo", "private")
61
62 resp, err := client.PostForm(ts.URL+"/myrepo/issues", map[string][]string{
63 "title": {""},
64 })
65 if err != nil {
66 t.Fatalf("POST: %v", err)
67 }
68 resp.Body.Close()
69 if resp.StatusCode != http.StatusBadRequest {
70 t.Errorf("empty title should return 400, got %d", resp.StatusCode)
71 }
72}
73
74func TestForgeServer_Issues_AddComment(t *testing.T) {
75 s, ts := newTestServer(t)
76 _, client := loginAsAdmin(t, s, ts)
77 setupRepoWithDisk(t, s, "myrepo", "private")
78
79 resp, _ := client.PostForm(ts.URL+"/myrepo/issues", map[string][]string{
80 "title": {"Bug report"},
81 "body": {"details"},
82 })
83 resp.Body.Close()
84
85 issueID := extractIssueIDFromList(t, client, ts.URL+"/myrepo/issues")
86
87 resp2, err := client.PostForm(ts.URL+"/myrepo/issue/comment", map[string][]string{
88 "issue_id": {issueID},
89 "text": {"looks good"},
90 })
91 if err != nil {
92 t.Fatalf("POST comment: %v", err)
93 }
94 resp2.Body.Close()
95 if resp2.StatusCode >= 400 {
96 t.Errorf("add comment: got %d", resp2.StatusCode)
97 }
98}
99
100func TestForgeServer_Issues_SetStatus(t *testing.T) {
101 s, ts := newTestServer(t)
102 _, client := loginAsAdmin(t, s, ts)
103 setupRepoWithDisk(t, s, "myrepo", "private")
104
105 client.PostForm(ts.URL+"/myrepo/issues", map[string][]string{"title": {"Close me"}}) //nolint:errcheck
106
107 issueID := extractIssueIDFromList(t, client, ts.URL+"/myrepo/issues")
108
109 resp, err := client.PostForm(ts.URL+"/myrepo/issue/status", map[string][]string{
110 "issue_id": {issueID},
111 "status": {"closed"},
112 })
113 if err != nil {
114 t.Fatalf("POST status: %v", err)
115 }
116 resp.Body.Close()
117 if resp.StatusCode >= 400 {
118 t.Errorf("set status: got %d", resp.StatusCode)
119 }
120}
121
122func TestForgeServer_Issues_ViewPage(t *testing.T) {
123 s, ts := newTestServer(t)
124 _, client := loginAsAdmin(t, s, ts)
125 setupRepoWithDisk(t, s, "myrepo", "private")
126
127 resp, err := client.PostForm(ts.URL+"/myrepo/issues", map[string][]string{
128 "title": {"View me"},
129 "body": {"body of the issue"},
130 })
131 if err != nil {
132 t.Fatalf("POST issue: %v", err)
133 }
134 resp.Body.Close()
135
136 loc := resp.Header.Get("Location")
137 if loc == "" {
138 t.Skip("no Location header on issue create; skipping view test")
139 }
140
141 resp2, err := client.Get(ts.URL + loc)
142 if err != nil {
143 t.Fatalf("GET issue page: %v", err)
144 }
145 defer resp2.Body.Close()
146 if resp2.StatusCode != http.StatusOK {
147 t.Errorf("issue view: want 200, got %d", resp2.StatusCode)
148 }
149 body, _ := io.ReadAll(resp2.Body)
150 if !strings.Contains(string(body), "View me") {
151 t.Error("issue page should show the issue title")
152 }
153 if !strings.Contains(string(body), "body of the issue") {
154 t.Error("issue page should show the issue body")
155 }
156}
157
158func TestForgeServer_Issues_ViewMissingIssue404(t *testing.T) {
159 s, ts := newTestServer(t)
160 _, client := loginAsAdmin(t, s, ts)
161 setupRepoWithDisk(t, s, "myrepo", "private")
162
163 resp, err := client.Get(ts.URL + "/myrepo/issue?id=nonexistentissue")
164 if err != nil {
165 t.Fatalf("GET: %v", err)
166 }
167 resp.Body.Close()
168 if resp.StatusCode != http.StatusNotFound {
169 t.Errorf("missing issue: want 404, got %d", resp.StatusCode)
170 }
171}
172
173func TestForgeServer_Issues_ViewOnMissingRepo404(t *testing.T) {
174 s, ts := newTestServer(t)
175 s.db.CreateUser("admin", "adminpass", true) //nolint:errcheck
176 client := loginAs(t, ts, "admin", "adminpass")
177
178 resp, err := client.Get(ts.URL + "/ghost/issues")
179 if err != nil {
180 t.Fatalf("GET: %v", err)
181 }
182 resp.Body.Close()
183 if resp.StatusCode != http.StatusNotFound {
184 t.Errorf("missing repo: want 404, got %d", resp.StatusCode)
185 }
186}
187
188func extractIssueIDFromList(t *testing.T, client *http.Client, listURL string) string {
189 t.Helper()
190 resp, err := client.Get(listURL)
191 if err != nil {
192 t.Fatalf("GET issue list: %v", err)
193 }
194 body, _ := io.ReadAll(resp.Body)
195 resp.Body.Close()
196 page := string(body)
197
198 idx := strings.Index(page, "?id=")
199 if idx == -1 {
200 t.Skip("could not find issue ID in listing; skipping")
201 }
202 end := strings.IndexAny(page[idx+4:], `"& `)
203 if end == -1 {
204 end = 64
205 }
206 return page[idx+4 : idx+4+end]
207}