Skip to content

Commit 13ee231

Browse files
authored
Merge pull request #1540 from javabudd/SESSION-1533
fix: ignore invalid conversation records by requiring id to be a string
2 parents 9b19332 + 7b355bc commit 13ee231

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed

ts/data/data.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,9 @@ async function getAllConversations(): Promise<Array<ConversationModel>> {
142142
const conversationsAttrs =
143143
(await channels.getAllConversations()) as Array<ConversationAttributes>;
144144

145-
return conversationsAttrs.map(attr => new ConversationModel(attr));
145+
return conversationsAttrs
146+
.filter(attr => typeof attr?.id === 'string' && attr.id.length)
147+
.map(attr => new ConversationModel(attr));
146148
}
147149

148150
/**

ts/node/sql.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ async function initializeSql({
196196

197197
console.info('total message count before cleaning: ', getMessageCount());
198198
console.info('total conversation count before cleaning: ', getConversationCount());
199+
cleanUpInvalidConversationIds();
199200
cleanUpOldOpengroupsOnStart();
200201
cleanUpUnusedNodeForKeyEntriesOnStart();
201202
cleanUpUnreadExpiredDaRMessages();
@@ -1851,6 +1852,19 @@ function cleanUpUnreadExpiredDaRMessages() {
18511852
);
18521853
}
18531854

1855+
/**
1856+
* Clean up invalid conversation ids.
1857+
* Even though the `ID` in the `CONVERSATIONS_TABLE` is defined in the sqlite3 schema as being a string primary key,
1858+
* this does not enforce NOT NULL in sqlite3. TODO: consider a database migration to fix this long-term
1859+
*/
1860+
function cleanUpInvalidConversationIds() {
1861+
const deleteResult = assertGlobalInstance()
1862+
.prepare(`DELETE FROM ${CONVERSATIONS_TABLE} WHERE id = '' OR id IS NULL OR typeof(id) != 'text';`)
1863+
.run();
1864+
1865+
console.info(`cleanUpInvalidConversationIds removed ${deleteResult.changes} rows`);
1866+
}
1867+
18541868
function getOutgoingWithoutExpiresAt() {
18551869
const rows = assertGlobalInstance()
18561870
.prepare(

ts/test/data/data_test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { afterEach, beforeEach, describe } from 'mocha';
2+
import Sinon from 'sinon';
3+
import { expect } from 'chai';
4+
import { Data } from '../../data/data';
5+
import { ConversationModel } from '../../models/conversation';
6+
import { channels } from '../../data/channels';
7+
8+
describe('data', () => {
9+
afterEach(() => {
10+
Sinon.restore();
11+
});
12+
13+
describe('getAllConversations', () => {
14+
let getAllConversationsStub: Record<string, any>;
15+
beforeEach(() => {
16+
channels.getAllConversations = () => {};
17+
18+
getAllConversationsStub = Sinon.stub(channels, 'getAllConversations');
19+
});
20+
21+
it('returns empty array when channels.getAllConversations yields invalid conversations', async () => {
22+
getAllConversationsStub.resolves([{} as any, { id: 123 } as any]);
23+
24+
const conversations = await Data.getAllConversations();
25+
26+
expect(conversations).to.be.an('array');
27+
expect(conversations).to.deep.equal([]);
28+
expect(getAllConversationsStub.calledOnce).to.eq(true);
29+
});
30+
31+
it('returns array of ConversationModel for valid conversations', async () => {
32+
const attrs = [{ id: 'abc' } as any, { id: 'def' } as any];
33+
const stub = getAllConversationsStub.resolves(attrs);
34+
35+
const conversations = await Data.getAllConversations();
36+
37+
expect(conversations).to.be.an('array');
38+
expect(conversations).to.have.length(2);
39+
expect(conversations[0]).to.be.instanceOf(ConversationModel);
40+
expect(conversations[1]).to.be.instanceOf(ConversationModel);
41+
expect(stub.calledOnce).to.eq(true);
42+
});
43+
44+
it('filters out invalid and keeps valid when mixed', async () => {
45+
const stub = getAllConversationsStub.resolves([
46+
{ id: 'ok' } as any,
47+
{} as any,
48+
{ id: null } as any,
49+
{ id: 'ok2' } as any,
50+
]);
51+
52+
const conversations = await Data.getAllConversations();
53+
54+
expect(conversations).to.be.an('array');
55+
expect(conversations).to.have.length(2);
56+
expect(conversations.every(c => c instanceof ConversationModel)).to.eq(true);
57+
expect(stub.calledOnce).to.eq(true);
58+
});
59+
});
60+
});

0 commit comments

Comments
 (0)