Skip to content

Commit 5ba1e33

Browse files
committed
Add integration tests
1 parent 3e5388e commit 5ba1e33

11 files changed

Lines changed: 2653 additions & 3 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
on: [push]
2+
3+
jobs:
4+
integration-test:
5+
runs-on: ubuntu-latest
6+
7+
steps:
8+
- uses: actions/checkout@v4
9+
10+
- name: Set up Go
11+
uses: actions/setup-go@v5
12+
with:
13+
go-version: 1.25
14+
15+
- name: Build Docker image
16+
run: docker build -t simple-retro-backend:test .
17+
18+
- name: Start service container
19+
run: |
20+
docker run -d \
21+
--name backend-test \
22+
-p 7878:7878 \
23+
-e SESSION_SECRET=${{ secrets.SESSION_SECRET }} \
24+
simple-retro-backend:test
25+
26+
# Wait for service to be ready
27+
for i in {1..30}; do
28+
if curl -s http://localhost:7878/api/health > /dev/null; then
29+
echo "Service is ready"
30+
break
31+
fi
32+
echo "Waiting for service..."
33+
sleep 1
34+
done
35+
36+
- name: Run integration tests
37+
run: go test -v ./integration_test/...
38+
env:
39+
API_BASE_URL: http://localhost:7878
40+
41+
- name: Stop container
42+
if: always()
43+
run: docker stop backend-test && docker rm backend-test

.github/workflows/unit_test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ jobs:
1010
- name: Setup Go
1111
uses: actions/setup-go@v4
1212
with:
13-
go-version: 1.21
13+
go-version: 1.25
1414

1515
- name: Get dependencies
1616
run: |
1717
go get .
1818
1919
- name: Build
20-
run: go build -v ./...
20+
run: go test -v $(go list ./... | grep -v /integration_test) # Exclude integration tests from build step
2121

2222
- name: Test
23-
run: go test -v ./...
23+
run: go test -v $(go list ./... | grep -v /integration_test) # Exclude integration tests from test step

