@remit/config-transfer 0.0.5 → 0.0.7
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/binder.ts +41 -7
- package/src/export.ts +1 -0
- package/src/import.test.ts +91 -1
package/package.json
CHANGED
package/src/binder.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { AppointFolderRole } from "./import-repositories.js";
|
|
|
18
18
|
export interface ConfigBinderRepositories {
|
|
19
19
|
configImport: Pick<IConfigImportRepository, "listByAccountConfig" | "update">;
|
|
20
20
|
accountSetting: Pick<IAccountSettingRepository, "upsert">;
|
|
21
|
-
filter: Pick<IFilterRepository, "update">;
|
|
21
|
+
filter: Pick<IFilterRepository, "listByAccountConfig" | "update">;
|
|
22
22
|
mailbox: Pick<IMailboxRepository, "listAllByAccount">;
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -34,9 +34,15 @@ export interface ConfigBinderDeps {
|
|
|
34
34
|
*/
|
|
35
35
|
export interface BindResult {
|
|
36
36
|
bound: number;
|
|
37
|
+
dropped: number;
|
|
37
38
|
stillPending: number;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
type RefState =
|
|
42
|
+
| { kind: "TargetGone" }
|
|
43
|
+
| { kind: "Waiting" }
|
|
44
|
+
| { kind: "Ready"; mailboxId: string };
|
|
45
|
+
|
|
40
46
|
/**
|
|
41
47
|
* Bind the folder references an import could not resolve, now that discovery
|
|
42
48
|
* has produced this account's mailboxes.
|
|
@@ -62,7 +68,7 @@ export const bindImportedFolders = async (
|
|
|
62
68
|
const imports = (
|
|
63
69
|
await repositories.configImport.listByAccountConfig(accountConfigId)
|
|
64
70
|
).filter((row) => row.state === ConfigImportState.Pending);
|
|
65
|
-
if (imports.length === 0) return { bound: 0, stillPending: 0 };
|
|
71
|
+
if (imports.length === 0) return { bound: 0, dropped: 0, stillPending: 0 };
|
|
66
72
|
|
|
67
73
|
const byPath = new Map(
|
|
68
74
|
(await repositories.mailbox.listAllByAccount(accountId)).map(
|
|
@@ -70,7 +76,14 @@ export const bindImportedFolders = async (
|
|
|
70
76
|
),
|
|
71
77
|
);
|
|
72
78
|
|
|
79
|
+
const filterIds = new Set(
|
|
80
|
+
(await repositories.filter.listByAccountConfig(accountConfigId)).map(
|
|
81
|
+
(filter) => filter.filterId,
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
|
|
73
85
|
let bound = 0;
|
|
86
|
+
let dropped = 0;
|
|
74
87
|
let stillPending = 0;
|
|
75
88
|
|
|
76
89
|
for (const row of imports) {
|
|
@@ -78,13 +91,16 @@ export const bindImportedFolders = async (
|
|
|
78
91
|
const remaining: ConfigImportUnresolvedRefItem[] = [];
|
|
79
92
|
|
|
80
93
|
for (const ref of row.unresolvedRefs) {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
const state = refStateOf(ref, accountId, byPath, filterIds);
|
|
95
|
+
if (state.kind === "TargetGone") {
|
|
96
|
+
dropped++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (state.kind === "Waiting") {
|
|
84
100
|
remaining.push(ref);
|
|
85
101
|
continue;
|
|
86
102
|
}
|
|
87
|
-
await bindRef(deps, accountConfigId, ref, mailboxId, document);
|
|
103
|
+
await bindRef(deps, accountConfigId, ref, state.mailboxId, document);
|
|
88
104
|
bound++;
|
|
89
105
|
}
|
|
90
106
|
|
|
@@ -99,11 +115,29 @@ export const bindImportedFolders = async (
|
|
|
99
115
|
});
|
|
100
116
|
}
|
|
101
117
|
|
|
102
|
-
return { bound, stillPending };
|
|
118
|
+
return { bound, dropped, stillPending };
|
|
103
119
|
};
|
|
104
120
|
|
|
105
121
|
type BoundDocument = ReturnType<typeof readConfigDocument>;
|
|
106
122
|
|
|
123
|
+
const refStateOf = (
|
|
124
|
+
ref: ConfigImportUnresolvedRefItem,
|
|
125
|
+
accountId: string,
|
|
126
|
+
byPath: ReadonlyMap<string, string>,
|
|
127
|
+
filterIds: ReadonlySet<string>,
|
|
128
|
+
): RefState => {
|
|
129
|
+
if (
|
|
130
|
+
ref.kind === ConfigImportRefKind.FilterAction &&
|
|
131
|
+
!filterIds.has(ref.target)
|
|
132
|
+
) {
|
|
133
|
+
return { kind: "TargetGone" };
|
|
134
|
+
}
|
|
135
|
+
const mailboxId =
|
|
136
|
+
ref.accountId === accountId ? byPath.get(ref.folderPath) : undefined;
|
|
137
|
+
if (mailboxId === undefined) return { kind: "Waiting" };
|
|
138
|
+
return { kind: "Ready", mailboxId };
|
|
139
|
+
};
|
|
140
|
+
|
|
107
141
|
const bindRef = async (
|
|
108
142
|
deps: ConfigBinderDeps,
|
|
109
143
|
accountConfigId: string,
|
package/src/export.ts
CHANGED
|
@@ -297,6 +297,7 @@ const userFlagsOf = (flags: AddressFlags | undefined): ConfigAddressFlags => {
|
|
|
297
297
|
const user: ConfigAddressFlags = {};
|
|
298
298
|
if (stored.trusted) user.trusted = carriedFlag(stored.trusted);
|
|
299
299
|
if (stored.blocked) user.blocked = carriedFlag(stored.blocked);
|
|
300
|
+
if (stored.neverSpam) user.neverSpam = carriedFlag(stored.neverSpam);
|
|
300
301
|
if (stored.muted) user.muted = carriedFlag(stored.muted);
|
|
301
302
|
if (stored.vip) user.vip = carriedFlag(stored.vip);
|
|
302
303
|
if (stored.category) user.category = carriedFlag(stored.category);
|
package/src/import.test.ts
CHANGED
|
@@ -229,6 +229,9 @@ const repositoriesOf = (store: Store, accountConfigId: string): any => {
|
|
|
229
229
|
const at = store.filters.findIndex(
|
|
230
230
|
(filter) => filter.filterId === filterId,
|
|
231
231
|
);
|
|
232
|
+
if (at === -1) {
|
|
233
|
+
throw new NotFoundError(`Filter not found: ${filterId}`);
|
|
234
|
+
}
|
|
232
235
|
const next = { ...store.filters[at], ...patch };
|
|
233
236
|
store.filters[at] = next;
|
|
234
237
|
return next;
|
|
@@ -1228,6 +1231,93 @@ test("a binder run on a configuration with no import does nothing", async () =>
|
|
|
1228
1231
|
TARGET_CONFIG_ID,
|
|
1229
1232
|
PASSWORD_ACCOUNT_ID,
|
|
1230
1233
|
);
|
|
1231
|
-
assert.deepEqual(result, { bound: 0, stillPending: 0 });
|
|
1234
|
+
assert.deepEqual(result, { bound: 0, dropped: 0, stillPending: 0 });
|
|
1232
1235
|
assert.equal(pendingImportOf([]), undefined);
|
|
1233
1236
|
});
|
|
1237
|
+
|
|
1238
|
+
test("a filter deleted before its folder is discovered drops its reference instead of failing the bind", async () => {
|
|
1239
|
+
const document = await exportSource(sourceWithLegacyFlagsFixture());
|
|
1240
|
+
const store = emptyStore();
|
|
1241
|
+
await apply(store, document);
|
|
1242
|
+
|
|
1243
|
+
const invoices = store.filters.find(
|
|
1244
|
+
(filter) => filter.name === "Invoices to Facturen",
|
|
1245
|
+
);
|
|
1246
|
+
assert.ok(invoices);
|
|
1247
|
+
store.filters = store.filters.filter(
|
|
1248
|
+
(filter) => filter.filterId !== invoices.filterId,
|
|
1249
|
+
);
|
|
1250
|
+
|
|
1251
|
+
discover(store, PASSWORD_ACCOUNT_ID, [
|
|
1252
|
+
"INBOX",
|
|
1253
|
+
"INBOX.Sent",
|
|
1254
|
+
"INBOX.Facturen",
|
|
1255
|
+
"INBOX.Lists.dev-null",
|
|
1256
|
+
]);
|
|
1257
|
+
discover(store, OAUTH_ACCOUNT_ID, ["INBOX"]);
|
|
1258
|
+
const binder = {
|
|
1259
|
+
repositories: repositoriesOf(store, TARGET_CONFIG_ID),
|
|
1260
|
+
appointFolderRole: appointFolderRoleInto(store),
|
|
1261
|
+
now: () => NOW,
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
const first = await bindImportedFolders(
|
|
1265
|
+
binder,
|
|
1266
|
+
TARGET_CONFIG_ID,
|
|
1267
|
+
PASSWORD_ACCOUNT_ID,
|
|
1268
|
+
);
|
|
1269
|
+
await bindImportedFolders(binder, TARGET_CONFIG_ID, OAUTH_ACCOUNT_ID);
|
|
1270
|
+
|
|
1271
|
+
assert.equal(first.dropped, 1);
|
|
1272
|
+
assert.equal(
|
|
1273
|
+
store.imports[0].unresolvedRefs.some(
|
|
1274
|
+
(ref) => ref.target === invoices.filterId,
|
|
1275
|
+
),
|
|
1276
|
+
false,
|
|
1277
|
+
);
|
|
1278
|
+
assert.equal(store.imports[0].state, "Complete");
|
|
1279
|
+
|
|
1280
|
+
const again = await bindImportedFolders(
|
|
1281
|
+
binder,
|
|
1282
|
+
TARGET_CONFIG_ID,
|
|
1283
|
+
PASSWORD_ACCOUNT_ID,
|
|
1284
|
+
);
|
|
1285
|
+
assert.deepEqual(again, { bound: 0, dropped: 0, stillPending: 0 });
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
test("a deleted filter's reference is dropped even when its folder never appears", async () => {
|
|
1289
|
+
const document = await exportSource(sourceWithLegacyFlagsFixture());
|
|
1290
|
+
const store = emptyStore();
|
|
1291
|
+
await apply(store, document);
|
|
1292
|
+
|
|
1293
|
+
const invoices = store.filters.find(
|
|
1294
|
+
(filter) => filter.name === "Invoices to Facturen",
|
|
1295
|
+
);
|
|
1296
|
+
assert.ok(invoices);
|
|
1297
|
+
store.filters = store.filters.filter(
|
|
1298
|
+
(filter) => filter.filterId !== invoices.filterId,
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
discover(store, PASSWORD_ACCOUNT_ID, ["INBOX"]);
|
|
1302
|
+
const result = await bindImportedFolders(
|
|
1303
|
+
{
|
|
1304
|
+
repositories: repositoriesOf(store, TARGET_CONFIG_ID),
|
|
1305
|
+
appointFolderRole: appointFolderRoleInto(store),
|
|
1306
|
+
now: () => NOW,
|
|
1307
|
+
},
|
|
1308
|
+
TARGET_CONFIG_ID,
|
|
1309
|
+
PASSWORD_ACCOUNT_ID,
|
|
1310
|
+
);
|
|
1311
|
+
|
|
1312
|
+
assert.equal(result.dropped, 1);
|
|
1313
|
+
assert.equal(
|
|
1314
|
+
store.imports[0].unresolvedRefs.some(
|
|
1315
|
+
(ref) => ref.target === invoices.filterId,
|
|
1316
|
+
),
|
|
1317
|
+
false,
|
|
1318
|
+
);
|
|
1319
|
+
assert.equal(
|
|
1320
|
+
pendingImportOf(store.imports)?.folderPaths.includes("INBOX.Facturen"),
|
|
1321
|
+
false,
|
|
1322
|
+
);
|
|
1323
|
+
});
|