Timeline: repair oldest/newest metrics if we fetch nothing

This commit is contained in:
Scott Nonnenberg
2020-12-04 12:41:40 -08:00
committed by GitHub
parent 56ae4a41eb
commit 6832b8acca
47 changed files with 579 additions and 173 deletions

View File

@@ -0,0 +1,182 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import * as Attachment from '../../types/Attachment';
import * as MIME from '../../types/MIME';
import { SignalService } from '../../protobuf';
import { stringToArrayBuffer } from '../../../js/modules/string_to_array_buffer';
describe('Attachment', () => {
describe('getFileExtension', () => {
it('should return file extension from content type', () => {
const input: Attachment.Attachment = {
data: stringToArrayBuffer('foo'),
contentType: MIME.IMAGE_GIF,
};
assert.strictEqual(Attachment.getFileExtension(input), 'gif');
});
it('should return file extension for QuickTime videos', () => {
const input: Attachment.Attachment = {
data: stringToArrayBuffer('foo'),
contentType: MIME.VIDEO_QUICKTIME,
};
assert.strictEqual(Attachment.getFileExtension(input), 'mov');
});
});
describe('getSuggestedFilename', () => {
context('for attachment with filename', () => {
it('should return existing filename if present', () => {
const attachment: Attachment.Attachment = {
fileName: 'funny-cat.mov',
data: stringToArrayBuffer('foo'),
contentType: MIME.VIDEO_QUICKTIME,
};
const actual = Attachment.getSuggestedFilename({ attachment });
const expected = 'funny-cat.mov';
assert.strictEqual(actual, expected);
});
});
context('for attachment without filename', () => {
it('should generate a filename based on timestamp', () => {
const attachment: Attachment.Attachment = {
data: stringToArrayBuffer('foo'),
contentType: MIME.VIDEO_QUICKTIME,
};
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
const actual = Attachment.getSuggestedFilename({
attachment,
timestamp,
});
const expected = 'signal-1970-01-01-000000.mov';
assert.strictEqual(actual, expected);
});
});
context('for attachment with index', () => {
it('should generate a filename based on timestamp', () => {
const attachment: Attachment.Attachment = {
data: stringToArrayBuffer('foo'),
contentType: MIME.VIDEO_QUICKTIME,
};
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
const actual = Attachment.getSuggestedFilename({
attachment,
timestamp,
index: 3,
});
const expected = 'signal-1970-01-01-000000_003.mov';
assert.strictEqual(actual, expected);
});
});
});
describe('isVisualMedia', () => {
it('should return true for images', () => {
const attachment: Attachment.Attachment = {
fileName: 'meme.gif',
data: stringToArrayBuffer('gif'),
contentType: MIME.IMAGE_GIF,
};
assert.isTrue(Attachment.isVisualMedia(attachment));
});
it('should return true for videos', () => {
const attachment: Attachment.Attachment = {
fileName: 'meme.mp4',
data: stringToArrayBuffer('mp4'),
contentType: MIME.VIDEO_MP4,
};
assert.isTrue(Attachment.isVisualMedia(attachment));
});
it('should return false for voice message attachment', () => {
const attachment: Attachment.Attachment = {
fileName: 'Voice Message.aac',
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
data: stringToArrayBuffer('voice message'),
contentType: MIME.AUDIO_AAC,
};
assert.isFalse(Attachment.isVisualMedia(attachment));
});
it('should return false for other attachments', () => {
const attachment: Attachment.Attachment = {
fileName: 'foo.json',
data: stringToArrayBuffer('{"foo": "bar"}'),
contentType: MIME.APPLICATION_JSON,
};
assert.isFalse(Attachment.isVisualMedia(attachment));
});
});
describe('isFile', () => {
it('should return true for JSON', () => {
const attachment: Attachment.Attachment = {
fileName: 'foo.json',
data: stringToArrayBuffer('{"foo": "bar"}'),
contentType: MIME.APPLICATION_JSON,
};
assert.isTrue(Attachment.isFile(attachment));
});
it('should return false for images', () => {
const attachment: Attachment.Attachment = {
fileName: 'meme.gif',
data: stringToArrayBuffer('gif'),
contentType: MIME.IMAGE_GIF,
};
assert.isFalse(Attachment.isFile(attachment));
});
it('should return false for videos', () => {
const attachment: Attachment.Attachment = {
fileName: 'meme.mp4',
data: stringToArrayBuffer('mp4'),
contentType: MIME.VIDEO_MP4,
};
assert.isFalse(Attachment.isFile(attachment));
});
it('should return false for voice message attachment', () => {
const attachment: Attachment.Attachment = {
fileName: 'Voice Message.aac',
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
data: stringToArrayBuffer('voice message'),
contentType: MIME.AUDIO_AAC,
};
assert.isFalse(Attachment.isFile(attachment));
});
});
describe('isVoiceMessage', () => {
it('should return true for voice message attachment', () => {
const attachment: Attachment.Attachment = {
fileName: 'Voice Message.aac',
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
data: stringToArrayBuffer('voice message'),
contentType: MIME.AUDIO_AAC,
};
assert.isTrue(Attachment.isVoiceMessage(attachment));
});
it('should return true for legacy Android voice message attachment', () => {
const attachment: Attachment.Attachment = {
data: stringToArrayBuffer('voice message'),
contentType: MIME.AUDIO_MP3,
};
assert.isTrue(Attachment.isVoiceMessage(attachment));
});
it('should return false for other attachments', () => {
const attachment: Attachment.Attachment = {
fileName: 'foo.gif',
data: stringToArrayBuffer('foo'),
contentType: MIME.IMAGE_GIF,
};
assert.isFalse(Attachment.isVoiceMessage(attachment));
});
});
});

