Timeline: repair oldest/newest metrics if we fetch nothing
This commit is contained in:
parent
56ae4a41eb
commit
6832b8acca
47 changed files with 579 additions and 173 deletions
53
ts/test-node/util/LatestQueue_test.ts
Normal file
53
ts/test-node/util/LatestQueue_test.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { LatestQueue } from '../../util/LatestQueue';
|
||||
|
||||
describe('LatestQueue', () => {
|
||||
it('if the queue is empty, new tasks are started immediately', done => {
|
||||
new LatestQueue().add(async () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('only enqueues the latest operation', done => {
|
||||
const queue = new LatestQueue();
|
||||
|
||||
const spy = sinon.spy();
|
||||
|
||||
let openFirstTaskGate: undefined | (() => void);
|
||||
const firstTaskGate = new Promise(resolve => {
|
||||
openFirstTaskGate = resolve;
|
||||
});
|
||||
if (!openFirstTaskGate) {
|
||||
throw new Error('Test is misconfigured; cannot grab inner resolve');
|
||||
}
|
||||
|
||||
queue.add(async () => {
|
||||
await firstTaskGate;
|
||||
spy('first');
|
||||
});
|
||||
|
||||
queue.add(async () => {
|
||||
spy('second');
|
||||
});
|
||||
|
||||
queue.add(async () => {
|
||||
spy('third');
|
||||
});
|
||||
|
||||
sinon.assert.notCalled(spy);
|
||||
|
||||
openFirstTaskGate();
|
||||
|
||||
queue.onceEmpty(() => {
|
||||
sinon.assert.calledTwice(spy);
|
||||
sinon.assert.calledWith(spy, 'first');
|
||||
sinon.assert.calledWith(spy, 'third');
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
32
ts/test-node/util/combineNames_test.ts
Normal file
32
ts/test-node/util/combineNames_test.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { combineNames } from '../../util/combineNames';
|
||||
|
||||
describe('combineNames', () => {
|
||||
it('returns undefined if no names provided', () => {
|
||||
assert.strictEqual(combineNames('', ''), undefined);
|
||||
});
|
||||
|
||||
it('returns first name only if family name not provided', () => {
|
||||
assert.strictEqual(combineNames('Alice'), 'Alice');
|
||||
});
|
||||
|
||||
it('returns returns combined names', () => {
|
||||
assert.strictEqual(combineNames('Alice', 'Jones'), 'Alice Jones');
|
||||
});
|
||||
|
||||
it('returns given name first if names in Chinese', () => {
|
||||
assert.strictEqual(combineNames('振宁', '杨'), '杨振宁');
|
||||
});
|
||||
|
||||
it('returns given name first if names in Japanese', () => {
|
||||
assert.strictEqual(combineNames('泰夫', '木田'), '木田泰夫');
|
||||
});
|
||||
|
||||
it('returns given name first if names in Korean', () => {
|
||||
assert.strictEqual(combineNames('채원', '도윤'), '도윤채원');
|
||||
});
|
||||
});
|
60
ts/test-node/util/getAnimatedPngDataIfExists_test.ts
Normal file
60
ts/test-node/util/getAnimatedPngDataIfExists_test.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { getAnimatedPngDataIfExists } from '../../util/getAnimatedPngDataIfExists';
|
||||
|
||||
describe('getAnimatedPngDataIfExists', () => {
|
||||
const fixture = (filename: string): Promise<Buffer> => {
|
||||
const fixturePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'fixtures',
|
||||
filename
|
||||
);
|
||||
return fs.promises.readFile(fixturePath);
|
||||
};
|
||||
|
||||
it('returns null for empty buffers', () => {
|
||||
assert.isNull(getAnimatedPngDataIfExists(Buffer.alloc(0)));
|
||||
});
|
||||
|
||||
it('returns null for non-PNG files', async () => {
|
||||
await Promise.all(
|
||||
[
|
||||
'kitten-1-64-64.jpg',
|
||||
'512x515-thumbs-up-lincoln.webp',
|
||||
'giphy-GVNvOUpeYmI7e.gif',
|
||||
'pixabay-Soap-Bubble-7141.mp4',
|
||||
'lorem-ipsum.txt',
|
||||
].map(async filename => {
|
||||
assert.isNull(getAnimatedPngDataIfExists(await fixture(filename)));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('returns null for non-animated PNG files', async () => {
|
||||
assert.isNull(
|
||||
getAnimatedPngDataIfExists(await fixture('20x200-yellow.png'))
|
||||
);
|
||||
});
|
||||
|
||||
it('returns data for animated PNG files', async () => {
|
||||
assert.deepEqual(
|
||||
getAnimatedPngDataIfExists(
|
||||
await fixture('Animated_PNG_example_bouncing_beach_ball.png')
|
||||
),
|
||||
{ numPlays: Infinity }
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
getAnimatedPngDataIfExists(await fixture('apng_with_2_plays.png')),
|
||||
{ numPlays: 2 }
|
||||
);
|
||||
});
|
||||
});
|
50
ts/test-node/util/getOwn_test.ts
Normal file
50
ts/test-node/util/getOwn_test.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { getOwn } from '../../util/getOwn';
|
||||
|
||||
describe('getOwn', () => {
|
||||
class Person {
|
||||
public birthYear: number;
|
||||
|
||||
constructor(birthYear: number) {
|
||||
this.birthYear = birthYear;
|
||||
}
|
||||
|
||||
getAge() {
|
||||
return new Date().getFullYear() - this.birthYear;
|
||||
}
|
||||
}
|
||||
|
||||
it('returns undefined when asking for a non-existent property', () => {
|
||||
const obj: Record<string, number> = { bar: 123 };
|
||||
assert.isUndefined(getOwn(obj, 'foo'));
|
||||
});
|
||||
|
||||
it('returns undefined when asking for a non-own property', () => {
|
||||
const obj: Record<string, number> = { bar: 123 };
|
||||
assert.isUndefined(getOwn(obj, 'hasOwnProperty'));
|
||||
|
||||
const person = new Person(1880);
|
||||
assert.isUndefined(getOwn(person, 'getAge'));
|
||||
});
|
||||
|
||||
it('returns own properties', () => {
|
||||
const obj: Record<string, number> = { foo: 123 };
|
||||
assert.strictEqual(getOwn(obj, 'foo'), 123);
|
||||
|
||||
const person = new Person(1880);
|
||||
assert.strictEqual(getOwn(person, 'birthYear'), 1880);
|
||||
});
|
||||
|
||||
it('works even if `hasOwnProperty` has been overridden for the object', () => {
|
||||
const obj: Record<string, unknown> = {
|
||||
foo: 123,
|
||||
hasOwnProperty: () => true,
|
||||
};
|
||||
assert.strictEqual(getOwn(obj, 'foo'), 123);
|
||||
assert.isUndefined(getOwn(obj, 'bar'));
|
||||
});
|
||||
});
|
45
ts/test-node/util/getTextWithMentions_test.ts
Normal file
45
ts/test-node/util/getTextWithMentions_test.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { expect } from 'chai';
|
||||
import { getTextWithMentions } from '../../util/getTextWithMentions';
|
||||
|
||||
describe('getTextWithMentions', () => {
|
||||
describe('given mention replacements', () => {
|
||||
it('replaces them', () => {
|
||||
const bodyRanges = [
|
||||
{
|
||||
length: 1,
|
||||
mentionUuid: 'abcdef',
|
||||
replacementText: 'fred',
|
||||
start: 4,
|
||||
},
|
||||
];
|
||||
const text = "Hey \uFFFC, I'm here";
|
||||
expect(getTextWithMentions(bodyRanges, text)).to.eql(
|
||||
"Hey @fred, I'm here"
|
||||
);
|
||||
});
|
||||
|
||||
it('sorts them to go from back to front', () => {
|
||||
const bodyRanges = [
|
||||
{
|
||||
length: 1,
|
||||
mentionUuid: 'blarg',
|
||||
replacementText: 'jerry',
|
||||
start: 0,
|
||||
},
|
||||
{
|
||||
length: 1,
|
||||
mentionUuid: 'abcdef',
|
||||
replacementText: 'fred',
|
||||
start: 7,
|
||||
},
|
||||
];
|
||||
const text = "\uFFFC says \uFFFC, I'm here";
|
||||
expect(getTextWithMentions(bodyRanges, text)).to.eql(
|
||||
"@jerry says @fred, I'm here"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
37
ts/test-node/util/getUserAgent_test.ts
Normal file
37
ts/test-node/util/getUserAgent_test.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
import { getUserAgent } from '../../util/getUserAgent';
|
||||
|
||||
describe('getUserAgent', () => {
|
||||
beforeEach(function beforeEach() {
|
||||
this.sandbox = sinon.createSandbox();
|
||||
});
|
||||
|
||||
afterEach(function afterEach() {
|
||||
this.sandbox.restore();
|
||||
});
|
||||
|
||||
it('returns the right User-Agent on Windows', function test() {
|
||||
this.sandbox.stub(process, 'platform').get(() => 'win32');
|
||||
assert.strictEqual(getUserAgent('1.2.3'), 'Signal-Desktop/1.2.3 Windows');
|
||||
});
|
||||
|
||||
it('returns the right User-Agent on macOS', function test() {
|
||||
this.sandbox.stub(process, 'platform').get(() => 'darwin');
|
||||
assert.strictEqual(getUserAgent('1.2.3'), 'Signal-Desktop/1.2.3 macOS');
|
||||
});
|
||||
|
||||
it('returns the right User-Agent on Linux', function test() {
|
||||
this.sandbox.stub(process, 'platform').get(() => 'linux');
|
||||
assert.strictEqual(getUserAgent('1.2.3'), 'Signal-Desktop/1.2.3 Linux');
|
||||
});
|
||||
|
||||
it('omits the platform on unsupported platforms', function test() {
|
||||
this.sandbox.stub(process, 'platform').get(() => 'freebsd');
|
||||
assert.strictEqual(getUserAgent('1.2.3'), 'Signal-Desktop/1.2.3');
|
||||
});
|
||||
});
|
51
ts/test-node/util/isFileDangerous_test.ts
Normal file
51
ts/test-node/util/isFileDangerous_test.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
// Copyright 2018-2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { isFileDangerous } from '../../util/isFileDangerous';
|
||||
|
||||
describe('isFileDangerous', () => {
|
||||
it('returns false for images', () => {
|
||||
assert.strictEqual(isFileDangerous('dog.gif'), false);
|
||||
assert.strictEqual(isFileDangerous('cat.jpg'), false);
|
||||
});
|
||||
|
||||
it('returns false for documents', () => {
|
||||
assert.strictEqual(isFileDangerous('resume.docx'), false);
|
||||
assert.strictEqual(isFileDangerous('price_list.pdf'), false);
|
||||
});
|
||||
|
||||
it('returns true for executable files', () => {
|
||||
assert.strictEqual(isFileDangerous('run.exe'), true);
|
||||
assert.strictEqual(isFileDangerous('install.pif'), true);
|
||||
});
|
||||
|
||||
it('returns true for Microsoft settings files', () => {
|
||||
assert.strictEqual(isFileDangerous('downl.SettingContent-ms'), true);
|
||||
});
|
||||
|
||||
it('returns false for non-dangerous files that end in ".", which can happen on Windows', () => {
|
||||
assert.strictEqual(isFileDangerous('dog.png.'), false);
|
||||
assert.strictEqual(isFileDangerous('resume.docx.'), false);
|
||||
});
|
||||
|
||||
it('returns true for dangerous files that end in ".", which can happen on Windows', () => {
|
||||
assert.strictEqual(isFileDangerous('run.exe.'), true);
|
||||
assert.strictEqual(isFileDangerous('install.pif.'), true);
|
||||
});
|
||||
|
||||
it('returns false for empty filename', () => {
|
||||
assert.strictEqual(isFileDangerous(''), false);
|
||||
});
|
||||
|
||||
it('returns false for exe at various parts of filename', () => {
|
||||
assert.strictEqual(isFileDangerous('.exemanifesto.txt'), false);
|
||||
assert.strictEqual(isFileDangerous('runexe'), false);
|
||||
assert.strictEqual(isFileDangerous('run_exe'), false);
|
||||
});
|
||||
|
||||
it('returns true for upper-case EXE', () => {
|
||||
assert.strictEqual(isFileDangerous('run.EXE'), true);
|
||||
});
|
||||
});
|
22
ts/test-node/util/isMuted_test.ts
Normal file
22
ts/test-node/util/isMuted_test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { isMuted } from '../../util/isMuted';
|
||||
|
||||
describe('isMuted', () => {
|
||||
it('returns false if passed undefined', () => {
|
||||
assert.isFalse(isMuted(undefined));
|
||||
});
|
||||
|
||||
it('returns false if passed a date in the past', () => {
|
||||
assert.isFalse(isMuted(0));
|
||||
assert.isFalse(isMuted(Date.now() - 123));
|
||||
});
|
||||
|
||||
it('returns false if passed a date in the future', () => {
|
||||
assert.isTrue(isMuted(Date.now() + 123));
|
||||
assert.isTrue(isMuted(Date.now() + 123456));
|
||||
});
|
||||
});
|
45
ts/test-node/util/isPathInside_test.ts
Normal file
45
ts/test-node/util/isPathInside_test.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { isPathInside } from '../../util/isPathInside';
|
||||
|
||||
describe('isPathInside', () => {
|
||||
it('returns false if the child path is not inside the parent path', () => {
|
||||
assert.isFalse(isPathInside('x', 'a/b'));
|
||||
assert.isFalse(isPathInside('a/b', '/a/b'));
|
||||
assert.isFalse(isPathInside('/a/b', 'a/b'));
|
||||
assert.isFalse(isPathInside('/x', '/a/b'));
|
||||
assert.isFalse(isPathInside('/x/y', '/a/b'));
|
||||
assert.isFalse(isPathInside('/a/x', '/a/b'));
|
||||
assert.isFalse(isPathInside('/a/bad', '/a/b'));
|
||||
assert.isFalse(isPathInside('/a/x', '/a/b'));
|
||||
assert.isFalse(isPathInside('/a/b', '/a/b'));
|
||||
assert.isFalse(isPathInside('/a/b/c/..', '/a/b'));
|
||||
assert.isFalse(isPathInside('/', '/'));
|
||||
assert.isFalse(isPathInside('/x/..', '/'));
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
assert.isFalse(isPathInside('C:\\a\\x\\y', 'C:\\a\\b'));
|
||||
assert.isFalse(isPathInside('D:\\a\\b\\c', 'C:\\a\\b'));
|
||||
}
|
||||
});
|
||||
|
||||
it('returns true if the child path is inside the parent path', () => {
|
||||
assert.isTrue(isPathInside('a/b/c', 'a/b'));
|
||||
assert.isTrue(isPathInside('a/b/c/d', 'a/b'));
|
||||
assert.isTrue(isPathInside('/a/b/c', '/a/b'));
|
||||
assert.isTrue(isPathInside('/a/b/c', '/a/b/'));
|
||||
assert.isTrue(isPathInside('/a/b/c/', '/a/b'));
|
||||
assert.isTrue(isPathInside('/a/b/c/', '/a/b/'));
|
||||
assert.isTrue(isPathInside('/a/b/c/d', '/a/b'));
|
||||
assert.isTrue(isPathInside('/a/b/c/d/..', '/a/b'));
|
||||
assert.isTrue(isPathInside('/x/y/z/z/y', '/'));
|
||||
assert.isTrue(isPathInside('x/y/z/z/y', '/'));
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
assert.isTrue(isPathInside('C:\\a\\b\\c', 'C:\\a\\b'));
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { nonRenderedRemoteParticipant } from '../../../util/ringrtc/nonRenderedRemoteParticipant';
|
||||
|
||||
describe('nonRenderedRemoteParticipant', () => {
|
||||
it('returns a video request object a width and height of 0', () => {
|
||||
assert.deepEqual(nonRenderedRemoteParticipant({ demuxId: 123 }), {
|
||||
demuxId: 123,
|
||||
width: 0,
|
||||
height: 0,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import { normalizeGroupCallTimestamp } from '../../../util/ringrtc/normalizeGroupCallTimestamp';
|
||||
|
||||
describe('normalizeGroupCallTimestamp', () => {
|
||||
it('returns undefined if passed NaN', () => {
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(NaN));
|
||||
});
|
||||
|
||||
it('returns undefined if passed 0', () => {
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(0));
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(-0));
|
||||
});
|
||||
|
||||
it('returns undefined if passed a negative number', () => {
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(-1));
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(-123));
|
||||
});
|
||||
|
||||
it('returns undefined if passed a string that cannot be parsed as a number', () => {
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(''));
|
||||
assert.isUndefined(normalizeGroupCallTimestamp('uhhh'));
|
||||
});
|
||||
|
||||
it('returns undefined if passed a BigInt of 0', () => {
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(BigInt(0)));
|
||||
});
|
||||
|
||||
it('returns undefined if passed a negative BigInt', () => {
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(BigInt(-1)));
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(BigInt(-123)));
|
||||
});
|
||||
|
||||
it('returns undefined if passed a non-parseable type', () => {
|
||||
[
|
||||
undefined,
|
||||
null,
|
||||
{},
|
||||
[],
|
||||
[123],
|
||||
Symbol('123'),
|
||||
{ [Symbol.toPrimitive]: () => 123 },
|
||||
// eslint-disable-next-line no-new-wrappers
|
||||
new Number(123),
|
||||
].forEach(value => {
|
||||
assert.isUndefined(normalizeGroupCallTimestamp(value));
|
||||
});
|
||||
});
|
||||
|
||||
it('returns positive numbers passed in', () => {
|
||||
assert.strictEqual(normalizeGroupCallTimestamp(1), 1);
|
||||
assert.strictEqual(normalizeGroupCallTimestamp(123), 123);
|
||||
});
|
||||
|
||||
it('parses strings as numbers', () => {
|
||||
assert.strictEqual(normalizeGroupCallTimestamp('1'), 1);
|
||||
assert.strictEqual(normalizeGroupCallTimestamp('123'), 123);
|
||||
});
|
||||
|
||||
it('only parses the first 15 characters of a string', () => {
|
||||
assert.strictEqual(
|
||||
normalizeGroupCallTimestamp('12345678901234567890123456789'),
|
||||
123456789012345
|
||||
);
|
||||
});
|
||||
|
||||
it('converts positive BigInts to numbers', () => {
|
||||
assert.strictEqual(normalizeGroupCallTimestamp(BigInt(1)), 1);
|
||||
assert.strictEqual(normalizeGroupCallTimestamp(BigInt(123)), 123);
|
||||
});
|
||||
});
|
176
ts/test-node/util/sgnlHref_test.ts
Normal file
176
ts/test-node/util/sgnlHref_test.ts
Normal file
|
@ -0,0 +1,176 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import Sinon from 'sinon';
|
||||
import { LoggerType } from '../../types/Logging';
|
||||
|
||||
import { isSgnlHref, parseSgnlHref } from '../../util/sgnlHref';
|
||||
|
||||
function shouldNeverBeCalled() {
|
||||
assert.fail('This should never be called');
|
||||
}
|
||||
|
||||
const explodingLogger: LoggerType = {
|
||||
fatal: shouldNeverBeCalled,
|
||||
error: shouldNeverBeCalled,
|
||||
warn: shouldNeverBeCalled,
|
||||
info: shouldNeverBeCalled,
|
||||
debug: shouldNeverBeCalled,
|
||||
trace: shouldNeverBeCalled,
|
||||
};
|
||||
|
||||
describe('sgnlHref', () => {
|
||||
describe('isSgnlHref', () => {
|
||||
it('returns false for non-strings', () => {
|
||||
const logger = {
|
||||
...explodingLogger,
|
||||
warn: Sinon.spy(),
|
||||
};
|
||||
|
||||
const castToString = (value: unknown): string => value as string;
|
||||
|
||||
assert.isFalse(isSgnlHref(castToString(undefined), logger));
|
||||
assert.isFalse(isSgnlHref(castToString(null), logger));
|
||||
assert.isFalse(isSgnlHref(castToString(123), logger));
|
||||
|
||||
Sinon.assert.calledThrice(logger.warn);
|
||||
});
|
||||
|
||||
it('returns false for invalid URLs', () => {
|
||||
assert.isFalse(isSgnlHref('', explodingLogger));
|
||||
assert.isFalse(isSgnlHref('sgnl', explodingLogger));
|
||||
assert.isFalse(isSgnlHref('sgnl://::', explodingLogger));
|
||||
});
|
||||
|
||||
it('returns false if the protocol is not "sgnl:"', () => {
|
||||
assert.isFalse(isSgnlHref('https://example', explodingLogger));
|
||||
assert.isFalse(
|
||||
isSgnlHref(
|
||||
'https://signal.art/addstickers/?pack_id=abc',
|
||||
explodingLogger
|
||||
)
|
||||
);
|
||||
assert.isFalse(isSgnlHref('signal://example', explodingLogger));
|
||||
});
|
||||
|
||||
it('returns true if the protocol is "sgnl:"', () => {
|
||||
assert.isTrue(isSgnlHref('sgnl://', explodingLogger));
|
||||
assert.isTrue(isSgnlHref('sgnl://example', explodingLogger));
|
||||
assert.isTrue(isSgnlHref('sgnl://example.com', explodingLogger));
|
||||
assert.isTrue(isSgnlHref('SGNL://example', explodingLogger));
|
||||
assert.isTrue(isSgnlHref('sgnl://example?foo=bar', explodingLogger));
|
||||
assert.isTrue(isSgnlHref('sgnl://example/', explodingLogger));
|
||||
assert.isTrue(isSgnlHref('sgnl://example#', explodingLogger));
|
||||
|
||||
assert.isTrue(isSgnlHref('sgnl:foo', explodingLogger));
|
||||
|
||||
assert.isTrue(isSgnlHref('sgnl://user:pass@example', explodingLogger));
|
||||
assert.isTrue(isSgnlHref('sgnl://example.com:1234', explodingLogger));
|
||||
assert.isTrue(
|
||||
isSgnlHref('sgnl://example.com/extra/path/data', explodingLogger)
|
||||
);
|
||||
assert.isTrue(
|
||||
isSgnlHref('sgnl://example/?foo=bar#hash', explodingLogger)
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts URL objects', () => {
|
||||
const invalid = new URL('https://example.com');
|
||||
assert.isFalse(isSgnlHref(invalid, explodingLogger));
|
||||
const valid = new URL('sgnl://example');
|
||||
assert.isTrue(isSgnlHref(valid, explodingLogger));
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseSgnlHref', () => {
|
||||
it('returns a null command for invalid URLs', () => {
|
||||
['', 'sgnl', 'https://example/?foo=bar'].forEach(href => {
|
||||
assert.deepEqual(parseSgnlHref(href, explodingLogger), {
|
||||
command: null,
|
||||
args: new Map<never, never>(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('parses the command for URLs with no arguments', () => {
|
||||
[
|
||||
'sgnl://foo',
|
||||
'sgnl://foo/',
|
||||
'sgnl://foo?',
|
||||
'SGNL://foo?',
|
||||
'sgnl://user:pass@foo',
|
||||
'sgnl://foo/path/data#hash-data',
|
||||
].forEach(href => {
|
||||
assert.deepEqual(parseSgnlHref(href, explodingLogger), {
|
||||
command: 'foo',
|
||||
args: new Map<string, string>(),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("parses a command's arguments", () => {
|
||||
assert.deepEqual(
|
||||
parseSgnlHref(
|
||||
'sgnl://Foo?bar=baz&qux=Quux&num=123&empty=&encoded=hello%20world',
|
||||
explodingLogger
|
||||
),
|
||||
{
|
||||
command: 'Foo',
|
||||
args: new Map([
|
||||
['bar', 'baz'],
|
||||
['qux', 'Quux'],
|
||||
['num', '123'],
|
||||
['empty', ''],
|
||||
['encoded', 'hello world'],
|
||||
]),
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('treats the port as part of the command', () => {
|
||||
assert.propertyVal(
|
||||
parseSgnlHref('sgnl://foo:1234', explodingLogger),
|
||||
'command',
|
||||
'foo:1234'
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores duplicate query parameters', () => {
|
||||
assert.deepPropertyVal(
|
||||
parseSgnlHref('sgnl://x?foo=bar&foo=totally-ignored', explodingLogger),
|
||||
'args',
|
||||
new Map([['foo', 'bar']])
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores other parts of the URL', () => {
|
||||
[
|
||||
'sgnl://foo?bar=baz',
|
||||
'sgnl://foo/?bar=baz',
|
||||
'sgnl://foo/lots/of/path?bar=baz',
|
||||
'sgnl://foo?bar=baz#hash',
|
||||
'sgnl://user:pass@foo?bar=baz',
|
||||
].forEach(href => {
|
||||
assert.deepEqual(parseSgnlHref(href, explodingLogger), {
|
||||
command: 'foo',
|
||||
args: new Map([['bar', 'baz']]),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't do anything fancy with arrays or objects in the query string", () => {
|
||||
// The `qs` module does things like this, which we don't want.
|
||||
assert.deepPropertyVal(
|
||||
parseSgnlHref('sgnl://x?foo[]=bar&foo[]=baz', explodingLogger),
|
||||
'args',
|
||||
new Map([['foo[]', 'bar']])
|
||||
);
|
||||
assert.deepPropertyVal(
|
||||
parseSgnlHref('sgnl://x?foo[bar][baz]=foobarbaz', explodingLogger),
|
||||
'args',
|
||||
new Map([['foo[bar][baz]', 'foobarbaz']])
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
36
ts/test-node/util/sleep_test.ts
Normal file
36
ts/test-node/util/sleep_test.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { useFakeTimers } from 'sinon';
|
||||
|
||||
import { sleep } from '../../util/sleep';
|
||||
|
||||
describe('sleep', () => {
|
||||
beforeEach(function beforeEach() {
|
||||
// This isn't a hook.
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
this.clock = useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(function afterEach() {
|
||||
this.clock.restore();
|
||||
});
|
||||
|
||||
it('returns a promise that resolves after the specified number of milliseconds', async function test() {
|
||||
let isDone = false;
|
||||
|
||||
(async () => {
|
||||
await sleep(123);
|
||||
isDone = true;
|
||||
})();
|
||||
|
||||
assert.isFalse(isDone);
|
||||
|
||||
await this.clock.tickAsync(100);
|
||||
assert.isFalse(isDone);
|
||||
|
||||
await this.clock.tickAsync(25);
|
||||
assert.isTrue(isDone);
|
||||
});
|
||||
});
|
95
ts/test-node/util/sniffImageMimeType_test.ts
Normal file
95
ts/test-node/util/sniffImageMimeType_test.ts
Normal file
|
@ -0,0 +1,95 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { assert } from 'chai';
|
||||
import {
|
||||
IMAGE_BMP,
|
||||
IMAGE_GIF,
|
||||
IMAGE_ICO,
|
||||
IMAGE_JPEG,
|
||||
IMAGE_PNG,
|
||||
IMAGE_WEBP,
|
||||
} from '../../types/MIME';
|
||||
|
||||
import { sniffImageMimeType } from '../../util/sniffImageMimeType';
|
||||
|
||||
describe('sniffImageMimeType', () => {
|
||||
const fixture = (filename: string): Promise<Buffer> => {
|
||||
const fixturePath = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
'..',
|
||||
'fixtures',
|
||||
filename
|
||||
);
|
||||
return fs.promises.readFile(fixturePath);
|
||||
};
|
||||
|
||||
it('returns undefined for empty buffers', () => {
|
||||
assert.isUndefined(sniffImageMimeType(new Uint8Array()));
|
||||
});
|
||||
|
||||
it('returns undefined for non-image files', async () => {
|
||||
await Promise.all(
|
||||
['pixabay-Soap-Bubble-7141.mp4', 'lorem-ipsum.txt'].map(
|
||||
async filename => {
|
||||
assert.isUndefined(sniffImageMimeType(await fixture(filename)));
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('sniffs ICO files', async () => {
|
||||
assert.strictEqual(
|
||||
sniffImageMimeType(await fixture('kitten-1-64-64.ico')),
|
||||
IMAGE_ICO
|
||||
);
|
||||
});
|
||||
|
||||
it('sniffs BMP files', async () => {
|
||||
assert.strictEqual(sniffImageMimeType(await fixture('2x2.bmp')), IMAGE_BMP);
|
||||
});
|
||||
|
||||
it('sniffs GIF files', async () => {
|
||||
assert.strictEqual(
|
||||
sniffImageMimeType(await fixture('giphy-GVNvOUpeYmI7e.gif')),
|
||||
IMAGE_GIF
|
||||
);
|
||||
});
|
||||
|
||||
it('sniffs WEBP files', async () => {
|
||||
assert.strictEqual(
|
||||
sniffImageMimeType(await fixture('512x515-thumbs-up-lincoln.webp')),
|
||||
IMAGE_WEBP
|
||||
);
|
||||
});
|
||||
|
||||
it('sniffs PNG files', async () => {
|
||||
await Promise.all(
|
||||
[
|
||||
'freepngs-2cd43b_bed7d1327e88454487397574d87b64dc_mv2.png',
|
||||
'Animated_PNG_example_bouncing_beach_ball.png',
|
||||
].map(async filename => {
|
||||
assert.strictEqual(
|
||||
sniffImageMimeType(await fixture(filename)),
|
||||
IMAGE_PNG
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('sniffs JPEG files', async () => {
|
||||
assert.strictEqual(
|
||||
sniffImageMimeType(await fixture('kitten-1-64-64.jpg')),
|
||||
IMAGE_JPEG
|
||||
);
|
||||
});
|
||||
|
||||
it('handles ArrayBuffers', async () => {
|
||||
const arrayBuffer = (await fixture('kitten-1-64-64.jpg')).buffer;
|
||||
assert.strictEqual(sniffImageMimeType(arrayBuffer), IMAGE_JPEG);
|
||||
});
|
||||
});
|
67
ts/test-node/util/windowsZoneIdentifier_test.ts
Normal file
67
ts/test-node/util/windowsZoneIdentifier_test.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2020 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import * as fse from 'fs-extra';
|
||||
import * as Sinon from 'sinon';
|
||||
import { assertRejects } from '../helpers';
|
||||
|
||||
import { writeWindowsZoneIdentifier } from '../../util/windowsZoneIdentifier';
|
||||
|
||||
describe('writeWindowsZoneIdentifier', () => {
|
||||
before(function thisNeeded() {
|
||||
if (process.platform !== 'win32') {
|
||||
this.skip();
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(async function thisNeeded() {
|
||||
this.sandbox = Sinon.createSandbox();
|
||||
this.tmpdir = await fs.promises.mkdtemp(
|
||||
path.join(os.tmpdir(), 'signal-test-')
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async function thisNeeded() {
|
||||
this.sandbox.restore();
|
||||
await fse.remove(this.tmpdir);
|
||||
});
|
||||
|
||||
it('writes zone transfer ID 3 (internet) to the Zone.Identifier file', async function thisNeeded() {
|
||||
const file = path.join(this.tmpdir, 'file.txt');
|
||||
await fse.outputFile(file, 'hello');
|
||||
|
||||
await writeWindowsZoneIdentifier(file);
|
||||
|
||||
assert.strictEqual(
|
||||
await fs.promises.readFile(`${file}:Zone.Identifier`, 'utf8'),
|
||||
'[ZoneTransfer]\r\nZoneId=3'
|
||||
);
|
||||
});
|
||||
|
||||
it('fails if there is an existing Zone.Identifier file', async function thisNeeded() {
|
||||
const file = path.join(this.tmpdir, 'file.txt');
|
||||
await fse.outputFile(file, 'hello');
|
||||
await fs.promises.writeFile(`${file}:Zone.Identifier`, '# already here');
|
||||
|
||||
await assertRejects(() => writeWindowsZoneIdentifier(file));
|
||||
});
|
||||
|
||||
it('fails if the original file does not exist', async function thisNeeded() {
|
||||
const file = path.join(this.tmpdir, 'file-never-created.txt');
|
||||
|
||||
await assertRejects(() => writeWindowsZoneIdentifier(file));
|
||||
});
|
||||
|
||||
it('fails if not on Windows', async function thisNeeded() {
|
||||
this.sandbox.stub(process, 'platform').get(() => 'darwin');
|
||||
|
||||
const file = path.join(this.tmpdir, 'file.txt');
|
||||
await fse.outputFile(file, 'hello');
|
||||
|
||||
await assertRejects(() => writeWindowsZoneIdentifier(file));
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue