@remit/drizzle-service 0.0.54 → 0.0.55
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
CHANGED
|
@@ -188,6 +188,34 @@ describe("MailboxSpecialUseRepo role lookups (sqlite)", () => {
|
|
|
188
188
|
assert.notEqual(confirmed?.mailboxId, keepsakes.mailboxId);
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
+
test("keeps a stale Trash appointment visible instead of answering the fallback", async () => {
|
|
192
|
+
// The appointed folder is gone — deleted by another client. `null` and the
|
|
193
|
+
// flagged folder both hide that; the resolution names the id the user chose
|
|
194
|
+
// so a refusal can offer the repair (#887).
|
|
195
|
+
const { accountId, accountConfigId } = await makeAccount();
|
|
196
|
+
await mailboxes.create(makeMailboxInput(accountId, "INBOX"));
|
|
197
|
+
const flagged = await mailboxes.create(
|
|
198
|
+
makeMailboxInput(accountId, "[Gmail]/Trash"),
|
|
199
|
+
);
|
|
200
|
+
await repo.create(flagged.mailboxId, "Trash");
|
|
201
|
+
const appointed = randomUUID();
|
|
202
|
+
await appoint(
|
|
203
|
+
accountConfigId,
|
|
204
|
+
accountId,
|
|
205
|
+
CanonicalMailboxRole.Trash,
|
|
206
|
+
appointed,
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
assert.deepEqual(await repo.resolveTrashRole(accountId), {
|
|
210
|
+
kind: "appointment_stale",
|
|
211
|
+
appointedMailboxId: appointed,
|
|
212
|
+
fallback: {
|
|
213
|
+
kind: "flagged",
|
|
214
|
+
mailbox: { mailboxId: flagged.mailboxId, fullPath: "[Gmail]/Trash" },
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
191
219
|
test("resolves an INBOX-nested Junk folder that advertises \\Junk", async () => {
|
|
192
220
|
const { accountId } = await makeAccount();
|
|
193
221
|
await mailboxes.create(makeMailboxInput(accountId, "INBOX"));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { describe, it } from "node:test";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The path a stale appointment last pointed at is a display-only sibling row
|
|
7
|
+
* (`FolderRoleAppointmentLabel`, #887). "Never read by resolution" is meant to
|
|
8
|
+
* be structural rather than a habit: this repository composes one setting name
|
|
9
|
+
* and has no way to reach the other. Read the source to prove it, because the
|
|
10
|
+
* guarantee is about what the file cannot do, not about what any one call
|
|
11
|
+
* returns.
|
|
12
|
+
*/
|
|
13
|
+
describe("MailboxSpecialUseRepo and the appointment label row", () => {
|
|
14
|
+
const source = readFileSync(
|
|
15
|
+
new URL("./i4-mailbox-special-use.ts", import.meta.url),
|
|
16
|
+
"utf8",
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
it("never names the label setting", () => {
|
|
20
|
+
assert.equal(source.includes("FolderRoleAppointmentLabel"), false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("composes the appointment name in exactly one place", () => {
|
|
24
|
+
const uses = source.split("composeFolderRoleAppointmentName(").length - 1;
|
|
25
|
+
assert.equal(uses, 1);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -7,8 +7,11 @@ import {
|
|
|
7
7
|
type CanonicalMailboxRoleValue,
|
|
8
8
|
composeFolderRoleAppointmentName,
|
|
9
9
|
type RoleMailboxCandidate,
|
|
10
|
+
type RoleResolution,
|
|
10
11
|
resolveConfirmedMailboxForRole,
|
|
11
12
|
resolveMailboxForRole,
|
|
13
|
+
resolveRoleForAccount,
|
|
14
|
+
type UnappointedRoleResolution,
|
|
12
15
|
} from "@remit/data-ports/folder-role";
|
|
13
16
|
import { CanonicalMailboxRole } from "@remit/domain-enums";
|
|
14
17
|
import { eq, inArray } from "drizzle-orm";
|
|
@@ -24,6 +27,39 @@ interface RoleCandidate extends RoleMailboxCandidate {
|
|
|
24
27
|
fullPath: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
interface RoleMailbox {
|
|
31
|
+
mailboxId: string;
|
|
32
|
+
fullPath: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const projectMailbox = (candidate: RoleCandidate): RoleMailbox => ({
|
|
36
|
+
mailboxId: candidate.mailboxId,
|
|
37
|
+
fullPath: candidate.fullPath,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const projectUnappointed = (
|
|
41
|
+
resolution: UnappointedRoleResolution<RoleCandidate>,
|
|
42
|
+
): UnappointedRoleResolution<RoleMailbox> =>
|
|
43
|
+
resolution.kind === "none"
|
|
44
|
+
? resolution
|
|
45
|
+
: { kind: resolution.kind, mailbox: projectMailbox(resolution.mailbox) };
|
|
46
|
+
|
|
47
|
+
const projectResolution = (
|
|
48
|
+
resolution: RoleResolution<RoleCandidate>,
|
|
49
|
+
): RoleResolution<RoleMailbox> => {
|
|
50
|
+
if (resolution.kind === "appointment_stale") {
|
|
51
|
+
return {
|
|
52
|
+
kind: "appointment_stale",
|
|
53
|
+
appointedMailboxId: resolution.appointedMailboxId,
|
|
54
|
+
fallback: projectUnappointed(resolution.fallback),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (resolution.kind === "appointed") {
|
|
58
|
+
return { kind: "appointed", mailbox: projectMailbox(resolution.mailbox) };
|
|
59
|
+
}
|
|
60
|
+
return projectUnappointed(resolution);
|
|
61
|
+
};
|
|
62
|
+
|
|
27
63
|
function rowToSpecialUse(
|
|
28
64
|
row: typeof mailboxSpecialUseTable.$inferSelect,
|
|
29
65
|
): MailboxSpecialUseItem {
|
|
@@ -118,6 +154,24 @@ export class MailboxSpecialUseRepo implements IMailboxSpecialUseRepository {
|
|
|
118
154
|
);
|
|
119
155
|
}
|
|
120
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Trash with its evidence, for the verbs that weigh it. Reads exactly what
|
|
159
|
+
* `findMailboxForRole` reads — a `null` answer is thrown away there, and this
|
|
160
|
+
* keeps it.
|
|
161
|
+
*/
|
|
162
|
+
async resolveTrashRole(
|
|
163
|
+
accountId: string,
|
|
164
|
+
): Promise<RoleResolution<RoleMailbox>> {
|
|
165
|
+
const role = CanonicalMailboxRole.Trash;
|
|
166
|
+
const [candidates, appointedMailboxId] = await Promise.all([
|
|
167
|
+
this.roleCandidates(accountId),
|
|
168
|
+
this.appointedMailboxId(accountId, role),
|
|
169
|
+
]);
|
|
170
|
+
return projectResolution(
|
|
171
|
+
resolveRoleForAccount(role, candidates, appointedMailboxId),
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
121
175
|
findArchiveMailbox(
|
|
122
176
|
accountId: string,
|
|
123
177
|
): Promise<{ mailboxId: string; fullPath: string } | null> {
|