Skip to content

Commit 7fbdc02

Browse files
authored
feat: crash details by group (#150)
1 parent 0b6a49f commit 7fbdc02

File tree

6 files changed

+140
-9
lines changed

6 files changed

+140
-9
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616

1717
- uses: actions/setup-node@v4
1818
with:
19-
node-version: "20.x"
19+
node-version: "22.x"
2020

2121
- name: Install
2222
run: npm install

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ jobs:
1414
with:
1515
persist-credentials: false
1616

17-
# Pin to Node.js 22.12 as 22.18+ breaks tsconfig-paths/register functionality
17+
# Use latest Node.js 22 LTS
1818
- uses: actions/setup-node@v4
1919
with:
20-
node-version: "22.12"
20+
node-version: "22.x"
2121

2222
- name: Install
2323
run: npm install

package.json

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,14 @@
77
"files": [
88
"dist"
99
],
10-
"engines": {
11-
"node": ">=18.0.0 <22.13.0"
12-
},
1310
"scripts": {
1411
"prepare": "husky install",
1512
"pretest": "npm run lint:fix",
16-
"test": "ts-node -r tsconfig-paths/register node_modules/jasmine/bin/jasmine --config=spec/support/jasmine.spec.json",
13+
"test": "NODE_OPTIONS='--no-experimental-strip-types' ts-node -r tsconfig-paths/register node_modules/jasmine/bin/jasmine --config=spec/support/jasmine.spec.json",
1714
"lint": "eslint .",
1815
"lint:fix": "npm run lint -- --fix",
19-
"e2e": "ts-node -r tsconfig-paths/register node_modules/jasmine/bin/jasmine --config=spec/support/jasmine.e2e.json",
20-
"e2e:teamcity": "ts-node -r tsconfig-paths/register node_modules/jasmine/bin/jasmine --config=spec/support/jasmine.teamcity.e2e.json",
16+
"e2e": "NODE_OPTIONS='--no-experimental-strip-types' ts-node -r tsconfig-paths/register node_modules/jasmine/bin/jasmine --config=spec/support/jasmine.e2e.json",
17+
"e2e:teamcity": "NODE_OPTIONS='--no-experimental-strip-types' ts-node -r tsconfig-paths/register node_modules/jasmine/bin/jasmine --config=spec/support/jasmine.teamcity.e2e.json",
2118
"release": "npm run build && npm publish --access public",
2219
"build": "npm run build:cjs && npm run build:esm",
2320
"build:cjs": "tsc -p tsconfig.cjs.json && tsconfig-replace-paths -p tsconfig.json -o ./dist/cjs -s ./src",

src/crash/crash-api-client/crash-api-client.e2e.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ describe('CrashApiClient', () => {
4141
});
4242
});
4343