View File

@@ -0,0 +1,185 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { contactSelector, getName } from '../../types/Contact';
describe('Contact', () => {
describe('getName', () => {
it('returns displayName if provided', () => {
const contact = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
};
const expected = 'displayName';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
it('returns organization if no displayName', () => {
const contact = {
name: {
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
};
const expected = 'Somewhere, Inc.';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
it('returns givenName + familyName if no displayName or organization', () => {
const contact = {
name: {
givenName: 'givenName',
familyName: 'familyName',
},
};
const expected = 'givenName familyName';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
it('returns just givenName', () => {
const contact = {
name: {
givenName: 'givenName',
},
};
const expected = 'givenName';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
it('returns just familyName', () => {
const contact = {
name: {
familyName: 'familyName',
},
};
const expected = 'familyName';
const actual = getName(contact);
assert.strictEqual(actual, expected);
});
});
describe('contactSelector', () => {
const regionCode = '1';
const signalAccount = '+1202555000';
const getAbsoluteAttachmentPath = (path: string) => `absolute:${path}`;
it('eliminates avatar if it has had an attachment download error', () => {
const contact = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
error: true,
},
},
};
const expected = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: undefined,
signalAccount,
number: undefined,
};
const actual = contactSelector(contact, {
regionCode,
signalAccount,
getAbsoluteAttachmentPath,
});
assert.deepEqual(actual, expected);
});
it('does not calculate absolute path if avatar is pending', () => {
const contact = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
pending: true,
},
},
};
const expected = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
pending: true,
path: undefined,
},
},
signalAccount,
number: undefined,
};
const actual = contactSelector(contact, {
regionCode,
signalAccount,
getAbsoluteAttachmentPath,
});
assert.deepEqual(actual, expected);
});
it('calculates absolute path', () => {
const contact = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
path: 'somewhere',
},
},
};
const expected = {
name: {
displayName: 'displayName',
givenName: 'givenName',
familyName: 'familyName',
},
organization: 'Somewhere, Inc.',
avatar: {
isProfile: true,
avatar: {
path: 'absolute:somewhere',
},
},
signalAccount,
number: undefined,
};
const actual = contactSelector(contact, {
regionCode,
signalAccount,
getAbsoluteAttachmentPath,
});
assert.deepEqual(actual, expected);
});
});
});

View File

@@ -0,0 +1,152 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import os from 'os';
import Sinon from 'sinon';
import { assert } from 'chai';
import * as Settings from '../../types/Settings';
describe('Settings', () => {
let sandbox: Sinon.SinonSandbox;
beforeEach(() => {
sandbox = Sinon.createSandbox();
});
afterEach(() => {
sandbox.restore();
});
describe('getAudioNotificationSupport', () => {
it('returns native support on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.strictEqual(
Settings.getAudioNotificationSupport(),
Settings.AudioNotificationSupport.Native
);
});
it('returns no support on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.strictEqual(
Settings.getAudioNotificationSupport(),
Settings.AudioNotificationSupport.None
);
});
it('returns native support on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.strictEqual(
Settings.getAudioNotificationSupport(),
Settings.AudioNotificationSupport.Native
);
});
it('returns custom support on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.strictEqual(
Settings.getAudioNotificationSupport(),
Settings.AudioNotificationSupport.Custom
);
});
});
describe('isAudioNotificationSupported', () => {
it('returns true on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isTrue(Settings.isAudioNotificationSupported());
});
it('returns false on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.isFalse(Settings.isAudioNotificationSupported());
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isAudioNotificationSupported());
});
it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isAudioNotificationSupported());
});
});
describe('isNotificationGroupingSupported', () => {
it('returns true on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isTrue(Settings.isNotificationGroupingSupported());
});
it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.isFalse(Settings.isNotificationGroupingSupported());
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isNotificationGroupingSupported());
});
it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isNotificationGroupingSupported());
});
});
describe('isHideMenuBarSupported', () => {
it('returns false on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isFalse(Settings.isHideMenuBarSupported());
});
it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.isTrue(Settings.isHideMenuBarSupported());
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isHideMenuBarSupported());
});
it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isHideMenuBarSupported());
});
});
describe('isDrawAttentionSupported', () => {
it('returns false on macOS', () => {
sandbox.stub(process, 'platform').value('darwin');
assert.isFalse(Settings.isDrawAttentionSupported());
});
it('returns true on Windows 7', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('7.0.0');
assert.isTrue(Settings.isDrawAttentionSupported());
});
it('returns true on Windows 8', () => {
sandbox.stub(process, 'platform').value('win32');
sandbox.stub(os, 'release').returns('8.0.0');
assert.isTrue(Settings.isDrawAttentionSupported());
});
it('returns true on Linux', () => {
sandbox.stub(process, 'platform').value('linux');
assert.isTrue(Settings.isDrawAttentionSupported());
});
});
});

