Introduce Service Id Types

Co-authored-by: Scott Nonnenberg <scott@signal.org>
This commit is contained in:
Fedor Indutny 2023-08-10 18:43:33 +02:00 committed by Jamie Kyle
parent 414c0a58d3
commit 366b875fd2
269 changed files with 5832 additions and 5550 deletions

View file

@ -4,106 +4,120 @@
import { assert } from 'chai';
import type { RecipientsByConversation } from '../../state/ducks/stories';
import type { UUIDStringType } from '../../types/UUID';
import type { ServiceIdString } from '../../types/ServiceId';
import { UUID } from '../../types/UUID';
import { generateAci } from '../../types/ServiceId';
import { generateStoryDistributionId } from '../../types/StoryDistributionId';
import {
getAllUuids,
filterUuids,
getAllServiceIds,
filterServiceIds,
} from '../../util/blockSendUntilConversationsAreVerified';
describe('both/util/blockSendUntilConversationsAreVerified', () => {
const UUID_1 = UUID.generate().toString();
const UUID_2 = UUID.generate().toString();
const UUID_3 = UUID.generate().toString();
const UUID_4 = UUID.generate().toString();
const SERVICE_ID_1 = generateAci();
const SERVICE_ID_2 = generateAci();
const SERVICE_ID_3 = generateAci();
const SERVICE_ID_4 = generateAci();
describe('#getAllUuids', () => {
const LIST_ID_1 = generateStoryDistributionId();
const LIST_ID_2 = generateStoryDistributionId();
const LIST_ID_3 = generateStoryDistributionId();
describe('#getAllServiceIds', () => {
it('should return empty set for empty object', () => {
const starting: RecipientsByConversation = {};
const expected: Array<UUIDStringType> = [];
const actual = getAllUuids(starting);
const expected: Array<ServiceIdString> = [];
const actual = getAllServiceIds(starting);
assert.sameMembers(Array.from(actual), expected);
});
it('should return uuids multiple conversations', () => {
it('should return serviceIds multiple conversations', () => {
const starting: RecipientsByConversation = {
abc: {
uuids: [UUID_1, UUID_2],
[LIST_ID_1]: {
serviceIds: [SERVICE_ID_1, SERVICE_ID_2],
},
def: {
uuids: [],
[LIST_ID_2]: {
serviceIds: [],
},
ghi: {
uuids: [UUID_2, UUID_3],
[LIST_ID_3]: {
serviceIds: [SERVICE_ID_2, SERVICE_ID_3],
},
};
const expected: Array<UUIDStringType> = [UUID_1, UUID_2, UUID_3];
const actual = getAllUuids(starting);
const expected: Array<ServiceIdString> = [
SERVICE_ID_1,
SERVICE_ID_2,
SERVICE_ID_3,
];
const actual = getAllServiceIds(starting);
assert.sameMembers(Array.from(actual), expected);
});
it('should return uuids from byDistributionId and its parent', () => {
it('should return serviceIds from byDistributionId and its parent', () => {
const starting: RecipientsByConversation = {
abc: {
uuids: [UUID_1, UUID_2],
[LIST_ID_1]: {
serviceIds: [SERVICE_ID_1, SERVICE_ID_2],
byDistributionId: {
abc: {
uuids: [UUID_3],
[LIST_ID_1]: {
serviceIds: [SERVICE_ID_3],
},
def: {
uuids: [],
[LIST_ID_2]: {
serviceIds: [],
},
ghi: {
uuids: [UUID_4],
[LIST_ID_3]: {
serviceIds: [SERVICE_ID_4],
},
},
},
};
const expected: Array<UUIDStringType> = [UUID_1, UUID_2, UUID_3, UUID_4];
const actual = getAllUuids(starting);
const expected: Array<ServiceIdString> = [
SERVICE_ID_1,
SERVICE_ID_2,
SERVICE_ID_3,
SERVICE_ID_4,
];
const actual = getAllServiceIds(starting);
assert.sameMembers(Array.from(actual), expected);
});
it('should return uuids from byDistributionId with empty parent', () => {
it('should return serviceIds from byDistributionId with empty parent', () => {
const starting: RecipientsByConversation = {
abc: {
uuids: [],
[LIST_ID_1]: {
serviceIds: [],
byDistributionId: {
abc: {
uuids: [UUID_3],
[LIST_ID_1]: {
serviceIds: [SERVICE_ID_3],
},
},
},
};
const expected: Array<UUIDStringType> = [UUID_3];
const actual = getAllUuids(starting);
const expected: Array<ServiceIdString> = [SERVICE_ID_3];
const actual = getAllServiceIds(starting);
assert.sameMembers(Array.from(actual), expected);
});
});
describe('#filterUuids', () => {
describe('#filterServiceIds', () => {
const starting: RecipientsByConversation = {
abc: {
uuids: [UUID_1],
[LIST_ID_1]: {
serviceIds: [SERVICE_ID_1],
byDistributionId: {
abc: {
uuids: [UUID_2, UUID_3],
[LIST_ID_1]: {
serviceIds: [SERVICE_ID_2, SERVICE_ID_3],
},
def: {
uuids: [UUID_1],
[LIST_ID_2]: {
serviceIds: [SERVICE_ID_1],
},
},
},
def: {
uuids: [UUID_1, UUID_4],
[LIST_ID_2]: {
serviceIds: [SERVICE_ID_1, SERVICE_ID_4],
},
ghi: {
uuids: [UUID_3],
[LIST_ID_3]: {
serviceIds: [SERVICE_ID_3],
byDistributionId: {
abc: {
uuids: [UUID_4],
[LIST_ID_1]: {
serviceIds: [SERVICE_ID_4],
},
},
},
@ -111,34 +125,35 @@ describe('both/util/blockSendUntilConversationsAreVerified', () => {
it('should return empty object if predicate always returns false', () => {
const expected: RecipientsByConversation = {};
const actual = filterUuids(starting, () => false);
const actual = filterServiceIds(starting, () => false);
assert.deepEqual(actual, expected);
});
it('should return exact copy of object if predicate always returns true', () => {
const expected = starting;
const actual = filterUuids(starting, () => true);
const actual = filterServiceIds(starting, () => true);
assert.notStrictEqual(actual, expected);
assert.deepEqual(actual, expected);
});
it('should return just a few uuids for selective predicate', () => {
it('should return just a few serviceIds for selective predicate', () => {
const expected: RecipientsByConversation = {
abc: {
uuids: [],
[LIST_ID_1]: {
serviceIds: [],
byDistributionId: {
abc: {
uuids: [UUID_2, UUID_3],
[LIST_ID_1]: {
serviceIds: [SERVICE_ID_2, SERVICE_ID_3],
},
},
},
ghi: {
uuids: [UUID_3],
[LIST_ID_3]: {
serviceIds: [SERVICE_ID_3],
},
};
const actual = filterUuids(
const actual = filterServiceIds(
starting,
(uuid: UUIDStringType) => uuid === UUID_2 || uuid === UUID_3
(uuid: ServiceIdString) =>
uuid === SERVICE_ID_2 || uuid === SERVICE_ID_3
);
assert.deepEqual(actual, expected);

View file

@ -3,8 +3,8 @@
import { assert } from 'chai';
import type { ConversationType } from '../../state/ducks/conversations';
import { UUID } from '../../types/UUID';
import type { UUIDStringType } from '../../types/UUID';
import { generateAci, normalizeAci } from '../../types/ServiceId';
import type { ServiceIdString } from '../../types/ServiceId';
import { getDefaultConversationWithUuid } from '../helpers/getDefaultConversation';
import { getGroupMemberships } from '../../util/getGroupMemberships';
@ -16,14 +16,14 @@ describe('getGroupMemberships', () => {
discoveredUnregisteredAt: Date.now(),
});
function getConversationByUuid(
uuid: UUIDStringType
function getConversationByServiceId(
serviceId: ServiceIdString
): undefined | ConversationType {
return [
normalConversation1,
normalConversation2,
unregisteredConversation,
].find(conversation => conversation.uuid === uuid);
].find(conversation => conversation.uuid === serviceId);
}
describe('memberships', () => {
@ -32,7 +32,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).memberships;
assert.isEmpty(result);
@ -43,7 +43,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).memberships;
assert.isEmpty(result);
@ -53,7 +53,7 @@ describe('getGroupMemberships', () => {
const conversation = {
memberships: [
{
uuid: UUID.generate().toString(),
uuid: generateAci(),
isAdmin: true,
},
],
@ -61,7 +61,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).memberships;
assert.isEmpty(result);
@ -71,7 +71,7 @@ describe('getGroupMemberships', () => {
const conversation = {
memberships: [
{
uuid: unregisteredConversation.uuid,
uuid: normalizeAci(unregisteredConversation.uuid, 'test'),
isAdmin: true,
},
],
@ -79,7 +79,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).memberships;
assert.lengthOf(result, 1);
@ -93,11 +93,11 @@ describe('getGroupMemberships', () => {
const conversation = {
memberships: [
{
uuid: normalConversation2.uuid,
uuid: normalizeAci(normalConversation2.uuid, 'test'),
isAdmin: false,
},
{
uuid: normalConversation1.uuid,
uuid: normalizeAci(normalConversation1.uuid, 'test'),
isAdmin: true,
},
],
@ -105,7 +105,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).memberships;
assert.lengthOf(result, 2);
@ -126,7 +126,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingApprovalMemberships;
assert.isEmpty(result);
@ -137,7 +137,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingApprovalMemberships;
assert.isEmpty(result);
@ -145,12 +145,12 @@ describe('getGroupMemberships', () => {
it("filters out conversation IDs that don't exist", () => {
const conversation = {
pendingApprovalMemberships: [{ uuid: UUID.generate().toString() }],
pendingApprovalMemberships: [{ uuid: generateAci() }],
};
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingApprovalMemberships;
assert.isEmpty(result);
@ -158,12 +158,14 @@ describe('getGroupMemberships', () => {
it('filters out unregistered conversations', () => {
const conversation = {
pendingApprovalMemberships: [{ uuid: unregisteredConversation.uuid }],
pendingApprovalMemberships: [
{ uuid: normalizeAci(unregisteredConversation.uuid, 'test') },
],
};
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingApprovalMemberships;
assert.isEmpty(result);
@ -172,14 +174,14 @@ describe('getGroupMemberships', () => {
it('hydrates pending-approval memberships', () => {
const conversation = {
pendingApprovalMemberships: [
{ uuid: normalConversation2.uuid },
{ uuid: normalConversation1.uuid },
{ uuid: normalizeAci(normalConversation2.uuid, 'test') },
{ uuid: normalizeAci(normalConversation1.uuid, 'test') },
],
};
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingApprovalMemberships;
assert.lengthOf(result, 2);
@ -194,7 +196,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingMemberships;
assert.isEmpty(result);
@ -205,7 +207,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingMemberships;
assert.isEmpty(result);
@ -215,15 +217,15 @@ describe('getGroupMemberships', () => {
const conversation = {
pendingMemberships: [
{
uuid: UUID.generate().toString(),
addedByUserId: normalConversation1.uuid,
uuid: generateAci(),
addedByUserId: normalizeAci(normalConversation1.uuid, 'test'),
},
],
};
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingMemberships;
assert.isEmpty(result);
@ -234,22 +236,22 @@ describe('getGroupMemberships', () => {
pendingMemberships: [
{
uuid: unregisteredConversation.uuid,
addedByUserId: normalConversation1.uuid,
addedByUserId: normalizeAci(normalConversation1.uuid, 'test'),
},
],
};
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingMemberships;
assert.isEmpty(result);
});
it('hydrates pending memberships', () => {
const abc = UUID.generate().toString();
const xyz = UUID.generate().toString();
const abc = generateAci();
const xyz = generateAci();
const conversation = {
pendingMemberships: [
@ -260,7 +262,7 @@ describe('getGroupMemberships', () => {
const result = getGroupMemberships(
conversation,
getConversationByUuid
getConversationByServiceId
).pendingMemberships;
assert.lengthOf(result, 2);

View file

@ -0,0 +1,33 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { isValidUuid } from '../../util/isValidUuid';
describe('isValidUuid', () => {
const LOWERCASE_V4_UUID = '9cb737ce-2bb3-4c21-9fe0-d286caa0ca68';
it('returns false for non-strings', () => {
assert.isFalse(isValidUuid(undefined));
assert.isFalse(isValidUuid(null));
assert.isFalse(isValidUuid(1234));
});
it('returns false for non-UUID strings', () => {
assert.isFalse(isValidUuid(''));
assert.isFalse(isValidUuid('hello world'));
assert.isFalse(isValidUuid(` ${LOWERCASE_V4_UUID}`));
assert.isFalse(isValidUuid(`${LOWERCASE_V4_UUID} `));
});
it("returns false for UUIDs that aren't version 4", () => {
assert.isFalse(isValidUuid('a200a6e0-d2d9-11eb-bda7-dd5936a30ddf'));
assert.isFalse(isValidUuid('2adb8b83-4f2c-55ca-a481-7f98b716e615'));
});
it('returns true for v4 UUIDs', () => {
assert.isTrue(isValidUuid(LOWERCASE_V4_UUID));
assert.isTrue(isValidUuid(LOWERCASE_V4_UUID.toUpperCase()));
});
});

View file

@ -1,40 +0,0 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { v4 as generateUuid } from 'uuid';
import * as sinon from 'sinon';
import type { LoggerType } from '../../types/Logging';
import { normalizeUuid } from '../../util/normalizeUuid';
describe('normalizeUuid', () => {
let warn: sinon.SinonStub;
let logger: Pick<LoggerType, 'warn'>;
beforeEach(() => {
warn = sinon.stub();
logger = { warn };
});
it('converts uuid to lower case', () => {
const uuid = generateUuid();
assert.strictEqual(normalizeUuid(uuid, 'context 1', logger), uuid);
assert.strictEqual(
normalizeUuid(uuid.toUpperCase(), 'context 2', logger),
uuid
);
sinon.assert.notCalled(warn);
});
it("warns if passed a string that's not a UUID", () => {
normalizeUuid('not-UUID-at-all', 'context 3', logger);
sinon.assert.calledOnce(warn);
sinon.assert.calledWith(
warn,
'Normalizing invalid uuid: not-UUID-at-all to not-uuid-at-all in ' +
'context "context 3"'
);
});
});