Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,14 @@
"@commitlint/config-conventional": "^19.6.0",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@types/jasmine": "^5.1.5",
"@types/jasmine": "^5.1.7",
"@types/node": "^22.10.1",
"@typescript-eslint/eslint-plugin": "^8.17.0",
"@typescript-eslint/parser": "^8.17.0",
"dotenv": "^16.4.7",
"dotenv": "^16.5.0",
"eslint": "^9.16.0",
"husky": "^9.1.7",
"jasmine": "^5.5.0",
"jasmine": "^5.7.0",
"jasmine-reporters": "^2.5.2",
"semantic-release": "^24.2.0",
"ts-node": "^10.9.2",
Expand Down
2 changes: 1 addition & 1 deletion src/events/events-api-client/events-api-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { createFakeEvents } from '@spec/fakes/events/events';
import { fakeEventsApiResponse } from '@spec/fakes/events/events-api-response';
import { createEvents } from './event';

describe('CrashApiClient', () => {
describe('EventsApiClient', () => {
const database = 'fred';
const id = 100000;
const comment = 'hello world!';
Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ export * from './common';
export * from './crash';
export * from './crashes';
export * from './events';
export * from './issue';
export * from './post';
export * from './summary';
export * from './versions';
export * from './symbols';
export * from './users';
export * from './users';
export * from './versions';
1 change: 1 addition & 0 deletions src/issue/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { IssueApiClient } from './issue-api-client/issue-api-client';
47 changes: 47 additions & 0 deletions src/issue/issue-api-client/issue-api-client.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { BugSplatApiClient } from '@common';
import { CrashApiClient } from '@crash';
import { IssueApiClient } from '@issue';
import { config } from '@spec/config';
import { postNativeCrashAndWaitForCrashToProcess } from '@spec/files/native/post-native-crash';

describe('IssueApiClient', () => {
let client: IssueApiClient;
const { host, email, password, database } = config;
const expectedMessage = 'hello world!';
let stackKeyId;

beforeEach(async () => {
const bugsplat = await BugSplatApiClient.createAuthenticatedClientForNode(
email,
password,
host
);
const crashClient = new CrashApiClient(bugsplat);
const response = await postNativeCrashAndWaitForCrashToProcess(
bugsplat,
crashClient,
database,
'myConsoleCrasher',
'1.0.0'
);
stackKeyId = response.stackKeyId;
client = new IssueApiClient(bugsplat);
});

describe('postStackKeyIssue', () => {
// Requires a defect tracker to be configured
it('should return 200 for valid database, stackKeyId, and notes', async () => {
const response = await client.postStackKeyIssue(database, stackKeyId, expectedMessage);

expect(response.status).toEqual(200);
});
});

describe('deleteStackKeyIssue', () => {
it('should return 200 for valid database, stackKeyId', async () => {
const response = await client.deleteStackKeyIssue(database, stackKeyId);

expect(response.status).toEqual(200);
});
});
});
65 changes: 65 additions & 0 deletions src/issue/issue-api-client/issue-api-client.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { IssueApiClient } from '@issue';
import { createFakeBugSplatApiClient } from '@spec/fakes/common/bugsplat-api-client';
import { createFakeFormData } from '@spec/fakes/common/form-data';
import { createFakeResponseBody } from '@spec/fakes/common/response';
import { fakeEventsApiResponse } from '@spec/fakes/events/events-api-response';

describe('IssueApiClient', () => {
const database = 'fred';
const id = 100000;
const notes = 'hello world!';
let client: IssueApiClient;
let fakeBugSplatApiClient;
let fakeFormData;

beforeEach(() => {
const fakeResponse = createFakeResponseBody(200, fakeEventsApiResponse);
fakeFormData = createFakeFormData();
fakeBugSplatApiClient = createFakeBugSplatApiClient(fakeFormData, fakeResponse);
client = new IssueApiClient(fakeBugSplatApiClient);
});

describe('postStackKeyIssue', () => {
beforeEach(async () => await client.postStackKeyIssue(database, id, notes));

it('should throw if database is falsy', async () => {
try {
await client.postStackKeyIssue('', id, notes);
fail('postStackKeyIssue was supposed to throw!');
} catch (error: any) {
expect(error.message).toMatch(/to be a non white space string/);
}
});

it('should throw if id is less than or equal to 0', async () => {
try {
await client.postStackKeyIssue(database, -1, notes);
fail('postStackKeyIssue was supposed to throw!');
} catch (error: any) {
expect(error.message).toMatch(/to be a positive non-zero number/);
}
});

it('should call fetch with route and formData', () => {
expect(fakeFormData.append).toHaveBeenCalledWith('database', database);
expect(fakeFormData.append).toHaveBeenCalledWith('stackKeyId', id.toString());
expect(fakeFormData.append).toHaveBeenCalledWith('notes', notes);
expect(fakeFormData.append).not.toHaveBeenCalledWith('linkDefectId');
expect(fakeBugSplatApiClient.fetch).toHaveBeenCalledWith(
'/api/logDefect',
jasmine.objectContaining({
method: 'POST',
body: fakeFormData,
duplex: 'half',
})
);
});

it('should call fetch with linkDefectId if provided', () => {
const linkDefectId = '123';
client.postStackKeyIssue(database, id, notes, linkDefectId);

expect(fakeFormData.append).toHaveBeenCalledWith('linkDefectId', linkDefectId);
});
});
});
50 changes: 50 additions & 0 deletions src/issue/issue-api-client/issue-api-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ApiClient, BugSplatResponse } from '@common';
import ac from 'argument-contracts';

export class IssueApiClient {
constructor(private _client: ApiClient) {}

postStackKeyIssue(
database: string,
stackKeyId: number,
notes: string,
linkDefectId?: string
): Promise<BugSplatResponse<IssuePostSuccessResponse>> {
ac.assertNonWhiteSpaceString(database, 'database');
if (stackKeyId <= 0) {
throw new Error(
`Expected stackKeyId to be a positive non-zero number. Value received: "${stackKeyId}"`
);
}

const method = 'POST';
const body = this._client.createFormData();
const duplex = 'half';

body.append('database', database);
body.append('stackKeyId', `${stackKeyId}`);
body.append('notes', notes);

if (linkDefectId) {
body.append('linkDefectId', linkDefectId);
}

return this._client.fetch('/api/logDefect', {
method,
body,
duplex,
} as RequestInit);
}

deleteStackKeyIssue(
database: string,
stackKeyId: number
): Promise<BugSplatResponse<IssueDeleteSuccessResponse>> {
return this._client.fetch(`/api/logDefect?database=${database}&stackKeyId=${stackKeyId}`, {
method: 'DELETE',
});
}
}

type IssuePostSuccessResponse = { status: string; stackKeyId: number; defectId: string };
type IssueDeleteSuccessResponse = { status: string; stackKeyId: number };
3 changes: 3 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
"@events": [
"src/events/index"
],
"@issue": [
"src/issue/index"
],
"@post": [
"src/post/index"
],
Expand Down