View File

@@ -0,0 +1,207 @@
// Copyright 2018-2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import * as Message from '../../../types/message/initializeAttachmentMetadata';
import { IncomingMessage } from '../../../types/Message';
import { SignalService } from '../../../protobuf';
import * as MIME from '../../../types/MIME';
import { stringToArrayBuffer } from '../../../../js/modules/string_to_array_buffer';
describe('Message', () => {
describe('initializeAttachmentMetadata', () => {
it('should classify visual media attachments', async () => {
const input: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [
{
contentType: MIME.IMAGE_JPEG,
data: stringToArrayBuffer('foo'),
fileName: 'foo.jpg',
size: 1111,
},
],
};
const expected: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [
{
contentType: MIME.IMAGE_JPEG,
data: stringToArrayBuffer('foo'),
fileName: 'foo.jpg',
size: 1111,
},
],
hasAttachments: 1,
hasVisualMediaAttachments: 1,
hasFileAttachments: undefined,
};
const actual = await Message.initializeAttachmentMetadata(input);
assert.deepEqual(actual, expected);
});
it('should classify file attachments', async () => {
const input: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [
{
contentType: MIME.APPLICATION_OCTET_STREAM,
data: stringToArrayBuffer('foo'),
fileName: 'foo.bin',
size: 1111,
},
],
};
const expected: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [
{
contentType: MIME.APPLICATION_OCTET_STREAM,
data: stringToArrayBuffer('foo'),
fileName: 'foo.bin',
size: 1111,
},
],
hasAttachments: 1,
hasVisualMediaAttachments: undefined,
hasFileAttachments: 1,
};
const actual = await Message.initializeAttachmentMetadata(input);
assert.deepEqual(actual, expected);
});
it('should classify voice message attachments', async () => {
const input: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [
{
contentType: MIME.AUDIO_AAC,
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
data: stringToArrayBuffer('foo'),
fileName: 'Voice Message.aac',
size: 1111,
},
],
};
const expected: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [
{
contentType: MIME.AUDIO_AAC,
flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE,
data: stringToArrayBuffer('foo'),
fileName: 'Voice Message.aac',
size: 1111,
},
],
hasAttachments: 1,
hasVisualMediaAttachments: undefined,
hasFileAttachments: undefined,
};
const actual = await Message.initializeAttachmentMetadata(input);
assert.deepEqual(actual, expected);
});
it('does not include long message attachments', async () => {
const input: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [
{
contentType: MIME.LONG_MESSAGE,
data: stringToArrayBuffer('foo'),
fileName: 'message.txt',
size: 1111,
},
],
};
const expected: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [
{
contentType: MIME.LONG_MESSAGE,
data: stringToArrayBuffer('foo'),
fileName: 'message.txt',
size: 1111,
},
],
hasAttachments: 0,
hasVisualMediaAttachments: undefined,
hasFileAttachments: undefined,
};
const actual = await Message.initializeAttachmentMetadata(input);
assert.deepEqual(actual, expected);
});
it('handles not attachments', async () => {
const input: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [],
};
const expected: IncomingMessage = {
type: 'incoming',
conversationId: 'foo',
id: '11111111-1111-1111-1111-111111111111',
timestamp: 1523317140899,
received_at: 1523317140899,
sent_at: 1523317140800,
attachments: [],
hasAttachments: 0,
hasVisualMediaAttachments: undefined,
hasFileAttachments: undefined,
};
const actual = await Message.initializeAttachmentMetadata(input);
assert.deepEqual(actual, expected);
});
});
});