Skip to content

Commit ea5f36d

Browse files
authored
feat: issue client (#146)
1 parent f972d1c commit ea5f36d

File tree

9 files changed

+189
-22
lines changed

9 files changed

+189
-22
lines changed

package-lock.json

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,14 @@
5656
"@commitlint/config-conventional": "^19.6.0",
5757
"@semantic-release/changelog": "^6.0.3",
5858
"@semantic-release/git": "^10.0.1",
59-
"@types/jasmine": "^5.1.5",
59+
"@types/jasmine": "^5.1.7",
6060
"@types/node": "^22.10.1",
6161
"@typescript-eslint/eslint-plugin": "^8.17.0",
6262
"@typescript-eslint/parser": "^8.17.0",
63-
"dotenv": "^16.4.7",
63+
"dotenv": "^16.5.0",
6464
"eslint": "^9.16.0",
6565
"husky": "^9.1.7",
66-
"jasmine": "^5.5.0",
66+
"jasmine": "^5.7.0",
6767
"jasmine-reporters": "^2.5.2",
6868
"semantic-release": "^24.2.0",
6969
"ts-node": "^10.9.2",

src/events/events-api-client/events-api-client.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createFakeEvents } from '@spec/fakes/events/events';
66
import { fakeEventsApiResponse } from '@spec/fakes/events/events-api-response';
77
import { createEvents } from './event';
88

