Skip to content

Commit 547eab1

Browse files
authored
test: add comprehensive test suite for discussion feature
Merge pull request #31 from net-escape/copilot/add-tests-for-discussion-feature
2 parents afc3c8d + 09354fa commit 547eab1

7 files changed

Lines changed: 571 additions & 9 deletions

File tree

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import test from 'ava'
2+
import supertest from 'supertest'
3+
import app from '../../../src/app'
4+
import { encryptData } from '../../../src/services/crypto'
5+
import { userSeeds } from '../../seeds/user'
6+
7+
const server = app.listen()
8+
const request = supertest.agent(server)
9+
10+
const admin = userSeeds.admin
11+
12+
test.before('Login as admin', async (t) => {
13+
const login = await request
14+
.post('/api/account/login')
15+
.send({
16+
username: admin.uid,
17+
password: await encryptData(admin.pwd!),
18+
})
19+
20+
t.is(login.status, 200)
21+
})
22+
23+
test('Admin can see all discussions including private ones', async (t) => {
24+
const res = await request
25+
.get('/api/discussions')
26+
27+
t.is(res.status, 200)
28+
t.truthy(res.body.data)
29+
t.truthy(Array.isArray(res.body.data.docs))
30+
31+
// Admin should see all types including private clarifications
32+
const types = res.body.data.docs.map((d: any) => d.type)
33+
// Should include at least OpenDiscussion (1), PublicAnnouncement (2), and PrivateClarification (3)
34+
t.true(types.includes(1))
35+
t.true(types.includes(2))
36+
t.true(types.includes(3))
37+
})
38+
39+
test('Admin can access any private discussion', async (t) => {
40+
// Discussion #3 is a private discussion created by another user
41+
const res = await request
42+
.get('/api/discussions/3')
43+
44+
t.is(res.status, 200)
45+
t.is(res.body.data.discussionId, 3)
46+
t.truthy(res.body.data.title)
47+
t.is(res.body.data.isJury, true) // Admin should be marked as jury
48+
})
49+
50+
test('Admin can create public announcement', async (t) => {
51+
const res = await request
52+
.post('/api/discussions')
53+
.send({
54+
type: 2, // PublicAnnouncement
55+
title: 'Admin Announcement',
56+
content: 'This is an important announcement',
57+
})
58+
59+
t.is(res.status, 200)
60+
t.truthy(res.body.data.discussionId)
61+
62+
// Verify the created announcement
63+
const getRes = await request
64+
.get(`/api/discussions/${res.body.data.discussionId}`)
65+
66+
t.is(getRes.status, 200)
67+
t.is(getRes.body.data.type, 2)
68+
t.is(getRes.body.data.title, 'Admin Announcement')
69+
})
70+
71+
test('Admin can add comment to announcement', async (t) => {
72+
const res = await request
73+
.post('/api/discussions/2/comments')
74+
.send({
75+
content: 'Admin comment on announcement',
76+
})
77+
78+
t.is(res.status, 200)
79+
80+
// Verify comment was added
81+
const getRes = await request
82+
.get('/api/discussions/2')
83+
84+
t.is(getRes.status, 200)
85+
const comments = getRes.body.data.comments
86+
const lastComment = comments[comments.length - 1]
87+
t.is(lastComment.content, 'Admin comment on announcement')
88+
})
89+
90+
test('Admin can create open discussion', async (t) => {
91+
const res = await request
92+
.post('/api/discussions')
93+
.send({
94+
type: 1, // OpenDiscussion
95+
title: 'Admin Discussion',
96+
content: 'Discussion created by admin',
97+
})
98+
99+
t.is(res.status, 200)
100+
t.truthy(res.body.data.discussionId)
101+
})
102+
103+
test('Admin can create private clarification', async (t) => {
104+
const res = await request
105+
.post('/api/discussions')
106+
.send({
107+
type: 3, // PrivateClarification
108+
title: 'Admin Private Question',
109+
content: 'Private question from admin',
110+
})
111+
112+
t.is(res.status, 200)
113+
t.truthy(res.body.data.discussionId)
114+
})
115+
116+
test('Admin can create discussion with problem reference', async (t) => {
117+
const res = await request
118+
.post('/api/discussions')
119+
.send({
120+
type: 1,
121+
title: 'Admin Question about Problem',
122+
problem: 1001,
123+
content: 'Question about problem 1001',
124+
})
125+
126+
t.is(res.status, 200)
127+
t.truthy(res.body.data.discussionId)
128+
129+
const getRes = await request
130+
.get(`/api/discussions/${res.body.data.discussionId}`)
131+
132+
t.is(getRes.status, 200)
133+
t.truthy(getRes.body.data.problem)
134+
t.is(getRes.body.data.problem.pid, 1001)
135+
})
136+
137+
test('Admin sees isJury=true for discussions', async (t) => {
138+
const res = await request
139+
.get('/api/discussions/1')
140+
141+
t.is(res.status, 200)
142+
t.is(res.body.data.isJury, true)
143+
})
144+
145+
test.after.always('close server', () => {
146+
server.close()
147+
})
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import test from 'ava'
2+
import supertest from 'supertest'
3+
import app from '../../../src/app'
4+
import { encryptData } from '../../../src/services/crypto'
5+
import { userSeeds } from '../../seeds/user'
6+
7+
const server = app.listen()
8+
const request = supertest.agent(server)
9+
10+
const user = userSeeds.primaryuser
11+
12+
test.before('Login', async (t) => {
13+
const login = await request
14+
.post('/api/account/login')
15+
.send({
16+
username: user.uid,
17+
password: await encryptData(user.pwd!),
18+
})
19+
20+
t.is(login.status, 200)
21+
})
22+
23+
test('List discussions - logged in user sees more', async (t) => {
24+
const res = await request
25+
.get('/api/discussions')
26+
27+
t.is(res.status, 200)
28+
t.truthy(res.body.data)
29+
t.truthy(Array.isArray(res.body.data.docs))
30+
31+
// User should see public discussions and their own private ones
32+
for (const doc of res.body.data.docs) {
33+
t.truthy(doc.discussionId)
34+
t.truthy(doc.title)
35+
}
36+
})
37+
38+
test('Can access own private discussion', async (t) => {
39+
// Discussion #3 is a private discussion created by primaryuser
40+
const res = await request
41+
.get('/api/discussions/3')
42+
43+
t.is(res.status, 200)
44+
t.is(res.body.data.discussionId, 3)
45+
t.truthy(res.body.data.title)
46+
})
47+
48+
test('Cannot create open discussion as normal user', async (t) => {
49+
// Note: Current implementation requires admin/managed status for all public discussion types
50+
const res = await request
51+
.post('/api/discussions')
52+
.send({
53+
type: 1, // OpenDiscussion
54+
title: 'Test Discussion from User',
55+
content: 'This is a test discussion content',
56+
})
57+
58+
t.is(res.status, 200)
59+
t.is(res.body.success, false)
60+
t.is(res.body.code, 403)
61+
})
62+
63+
test('Create private clarification', async (t) => {
64+
const res = await request
65+
.post('/api/discussions')
66+
.send({
67+
type: 3, // PrivateClarification
68+
title: 'Private Question',
69+
content: 'This is a private question',
70+
})
71+
72+
t.is(res.status, 200)
73+
t.truthy(res.body.data.discussionId)
74+
})
75+
76+
test('Cannot create discussion with problem reference as normal user', async (t) => {
77+
// Note: Current implementation requires admin/managed status for OpenDiscussion type
78+
const res = await request
79+
.post('/api/discussions')
80+
.send({
81+
type: 1, // OpenDiscussion
82+
title: 'Question about Problem 1000',
83+
problem: 1000,
84+
content: 'I have a question about this problem',
85+
})
86+
87+
t.is(res.status, 200)
88+
t.is(res.body.success, false)
89+
t.is(res.body.code, 403)
90+
})
91+
92+
test('Cannot create public announcement as normal user', async (t) => {
93+
const res = await request
94+
.post('/api/discussions')
95+
.send({
96+
type: 2, // PublicAnnouncement
97+
title: 'Trying to create announcement',
98+
content: 'Should fail',
99+
})
100+
101+
t.is(res.status, 200)
102+
t.is(res.body.success, false)
103+
t.is(res.body.code, 403)
104+
})
105+
106+
test('Create discussion with missing title', async (t) => {
107+
const res = await request
108+
.post('/api/discussions')
109+
.send({
110+
type: 1,
111+
content: 'Content without title',
112+
})
113+
114+
t.is(res.status, 200)
115+
t.is(res.body.success, false)
116+
t.is(res.body.code, 400)
117+
})
118+
119+
test('Create discussion with missing content', async (t) => {
120+
const res = await request
121+
.post('/api/discussions')
122+
.send({
123+
type: 1,
124+
title: 'Title without content',
125+
})
126+
127+
t.is(res.status, 200)
128+
t.is(res.body.success, false)
129+
t.is(res.body.code, 400)
130+
})
131+
132+
test('Add comment to open discussion', async (t) => {
133+
const res = await request
134+
.post('/api/discussions/1/comments')
135+
.send({
136+
content: 'This is a test comment',
137+
})
138+
139+
t.is(res.status, 200)
140+
141+
// Verify comment was added
142+
const getRes = await request
143+
.get('/api/discussions/1')
144+
145+
t.is(getRes.status, 200)
146+
const comments = getRes.body.data.comments
147+
const lastComment = comments[comments.length - 1]
148+
t.is(lastComment.content, 'This is a test comment')
149+
t.is(lastComment.author.uid, user.uid)
150+
})
151+
152+
test('Cannot add comment to announcement as normal user', async (t) => {
153+
const res = await request
154+
.post('/api/discussions/2/comments')
155+
.send({
156+
content: 'Trying to comment on announcement',
157+
})
158+
159+
t.is(res.status, 200)
160+
t.is(res.body.success, false)
161+
t.is(res.body.code, 403)
162+
})
163+
164+
test('Add comment with missing content', async (t) => {
165+
const res = await request
166+
.post('/api/discussions/1/comments')
167+
.send({})
168+
169+
t.is(res.status, 200)
170+
t.is(res.body.success, false)
171+
t.is(res.body.code, 400)
172+
})
173+
174+
test('Add comment to non-existent discussion', async (t) => {
175+
const res = await request
176+
.post('/api/discussions/99999/comments')
177+
.send({
178+
content: 'Comment on non-existent discussion',
179+
})
180+
181+
t.is(res.status, 200)
182+
t.is(res.body.success, false)
183+
t.is(res.body.code, 404)
184+
})
185+
186+
test('Filter discussions by type', async (t) => {
187+
const res = await request
188+
.get('/api/discussions')
189+
.query({ type: 1 }) // OpenDiscussion only
190+
191+
t.is(res.status, 200)
192+
t.truthy(res.body.data)
193+
194+
for (const doc of res.body.data.docs) {
195+
t.is(doc.type, 1)
196+
}
197+
})
198+
199+
test('Filter discussions by author', async (t) => {
200+
const res = await request
201+
.get('/api/discussions')
202+
.query({ author: 'admin' })
203+
204+
t.is(res.status, 200)
205+
t.truthy(res.body.data)
206+
207+
for (const doc of res.body.data.docs) {
208+
t.is(doc.author.uid, 'admin')
209+
}
210+
})
211+
212+
test.after.always('close server', () => {
213+
server.close()
214+
})

0 commit comments

Comments
 (0)