@remit/drizzle-service 0.0.106 → 0.0.108
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -2
- package/src/id.ts +9 -0
- package/src/index.ts +4 -0
- package/src/repair/junk-only-address.sqlite.test.ts +2 -0
- package/src/repos/account.conformance.d1.test.ts +3 -1
- package/src/repos/account.conformance.sqlite.test.ts +2 -0
- package/src/repos/calendar-default.suite.ts +5 -1
- package/src/repos/calendar-put.suite.ts +5 -1
- package/src/repos/cascade-delete.ts +1 -1
- package/src/repos/envelope.sqlite.test.ts +2 -0
- package/src/repos/envelope.test.ts +2 -0
- package/src/repos/envelope.ts +5 -3
- package/src/repos/i4-account-config.suite.ts +5 -1
- package/src/repos/i4-account.test.ts +3 -0
- package/src/repos/i4-address-junk-move.sqlite.test.ts +2 -0
- package/src/repos/i4-mailbox-special-use.sqlite.test.ts +2 -0
- package/src/repos/i4-mailbox.sqlite.test.ts +3 -0
- package/src/repos/i4-mailbox.test.ts +4 -3
- package/src/repos/i4-mailbox.ts +7 -28
- package/src/repos/i4-outbox-attachment.test.ts +2 -0
- package/src/repos/message-objects.sqlite.test.ts +271 -0
- package/src/repos/message-objects.ts +70 -0
- package/src/repos/message.sqlite.test.ts +11 -17
- package/src/repos/message.test.ts +4 -6
- package/src/repos/message.ts +341 -46
- package/src/repos/thread-message-body-muted.sqlite.test.ts +2 -0
- package/src/repos/thread-message-field-terms.sqlite.test.ts +2 -0
- package/src/repos/thread-message.sqlite.test.ts +2 -0
- package/src/repos/thread-message.test.ts +5 -0
- package/src/search/thread-message-search.sqlite.test.ts +2 -0
- package/src/sqlite-repositories.ts +18 -8
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import {
|
|
3
|
+
existsSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
mkdtempSync,
|
|
6
|
+
rmSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { after, before, describe, test } from "node:test";
|
|
12
|
+
import type { StorageService } from "@remit/storage-service";
|
|
13
|
+
import { createFilesystemStorageService } from "@remit/storage-service/filesystem";
|
|
14
|
+
import { BETTER_SQLITE3_LIMITS } from "../bound-parameters.js";
|
|
15
|
+
import {
|
|
16
|
+
createSqliteRepositories,
|
|
17
|
+
type SqliteRepositories,
|
|
18
|
+
} from "../sqlite-repositories.js";
|
|
19
|
+
import { createTestDb, randomId, type TestDb } from "../test-db.js";
|
|
20
|
+
import { mailboxCreated } from "./test-helpers.js";
|
|
21
|
+
|
|
22
|
+
const ACCOUNT_CONFIG_ID = "cfg-objects";
|
|
23
|
+
|
|
24
|
+
const bodyPart = (partPath: string) => ({
|
|
25
|
+
partPath,
|
|
26
|
+
parentPartPath: null,
|
|
27
|
+
mediaType: "TEXT" as const,
|
|
28
|
+
mediaSubtype: "plain",
|
|
29
|
+
transferEncoding: "7BIT" as const,
|
|
30
|
+
sizeOctets: 4,
|
|
31
|
+
isMultipart: false,
|
|
32
|
+
parameters: [],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("message objects follow the last row that references them", () => {
|
|
36
|
+
let db: TestDb;
|
|
37
|
+
let close: () => Promise<void>;
|
|
38
|
+
let directory: string;
|
|
39
|
+
let storage: StorageService;
|
|
40
|
+
let failDeletesAfter = Number.POSITIVE_INFINITY;
|
|
41
|
+
let repos: SqliteRepositories;
|
|
42
|
+
let deleteCalls: number[] = [];
|
|
43
|
+
|
|
44
|
+
before(async () => {
|
|
45
|
+
({ db, close } = await createTestDb());
|
|
46
|
+
directory = mkdtempSync(join(tmpdir(), "message-objects-"));
|
|
47
|
+
storage = createFilesystemStorageService(directory);
|
|
48
|
+
const counted: Pick<StorageService, "deleteKeys"> = {
|
|
49
|
+
deleteKeys: (keys) => {
|
|
50
|
+
deleteCalls.push(keys.length);
|
|
51
|
+
if (failDeletesAfter <= 0) {
|
|
52
|
+
return Promise.reject(new Error("storage went away"));
|
|
53
|
+
}
|
|
54
|
+
failDeletesAfter -= 1;
|
|
55
|
+
return storage.deleteKeys(keys);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
repos = createSqliteRepositories(
|
|
59
|
+
{ app: db, mail: db, search: db },
|
|
60
|
+
BETTER_SQLITE3_LIMITS,
|
|
61
|
+
{ storage: counted },
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
after(async () => {
|
|
66
|
+
await close();
|
|
67
|
+
rmSync(directory, { recursive: true, force: true });
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const folder = async (accountId: string, fullPath: string) =>
|
|
71
|
+
mailboxCreated(
|
|
72
|
+
await repos.mailbox.create({
|
|
73
|
+
accountId,
|
|
74
|
+
namespaceType: "personal",
|
|
75
|
+
namespacePrefix: "",
|
|
76
|
+
hierarchyDelimiter: "/",
|
|
77
|
+
fullPath,
|
|
78
|
+
uidValidity: 1,
|
|
79
|
+
uidNext: 1,
|
|
80
|
+
highestModseq: "0",
|
|
81
|
+
messageCount: 0,
|
|
82
|
+
unseenCount: 0,
|
|
83
|
+
deletedCount: 0,
|
|
84
|
+
totalSize: 0,
|
|
85
|
+
lastSyncUid: 0,
|
|
86
|
+
highWaterMarkUid: 0,
|
|
87
|
+
lastMessageSyncAt: 0,
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const message = async (
|
|
92
|
+
accountId: string,
|
|
93
|
+
mailboxId: string,
|
|
94
|
+
uid: number,
|
|
95
|
+
body: { from?: string } = {},
|
|
96
|
+
) => {
|
|
97
|
+
const messageId = randomId();
|
|
98
|
+
const owner = body.from ?? messageId;
|
|
99
|
+
const stored = await storage.storeMessageBody({
|
|
100
|
+
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
101
|
+
accountId,
|
|
102
|
+
messageId: owner,
|
|
103
|
+
content: Buffer.from(`body of ${owner}`),
|
|
104
|
+
});
|
|
105
|
+
await storage.storeBodyPart({
|
|
106
|
+
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
107
|
+
accountId,
|
|
108
|
+
messageId,
|
|
109
|
+
partPath: "1",
|
|
110
|
+
content: Buffer.from("part"),
|
|
111
|
+
});
|
|
112
|
+
await repos.message.create({
|
|
113
|
+
messageId,
|
|
114
|
+
mailboxId,
|
|
115
|
+
uid,
|
|
116
|
+
sequenceNumber: uid,
|
|
117
|
+
rfc822Size: 10,
|
|
118
|
+
internalDate: 1700000000000,
|
|
119
|
+
bodyStorageKey: stored.uri,
|
|
120
|
+
});
|
|
121
|
+
await repos.envelope.upsertBodyParts(messageId, [bodyPart("1")]);
|
|
122
|
+
return { messageId, accountId, bodyStorageKey: stored.uri };
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const part = (row: { accountId: string; messageId: string }) =>
|
|
126
|
+
storage.retrieveBodyPart(
|
|
127
|
+
ACCOUNT_CONFIG_ID,
|
|
128
|
+
row.accountId,
|
|
129
|
+
row.messageId,
|
|
130
|
+
"1",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
test("a copy sharing the source's body keeps it until the copy goes too", async () => {
|
|
134
|
+
const accountId = randomId();
|
|
135
|
+
const inbox = await folder(accountId, "INBOX");
|
|
136
|
+
const source = await message(accountId, inbox.mailboxId, 1);
|
|
137
|
+
const copy = await message(accountId, inbox.mailboxId, 2, {
|
|
138
|
+
from: source.messageId,
|
|
139
|
+
});
|
|
140
|
+
assert.equal(copy.bodyStorageKey, source.bodyStorageKey);
|
|
141
|
+
|
|
142
|
+
await repos.message.delete(source.messageId);
|
|
143
|
+
|
|
144
|
+
assert.ok(await storage.retrieve(copy.bodyStorageKey));
|
|
145
|
+
assert.ok(await part(copy));
|
|
146
|
+
|
|
147
|
+
await repos.message.delete(copy.messageId);
|
|
148
|
+
|
|
149
|
+
assert.equal(await storage.exists(copy.bodyStorageKey), false);
|
|
150
|
+
assert.equal(await part(copy), null);
|
|
151
|
+
assert.equal(await part(source), null);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("a placement that no longer matches keeps the row and its objects", async () => {
|
|
155
|
+
const accountId = randomId();
|
|
156
|
+
const inbox = await folder(accountId, "INBOX");
|
|
157
|
+
const kept = await message(accountId, inbox.mailboxId, 1);
|
|
158
|
+
|
|
159
|
+
assert.equal(
|
|
160
|
+
await repos.message.deleteIfPlacement(kept.messageId, { uid: 2 }),
|
|
161
|
+
false,
|
|
162
|
+
);
|
|
163
|
+
assert.ok(await storage.retrieve(kept.bodyStorageKey));
|
|
164
|
+
|
|
165
|
+
assert.equal(
|
|
166
|
+
await repos.message.deleteIfPlacement(kept.messageId, { uid: 1 }),
|
|
167
|
+
true,
|
|
168
|
+
);
|
|
169
|
+
assert.equal(await storage.exists(kept.bodyStorageKey), false);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("a body stored under the key shape without an account config removes its message prefix", async () => {
|
|
173
|
+
const accountId = randomId();
|
|
174
|
+
const inbox = await folder(accountId, "INBOX");
|
|
175
|
+
const messageId = randomId();
|
|
176
|
+
const legacy = join(
|
|
177
|
+
directory,
|
|
178
|
+
"accounts",
|
|
179
|
+
accountId,
|
|
180
|
+
"messages",
|
|
181
|
+
messageId,
|
|
182
|
+
);
|
|
183
|
+
mkdirSync(join(legacy, "parts"), { recursive: true });
|
|
184
|
+
writeFileSync(join(legacy, "body.eml"), "legacy");
|
|
185
|
+
writeFileSync(join(legacy, "parts", "1"), "part");
|
|
186
|
+
await repos.message.create({
|
|
187
|
+
messageId,
|
|
188
|
+
mailboxId: inbox.mailboxId,
|
|
189
|
+
uid: 1,
|
|
190
|
+
sequenceNumber: 1,
|
|
191
|
+
rfc822Size: 10,
|
|
192
|
+
internalDate: 1700000000000,
|
|
193
|
+
bodyStorageKey: `file://${join(legacy, "body.eml")}`,
|
|
194
|
+
});
|
|
195
|
+
await repos.envelope.upsertBodyParts(messageId, [bodyPart("1")]);
|
|
196
|
+
|
|
197
|
+
await repos.message.delete(messageId);
|
|
198
|
+
|
|
199
|
+
assert.equal(existsSync(legacy), false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("a folder delete that fails part way has already removed the batches it finished", async () => {
|
|
203
|
+
const accountId = randomId();
|
|
204
|
+
const doomed = await folder(accountId, "Doomed");
|
|
205
|
+
const rows = [];
|
|
206
|
+
for (let uid = 1; uid <= 101; uid += 1) {
|
|
207
|
+
rows.push(await message(accountId, doomed.mailboxId, uid));
|
|
208
|
+
}
|
|
209
|
+
failDeletesAfter = 1;
|
|
210
|
+
|
|
211
|
+
await assert.rejects(
|
|
212
|
+
repos.mailbox.deleteMailboxWithMail(accountId, doomed.mailboxId),
|
|
213
|
+
/storage went away/,
|
|
214
|
+
);
|
|
215
|
+
failDeletesAfter = Number.POSITIVE_INFINITY;
|
|
216
|
+
|
|
217
|
+
const gone = await Promise.all(
|
|
218
|
+
rows.map((row) => storage.exists(row.bodyStorageKey)),
|
|
219
|
+
);
|
|
220
|
+
assert.equal(gone.filter((exists) => !exists).length, 100);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const gone = async (
|
|
224
|
+
rows: readonly {
|
|
225
|
+
accountId: string;
|
|
226
|
+
messageId: string;
|
|
227
|
+
bodyStorageKey: string;
|
|
228
|
+
}[],
|
|
229
|
+
) => {
|
|
230
|
+
for (const row of rows) {
|
|
231
|
+
assert.equal(await storage.exists(row.bodyStorageKey), false);
|
|
232
|
+
assert.equal(await part(row), null);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
test("deleting many messages makes one storage call per batch and keeps a body a copy shares", async () => {
|
|
237
|
+
const accountId = randomId();
|
|
238
|
+
const inbox = await folder(accountId, "INBOX");
|
|
239
|
+
const rows = [];
|
|
240
|
+
for (let uid = 1; uid <= 300; uid += 1) {
|
|
241
|
+
rows.push(await message(accountId, inbox.mailboxId, uid));
|
|
242
|
+
}
|
|
243
|
+
const [shared, ...unshared] = rows;
|
|
244
|
+
const copy = await message(accountId, inbox.mailboxId, 301, {
|
|
245
|
+
from: shared.messageId,
|
|
246
|
+
});
|
|
247
|
+
deleteCalls = [];
|
|
248
|
+
|
|
249
|
+
await repos.message.deleteMany(rows.map((row) => row.messageId));
|
|
250
|
+
|
|
251
|
+
assert.equal(deleteCalls.length, 3);
|
|
252
|
+
assert.ok(await storage.retrieve(copy.bodyStorageKey));
|
|
253
|
+
assert.ok(await part(copy));
|
|
254
|
+
await gone(unshared);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("a folder delete makes one storage call per batch and removes every object", async () => {
|
|
258
|
+
const accountId = randomId();
|
|
259
|
+
const doomed = await folder(accountId, "Archive");
|
|
260
|
+
const rows = [];
|
|
261
|
+
for (let uid = 1; uid <= 250; uid += 1) {
|
|
262
|
+
rows.push(await message(accountId, doomed.mailboxId, uid));
|
|
263
|
+
}
|
|
264
|
+
deleteCalls = [];
|
|
265
|
+
|
|
266
|
+
await repos.mailbox.deleteMailboxWithMail(accountId, doomed.mailboxId);
|
|
267
|
+
|
|
268
|
+
assert.equal(deleteCalls.length, 3);
|
|
269
|
+
await gone(rows);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMockStorageService,
|
|
3
|
+
messageObjectKeysOf,
|
|
4
|
+
type StorageService,
|
|
5
|
+
} from "@remit/storage-service";
|
|
6
|
+
import type { RepositoryDb } from "../db.js";
|
|
7
|
+
import { messageTable } from "../schema/message-data.js";
|
|
8
|
+
import { inList } from "./in-list.js";
|
|
9
|
+
|
|
10
|
+
export interface MessageObjectStore {
|
|
11
|
+
storage: Pick<StorageService, "deleteKeys">;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const messageObjectsInMemory = (): MessageObjectStore => ({
|
|
15
|
+
storage: createMockStorageService(),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export interface RemovedMessageRow {
|
|
19
|
+
messageId: string;
|
|
20
|
+
bodyStorageKey: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface RemovedBodyPartRow {
|
|
24
|
+
messageId: string;
|
|
25
|
+
partPath: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class MessageObjects {
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly db: Pick<RepositoryDb<Record<string, unknown>>, "select">,
|
|
31
|
+
private readonly store: MessageObjectStore,
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
async reclaim(
|
|
35
|
+
removed: readonly RemovedMessageRow[],
|
|
36
|
+
removedParts: readonly RemovedBodyPartRow[],
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const partPathsOf = new Map<string, string[]>();
|
|
39
|
+
for (const { messageId, partPath } of removedParts) {
|
|
40
|
+
partPathsOf.set(messageId, [
|
|
41
|
+
...(partPathsOf.get(messageId) ?? []),
|
|
42
|
+
partPath,
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
const own: string[] = [];
|
|
46
|
+
const sharedBy = new Map<string, string[]>();
|
|
47
|
+
for (const { messageId, bodyStorageKey } of removed) {
|
|
48
|
+
if (bodyStorageKey === null) continue;
|
|
49
|
+
const keys = messageObjectKeysOf(
|
|
50
|
+
bodyStorageKey,
|
|
51
|
+
messageId,
|
|
52
|
+
partPathsOf.get(messageId) ?? [],
|
|
53
|
+
);
|
|
54
|
+
own.push(...keys.own);
|
|
55
|
+
sharedBy.set(bodyStorageKey, keys.shared);
|
|
56
|
+
}
|
|
57
|
+
if (sharedBy.size === 0) return;
|
|
58
|
+
const referenced = await this.db
|
|
59
|
+
.select({ bodyStorageKey: messageTable.bodyStorageKey })
|
|
60
|
+
.from(messageTable)
|
|
61
|
+
.where(inList(messageTable.bodyStorageKey, [...sharedBy.keys()]));
|
|
62
|
+
for (const { bodyStorageKey } of referenced) {
|
|
63
|
+
if (bodyStorageKey !== null) sharedBy.delete(bodyStorageKey);
|
|
64
|
+
}
|
|
65
|
+
await this.store.storage.deleteKeys([
|
|
66
|
+
...own,
|
|
67
|
+
...[...sharedBy.values()].flat(),
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -10,6 +10,7 @@ import { threadMessageTable } from "../schema/thread-message.js";
|
|
|
10
10
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
11
11
|
import { createSqliteTestDb, type SqliteTestDb } from "../test-db-sqlite.js";
|
|
12
12
|
import type { DrizzleMessageRepository } from "./message.js";
|
|
13
|
+
import { messageObjectsInMemory } from "./message-objects.js";
|
|
13
14
|
|
|
14
15
|
const SCHEMA = { ...messageDataSchema, threadMessages: threadMessageTable };
|
|
15
16
|
|
|
@@ -47,6 +48,7 @@ describe("DrizzleMessageRepository (sqlite)", () => {
|
|
|
47
48
|
repo = createSqliteRepositories(
|
|
48
49
|
{ app: db, mail: db, search: db },
|
|
49
50
|
BETTER_SQLITE3_LIMITS,
|
|
51
|
+
messageObjectsInMemory(),
|
|
50
52
|
).message;
|
|
51
53
|
});
|
|
52
54
|
|
|
@@ -83,8 +85,6 @@ describe("DrizzleMessageRepository (sqlite)", () => {
|
|
|
83
85
|
const created = await repo.create({
|
|
84
86
|
...BASE_INPUT,
|
|
85
87
|
messageId: AUTO_MOVED_ID,
|
|
86
|
-
envelopeId: deriveEnvelopeId(AUTO_MOVED_ID),
|
|
87
|
-
rootBodyPartId: deriveRootBodyPartId(AUTO_MOVED_ID),
|
|
88
88
|
movedByRemit: true,
|
|
89
89
|
filterMove: filterMove as unknown as never,
|
|
90
90
|
});
|
|
@@ -158,8 +158,6 @@ describe("DrizzleMessageRepository (sqlite)", () => {
|
|
|
158
158
|
sequenceNumber: 2,
|
|
159
159
|
rfc822Size: 1024,
|
|
160
160
|
internalDate: NOW,
|
|
161
|
-
envelopeId: deriveEnvelopeId(inboundId),
|
|
162
|
-
rootBodyPartId: deriveRootBodyPartId(inboundId),
|
|
163
161
|
});
|
|
164
162
|
|
|
165
163
|
assert.equal(created, true);
|
|
@@ -180,8 +178,6 @@ describe("DrizzleMessageRepository (sqlite)", () => {
|
|
|
180
178
|
...BASE_INPUT,
|
|
181
179
|
messageId: LOCK_ID,
|
|
182
180
|
uid: 7,
|
|
183
|
-
envelopeId: deriveEnvelopeId(LOCK_ID),
|
|
184
|
-
rootBodyPartId: deriveRootBodyPartId(LOCK_ID),
|
|
185
181
|
status: "moving" as const,
|
|
186
182
|
syncStatus: "pending" as const,
|
|
187
183
|
});
|
|
@@ -253,8 +249,6 @@ describe("DrizzleMessageRepository (sqlite)", () => {
|
|
|
253
249
|
...BASE_INPUT,
|
|
254
250
|
messageId: GONE_ID,
|
|
255
251
|
uid: 9,
|
|
256
|
-
envelopeId: deriveEnvelopeId(GONE_ID),
|
|
257
|
-
rootBodyPartId: deriveRootBodyPartId(GONE_ID),
|
|
258
252
|
});
|
|
259
253
|
});
|
|
260
254
|
|
|
@@ -317,19 +311,19 @@ describe("DrizzleMessageRepository (sqlite)", () => {
|
|
|
317
311
|
);
|
|
318
312
|
});
|
|
319
313
|
|
|
320
|
-
test("
|
|
314
|
+
test("copyMessage gives a copy the source's search text", async () => {
|
|
321
315
|
const source = "00000000-0000-0000-4444-000000000001";
|
|
322
316
|
const copy = "00000000-0000-0000-4444-000000000002";
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
[copy, 202],
|
|
326
|
-
] as const) {
|
|
327
|
-
await repo.create({ ...BASE_INPUT, messageId, uid });
|
|
328
|
-
await repo.update(messageId, { bodyStorageKey: "file:///bodies/shared" });
|
|
329
|
-
}
|
|
317
|
+
await repo.create({ ...BASE_INPUT, messageId: source, uid: 201 });
|
|
318
|
+
await repo.update(source, { bodyStorageKey: "file:///bodies/shared" });
|
|
330
319
|
await repo.setSearchText(source, "the shared body");
|
|
331
320
|
|
|
332
|
-
await repo.
|
|
321
|
+
await repo.copyMessage(source, {
|
|
322
|
+
...BASE_INPUT,
|
|
323
|
+
messageId: copy,
|
|
324
|
+
uid: 202,
|
|
325
|
+
bodyStorageKey: "file:///bodies/shared",
|
|
326
|
+
});
|
|
333
327
|
|
|
334
328
|
const missing = await repo.listMissingSearchText("", 100);
|
|
335
329
|
assert.equal(
|
|
@@ -6,9 +6,10 @@ import {
|
|
|
6
6
|
rootBodyPartId as deriveRootBodyPartId,
|
|
7
7
|
} from "../id.js";
|
|
8
8
|
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
9
|
+
import { createTestDb, type TestDb } from "../test-db.js";
|
|
9
10
|
import type { DrizzleEnvelopeRepository } from "./envelope.js";
|
|
10
11
|
import type { DrizzleMessageRepository } from "./message.js";
|
|
11
|
-
import {
|
|
12
|
+
import { messageObjectsInMemory } from "./message-objects.js";
|
|
12
13
|
|
|
13
14
|
describe("DrizzleMessageRepository", () => {
|
|
14
15
|
let db: TestDb;
|
|
@@ -37,10 +38,11 @@ describe("DrizzleMessageRepository", () => {
|
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
before(async () => {
|
|
40
|
-
({ db, stop } = await createTestDb());
|
|
41
|
+
({ db, close: stop } = await createTestDb());
|
|
41
42
|
const repos = createSqliteRepositories(
|
|
42
43
|
{ app: db, mail: db, search: db },
|
|
43
44
|
BETTER_SQLITE3_LIMITS,
|
|
45
|
+
messageObjectsInMemory(),
|
|
44
46
|
);
|
|
45
47
|
messageRepo = repos.message;
|
|
46
48
|
envelopeRepo = repos.envelope;
|
|
@@ -147,8 +149,6 @@ describe("DrizzleMessageRepository", () => {
|
|
|
147
149
|
sequenceNumber: 1,
|
|
148
150
|
rfc822Size: 512,
|
|
149
151
|
internalDate: NOW,
|
|
150
|
-
envelopeId: deriveEnvelopeId(MOVE_MESSAGE_ID),
|
|
151
|
-
rootBodyPartId: deriveRootBodyPartId(MOVE_MESSAGE_ID),
|
|
152
152
|
status: "active" as const,
|
|
153
153
|
syncStatus: "synced" as const,
|
|
154
154
|
});
|
|
@@ -262,8 +262,6 @@ describe("DrizzleMessageRepository", () => {
|
|
|
262
262
|
sequenceNumber: 1,
|
|
263
263
|
rfc822Size: 10,
|
|
264
264
|
internalDate: NOW,
|
|
265
|
-
envelopeId: DEL_ENVELOPE_ID,
|
|
266
|
-
rootBodyPartId: DEL_ROOT_BODY_PART_ID,
|
|
267
265
|
status: "active",
|
|
268
266
|
syncStatus: "synced",
|
|
269
267
|
});
|