Skip to content

Commit 2ba5c2e

Browse files
authored
Concurrent file uploads and result creation (#34)
* Concurrent file uploads and result creation * Update tests
1 parent 79d0422 commit 2ba5c2e

File tree

6 files changed

+213
-89
lines changed

6 files changed

+213
-89
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "qas-cli",
3-
"version": "0.4.3",
3+
"version": "0.4.4",
44
"description": "QAS CLI is a command line tool for submitting your automation test results to QA Sphere at https://qasphere.com/",
55
"type": "module",
66
"main": "./build/bin/qasphere.js",

src/api/run.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CreateResultRequest, ResourceId, RunTCase } from './schemas'
1+
import { CreateResultsRequest, ResourceId, RunTCase } from './schemas'
22
import { jsonResponse, withJson } from './utils'
33

44
export interface CreateRunRequest {
@@ -24,16 +24,16 @@ export const createRunApi = (fetcher: typeof fetch) => {
2424
fetcher(`/api/public/v0/project/${projectCode}/run/${runId}/tcase`)
2525
.then((r) => jsonResponse<{ tcases: RunTCase[] }>(r))
2626
.then((r) => r.tcases),
27-
createResultStatus: (
27+
28+
createResults: (
2829
projectCode: ResourceId,
2930
runId: ResourceId,
30-
tcaseId: ResourceId,
31-
req: CreateResultRequest
31+
req: CreateResultsRequest
3232
) =>
33-
fetcher(`/api/public/v0/project/${projectCode}/run/${runId}/tcase/${tcaseId}/result`, {
33+
fetcher(`/api/public/v0/project/${projectCode}/run/${runId}/result/batch`, {
3434
body: JSON.stringify(req),
3535
method: 'POST',
36-
}).then((r) => jsonResponse<{ id: number }>(r)),
36+
}).then((r) => jsonResponse<{ ids: number[] }>(r)),
3737

3838
createRun: (projectCode: ResourceId, req: CreateRunRequest) =>
3939
fetcher(`/api/public/v0/project/${projectCode}/run`, {

src/api/schemas.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ export interface RunTCase {
1919
folder: Folder
2020
}
2121

22-
export interface CreateResultRequest {
22+
export interface CreateResultsRequestItem {
23+
tcaseId: string
2324
status: ResultStatus
2425
comment?: string
2526
}
27+
28+
export interface CreateResultsRequest {
29+
items: CreateResultsRequestItem[]
30+
}

src/tests/result-upload.spec.ts

Lines changed: 63 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { setupServer } from 'msw/node'
44
import { HttpResponse, http } from 'msw'
55
import { runTestCases } from './fixtures/testcases'
66
import { countMockedApiCalls } from './utils'
7+
import { setMaxResultsInRequest } from '../utils/result-upload/ResultUploader'
78

89
const projectCode = 'TEST'
910
const runId = '1'
@@ -56,11 +57,11 @@ const server = setupServer(
5657
})
5758
}),
5859
http.post(
59-
new RegExp(`${baseURL}/api/public/v0/project/${projectCode}/run/${runId}/tcase/.+/result`),
60+
new RegExp(`${baseURL}/api/public/v0/project/${projectCode}/run/${runId}/result/batch`),
6061
({ request }) => {
6162
expect(request.headers.get('Authorization')).toEqual('ApiKey QAS_TOKEN')
6263
return HttpResponse.json({
63-
id: 0,
64+
ids: [0],
6465
})
6566
}
6667
),
@@ -83,12 +84,13 @@ afterAll(() => {
8384
afterEach(() => {
8485
server.resetHandlers()
8586
server.events.removeAllListeners()
87+
setMaxResultsInRequest(50)
8688
})
8789

8890
const countFileUploadApiCalls = () =>
8991
countMockedApiCalls(server, (req) => req.url.endsWith('/file'))
9092
const countResultUploadApiCalls = () =>
91-
countMockedApiCalls(server, (req) => new URL(req.url).pathname.endsWith('/result'))
93+
countMockedApiCalls(server, (req) => new URL(req.url).pathname.endsWith('/result/batch'))
9294

9395
const fileTypes = [
9496
{
@@ -116,22 +118,23 @@ fileTypes.forEach((fileType) => {
116118
]
117119

118120
for (const pattern of patterns) {
119-
const fileUploadCount = countFileUploadApiCalls()
120-
const tcaseUploadCount = countResultUploadApiCalls()
121+
const numFileUploadCalls = countFileUploadApiCalls()
122+
const numResultUploadCalls = countResultUploadApiCalls()
121123
await run(pattern)
122-
expect(fileUploadCount()).toBe(0)
123-
expect(tcaseUploadCount()).toBe(5)
124+
expect(numFileUploadCalls()).toBe(0)
125+
expect(numResultUploadCalls()).toBe(1) // 5 results total
124126
}
125127
})
126128

127129
test('Passing correct Run URL pattern without https, should result in success', async () => {
128-
const fileUploadCount = countFileUploadApiCalls()
129-
const tcaseUploadCount = countResultUploadApiCalls()
130+
const numFileUploadCalls = countFileUploadApiCalls()
131+
const numResultUploadCalls = countResultUploadApiCalls()
132+
setMaxResultsInRequest(1)
130133
await run(
131134
`${fileType.command} -r ${qasHost}/project/${projectCode}/run/${runId} ${fileType.dataBasePath}/matching-tcases.${fileType.fileExtension}`
132135
)
133-
expect(fileUploadCount()).toBe(0)
134-
expect(tcaseUploadCount()).toBe(5)
136+
expect(numFileUploadCalls()).toBe(0)
137+
expect(numResultUploadCalls()).toBe(5) // 5 results total
135138
})
136139

137140
test('Passing incorrect Run URL pattern should result in failure', async () => {
@@ -141,8 +144,8 @@ fileTypes.forEach((fileType) => {
141144
]
142145

143146
for (const pattern of patterns) {
144-
const fileUploadCount = countFileUploadApiCalls()
145-
const tcaseUploadCount = countResultUploadApiCalls()
147+
const numFileUploadCalls = countFileUploadApiCalls()
148+
const numResultUploadCalls = countResultUploadApiCalls()
146149
let isError = false
147150

148151
try {
@@ -151,105 +154,110 @@ fileTypes.forEach((fileType) => {
151154
isError = true
152155
}
153156
expect(isError).toBeTruthy()
154-
expect(fileUploadCount()).toBe(0)
155-
expect(tcaseUploadCount()).toBe(0)
157+
expect(numFileUploadCalls()).toBe(0)
158+
expect(numResultUploadCalls()).toBe(0)
156159
}
157160
})
158161
})
159162

160163
describe('Uploading test results', () => {
161164
test('Test cases on reports with all matching test cases on QAS should be successful', async () => {
162-
const fileUploadCount = countFileUploadApiCalls()
163-
const tcaseUploadCount = countResultUploadApiCalls()
165+
const numFileUploadCalls = countFileUploadApiCalls()
166+
const numResultUploadCalls = countResultUploadApiCalls()
167+
setMaxResultsInRequest(2)
164168
await run(
165169
`${fileType.command} -r ${runURL} ${fileType.dataBasePath}/matching-tcases.${fileType.fileExtension}`
166170
)
167-
expect(fileUploadCount()).toBe(0)
168-
expect(tcaseUploadCount()).toBe(5)
171+
expect(numFileUploadCalls()).toBe(0)
172+
expect(numResultUploadCalls()).toBe(3) // 5 results total
169173
})
170174

171175
test('Test cases on reports with a missing test case on QAS should throw an error', async () => {
172-
const fileUploadCount = countFileUploadApiCalls()
173-
const tcaseUploadCount = countResultUploadApiCalls()
176+
const numFileUploadCalls = countFileUploadApiCalls()
177+
const numResultUploadCalls = countResultUploadApiCalls()
174178
await expect(
175179
run(
176180
`${fileType.command} -r ${runURL} ${fileType.dataBasePath}/missing-tcases.${fileType.fileExtension}`
177181
)
178182
).rejects.toThrowError()
179-
expect(fileUploadCount()).toBe(0)
180-
expect(tcaseUploadCount()).toBe(0)
183+
expect(numFileUploadCalls()).toBe(0)
184+
expect(numResultUploadCalls()).toBe(0)
181185
})
182186

183187
test('Test cases on reports with a missing test case on QAS should be successful when forced', async () => {
184-
const fileUploadCount = countFileUploadApiCalls()
185-
const tcaseUploadCount = countResultUploadApiCalls()
188+
const numFileUploadCalls = countFileUploadApiCalls()
189+
const numResultUploadCalls = countResultUploadApiCalls()
190+
setMaxResultsInRequest(3)
186191
await run(
187192
`${fileType.command} -r ${runURL} --force ${fileType.dataBasePath}/missing-tcases.${fileType.fileExtension}`
188193
)
189-
expect(fileUploadCount()).toBe(0)
190-
expect(tcaseUploadCount()).toBe(4)
194+
expect(numFileUploadCalls()).toBe(0)
195+
expect(numResultUploadCalls()).toBe(2) // 4 results total
191196
})
192197

193198
test('Test cases on reports with missing test cases should be successful with --ignore-unmatched', async () => {
194-
const fileUploadCount = countFileUploadApiCalls()
195-
const tcaseUploadCount = countResultUploadApiCalls()
199+
const numFileUploadCalls = countFileUploadApiCalls()
200+
const numResultUploadCalls = countResultUploadApiCalls()
196201
await run(
197202
`${fileType.command} -r ${runURL} --ignore-unmatched ${fileType.dataBasePath}/missing-tcases.${fileType.fileExtension}`
198203
)
199-
expect(fileUploadCount()).toBe(0)
200-
expect(tcaseUploadCount()).toBe(4)
204+
expect(numFileUploadCalls()).toBe(0)
205+
expect(numResultUploadCalls()).toBe(1) // 4 results total
201206
})
202207

203208
test('Test cases from multiple reports should be processed successfully', async () => {
204-
const fileUploadCount = countFileUploadApiCalls()
205-
const tcaseUploadCount = countResultUploadApiCalls()
209+
const numFileUploadCalls = countFileUploadApiCalls()
210+
const numResultUploadCalls = countResultUploadApiCalls()
211+
setMaxResultsInRequest(2)
206212
await run(
207213
`${fileType.command} -r ${runURL} --force ${fileType.dataBasePath}/missing-tcases.${fileType.fileExtension} ${fileType.dataBasePath}/missing-tcases.${fileType.fileExtension}`
208214
)
209-
expect(fileUploadCount()).toBe(0)
210-
expect(tcaseUploadCount()).toBe(8)
215+
expect(numFileUploadCalls()).toBe(0)
216+
expect(numResultUploadCalls()).toBe(4) // 8 results total
211217
})
212218

213219
test('Test suite with empty tcases should not result in error and be skipped', async () => {
214-
const fileUploadCount = countFileUploadApiCalls()
215-
const tcaseUploadCount = countResultUploadApiCalls()
220+
const numFileUploadCalls = countFileUploadApiCalls()
221+
const numResultUploadCalls = countResultUploadApiCalls()
216222
await run(
217223
`${fileType.command} -r ${runURL} --force ${fileType.dataBasePath}/empty-tsuite.${fileType.fileExtension}`
218224
)
219-
expect(fileUploadCount()).toBe(0)
220-
expect(tcaseUploadCount()).toBe(1)
225+
expect(numFileUploadCalls()).toBe(0)
226+
expect(numResultUploadCalls()).toBe(1) // 1 result total
221227
})
222228
})
223229

224230
describe('Uploading with attachments', () => {
225231
test('Attachments should be uploaded', async () => {
226-
const fileUploadCount = countFileUploadApiCalls()
227-
const tcaseUploadCount = countResultUploadApiCalls()
232+
const numFileUploadCalls = countFileUploadApiCalls()
233+
const numResultUploadCalls = countResultUploadApiCalls()
234+
setMaxResultsInRequest(3)
228235
await run(
229236
`${fileType.command} -r ${runURL} --attachments ${fileType.dataBasePath}/matching-tcases.${fileType.fileExtension}`
230237
)
231-
expect(fileUploadCount()).toBe(5)
232-
expect(tcaseUploadCount()).toBe(5)
238+
expect(numFileUploadCalls()).toBe(5)
239+
expect(numResultUploadCalls()).toBe(2) // 5 results total
233240
})
234241
test('Missing attachments should throw an error', async () => {
235-
const fileUploadCount = countFileUploadApiCalls()
236-
const tcaseUploadCount = countResultUploadApiCalls()
242+
const numFileUploadCalls = countFileUploadApiCalls()
243+
const numResultUploadCalls = countResultUploadApiCalls()
237244
await expect(
238245
run(
239246
`${fileType.command} -r ${runURL} --attachments ${fileType.dataBasePath}/missing-attachments.${fileType.fileExtension}`
240247
)
241248
).rejects.toThrow()
242-
expect(fileUploadCount()).toBe(0)
243-
expect(tcaseUploadCount()).toBe(0)
249+
expect(numFileUploadCalls()).toBe(0)
250+
expect(numResultUploadCalls()).toBe(0)
244251
})
245252
test('Missing attachments should be successful when forced', async () => {
246-
const fileUploadCount = countFileUploadApiCalls()
247-
const tcaseUploadCount = countResultUploadApiCalls()
253+
const numFileUploadCalls = countFileUploadApiCalls()
254+
const numResultUploadCalls = countResultUploadApiCalls()
255+
setMaxResultsInRequest(1)
248256
await run(
249257
`${fileType.command} -r ${runURL} --attachments --force ${fileType.dataBasePath}/missing-attachments.${fileType.fileExtension}`
250258
)
251-
expect(fileUploadCount()).toBe(4)
252-
expect(tcaseUploadCount()).toBe(5)
259+
expect(numFileUploadCalls()).toBe(4)
260+
expect(numResultUploadCalls()).toBe(5) // 5 results total
253261
})
254262
})
255263

@@ -318,17 +326,17 @@ fileTypes.forEach((fileType) => {
318326
})
319327

320328
test('Should reuse existing run when run title is already used', async () => {
321-
const fileUploadCount = countFileUploadApiCalls()
322-
const tcaseUploadCount = countResultUploadApiCalls()
329+
const numFileUploadCalls = countFileUploadApiCalls()
330+
const numResultUploadCalls = countResultUploadApiCalls()
323331

324332
createRunTitleConflict = true
325333
await run(
326334
`${fileType.command} --run-name "duplicate run title" ${fileType.dataBasePath}/matching-tcases.${fileType.fileExtension}`
327335
)
328336

329337
expect(lastCreatedRunTitle).toBe('duplicate run title')
330-
expect(fileUploadCount()).toBe(0)
331-
expect(tcaseUploadCount()).toBe(5)
338+
expect(numFileUploadCalls()).toBe(0)
339+
expect(numResultUploadCalls()).toBe(1) // 5 results total
332340
})
333341

334342
test('Should use default name template when --run-name is not specified', async () => {

src/utils/misc.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ export const twirlLoader = () => {
4040
if (timer) {
4141
clearInterval(timer)
4242
}
43+
x = chars.length - 1
44+
update()
4345
process.stdout.write('\n')
4446
},
4547
setText: (newText: string) => {

0 commit comments

Comments
 (0)