Skip to content

Commit d447fd4

Browse files
committed
test: fill remaining coverage gaps — findContributor, multi-chunk POST, edge cases
- Extract findContributor() from app.js to Util.js for direct unit testing - Add findContributor unit tests: match, no match, empty array, case sensitivity, duplicates - Add Util.post multi-chunk body test and UTF-8 multi-byte character test - Add /remove non-existent user e2e test (documents silent-success behavior) - Add getContributorInfo edge cases: empty includedRepositories, startDate in URLs - Add getRepositories boundary tests: exactly 100 repos, empty org (0 repos)
1 parent 3a0d2ab commit d447fd4

5 files changed

Lines changed: 168 additions & 12 deletions

File tree

src/server/app.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -427,14 +427,4 @@ io.on('connection', (socket) => {
427427
})
428428
})
429429

430-
function findContributor(contributorName, admindata) {
431-
let result = null
432-
433-
admindata.forEach((contributor) => {
434-
if (contributor.username === contributorName) {
435-
result = contributor
436-
}
437-
})
438-
439-
return result
440-
}
430+
const findContributor = Util.findContributor

src/server/util/Util.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ function post(req, res, callback) {
1717
}
1818
}
1919

20+
function findContributor(contributorName, admindata) {
21+
let result = null
22+
23+
admindata.forEach((contributor) => {
24+
if (contributor.username === contributorName) {
25+
result = contributor
26+
}
27+
})
28+
29+
return result
30+
}
31+
2032
module.exports = {
21-
post
33+
post,
34+
findContributor
2235
}

tests/e2e/server.e2e.test.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,21 @@ describe('Server E2E Tests', () => {
487487
const contribRes = await request(TEST_PORT, '/contributor?username=PavanTaddi9')
488488
assert.deepStrictEqual(contribRes.body, { error: 'Contributor PavanTaddi9 doesn\'t exist' })
489489
})
490+
491+
test('removing non-existent user still returns Success', async () => {
492+
const beforeData = await request(TEST_PORT, '/data')
493+
const countBefore = Object.keys(beforeData.body).length
494+
495+
const res = await request(TEST_PORT, '/remove', {
496+
method: 'POST',
497+
body: { token: TEST_ADMIN_PASSWORD, username: 'TOTALLY_FAKE_USER' },
498+
})
499+
assert.deepStrictEqual(res.body, { message: 'Success' })
500+
501+
// Data should be unchanged since user didn't exist
502+
const afterData = await request(TEST_PORT, '/data')
503+
assert.strictEqual(Object.keys(afterData.body).length, countBefore)
504+
})
490505
})
491506

492507
describe('POST /add', () => {

tests/unit/api-network.unit.test.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,44 @@ describe('API.getContributorInfo', () => {
183183
assert.strictEqual(result.mergedPRsNumber, -1)
184184
assert.strictEqual(result.issuesNumber, -1)
185185
})
186+
187+
test('handles empty includedRepositories array', async () => {
188+
mockAxiosGetByUrl({
189+
'/users/alice': { data: { avatar_url: 'https://example.com/a.png' } },
190+
'is:Open': { data: { total_count: 1 } },
191+
'is:Merged': { data: { total_count: 2 } },
192+
'is:issue': { data: { total_count: 3 } },
193+
})
194+
195+
const result = await API.getContributorInfo('Org', 'alice', [])
196+
197+
assert.strictEqual(result.openPRsNumber, 1)
198+
assert.strictEqual(result.mergedPRsNumber, 2)
199+
assert.strictEqual(result.issuesNumber, 3)
200+
// With empty repos, URLs should not contain any repo: filter
201+
assert.ok(!result.openPRsLink.includes('repo:'))
202+
assert.ok(!result.mergedPRsLink.includes('repo:'))
203+
assert.ok(!result.issuesLink.includes('repo:'))
204+
// But should still have chore exclusion
205+
assert.ok(result.openPRsLink.includes('-label:chore'))
206+
})
207+
208+
test('uses startDate from config in URLs', async () => {
209+
let capturedUrls = []
210+
axios.get = async (url) => {
211+
capturedUrls.push(url)
212+
if (url.includes('/users/')) return { data: { avatar_url: 'https://example.com/a.png' } }
213+
return { data: { total_count: 0 } }
214+
}
215+
216+
await API.getContributorInfo('Org', 'alice', ['repo1'])
217+
218+
// Config fixture has startDate: '2024-12-01'
219+
const searchUrls = capturedUrls.filter(u => u.includes('/search/'))
220+
for (const url of searchUrls) {
221+
assert.ok(url.includes('2024-12-01'), `URL should contain startDate: ${url}`)
222+
}
223+
})
186224
})
187225

