@remit/backend 0.0.100 → 0.0.101
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 +1 -1
- package/src/handlers/thread.test.ts +118 -5
- package/src/handlers/thread.ts +13 -9
package/package.json
CHANGED
|
@@ -126,7 +126,11 @@ describe("executeThreadSearch", () => {
|
|
|
126
126
|
updatedAt: 0,
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
type RecordedCall = {
|
|
129
|
+
type RecordedCall = {
|
|
130
|
+
search: { category?: Category[] };
|
|
131
|
+
continuationToken?: string;
|
|
132
|
+
limit?: number;
|
|
133
|
+
};
|
|
130
134
|
|
|
131
135
|
const fakeClient = (rows: ThreadMessageItem[]) => {
|
|
132
136
|
const windowCalls: RecordedCall[] = [];
|
|
@@ -134,7 +138,8 @@ describe("executeThreadSearch", () => {
|
|
|
134
138
|
|
|
135
139
|
// The fake applies the category predicate itself, the way a port does, so
|
|
136
140
|
// a request that never reaches `search` cannot answer correctly by
|
|
137
|
-
// accident.
|
|
141
|
+
// accident. The window also honours `limit`, so a handler deriving a count
|
|
142
|
+
// from the rows it got back cannot pass for one that asked the port.
|
|
138
143
|
const matching = (categories?: Category[]) =>
|
|
139
144
|
categories?.length
|
|
140
145
|
? rows.filter((row) => categories.includes(row.category))
|
|
@@ -142,10 +147,14 @@ describe("executeThreadSearch", () => {
|
|
|
142
147
|
|
|
143
148
|
const client: ThreadSearchClient = {
|
|
144
149
|
threadMessage: {
|
|
145
|
-
async searchByMailboxWindow(_account, _mailbox, search) {
|
|
146
|
-
windowCalls.push({
|
|
150
|
+
async searchByMailboxWindow(_account, _mailbox, search, options) {
|
|
151
|
+
windowCalls.push({
|
|
152
|
+
search,
|
|
153
|
+
continuationToken: options?.continuationToken,
|
|
154
|
+
limit: options?.limit,
|
|
155
|
+
});
|
|
147
156
|
return {
|
|
148
|
-
items: matching(search.category),
|
|
157
|
+
items: matching(search.category).slice(0, options?.limit),
|
|
149
158
|
continuationToken: undefined,
|
|
150
159
|
};
|
|
151
160
|
},
|
|
@@ -276,6 +285,110 @@ describe("executeThreadSearch", () => {
|
|
|
276
285
|
assert.equal(response.count, matches);
|
|
277
286
|
});
|
|
278
287
|
|
|
288
|
+
// #305: `senderTrust` and `dkimMismatch` are resolved by enriching rows, so
|
|
289
|
+
// the only number the handler could put in `count` is this page's post-filter
|
|
290
|
+
// length — a page length wearing the name of a total. Absent instead.
|
|
291
|
+
it("omits count under senderTrust rather than counting the page", async () => {
|
|
292
|
+
const { client, countCalls } = fakeClient(ACCOUNT_ROWS);
|
|
293
|
+
|
|
294
|
+
const response = await executeThreadSearch(client, ACCOUNT, MAILBOX, {
|
|
295
|
+
senderTrust: [SenderTrust.Unknown],
|
|
296
|
+
count: true,
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
assert.equal(response.count, undefined);
|
|
300
|
+
assert.equal(countCalls.length, 0, "nothing to count off the row");
|
|
301
|
+
assert.equal(response.items?.length, ACCOUNT_ROWS.length);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("omits count under dkimMismatch rather than counting the page", async () => {
|
|
305
|
+
const { client } = fakeClient(ACCOUNT_ROWS);
|
|
306
|
+
|
|
307
|
+
const response = await executeThreadSearch(client, ACCOUNT, MAILBOX, {
|
|
308
|
+
dkimMismatch: true,
|
|
309
|
+
count: true,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
assert.equal(response.count, undefined);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// The count it asked for cannot be answered and the rows it declined are not
|
|
316
|
+
// wanted, so there is nothing left to read.
|
|
317
|
+
it("reads nothing for a count-only request under an off-row criterion", async () => {
|
|
318
|
+
const { client, windowCalls, countCalls } = fakeClient(ACCOUNT_ROWS);
|
|
319
|
+
|
|
320
|
+
const response = await executeThreadSearch(client, ACCOUNT, MAILBOX, {
|
|
321
|
+
dkimMismatch: true,
|
|
322
|
+
count: true,
|
|
323
|
+
results: false,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
assert.deepEqual(response, {});
|
|
327
|
+
assert.equal(windowCalls.length, 0);
|
|
328
|
+
assert.equal(countCalls.length, 0);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("answers the same count whether or not rows were asked for", async () => {
|
|
332
|
+
const criteria = {
|
|
333
|
+
category: [MessageCategory.personal, MessageCategory.marketing],
|
|
334
|
+
count: true,
|
|
335
|
+
};
|
|
336
|
+
const withRows = await executeThreadSearch(
|
|
337
|
+
fakeClient(ACCOUNT_ROWS).client,
|
|
338
|
+
ACCOUNT,
|
|
339
|
+
MAILBOX,
|
|
340
|
+
criteria,
|
|
341
|
+
);
|
|
342
|
+
const countOnly = await executeThreadSearch(
|
|
343
|
+
fakeClient(ACCOUNT_ROWS).client,
|
|
344
|
+
ACCOUNT,
|
|
345
|
+
MAILBOX,
|
|
346
|
+
{ ...criteria, results: false },
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
assert.equal(withRows.count, 2);
|
|
350
|
+
assert.equal(countOnly.count, withRows.count);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// The page size bounds the rows; it has no say in how many messages match.
|
|
354
|
+
it("holds the count still while the page size moves", async () => {
|
|
355
|
+
const responses = [];
|
|
356
|
+
for (const limit of [1, 500]) {
|
|
357
|
+
const { client } = fakeClient(ACCOUNT_ROWS);
|
|
358
|
+
responses.push(
|
|
359
|
+
await executeThreadSearch(client, ACCOUNT, MAILBOX, {
|
|
360
|
+
count: true,
|
|
361
|
+
limit,
|
|
362
|
+
}),
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
assert.deepEqual(
|
|
367
|
+
responses.map((response) => response.items?.length),
|
|
368
|
+
[1, ACCOUNT_ROWS.length],
|
|
369
|
+
"the page did move",
|
|
370
|
+
);
|
|
371
|
+
assert.deepEqual(
|
|
372
|
+
responses.map((response) => response.count),
|
|
373
|
+
[ACCOUNT_ROWS.length, ACCOUNT_ROWS.length],
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// The cursor never reaches the count query, so the number does not shrink as
|
|
378
|
+
// the user pages: page three claims the same total page one did.
|
|
379
|
+
it("holds the count still while the caller pages", async () => {
|
|
380
|
+
const { client, windowCalls, countCalls } = fakeClient(ACCOUNT_ROWS);
|
|
381
|
+
|
|
382
|
+
const response = await executeThreadSearch(client, ACCOUNT, MAILBOX, {
|
|
383
|
+
count: true,
|
|
384
|
+
continuationToken: "page-three",
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
assert.equal(windowCalls[0].continuationToken, "page-three");
|
|
388
|
+
assert.equal(countCalls.length, 1);
|
|
389
|
+
assert.equal(response.count, ACCOUNT_ROWS.length);
|
|
390
|
+
});
|
|
391
|
+
|
|
279
392
|
it("projects category, so the DynamoDB port reads it with the row", () => {
|
|
280
393
|
assert.ok(buildSearchThreadsOptions({}).attributes.includes("category"));
|
|
281
394
|
assert.ok(buildListThreadsOptions({}).attributes.includes("category"));
|
package/src/handlers/thread.ts
CHANGED
|
@@ -161,10 +161,16 @@ export type ThreadSearchParams = {
|
|
|
161
161
|
* `results` toggle omits `items` (count-only) and `count` adds the match count.
|
|
162
162
|
*
|
|
163
163
|
* `count` names the whole match, never the page: a page size bounds the rows a
|
|
164
|
-
* response carries and has no bearing on how many messages match (#509).
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
164
|
+
* response carries and has no bearing on how many messages match (#509).
|
|
165
|
+
*
|
|
166
|
+
* An off-row criterion is the one shape that has no such number. `senderTrust`
|
|
167
|
+
* and `dkimMismatch` are resolved by enriching a window, so counting them means
|
|
168
|
+
* enriching every candidate row in the mailbox. `count` is therefore absent
|
|
169
|
+
* (#305) — a page's post-filter length labelled `count` is a page length
|
|
170
|
+
* presented as a total, which is the defect the field exists to avoid. Absence
|
|
171
|
+
* is the same answer the response already gives for a count nobody asked for,
|
|
172
|
+
* so no caller needs a second shape to read it. A count-only request under an
|
|
173
|
+
* off-row criterion therefore reads nothing at all.
|
|
168
174
|
*/
|
|
169
175
|
export const executeThreadSearch = async (
|
|
170
176
|
client: ThreadSearchClient,
|
|
@@ -194,6 +200,7 @@ export const executeThreadSearch = async (
|
|
|
194
200
|
});
|
|
195
201
|
|
|
196
202
|
if (hasOffRowCriteria(offRow)) {
|
|
203
|
+
if (!wantResults) return {};
|
|
197
204
|
const window = await client.threadMessage.searchByMailboxWindow(
|
|
198
205
|
accountConfigId,
|
|
199
206
|
mailboxId,
|
|
@@ -205,12 +212,9 @@ export const executeThreadSearch = async (
|
|
|
205
212
|
client,
|
|
206
213
|
accountConfigId,
|
|
207
214
|
);
|
|
208
|
-
const filtered = filterByOffRowCriteria(enriched, offRow);
|
|
209
215
|
return {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
: {}),
|
|
213
|
-
...(wantCount ? { count: filtered.length } : {}),
|
|
216
|
+
items: filterByOffRowCriteria(enriched, offRow),
|
|
217
|
+
continuationToken: window.continuationToken,
|
|
214
218
|
};
|
|
215
219
|
}
|
|
216
220
|
|