Skip to content

Commit 98c9ca7

Browse files
authored
Adds symbol post and delete support (#31)
Fixes #30
1 parent 91bf045 commit 98c9ca7

File tree

11 files changed

+509
-4
lines changed

11 files changed

+509
-4
lines changed

package-lock.json

+28-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
"argument-contracts": "^1.2.3",
3434
"fetch-ponyfill": "^7.1.0",
3535
"form-data": "^4.0.0",
36-
"node-fetch": "^2.6.1"
36+
"node-fetch": "^2.6.1",
37+
"rxjs": "^7.1.0"
3738
},
3839
"devDependencies": {
3940
"@types/jasmine": "^3.7.4",

spec/fakes/common/response.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
export function createFakeResponseBody(status, json, headers) {
1+
export function createFakeResponseBody(
2+
status: number,
3+
json: any = {},
4+
headers: any = []
5+
) {
26
return {
37
status: status,
48
json: async() => (json),

src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './common';
22
export * from './crash';
3-
export * from './events';
3+
export * from './events';
4+
export * from './symbols';

src/symbols/exists.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import fs from 'fs';
2+
3+
export function exists(file: string): boolean {
4+
try {
5+
fs.accessSync(file);
6+
} catch {
7+
return false;
8+
}
9+
10+
return true;
11+
}

src/symbols/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { SymbolsApiClient } from './symbols-api-client/symbols-api-client';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import * as ExistsModule from '../exists';
2+
import { S3ApiClient } from './s3-api-client';
3+
4+
describe('S3ApiClient', () => {
5+
describe('uploadFileToPresignedUrl', () => {
6+
let s3ApiClient;
7+
let fakeReadStream;
8+
let fakeSuccessResponse;
9+
10+
beforeEach(() => {
11+
fakeReadStream = '🦦';
12+
fakeSuccessResponse = { status: 200 };
13+
spyOn(ExistsModule, 'exists').and.returnValue(true);
14+
s3ApiClient = new S3ApiClient();
15+
s3ApiClient._fs = jasmine.createSpyObj('fs', ['createReadStream']);
16+
s3ApiClient._fs.createReadStream.and.returnValue(fakeReadStream);
17+
s3ApiClient._fetch = jasmine.createSpy();
18+
s3ApiClient._fetch.and.resolveTo(fakeSuccessResponse);
19+
});
20+
21+
it('should throw if file does not exist', async () => {
22+
const path = '/file/not/found';
23+
(<jasmine.Spy>ExistsModule.exists).and.returnValue(false);
24+
await expectAsync(s3ApiClient.uploadFileToPresignedUrl('url', path)).toBeRejectedWithError(`File does not exist at path: ${path}!`);
25+
});
26+
27+
it('should call fetch with presignedUrl', async () => {
28+
const url = 'https://bugsplat.com';
29+
const path = '/some/fake/path';
30+
31+
await s3ApiClient.uploadFileToPresignedUrl(url, path);
32+
33+
expect(s3ApiClient._fetch).toHaveBeenCalledWith(url, jasmine.anything());
34+
});
35+
36+
it('should call fetch with init containing method and headers', async () => {
37+
const url = 'https://bugsplat.com';
38+
const path = '/some/fake/path';
39+
const size = 1337;
40+
41+
await s3ApiClient.uploadFileToPresignedUrl(url, path, size);
42+
43+
expect(s3ApiClient._fetch).toHaveBeenCalledWith(
44+
jasmine.anything(),
45+
jasmine.objectContaining({
46+
method: 'PUT',
47+
headers: {
48+
'content-type': 'application/octet-stream',
49+
'content-length': `${size}`
50+
},
51+
body: fakeReadStream
52+
})
53+
);
54+
});
55+
56+
it('should return response', async () => {
57+
const url = 'https://bugsplat.com';
58+
const path = '/some/fake/path';
59+
const size = 1337;
60+
61+
const response = await s3ApiClient.uploadFileToPresignedUrl(url, path, size);
62+
63+
expect(response).toEqual(fakeSuccessResponse);
64+
});
65+
66+
describe('error', () => {
67+
it('should throw if response status is not 200', async () => {
68+
const url = 'https://bugsplat.com';
69+
const path = '/some/fake/path';
70+
const size = 1337;
71+
s3ApiClient._fetch.and.resolveTo({ status: 500 });
72+
73+
await expectAsync(s3ApiClient.uploadFileToPresignedUrl(url, path, size)).toBeRejectedWithError(`Error uploading ${path} to presignedUrl`);
74+
});
75+
});
76+
});
77+
});
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import fetchPonyfill from 'fetch-ponyfill';
2+
import fs from 'fs';
3+
import { exists } from '../exists';
4+
5+
export class S3ApiClient {
6+
7+
private _fetch = fetchPonyfill().fetch;
8+
private _fs = fs;
9+
10+
async uploadFileToPresignedUrl(presignedUrl: string, file: string, size: number): Promise<Response> {
11+
if (!exists(file)) {
12+
throw new Error(`File does not exist at path: ${file}!`);
13+
}
14+
15+
const response = await this._fetch(presignedUrl, {
16+
method: 'PUT',
17+
headers: {
18+
'content-type': 'application/octet-stream',
19+
'content-length': `${size}`
20+
},
21+
body: <any>this._fs.createReadStream(file),
22+
});
23+
24+
if (response.status !== 200) {
25+
throw new Error(`Error uploading ${file} to presignedUrl`);
26+
}
27+
28+
return response;
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { config } from "../../../spec/config";
2+
import { BugSplatApiClient } from "../../common";
3+
import { SymbolsApiClient } from "./symbols-api-client";
4+
5+
describe('SymbolsApiClient', () => {
6+
let client: SymbolsApiClient;
7+
let host = config.host;
8+
let email = config.email;
9+
let password = config.password;
10+
11+
const database = 'fred';
12+
const application = 'bugsplat-js-api-client';
13+
const version = '1.0.0';
14+
15+
beforeEach(async () => {
16+
const bugsplat = new BugSplatApiClient(host);
17+
await bugsplat.login(email, password);
18+
client = new SymbolsApiClient(bugsplat);
19+
});
20+
21+
describe('delete', () => {
22+
it('should return 200 for delete with valid database, application and version', async () => {
23+
const response = await client.delete(
24+
database,
25+
application,
26+
version
27+
);
28+
29+
expect(response.status).toEqual(200);
30+
});
31+
});
32+
33+
describe('post', () => {
34+
it('should return 200 for post with valid database, application, version and files', async () => {
35+
const file = './dist/cjs/index.js.map';
36+
const response = await client.post(
37+
database,
38+
application,
39+
version,
40+
[file]
41+
);
42+
43+
expect(response[0].status).toEqual(200);
44+
});
45+
});
46+
});

0 commit comments

Comments
 (0)