188226
describe('API.getRepositories', () => {
@@ -225,6 +263,30 @@ describe('API.getRepositories', () => {
225263
// fetchRepositories returns '' on failure, length <= 99 stops pagination
226264
assert.deepStrictEqual(result, [''])
227265
})
266+
267+
test('exactly 100 repos triggers another page fetch', async () => {
268+
const page1 = Array.from({ length: 100 }, (_, i) => ({ name: `repo-${i}` }))
269+
const page2 = [] // empty page means no more repos
270+
let callNum = 0
271+
272+
axios.get = async (_url) => {
273+
callNum++
274+
if (callNum === 1) return { data: page1 }
275+
return { data: page2 }
276+
}
277+
278+
const result = await API.getRepositories('TestOrg')
279+
assert.strictEqual(result.length, 2) // fetched 2 pages
280+
assert.strictEqual(result[0].length, 100)
281+
assert.strictEqual(result[1].length, 0) // empty second page
282+
})
283+
284+
test('returns empty result for org with no repos', async () => {
285+
mockAxiosGet({ data: [] })
286+
287+
const result = await API.getRepositories('EmptyOrg')
288+
assert.deepStrictEqual(result, [[]])
289+
})
228290
})
229291

230292
describe('API internal error handling', () => {

tests/unit/util.unit.test.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,80 @@ describe('Util.post', () => {
126126
})
127127
})
128128
})
129+
130+
test('handles multi-chunk POST body', async () => {
131+
const req = new EventEmitter()
132+
req.method = 'POST'
133+
const res = mockRes()
134+
const fullBody = { key: 'value', nested: { a: 1 } }
135+
const json = JSON.stringify(fullBody)
136+
const mid = Math.floor(json.length / 2)
137+
138+
process.nextTick(() => {
139+
req.emit('data', Buffer.from(json.slice(0, mid)))
140+
req.emit('data', Buffer.from(json.slice(mid)))
141+
req.emit('end')
142+
})
143+
144+
await new Promise((resolve) => {
145+
Util.post(req, res, (params) => {
146+
assert.deepStrictEqual(params, fullBody)
147+
resolve()
148+
})
149+
})
150+
})
151+
152+
test('handles UTF-8 multi-byte characters in JSON', async () => {
153+
const body = { name: '日本語テスト', emoji: '🚀' }
154+
const req = mockReq('POST', JSON.stringify(body))
155+
const res = mockRes()
156+
157+
await new Promise((resolve) => {
158+
Util.post(req, res, (params) => {
159+
assert.deepStrictEqual(params, body)
160+
resolve()
161+
})
162+
})
163+
})
164+
})
165+
166+
describe('Util.findContributor', () => {
167+
test('returns matching contributor object', () => {
168+
const admindata = [
169+
{ username: 'alice', avatarUrl: 'https://example.com/alice.png' },
170+
{ username: 'bob', avatarUrl: 'https://example.com/bob.png' },
171+
]
172+
const result = Util.findContributor('bob', admindata)
173+
assert.deepStrictEqual(result, { username: 'bob', avatarUrl: 'https://example.com/bob.png' })
174+
})
175+
176+
test('returns null for non-existent contributor', () => {
177+
const admindata = [
178+
{ username: 'alice', avatarUrl: 'https://example.com/alice.png' },
179+
]
180+
const result = Util.findContributor('nonexistent', admindata)
181+
assert.strictEqual(result, null)
182+
})
183+
184+
test('returns null for empty admindata array', () => {
185+
const result = Util.findContributor('alice', [])
186+
assert.strictEqual(result, null)
187+
})
188+
189+
test('is case-sensitive', () => {
190+
const admindata = [
191+
{ username: 'Alice', avatarUrl: 'https://example.com/alice.png' },
192+
]
193+
assert.strictEqual(Util.findContributor('alice', admindata), null)
194+
assert.notStrictEqual(Util.findContributor('Alice', admindata), null)
195+
})
196+
197+
test('returns last match if duplicates exist', () => {
198+
const admindata = [
199+
{ username: 'alice', avatarUrl: 'first' },
200+
{ username: 'alice', avatarUrl: 'second' },
201+
]
202+
const result = Util.findContributor('alice', admindata)
203+
assert.strictEqual(result.avatarUrl, 'second')
204+
})
129205
})

0 commit comments

Comments
 (0)