9-
describe('CrashApiClient', () => {
9+
describe('EventsApiClient', () => {
1010
const database = 'fred';
1111
const id = 100000;
1212
const comment = 'hello world!';

src/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ export * from './common';
22
export * from './crash';
33
export * from './crashes';
44
export * from './events';
5+
export * from './issue';
56
export * from './post';
67
export * from './summary';
7-
export * from './versions';
88
export * from './symbols';
9-
export * from './users';
9+
export * from './users';
10+
export * from './versions';

src/issue/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { IssueApiClient } from './issue-api-client/issue-api-client';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { BugSplatApiClient } from '@common';
2+
import { CrashApiClient } from '@crash';
3+
import { IssueApiClient } from '@issue';
4+
import { config } from '@spec/config';
5+
import { postNativeCrashAndWaitForCrashToProcess } from '@spec/files/native/post-native-crash';
6+
7+
describe('IssueApiClient', () => {
8+
let client: IssueApiClient;
9+
const { host, email, password, database } = config;
10+
const expectedMessage = 'hello world!';
11+
let stackKeyId;
12+
13+
beforeEach(async () => {
14+
const bugsplat = await BugSplatApiClient.createAuthenticatedClientForNode(
15+
email,
16+
password,
17+
host
18+
);
19+
const crashClient = new CrashApiClient(bugsplat);
20+
const response = await postNativeCrashAndWaitForCrashToProcess(
21+
bugsplat,
22+
crashClient,
23+
database,
24+
'myConsoleCrasher',
25+
'1.0.0'
26+
);
27+
stackKeyId = response.stackKeyId;
28+
client = new IssueApiClient(bugsplat);
29+
});
30+
31+
describe('postStackKeyIssue', () => {
32+
// Requires a defect tracker to be configured
33+
it('should return 200 for valid database, stackKeyId, and notes', async () => {
34+
const response = await client.postStackKeyIssue(database, stackKeyId, expectedMessage);
35+
36+
expect(response.status).toEqual(200);
37+
});
38+
});
39+
40+
describe('deleteStackKeyIssue', () => {
41+
it('should return 200 for valid database, stackKeyId', async () => {
42+
const response = await client.deleteStackKeyIssue(database, stackKeyId);
43+
44+
expect(response.status).toEqual(200);
45+
});
46+
});
47+
});
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { IssueApiClient } from '@issue';
2+
import { createFakeBugSplatApiClient } from '@spec/fakes/common/bugsplat-api-client';
3+
import { createFakeFormData } from '@spec/fakes/common/form-data';
4+
import { createFakeResponseBody } from '@spec/fakes/common/response';
5+
import { fakeEventsApiResponse } from '@spec/fakes/events/events-api-response';
6+
7+
describe('IssueApiClient', () => {
8+
const database = 'fred';
9+
const id = 100000;
10+
const notes = 'hello world!';
11+
let client: IssueApiClient;
12+
let fakeBugSplatApiClient;
13+
let fakeFormData;
14+
15+
beforeEach(() => {
16+
const fakeResponse = createFakeResponseBody(200, fakeEventsApiResponse);
17+
fakeFormData = createFakeFormData();
18+
fakeBugSplatApiClient = createFakeBugSplatApiClient(fakeFormData, fakeResponse);
19+
client = new IssueApiClient(fakeBugSplatApiClient);
20+
});
21+
22+
describe('postStackKeyIssue', () => {
23+
beforeEach(async () => await client.postStackKeyIssue(database, id, notes));
24+
25+
it('should throw if database is falsy', async () => {
26+
try {
27+
await client.postStackKeyIssue('', id, notes);
28+
fail('postStackKeyIssue was supposed to throw!');
29+
} catch (error: any) {
30+
expect(error.message).toMatch(/to be a non white space string/);
31+
}
32+
});
33+
34+
it('should throw if id is less than or equal to 0', async () => {
35+
try {
36+
await client.postStackKeyIssue(database, -1, notes);
37+
fail('postStackKeyIssue was supposed to throw!');
38+
} catch (error: any) {
39+
expect(error.message).toMatch(/to be a positive non-zero number/);
40+
}
41+
});
42+
43+
it('should call fetch with route and formData', () => {
44+
expect(fakeFormData.append).toHaveBeenCalledWith('database', database);
45+
expect(fakeFormData.append).toHaveBeenCalledWith('stackKeyId', id.toString());
46+
expect(fakeFormData.append).toHaveBeenCalledWith('notes', notes);
47+
expect(fakeFormData.append).not.toHaveBeenCalledWith('linkDefectId');
48+
expect(fakeBugSplatApiClient.fetch).toHaveBeenCalledWith(
49+
'/api/logDefect',
50+
jasmine.objectContaining({
51+
method: 'POST',
52+
body: fakeFormData,
53+
duplex: 'half',
54+
})
55+
);
56+
});
57+
58+
it('should call fetch with linkDefectId if provided', () => {
59+
const linkDefectId = '123';
60+
client.postStackKeyIssue(database, id, notes, linkDefectId);
61+
62+
expect(fakeFormData.append).toHaveBeenCalledWith('linkDefectId', linkDefectId);
63+
});
64+
});
65+
});
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { ApiClient, BugSplatResponse } from '@common';
2+
import ac from 'argument-contracts';
3+
4+
export class IssueApiClient {
5+
constructor(private _client: ApiClient) {}
6+
7+
postStackKeyIssue(
8+
database: string,
9+
stackKeyId: number,
10+
notes: string,
11+
linkDefectId?: string
12+
): Promise<BugSplatResponse<IssuePostSuccessResponse>> {
13+
ac.assertNonWhiteSpaceString(database, 'database');
14+
if (stackKeyId <= 0) {
15+
throw new Error(
16+
`Expected stackKeyId to be a positive non-zero number. Value received: "${stackKeyId}"`
17+
);
18+
}
19+
20+
const method = 'POST';
21+
const body = this._client.createFormData();
22+
const duplex = 'half';
23+
24+
body.append('database', database);
25+
body.append('stackKeyId', `${stackKeyId}`);
26+
body.append('notes', notes);
27+
28+
if (linkDefectId) {
29+
body.append('linkDefectId', linkDefectId);
30+
}
31+
32+
return this._client.fetch('/api/logDefect', {
33+
method,
34+
body,
35+
duplex,
36+
} as RequestInit);
37+
}
38+
39+
deleteStackKeyIssue(
40+
database: string,
41+
stackKeyId: number
42+
): Promise<BugSplatResponse<IssueDeleteSuccessResponse>> {
43+
return this._client.fetch(`/api/logDefect?database=${database}&stackKeyId=${stackKeyId}`, {
44+
method: 'DELETE',
45+
});
46+
}
47+
}
48+
49+
type IssuePostSuccessResponse = { status: string; stackKeyId: number; defectId: string };
50+
type IssueDeleteSuccessResponse = { status: string; stackKeyId: number };

tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
"@events": [
3030
"src/events/index"
3131
],
32+
"@issue": [
33+
"src/issue/index"
34+
],
3235
"@post": [
3336
"src/post/index"
3437
],

0 commit comments

Comments
 (0)