integration_test/answer_test.go

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
package integration_test
2+
3+
import (
4+
"api/types"
5+
"net/http"
6+
"testing"
7+
8+
"github.com/google/uuid"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
func TestCreateAnswer(t *testing.T) {
14+
client := NewTestClient(t)
15+
16+
t.Run("successfully creates an answer", func(t *testing.T) {
17+
_, err := client.SetupRetrospective("Answer Test Retro", "Description")
18+
require.NoError(t, err)
19+
20+
question, resp, err := client.CreateQuestion("Test Question?")
21+
require.NoError(t, err)
22+
resp.Body.Close()
23+
24+
answer, resp, err := client.CreateAnswer(question.ID, "Test Answer")
25+
require.NoError(t, err)
26+
defer resp.Body.Close()
27+
28+
assert.Equal(t, http.StatusOK, resp.StatusCode)
29+
assert.NotEqual(t, uuid.Nil, answer.ID)
30+
assert.Equal(t, question.ID, answer.QuestionID)
31+
assert.Equal(t, "Test Answer", answer.Text)
32+
assert.Equal(t, 1, answer.Position)
33+
assert.Equal(t, 0, answer.Votes)
34+
})
35+
36+
t.Run("creates answer with empty text", func(t *testing.T) {
37+
_, err := client.SetupRetrospective("Empty Answer Retro", "Description")
38+
require.NoError(t, err)
39+
40+
question, resp, err := client.CreateQuestion("Test Question?")
41+
require.NoError(t, err)
42+
resp.Body.Close()
43+
44+
answer, resp, err := client.CreateAnswer(question.ID, "")
45+
require.NoError(t, err)
46+
defer resp.Body.Close()
47+
48+
assert.Equal(t, http.StatusOK, resp.StatusCode)
49+
assert.Equal(t, "", answer.Text)
50+
})
51+
52+
t.Run("creates multiple answers with incrementing positions", func(t *testing.T) {
53+
_, err := client.SetupRetrospective("Multi Answer Retro", "Description")
54+
require.NoError(t, err)
55+
56+
question, resp, err := client.CreateQuestion("Test Question?")
57+
require.NoError(t, err)
58+
resp.Body.Close()
59+
60+
a1, resp, err := client.CreateAnswer(question.ID, "Answer 1")
61+
require.NoError(t, err)
62+
resp.Body.Close()
63+
64+
a2, resp, err := client.CreateAnswer(question.ID, "Answer 2")
65+
require.NoError(t, err)
66+
resp.Body.Close()
67+
68+
a3, resp, err := client.CreateAnswer(question.ID, "Answer 3")
69+
require.NoError(t, err)
70+
resp.Body.Close()
71+
72+
assert.Equal(t, 1, a1.Position)
73+
assert.Equal(t, 2, a2.Position)
74+
assert.Equal(t, 3, a3.Position)
75+
})
76+
77+
t.Run("fails with text exceeding limit", func(t *testing.T) {
78+
_, err := client.SetupRetrospective("Long Answer Retro", "Description")
79+
require.NoError(t, err)
80+
81+
question, resp, err := client.CreateQuestion("Test Question?")
82+
require.NoError(t, err)
83+
resp.Body.Close()
84+
85+
longText := GenerateString(601) // ANSWER_LIMIT is 600
86+
_, resp, err = client.CreateAnswer(question.ID, longText)
87+
require.NoError(t, err)
88+
defer resp.Body.Close()
89+
90+
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
91+
92+
errResp, err := ParseErrorResponse(resp)
93+
require.NoError(t, err)
94+
assert.Contains(t, errResp.Error, "answer text too big")
95+
})
96+
97+
t.Run("fails without retrospective cookie", func(t *testing.T) {
98+
// Use a fresh client without any cookies
99+
freshClient := NewTestClient(t)
100+
101+
_, resp, err := freshClient.CreateAnswer(uuid.New(), "Unauthorized answer")
102+
require.NoError(t, err)
103+
defer resp.Body.Close()
104+
105+
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
106+
})
107+
}
108+
109+
func TestUpdateAnswer(t *testing.T) {
110+
client := NewTestClient(t)
111+
112+
t.Run("successfully updates an answer", func(t *testing.T) {
113+
_, err := client.SetupRetrospective("Update Answer Retro", "Description")
114+
require.NoError(t, err)
115+
116+
question, resp, err := client.CreateQuestion("Test Question?")
117+
require.NoError(t, err)
118+
resp.Body.Close()
119+
120+
answer, resp, err := client.CreateAnswer(question.ID, "Original text")
121+
require.NoError(t, err)
122+
resp.Body.Close()
123+
124+
updated, resp, err := client.UpdateAnswer(answer.ID, question.ID, "Updated text")
125+
require.NoError(t, err)
126+
defer resp.Body.Close()
127+
128+
assert.Equal(t, http.StatusOK, resp.StatusCode)
129+
assert.Equal(t, answer.ID, updated.ID)
130+
assert.Equal(t, "Updated text", updated.Text)
131+
})
132+
133+
t.Run("preserves text when updating with empty text", func(t *testing.T) {
134+
// Note: The API preserves the original text when updating with empty text
135+
_, err := client.SetupRetrospective("Empty Update Answer", "Description")
136+
require.NoError(t, err)
137+
138+
question, resp, err := client.CreateQuestion("Test Question?")
139+
require.NoError(t, err)
140+
resp.Body.Close()
141+
142+
answer, resp, err := client.CreateAnswer(question.ID, "Original text")
143+
require.NoError(t, err)
144+
resp.Body.Close()
145+
146+
updated, resp, err := client.UpdateAnswer(answer.ID, question.ID, "")
147+
require.NoError(t, err)
148+
defer resp.Body.Close()
149+
150+
assert.Equal(t, http.StatusOK, resp.StatusCode)
151+
// API preserves original text when empty string is sent
152+
assert.Equal(t, "Original text", updated.Text)
153+
})
154+
155+
t.Run("fails with text exceeding limit", func(t *testing.T) {
156+
_, err := client.SetupRetrospective("Long Update Answer", "Description")
157+
require.NoError(t, err)
158+
159+
question, resp, err := client.CreateQuestion("Test Question?")
160+
require.NoError(t, err)
161+
resp.Body.Close()
162+
163+
answer, resp, err := client.CreateAnswer(question.ID, "Original text")
164+
require.NoError(t, err)
165+
resp.Body.Close()
166+
167+
longText := GenerateString(601)
168+
_, resp, err = client.UpdateAnswer(answer.ID, question.ID, longText)
169+
require.NoError(t, err)
170+
defer resp.Body.Close()
171+
172+
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
173+
})
174+
175+
t.Run("returns 404 for non-existent answer", func(t *testing.T) {
176+
_, err := client.SetupRetrospective("Non-existent Update", "Description")
177+
require.NoError(t, err)
178+
179+
question, resp, err := client.CreateQuestion("Test Question?")
180+
require.NoError(t, err)
181+
resp.Body.Close()
182+
183+
nonExistentID := uuid.New()
184+
_, resp, err = client.UpdateAnswer(nonExistentID, question.ID, "New text")
185+
require.NoError(t, err)
186+
defer resp.Body.Close()
187+
188+
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
189+
})
190+
191+
t.Run("fails with invalid UUID", func(t *testing.T) {
192+
_, err := client.SetupRetrospective("Invalid UUID Answer", "Description")
193+
require.NoError(t, err)
194+
195+
resp, err := client.DoRequest(http.MethodPatch, "/api/answer/invalid-uuid", types.AnswerCreateRequest{
196+
QuestionID: uuid.New(),
197+
Text: "text",
198+
})
199+
require.NoError(t, err)
200+
defer resp.Body.Close()
201+
202+
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
203+
})
204+
}
205+
206+
func TestDeleteAnswer(t *testing.T) {
207+
client := NewTestClient(t)
208+
209+
t.Run("successfully deletes an answer", func(t *testing.T) {
210+
retro, err := client.SetupRetrospective("Delete Answer Retro", "Description")
211+
require.NoError(t, err)
212+
213+
question, resp, err := client.CreateQuestion("Test Question?")
214+
require.NoError(t, err)
215+
resp.Body.Close()
216+
217+
answer, resp, err := client.CreateAnswer(question.ID, "To be deleted")
218+
require.NoError(t, err)
219+
resp.Body.Close()
220+
221+
deleted, resp, err := client.DeleteAnswer(answer.ID)
222+
require.NoError(t, err)
223+
defer resp.Body.Close()
224+
225+
assert.Equal(t, http.StatusOK, resp.StatusCode)
226+
assert.Equal(t, answer.ID, deleted.ID)
227+
228+
// Verify deletion
229+
retrieved, resp, err := client.GetRetrospective(retro.ID)
230+
require.NoError(t, err)
231+
resp.Body.Close()
232+
233+
for _, q := range retrieved.Questions {
234+
for _, a := range q.Answers {
235+
assert.NotEqual(t, answer.ID, a.ID)
236+
}
237+
}
238+
})
239+
240+
t.Run("deletes answer with votes (cascade)", func(t *testing.T) {
241+
_, err := client.SetupRetrospective("Cascade Delete Answer", "Description")
242+
require.NoError(t, err)
243+
244+
question, resp, err := client.CreateQuestion("Test Question?")
245+
require.NoError(t, err)
246+
resp.Body.Close()
247+
248+
answer, resp, err := client.CreateAnswer(question.ID, "Answer with votes")
249+
require.NoError(t, err)
250+
resp.Body.Close()
251+
252+
// Add a vote
253+
resp, err = client.VoteAnswer(answer.ID, types.VoteAdd)
254+
require.NoError(t, err)
255+
resp.Body.Close()
256+
257+
// Delete the answer
258+
_, resp, err = client.DeleteAnswer(answer.ID)
259+
require.NoError(t, err)
260+
defer resp.Body.Close()
261+
262+
assert.Equal(t, http.StatusOK, resp.StatusCode)
263+
})
264+
265+
t.Run("returns 404 for non-existent answer", func(t *testing.T) {
266+
_, err := client.SetupRetrospective("Non-existent Delete", "Description")
267+
require.NoError(t, err)
268+
269+
nonExistentID := uuid.New()
270+
_, resp, err := client.DeleteAnswer(nonExistentID)
271+
require.NoError(t, err)
272+
defer resp.Body.Close()
273+
274+
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
275+
})
276+
}
277+
278+
func TestAnswerFullFlow(t *testing.T) {
279+
client := NewTestClient(t)
280+
281+
t.Run("complete CRUD flow", func(t *testing.T) {
282+
retro, err := client.SetupRetrospective("Answer Flow Retro", "Description")
283+
require.NoError(t, err)
284+
285+
question, resp, err := client.CreateQuestion("Test Question?")
286+
require.NoError(t, err)
287+
resp.Body.Close()
288+
289+
// Create
290+
created, resp, err := client.CreateAnswer(question.ID, "Initial answer")
291+
require.NoError(t, err)
292+
resp.Body.Close()
293+
assert.NotEqual(t, uuid.Nil, created.ID)
294+
assert.Equal(t, 1, created.Position)
295+
296+
// Verify creation
297+
retrieved, resp, err := client.GetRetrospective(retro.ID)
298+
require.NoError(t, err)
299+
resp.Body.Close()
300+
assert.Len(t, retrieved.Questions[0].Answers, 1)
301+
assert.Equal(t, created.ID, retrieved.Questions[0].Answers[0].ID)
302+
303+
// Update
304+
updated, resp, err := client.UpdateAnswer(created.ID, question.ID, "Updated answer")
305+
require.NoError(t, err)
306+
resp.Body.Close()
307+
assert.Equal(t, "Updated answer", updated.Text)
308+
309+
// Verify update
310+
retrieved, resp, err = client.GetRetrospective(retro.ID)
311+
require.NoError(t, err)
312+
resp.Body.Close()
313+
assert.Equal(t, "Updated answer", retrieved.Questions[0].Answers[0].Text)
314+
315+
// Delete
316+
deleted, resp, err := client.DeleteAnswer(created.ID)
317+
require.NoError(t, err)
318+
resp.Body.Close()
319+
assert.Equal(t, created.ID, deleted.ID)
320+
321+
// Verify deletion
322+
retrieved, resp, err = client.GetRetrospective(retro.ID)
323+
require.NoError(t, err)
324+
resp.Body.Close()
325+
assert.Empty(t, retrieved.Questions[0].Answers)
326+
})
327+
}

0 commit comments

Comments
 (0)