Revert "Refactor outbound delivery state"
This reverts commit 9c48a95eb5
.
This commit is contained in:
parent
77668c3247
commit
ad217c808d
29 changed files with 694 additions and 3197 deletions
|
@ -1,564 +0,0 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { sampleSize, times } from 'lodash';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import {
|
||||
SendAction,
|
||||
SendActionType,
|
||||
SendState,
|
||||
SendStateByConversationId,
|
||||
SendStatus,
|
||||
deserializeDatabaseSendStates,
|
||||
isDelivered,
|
||||
isMessageJustForMe,
|
||||
isRead,
|
||||
isSent,
|
||||
maxStatus,
|
||||
sendStateReducer,
|
||||
serializeSendStateForDatabase,
|
||||
someSendStatus,
|
||||
} from '../../messages/MessageSendState';
|
||||
|
||||
describe('message send state utilities', () => {
|
||||
describe('maxStatus', () => {
|
||||
const expectedOrder = [
|
||||
SendStatus.Failed,
|
||||
SendStatus.Pending,
|
||||
SendStatus.Sent,
|
||||
SendStatus.Delivered,
|
||||
SendStatus.Read,
|
||||
SendStatus.Viewed,
|
||||
];
|
||||
|
||||
it('returns the input if arguments are equal', () => {
|
||||
expectedOrder.forEach(status => {
|
||||
assert.strictEqual(maxStatus(status, status), status);
|
||||
});
|
||||
});
|
||||
|
||||
it('orders the statuses', () => {
|
||||
times(100, () => {
|
||||
const [a, b] = sampleSize(expectedOrder, 2);
|
||||
const isABigger = expectedOrder.indexOf(a) > expectedOrder.indexOf(b);
|
||||
const expected = isABigger ? a : b;
|
||||
|
||||
const actual = maxStatus(a, b);
|
||||
assert.strictEqual(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isRead', () => {
|
||||
it('returns true for read and viewed statuses', () => {
|
||||
assert.isTrue(isRead(SendStatus.Read));
|
||||
assert.isTrue(isRead(SendStatus.Viewed));
|
||||
});
|
||||
|
||||
it('returns false for non-read statuses', () => {
|
||||
assert.isFalse(isRead(SendStatus.Delivered));
|
||||
assert.isFalse(isRead(SendStatus.Sent));
|
||||
assert.isFalse(isRead(SendStatus.Pending));
|
||||
assert.isFalse(isRead(SendStatus.Failed));
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDelivered', () => {
|
||||
it('returns true for delivered, read, and viewed statuses', () => {
|
||||
assert.isTrue(isDelivered(SendStatus.Delivered));
|
||||
assert.isTrue(isDelivered(SendStatus.Read));
|
||||
assert.isTrue(isDelivered(SendStatus.Viewed));
|
||||
});
|
||||
|
||||
it('returns false for non-delivered statuses', () => {
|
||||
assert.isFalse(isDelivered(SendStatus.Sent));
|
||||
assert.isFalse(isDelivered(SendStatus.Pending));
|
||||
assert.isFalse(isDelivered(SendStatus.Failed));
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSent', () => {
|
||||
it('returns true for all statuses sent and "above"', () => {
|
||||
assert.isTrue(isSent(SendStatus.Sent));
|
||||
assert.isTrue(isSent(SendStatus.Delivered));
|
||||
assert.isTrue(isSent(SendStatus.Read));
|
||||
assert.isTrue(isSent(SendStatus.Viewed));
|
||||
});
|
||||
|
||||
it('returns false for non-sent statuses', () => {
|
||||
assert.isFalse(isSent(SendStatus.Pending));
|
||||
assert.isFalse(isSent(SendStatus.Failed));
|
||||
});
|
||||
});
|
||||
|
||||
describe('someSendStatus', () => {
|
||||
it('returns false if there are no send states', () => {
|
||||
const alwaysTrue = () => true;
|
||||
assert.isFalse(someSendStatus(undefined, alwaysTrue));
|
||||
assert.isFalse(someSendStatus({}, alwaysTrue));
|
||||
});
|
||||
|
||||
it('returns false if no send states match', () => {
|
||||
const sendStateByConversationId: SendStateByConversationId = {
|
||||
abc: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
def: {
|
||||
status: SendStatus.Read,
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
assert.isFalse(
|
||||
someSendStatus(
|
||||
sendStateByConversationId,
|
||||
(status: SendStatus) => status === SendStatus.Delivered
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true if at least one send state matches', () => {
|
||||
const sendStateByConversationId: SendStateByConversationId = {
|
||||
abc: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
def: {
|
||||
status: SendStatus.Read,
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
};
|
||||
|
||||
assert.isTrue(
|
||||
someSendStatus(
|
||||
sendStateByConversationId,
|
||||
(status: SendStatus) => status === SendStatus.Read
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isMessageJustForMe', () => {
|
||||
const ourConversationId = uuid();
|
||||
|
||||
it('returns false if the conversation has an empty send state', () => {
|
||||
assert.isFalse(isMessageJustForMe(undefined, ourConversationId));
|
||||
assert.isFalse(isMessageJustForMe({}, ourConversationId));
|
||||
});
|
||||
|
||||
it('returns false if the message is for anyone else', () => {
|
||||
assert.isFalse(
|
||||
isMessageJustForMe(
|
||||
{
|
||||
[ourConversationId]: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: 123,
|
||||
},
|
||||
[uuid()]: {
|
||||
status: SendStatus.Pending,
|
||||
updatedAt: 123,
|
||||
},
|
||||
},
|
||||
ourConversationId
|
||||
)
|
||||
);
|
||||
// This is an invalid state, but we still want to test the behavior.
|
||||
assert.isFalse(
|
||||
isMessageJustForMe(
|
||||
{
|
||||
[uuid()]: {
|
||||
status: SendStatus.Pending,
|
||||
updatedAt: 123,
|
||||
},
|
||||
},
|
||||
ourConversationId
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('returns true if the message is just for you', () => {
|
||||
assert.isTrue(
|
||||
isMessageJustForMe(
|
||||
{
|
||||
[ourConversationId]: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: 123,
|
||||
},
|
||||
},
|
||||
ourConversationId
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendStateReducer', () => {
|
||||
const assertTransition = (
|
||||
startStatus: SendStatus,
|
||||
actionType: SendActionType,
|
||||
expectedStatus: SendStatus
|
||||
): void => {
|
||||
const startState: SendState = {
|
||||
status: startStatus,
|
||||
updatedAt: 1,
|
||||
};
|
||||
const action: SendAction = {
|
||||
type: actionType,
|
||||
updatedAt: 2,
|
||||
};
|
||||
const result = sendStateReducer(startState, action);
|
||||
assert.strictEqual(result.status, expectedStatus);
|
||||
assert.strictEqual(
|
||||
result.updatedAt,
|
||||
startStatus === expectedStatus ? 1 : 2
|
||||
);
|
||||
};
|
||||
|
||||
describe('transitions from Pending', () => {
|
||||
it('goes from Pending → Failed with a failure', () => {
|
||||
const result = sendStateReducer(
|
||||
{ status: SendStatus.Pending, updatedAt: 999 },
|
||||
{ type: SendActionType.Failed, updatedAt: 123 }
|
||||
);
|
||||
assert.deepEqual(result, {
|
||||
status: SendStatus.Failed,
|
||||
updatedAt: 123,
|
||||
});
|
||||
});
|
||||
|
||||
it('does nothing when receiving ManuallyRetried', () => {
|
||||
assertTransition(
|
||||
SendStatus.Pending,
|
||||
SendActionType.ManuallyRetried,
|
||||
SendStatus.Pending
|
||||
);
|
||||
});
|
||||
|
||||
it('goes from Pending to all other sent states', () => {
|
||||
assertTransition(
|
||||
SendStatus.Pending,
|
||||
SendActionType.Sent,
|
||||
SendStatus.Sent
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Pending,
|
||||
SendActionType.GotDeliveryReceipt,
|
||||
SendStatus.Delivered
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Pending,
|
||||
SendActionType.GotReadReceipt,
|
||||
SendStatus.Read
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Pending,
|
||||
SendActionType.GotViewedReceipt,
|
||||
SendStatus.Viewed
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitions from Failed', () => {
|
||||
it('does nothing when receiving a Failed action', () => {
|
||||
const result = sendStateReducer(
|
||||
{
|
||||
status: SendStatus.Failed,
|
||||
updatedAt: 123,
|
||||
},
|
||||
{
|
||||
type: SendActionType.Failed,
|
||||
updatedAt: 999,
|
||||
}
|
||||
);
|
||||
assert.deepEqual(result, {
|
||||
status: SendStatus.Failed,
|
||||
updatedAt: 123,
|
||||
});
|
||||
});
|
||||
|
||||
it('goes from Failed to all other states', () => {
|
||||
assertTransition(
|
||||
SendStatus.Failed,
|
||||
SendActionType.ManuallyRetried,
|
||||
SendStatus.Pending
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Failed,
|
||||
SendActionType.Sent,
|
||||
SendStatus.Sent
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Failed,
|
||||
SendActionType.GotDeliveryReceipt,
|
||||
SendStatus.Delivered
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Failed,
|
||||
SendActionType.GotReadReceipt,
|
||||
SendStatus.Read
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Failed,
|
||||
SendActionType.GotViewedReceipt,
|
||||
SendStatus.Viewed
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitions from Sent', () => {
|
||||
it('does nothing when trying to go "backwards"', () => {
|
||||
[SendActionType.Failed, SendActionType.ManuallyRetried].forEach(
|
||||
type => {
|
||||
assertTransition(SendStatus.Sent, type, SendStatus.Sent);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('does nothing when receiving a Sent action', () => {
|
||||
assertTransition(SendStatus.Sent, SendActionType.Sent, SendStatus.Sent);
|
||||
});
|
||||
|
||||
it('can go forward to other states', () => {
|
||||
assertTransition(
|
||||
SendStatus.Sent,
|
||||
SendActionType.GotDeliveryReceipt,
|
||||
SendStatus.Delivered
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Sent,
|
||||
SendActionType.GotReadReceipt,
|
||||
SendStatus.Read
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Sent,
|
||||
SendActionType.GotViewedReceipt,
|
||||
SendStatus.Viewed
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitions from Delivered', () => {
|
||||
it('does nothing when trying to go "backwards"', () => {
|
||||
[
|
||||
SendActionType.Failed,
|
||||
SendActionType.ManuallyRetried,
|
||||
SendActionType.Sent,
|
||||
].forEach(type => {
|
||||
assertTransition(SendStatus.Delivered, type, SendStatus.Delivered);
|
||||
});
|
||||
});
|
||||
|
||||
it('does nothing when receiving a delivery receipt', () => {
|
||||
assertTransition(
|
||||
SendStatus.Delivered,
|
||||
SendActionType.GotDeliveryReceipt,
|
||||
SendStatus.Delivered
|
||||
);
|
||||
});
|
||||
|
||||
it('can go forward to other states', () => {
|
||||
assertTransition(
|
||||
SendStatus.Delivered,
|
||||
SendActionType.GotReadReceipt,
|
||||
SendStatus.Read
|
||||
);
|
||||
assertTransition(
|
||||
SendStatus.Delivered,
|
||||
SendActionType.GotViewedReceipt,
|
||||
SendStatus.Viewed
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitions from Read', () => {
|
||||
it('does nothing when trying to go "backwards"', () => {
|
||||
[
|
||||
SendActionType.Failed,
|
||||
SendActionType.ManuallyRetried,
|
||||
SendActionType.Sent,
|
||||
SendActionType.GotDeliveryReceipt,
|
||||
].forEach(type => {
|
||||
assertTransition(SendStatus.Read, type, SendStatus.Read);
|
||||
});
|
||||
});
|
||||
|
||||
it('does nothing when receiving a read receipt', () => {
|
||||
assertTransition(
|
||||
SendStatus.Read,
|
||||
SendActionType.GotReadReceipt,
|
||||
SendStatus.Read
|
||||
);
|
||||
});
|
||||
|
||||
it('can go forward to the "viewed" state', () => {
|
||||
assertTransition(
|
||||
SendStatus.Read,
|
||||
SendActionType.GotViewedReceipt,
|
||||
SendStatus.Viewed
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitions from Viewed', () => {
|
||||
it('ignores all actions', () => {
|
||||
[
|
||||
SendActionType.Failed,
|
||||
SendActionType.ManuallyRetried,
|
||||
SendActionType.Sent,
|
||||
SendActionType.GotDeliveryReceipt,
|
||||
SendActionType.GotReadReceipt,
|
||||
SendActionType.GotViewedReceipt,
|
||||
].forEach(type => {
|
||||
assertTransition(SendStatus.Viewed, type, SendStatus.Viewed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy transitions', () => {
|
||||
it('allows actions without timestamps', () => {
|
||||
const startState: SendState = {
|
||||
status: SendStatus.Pending,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
const action: SendAction = {
|
||||
type: SendActionType.Sent,
|
||||
updatedAt: undefined,
|
||||
};
|
||||
const result = sendStateReducer(startState, action);
|
||||
assert.isUndefined(result.updatedAt);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('serializeSendStateForDatabase', () => {
|
||||
it('serializes legacy states without an update timestamp', () => {
|
||||
assert.deepEqual(
|
||||
serializeSendStateForDatabase({
|
||||
messageId: 'abc',
|
||||
destinationConversationId: 'def',
|
||||
status: SendStatus.Delivered,
|
||||
}),
|
||||
{
|
||||
destinationConversationId: 'def',
|
||||
messageId: 'abc',
|
||||
status: 'Delivered',
|
||||
updatedAt: 0,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('serializes send states', () => {
|
||||
assert.deepEqual(
|
||||
serializeSendStateForDatabase({
|
||||
messageId: 'abc',
|
||||
destinationConversationId: 'def',
|
||||
status: SendStatus.Read,
|
||||
updatedAt: 956206800000,
|
||||
}),
|
||||
{
|
||||
destinationConversationId: 'def',
|
||||
messageId: 'abc',
|
||||
status: 'Read',
|
||||
updatedAt: 956206800000,
|
||||
}
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
serializeSendStateForDatabase({
|
||||
messageId: 'abc',
|
||||
destinationConversationId: 'def',
|
||||
status: SendStatus.Failed,
|
||||
updatedAt: 956206800000,
|
||||
}),
|
||||
{
|
||||
destinationConversationId: 'def',
|
||||
messageId: 'abc',
|
||||
status: 'Failed',
|
||||
updatedAt: 956206800000,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deserializeDatabaseSendStates', () => {
|
||||
it('returns an empty object if passed no send states', () => {
|
||||
assert.deepEqual(deserializeDatabaseSendStates({}), {});
|
||||
assert.deepEqual(
|
||||
deserializeDatabaseSendStates({
|
||||
sendConversationIdsJoined: undefined,
|
||||
sendStatusesJoined: undefined,
|
||||
sendUpdatedAtsJoined: undefined,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
assert.deepEqual(
|
||||
deserializeDatabaseSendStates({
|
||||
sendConversationIdsJoined: null,
|
||||
sendStatusesJoined: null,
|
||||
sendUpdatedAtsJoined: null,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
assert.deepEqual(
|
||||
deserializeDatabaseSendStates({
|
||||
sendConversationIdsJoined: '',
|
||||
sendStatusesJoined: '',
|
||||
sendUpdatedAtsJoined: '',
|
||||
}),
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('deserializes one send state', () => {
|
||||
assert.deepEqual(
|
||||
deserializeDatabaseSendStates({
|
||||
sendConversationIdsJoined: 'abc',
|
||||
sendStatusesJoined: 'Delivered',
|
||||
sendUpdatedAtsJoined: '956206800000',
|
||||
}),
|
||||
{
|
||||
abc: {
|
||||
status: SendStatus.Delivered,
|
||||
updatedAt: 956206800000,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('deserializes multiple send states', () => {
|
||||
assert.deepEqual(
|
||||
deserializeDatabaseSendStates({
|
||||
sendConversationIdsJoined: 'abc,def',
|
||||
sendStatusesJoined: 'Delivered,Sent',
|
||||
sendUpdatedAtsJoined: '956206800000,1271739600000',
|
||||
}),
|
||||
{
|
||||
abc: {
|
||||
status: SendStatus.Delivered,
|
||||
updatedAt: 956206800000,
|
||||
},
|
||||
def: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: 1271739600000,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('deserializes send states that lack an updated timestamp', () => {
|
||||
assert.deepEqual(
|
||||
deserializeDatabaseSendStates({
|
||||
sendConversationIdsJoined: 'abc,def',
|
||||
sendStatusesJoined: 'Delivered,Sent',
|
||||
sendUpdatedAtsJoined: '956206800000,0',
|
||||
}).def,
|
||||
{
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: undefined,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,263 +0,0 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { getDefaultConversation } from '../helpers/getDefaultConversation';
|
||||
import { ConversationType } from '../../state/ducks/conversations';
|
||||
import { SendStatus } from '../../messages/MessageSendState';
|
||||
|
||||
import { migrateLegacySendAttributes } from '../../messages/migrateLegacySendAttributes';
|
||||
|
||||
describe('migrateLegacySendAttributes', () => {
|
||||
const defaultMessage = {
|
||||
type: 'outgoing' as const,
|
||||
sent_at: 123,
|
||||
sent: true,
|
||||
};
|
||||
|
||||
const createGetConversation = (
|
||||
...conversations: ReadonlyArray<ConversationType>
|
||||
) => {
|
||||
const lookup = new Map<string, ConversationType>();
|
||||
conversations.forEach(conversation => {
|
||||
[conversation.id, conversation.uuid, conversation.e164].forEach(
|
||||
property => {
|
||||
if (property) {
|
||||
lookup.set(property, conversation);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return (id?: string | null) => (id ? lookup.get(id) : undefined);
|
||||
};
|
||||
|
||||
it("doesn't migrate messages that already have the modern send state", () => {
|
||||
const ourConversationId = uuid();
|
||||
const message = {
|
||||
...defaultMessage,
|
||||
sendStateByConversationId: {
|
||||
[ourConversationId]: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: 123,
|
||||
},
|
||||
},
|
||||
};
|
||||
const getConversation = () => undefined;
|
||||
|
||||
assert.isUndefined(
|
||||
migrateLegacySendAttributes(message, getConversation, ourConversationId)
|
||||
);
|
||||
});
|
||||
|
||||
it("doesn't migrate messages that aren't outgoing", () => {
|
||||
const ourConversationId = uuid();
|
||||
const message = {
|
||||
...defaultMessage,
|
||||
type: 'incoming' as const,
|
||||
};
|
||||
const getConversation = () => undefined;
|
||||
|
||||
assert.isUndefined(
|
||||
migrateLegacySendAttributes(message, getConversation, ourConversationId)
|
||||
);
|
||||
});
|
||||
|
||||
it('advances the send state machine, starting from "pending", for different state types', () => {
|
||||
let e164Counter = 0;
|
||||
const getTestConversation = () => {
|
||||
const last4Digits = e164Counter.toString().padStart(4);
|
||||
assert.strictEqual(
|
||||
last4Digits.length,
|
||||
4,
|
||||
'Test setup failure: E164 is too long'
|
||||
);
|
||||
e164Counter += 1;
|
||||
return getDefaultConversation({ e164: `+1999555${last4Digits}` });
|
||||
};
|
||||
|
||||
// This is aliased for clarity.
|
||||
const ignoredUuid = uuid;
|
||||
|
||||
const failedConversationByUuid = getTestConversation();
|
||||
const failedConversationByE164 = getTestConversation();
|
||||
const pendingConversation = getTestConversation();
|
||||
const sentConversation = getTestConversation();
|
||||
const deliveredConversation = getTestConversation();
|
||||
const readConversation = getTestConversation();
|
||||
const conversationNotInRecipientsList = getTestConversation();
|
||||
const ourConversation = getTestConversation();
|
||||
|
||||
const message = {
|
||||
...defaultMessage,
|
||||
recipients: [
|
||||
failedConversationByUuid.uuid,
|
||||
failedConversationByE164.uuid,
|
||||
pendingConversation.uuid,
|
||||
sentConversation.uuid,
|
||||
deliveredConversation.uuid,
|
||||
readConversation.uuid,
|
||||
ignoredUuid(),
|
||||
ourConversation.uuid,
|
||||
],
|
||||
errors: [
|
||||
Object.assign(new Error('looked up by UUID'), {
|
||||
identifier: failedConversationByUuid.uuid,
|
||||
}),
|
||||
Object.assign(new Error('looked up by E164'), {
|
||||
number: failedConversationByE164.e164,
|
||||
}),
|
||||
Object.assign(new Error('ignored error'), {
|
||||
identifier: ignoredUuid(),
|
||||
}),
|
||||
new Error('a different error'),
|
||||
],
|
||||
sent_to: [
|
||||
sentConversation.e164,
|
||||
conversationNotInRecipientsList.uuid,
|
||||
ignoredUuid(),
|
||||
ourConversation.uuid,
|
||||
],
|
||||
delivered_to: [
|
||||
deliveredConversation.uuid,
|
||||
ignoredUuid(),
|
||||
ourConversation.uuid,
|
||||
],
|
||||
read_by: [readConversation.uuid, ignoredUuid()],
|
||||
};
|
||||
const getConversation = createGetConversation(
|
||||
failedConversationByUuid,
|
||||
failedConversationByE164,
|
||||
pendingConversation,
|
||||
sentConversation,
|
||||
deliveredConversation,
|
||||
readConversation,
|
||||
conversationNotInRecipientsList,
|
||||
ourConversation
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
migrateLegacySendAttributes(message, getConversation, ourConversation.id),
|
||||
{
|
||||
[ourConversation.id]: {
|
||||
status: SendStatus.Delivered,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
[failedConversationByUuid.id]: {
|
||||
status: SendStatus.Failed,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
[failedConversationByE164.id]: {
|
||||
status: SendStatus.Failed,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
[pendingConversation.id]: {
|
||||
status: SendStatus.Pending,
|
||||
updatedAt: message.sent_at,
|
||||
},
|
||||
[sentConversation.id]: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
[conversationNotInRecipientsList.id]: {
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
[deliveredConversation.id]: {
|
||||
status: SendStatus.Delivered,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
[readConversation.id]: {
|
||||
status: SendStatus.Read,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('considers our own conversation sent if the "sent" attribute is set', () => {
|
||||
const ourConversation = getDefaultConversation();
|
||||
const conversation1 = getDefaultConversation();
|
||||
const conversation2 = getDefaultConversation();
|
||||
|
||||
const message = {
|
||||
...defaultMessage,
|
||||
recipients: [conversation1.id, conversation2.id],
|
||||
sent: true,
|
||||
};
|
||||
const getConversation = createGetConversation(
|
||||
ourConversation,
|
||||
conversation1,
|
||||
conversation2
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
migrateLegacySendAttributes(
|
||||
message,
|
||||
getConversation,
|
||||
ourConversation.id
|
||||
)?.[ourConversation.id],
|
||||
{
|
||||
status: SendStatus.Sent,
|
||||
updatedAt: undefined,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it("considers our own conversation failed if the message isn't marked sent and we aren't elsewhere in the recipients list", () => {
|
||||
const ourConversation = getDefaultConversation();
|
||||
const conversation1 = getDefaultConversation();
|
||||
const conversation2 = getDefaultConversation();
|
||||
|
||||
const message = {
|
||||
...defaultMessage,
|
||||
recipients: [conversation1.id, conversation2.id],
|
||||
sent: false,
|
||||
};
|
||||
const getConversation = createGetConversation(
|
||||
ourConversation,
|
||||
conversation1,
|
||||
conversation2
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
migrateLegacySendAttributes(
|
||||
message,
|
||||
getConversation,
|
||||
ourConversation.id
|
||||
)?.[ourConversation.id],
|
||||
{
|
||||
status: SendStatus.Failed,
|
||||
updatedAt: undefined,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('migrates a typical legacy note to self message', () => {
|
||||
const ourConversation = getDefaultConversation();
|
||||
const message = {
|
||||
...defaultMessage,
|
||||
conversationId: ourConversation.id,
|
||||
recipients: [],
|
||||
destination: ourConversation.uuid,
|
||||
sent_to: [ourConversation.uuid],
|
||||
sent: true,
|
||||
synced: true,
|
||||
unidentifiedDeliveries: [],
|
||||
delivered_to: [ourConversation.id],
|
||||
read_by: [ourConversation.id],
|
||||
};
|
||||
const getConversation = createGetConversation(ourConversation);
|
||||
|
||||
assert.deepEqual(
|
||||
migrateLegacySendAttributes(message, getConversation, ourConversation.id),
|
||||
{
|
||||
[ourConversation.id]: {
|
||||
status: SendStatus.Read,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
|
@ -9,14 +9,11 @@ import {
|
|||
filter,
|
||||
find,
|
||||
groupBy,
|
||||
isEmpty,
|
||||
isIterable,
|
||||
map,
|
||||
reduce,
|
||||
repeat,
|
||||
size,
|
||||
take,
|
||||
zipObject,
|
||||
} from '../../util/iterables';
|
||||
|
||||
describe('iterable utilities', () => {
|
||||
|
@ -64,15 +61,6 @@ describe('iterable utilities', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('repeat', () => {
|
||||
it('repeats the same value forever', () => {
|
||||
const result = repeat('foo');
|
||||
|
||||
const truncated = [...take(result, 10)];
|
||||
assert.deepEqual(truncated, Array(10).fill('foo'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('size', () => {
|
||||
it('returns the length of a string', () => {
|
||||
assert.strictEqual(size(''), 0);
|
||||
|
@ -273,28 +261,6 @@ describe('iterable utilities', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('isEmpty', () => {
|
||||
it('returns true for empty iterables', () => {
|
||||
assert.isTrue(isEmpty(''));
|
||||
assert.isTrue(isEmpty([]));
|
||||
assert.isTrue(isEmpty(new Set()));
|
||||
});
|
||||
|
||||
it('returns false for non-empty iterables', () => {
|
||||
assert.isFalse(isEmpty(' '));
|
||||
assert.isFalse(isEmpty([1, 2]));
|
||||
assert.isFalse(isEmpty(new Set([3, 4])));
|
||||
});
|
||||
|
||||
it('does not "look past" the first element', () => {
|
||||
function* numbers() {
|
||||
yield 1;
|
||||
throw new Error('this should never happen');
|
||||
}
|
||||
assert.isFalse(isEmpty(numbers()));
|
||||
});
|
||||
});
|
||||
|
||||
describe('map', () => {
|
||||
it('returns an empty iterable when passed an empty iterable', () => {
|
||||
const fn = sinon.fake();
|
||||
|
@ -386,23 +352,4 @@ describe('iterable utilities', () => {
|
|||
assert.deepEqual([...take(set, 10000)], [1, 2, 3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('zipObject', () => {
|
||||
it('zips up an object', () => {
|
||||
assert.deepEqual(zipObject(['foo', 'bar'], [1, 2]), { foo: 1, bar: 2 });
|
||||
});
|
||||
|
||||
it('stops if the keys "run out" first', () => {
|
||||
assert.deepEqual(zipObject(['foo', 'bar'], [1, 2, 3, 4, 5, 6]), {
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('stops if the values "run out" first', () => {
|
||||
assert.deepEqual(zipObject(['foo', 'bar', 'baz'], [1]), {
|
||||
foo: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue