2021-01-06 17:23:15 +00:00
|
|
|
// Copyright 2021 Signal Messenger, LLC
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
import { assert } from 'chai';
|
|
|
|
import * as sinon from 'sinon';
|
|
|
|
import got from 'got';
|
|
|
|
import FormData from 'form-data';
|
|
|
|
import * as util from 'util';
|
|
|
|
import * as zlib from 'zlib';
|
|
|
|
|
2022-01-11 20:02:46 +00:00
|
|
|
import * as durations from '../../util/durations';
|
|
|
|
import { upload } from '../../logging/uploadDebugLog';
|
|
|
|
import * as logger from '../../logging/log';
|
2021-01-06 17:23:15 +00:00
|
|
|
|
|
|
|
const gzip: (_: zlib.InputType) => Promise<Buffer> = util.promisify(zlib.gzip);
|
|
|
|
|
2021-09-17 18:27:53 +00:00
|
|
|
describe('upload', () => {
|
2023-10-11 19:06:43 +00:00
|
|
|
beforeEach(function (this: Mocha.Context) {
|
2021-01-06 17:23:15 +00:00
|
|
|
this.sandbox = sinon.createSandbox();
|
|
|
|
|
2022-06-21 00:31:32 +00:00
|
|
|
this.sandbox.stub(process, 'platform').get(() => 'freebsd');
|
2021-01-06 17:23:15 +00:00
|
|
|
|
|
|
|
this.fakeGet = this.sandbox.stub(got, 'get');
|
|
|
|
this.fakePost = this.sandbox.stub(got, 'post');
|
|
|
|
|
|
|
|
this.fakeGet.resolves({
|
|
|
|
body: {
|
|
|
|
fields: {
|
|
|
|
foo: 'bar',
|
|
|
|
key: 'abc123',
|
|
|
|
},
|
|
|
|
url: 'https://example.com/fake-upload',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
this.fakePost.resolves({ statusCode: 204 });
|
|
|
|
});
|
|
|
|
|
2023-10-11 19:06:43 +00:00
|
|
|
afterEach(function (this: Mocha.Context) {
|
2021-01-06 17:23:15 +00:00
|
|
|
this.sandbox.restore();
|
|
|
|
});
|
|
|
|
|
2023-10-11 19:06:43 +00:00
|
|
|
it('makes a request to get the S3 bucket, then uploads it there', async function (this: Mocha.Context) {
|
2021-01-06 17:23:15 +00:00
|
|
|
assert.strictEqual(
|
2022-01-11 20:02:46 +00:00
|
|
|
await upload({ content: 'hello world', appVersion: '1.2.3', logger }),
|
2021-06-01 22:16:10 +00:00
|
|
|
'https://debuglogs.org/abc123.gz'
|
2021-01-06 17:23:15 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
sinon.assert.calledOnce(this.fakeGet);
|
2022-01-14 21:50:45 +00:00
|
|
|
sinon.assert.calledWith(this.fakeGet, 'https://debuglogs.org/', {
|
2021-10-06 16:25:22 +00:00
|
|
|
responseType: 'json',
|
2022-06-21 00:31:32 +00:00
|
|
|
headers: { 'User-Agent': 'Signal-Desktop/1.2.3' },
|
2022-01-11 20:02:46 +00:00
|
|
|
timeout: { request: durations.MINUTE },
|
2021-01-06 17:23:15 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const compressedContent = await gzip('hello world');
|
|
|
|
|
|
|
|
sinon.assert.calledOnce(this.fakePost);
|
|
|
|
sinon.assert.calledWith(this.fakePost, 'https://example.com/fake-upload', {
|
2022-06-21 00:31:32 +00:00
|
|
|
headers: { 'User-Agent': 'Signal-Desktop/1.2.3' },
|
2022-01-11 20:02:46 +00:00
|
|
|
timeout: { request: durations.MINUTE },
|
2021-01-06 17:23:15 +00:00
|
|
|
body: sinon.match((value: unknown) => {
|
|
|
|
if (!(value instanceof FormData)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// `FormData` doesn't offer high-level APIs for fetching data, so we do this.
|
|
|
|
const buffer = value.getBuffer();
|
|
|
|
assert(
|
|
|
|
buffer.includes(compressedContent),
|
|
|
|
'gzipped content was not in body'
|
|
|
|
);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}, 'FormData'),
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-10-11 19:06:43 +00:00
|
|
|
it("rejects if we can't get a token", async function (this: Mocha.Context) {
|
2021-01-06 17:23:15 +00:00
|
|
|
this.fakeGet.rejects(new Error('HTTP request failure'));
|
|
|
|
|
|
|
|
let err: unknown;
|
|
|
|
try {
|
2022-01-11 20:02:46 +00:00
|
|
|
await upload({ content: 'hello world', appVersion: '1.2.3', logger });
|
2021-01-06 17:23:15 +00:00
|
|
|
} catch (e) {
|
|
|
|
err = e;
|
|
|
|
}
|
|
|
|
assert.instanceOf(err, Error);
|
|
|
|
});
|
|
|
|
|
2023-10-11 19:06:43 +00:00
|
|
|
it('rejects with an invalid token body', async function (this: Mocha.Context) {
|
2021-01-06 17:23:15 +00:00
|
|
|
const bodies = [
|
|
|
|
null,
|
|
|
|
{},
|
|
|
|
{ fields: {} },
|
|
|
|
{ fields: { nokey: 'ok' } },
|
|
|
|
{ fields: { key: '123' } },
|
|
|
|
{ fields: { key: '123' }, url: { not: 'a string' } },
|
|
|
|
{ fields: { key: '123' }, url: 'http://notsecure.example.com' },
|
|
|
|
{ fields: { key: '123' }, url: 'not a valid URL' },
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const body of bodies) {
|
|
|
|
this.fakeGet.resolves({ body });
|
|
|
|
|
|
|
|
let err: unknown;
|
|
|
|
try {
|
|
|
|
// Again, these should be run serially.
|
|
|
|
// eslint-disable-next-line no-await-in-loop
|
2022-01-11 20:02:46 +00:00
|
|
|
await upload({ content: 'hello world', appVersion: '1.2.3', logger });
|
2021-01-06 17:23:15 +00:00
|
|
|
} catch (e) {
|
|
|
|
err = e;
|
|
|
|
}
|
|
|
|
assert.instanceOf(err, Error);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2023-10-11 19:06:43 +00:00
|
|
|
it("rejects if the upload doesn't return a 204", async function (this: Mocha.Context) {
|
2021-01-06 17:23:15 +00:00
|
|
|
this.fakePost.resolves({ statusCode: 400 });
|
|
|
|
|
|
|
|
let err: unknown;
|
|
|
|
try {
|
2022-01-11 20:02:46 +00:00
|
|
|
await upload({ content: 'hello world', appVersion: '1.2.3', logger });
|
2021-01-06 17:23:15 +00:00
|
|
|
} catch (e) {
|
|
|
|
err = e;
|
|
|
|
}
|
|
|
|
assert.instanceOf(err, Error);
|
|
|
|
});
|
|
|
|
});
|