44+
describe('getCrashByGroupId', () => {
45+
it('should return crash details for the given stack key group', async () => {
46+
const response = await crashClient.getCrashByGroupId(config.database, stackKeyId);
47+
48+
expect(response).toBeDefined();
49+
expect(typeof response.stackKeyId).toBe('number');
50+
expect(response.stackKeyId).toBeGreaterThan(0);
51+
expect(response.appName).toEqual(application);
52+
expect(response.appVersion).toBeDefined();
53+
expect(typeof response.appVersion).toBe('string');
54+
expect(response.appVersion.length).toBeGreaterThan(0);
55+
});
56+
});
57+
4458
describe('reprocessCrash', () => {
4559
it('should return 200 for a recent crash that has symbols', async () => {
4660
const response = await crashClient.reprocessCrash(config.database, id);

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,92 @@ describe('CrashApiClient', () => {
9191
});
9292
});
9393

94+
describe('getCrashByGroupId', () => {
95+
let client: CrashApiClient;
96+
let fakeBugSplatApiClient;
97+
let fakeCrashApiResponse;
98+
let result;
99+
const groupId = 12345;
100+
101+
beforeEach(async () => {
102+
fakeCrashApiResponse = createFakeCrashApiResponse();
103+
const fakeResponse = createFakeResponseBody(200, fakeCrashApiResponse);
104+
fakeBugSplatApiClient = createFakeBugSplatApiClient(fakeFormData, fakeResponse);
105+
client = new CrashApiClient(fakeBugSplatApiClient);
106+
107+
result = await client.getCrashByGroupId(database, groupId);
108+
});
109+
110+
it('should call fetch with correct route', () => {
111+
expect(fakeBugSplatApiClient.fetch).toHaveBeenCalledWith(
112+
'/api/crash/details',
113+
jasmine.anything()
114+
);
115+
});
116+
117+
it('should call fetch with formData containing database and stackKeyId', () => {
118+
expect(fakeFormData.append).toHaveBeenCalledWith('database', database);
119+
expect(fakeFormData.append).toHaveBeenCalledWith('stackKeyId', groupId.toString());
120+
expect(fakeBugSplatApiClient.fetch).toHaveBeenCalledWith(
121+
jasmine.any(String),
122+
jasmine.objectContaining({
123+
method: 'POST',
124+
body: fakeFormData,
125+
cache: 'no-cache',
126+
credentials: 'include',
127+
redirect: 'follow',
128+
})
129+
);
130+
});
131+
132+
it('should return response json', () => {
133+
expect(result).toEqual(jasmine.objectContaining(createCrashDetails(fakeCrashApiResponse)));
134+
});
135+
136+
it('should throw if status is not 200', async () => {
137+
const message = 'Bad request';
138+
139+
try {
140+
const fakeErrorBody = { message };
141+
const fakeResponse = createFakeResponseBody(400, fakeErrorBody, false);
142+
const fakeBugSplatApiClient = createFakeBugSplatApiClient(fakeFormData, fakeResponse);
143+
const client = new CrashApiClient(fakeBugSplatApiClient);
144+
145+
await client.getCrashByGroupId(database, groupId);
146+
fail('getCrashByGroupId was supposed to throw!');
147+
} catch (error: any) {
148+
expect(error.message).toEqual(message);
149+
}
150+
});
151+
152+
it('should throw if database is falsy', async () => {
153+
try {
154+
await client.getCrashByGroupId('', groupId);
155+
fail('getCrashByGroupId was supposed to throw!');
156+
} catch (error: any) {
157+
expect(error.message).toMatch(/to be a non white space string/);
158+
}
159+
});
160+
161+
it('should throw if groupId is less than or equal to 0', async () => {
162+
try {
163+
await client.getCrashByGroupId(database, 0);
164+
fail('getCrashByGroupId was supposed to throw!');
165+
} catch (error: any) {
166+
expect(error.message).toMatch(/to be a positive non-zero number/);
167+
}
168+
});
169+
170+
it('should throw if groupId is negative', async () => {
171+
try {
172+
await client.getCrashByGroupId(database, -1);
173+
fail('getCrashByGroupId was supposed to throw!');
174+
} catch (error: any) {
175+
expect(error.message).toMatch(/to be a positive non-zero number/);
176+
}
177+
});
178+
});
179+
94180
describe('reprocessCrash', () => {
95181
let client: CrashApiClient;
96182
let fakeReprocessApiResponse;

src/crash/crash-api-client/crash-api-client.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,40 @@ import {
1010
export class CrashApiClient {
1111
constructor(private _client: ApiClient) {}
1212

13+
async getCrashByGroupId(database: string, groupId: number): Promise<CrashDetails> {
14+
ac.assertNonWhiteSpaceString(database, 'database');
15+
if (groupId <= 0) {
16+
throw new Error(
17+
`Expected groupId to be a positive non-zero number. Value received: "${groupId}"`
18+
);
19+
}
20+
21+
const formData = this._client.createFormData();
22+
formData.append('database', database);
23+
formData.append('stackKeyId', groupId.toString());
24+
25+
const init = {
26+
method: 'POST',
27+
body: formData,
28+
cache: 'no-cache',
29+
credentials: 'include',
30+
redirect: 'follow',
31+
duplex: 'half',
32+
} as RequestInit;
33+
34+
const response = await this._client.fetch<GetCrashByIdResponse>(
35+
'/api/crash/details',
36+
init
37+
);
38+
const json = await response.json();
39+
40+
if (response.status !== 200) {
41+
throw new Error((json as Error).message);
42+
}
43+
44+
return createCrashDetails(json as CrashDetailsRawResponse);
45+
}
46+
1347
async getCrashById(database: string, crashId: number): Promise<CrashDetails> {
1448
ac.assertNonWhiteSpaceString(database, 'database');
1549
if (crashId <= 0) {

0 commit comments

Comments
 (0)