@remit/drizzle-service 0.0.103 → 0.0.104
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 -3
- package/src/repos/account.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-collection.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-default.d1.test.ts +4 -0
- package/src/repos/calendar-default.sqlite.test.ts +3 -76
- package/src/repos/calendar-default.suite.ts +68 -0
- package/src/repos/calendar-event-index.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-feed-token.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-object.conformance.d1.test.ts +20 -0
- package/src/repos/calendar-put.d1.test.ts +4 -0
- package/src/repos/calendar-put.sqlite.test.ts +3 -209
- package/src/repos/calendar-put.suite.ts +210 -0
- package/src/repos/cascade-delete.ts +1 -1
- package/src/repos/filter.d1.test.ts +4 -0
- package/src/repos/filter.suite.ts +476 -0
- package/src/repos/filter.test.ts +3 -472
- package/src/repos/i4-account-config.d1.test.ts +4 -0
- package/src/repos/i4-account-config.suite.ts +131 -0
- package/src/repos/i4-account-config.test.ts +3 -127
- package/src/repos/i4-account-export-request.d1.test.ts +4 -0
- package/src/repos/i4-account-export-request.suite.ts +44 -0
- package/src/repos/i4-account-export-request.test.ts +3 -40
- package/src/repos/i4-account-setting.d1.test.ts +4 -0
- package/src/repos/i4-account-setting.suite.ts +100 -0
- package/src/repos/i4-account-setting.test.ts +3 -93
- package/src/repos/label.conformance.d1.test.ts +20 -0
- package/src/repos/message.sqlite.test.ts +54 -0
- package/src/repos/message.ts +66 -0
- package/src/repos/thread-message-body-muted.sqlite.test.ts +23 -19
- package/src/repos/thread-message-field-terms.sqlite.test.ts +9 -5
- package/src/repos/thread-message.sqlite.test.ts +6 -4
- package/src/repos/thread-message.test.ts +23 -11
- package/src/repos/thread-message.ts +13 -9
- package/src/repos/thread-search-predicates.ts +36 -63
- package/src/schema/message-data.ts +3 -0
- package/src/schema-sets/mail.ts +1 -0
- package/src/search/thread-message-search.sqlite.test.ts +49 -8
- package/src/search/thread-message-search.ts +13 -4
- package/src/test-d1.ts +40 -0
- package/src/test-db-sqlite.ts +1 -1
- package/src/test-shipped-sqlite-schema.ts +8 -6
- package/src/test-stores.ts +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remit/drizzle-service",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.104",
|
|
4
4
|
"description": "Drizzle ORM service over SQLite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -25,14 +25,15 @@
|
|
|
25
25
|
"@remit/calendar-service": "*",
|
|
26
26
|
"@remit/config-transfer": "*",
|
|
27
27
|
"drizzle-kit": "^0.31.1",
|
|
28
|
+
"miniflare": "^4.20260730.0",
|
|
28
29
|
"tsx": "*"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@remit/data-ports": "*",
|
|
32
33
|
"@remit/domain-enums": "*",
|
|
33
34
|
"drizzle-orm": "^0.45.2",
|
|
34
|
-
"short-uuid": "
|
|
35
|
-
"uuid": "
|
|
35
|
+
"short-uuid": "^6.0.3",
|
|
36
|
+
"uuid": "^13.0.2",
|
|
36
37
|
"@remit/drizzle-sqlite-schema": "*",
|
|
37
38
|
"better-sqlite3": "^12.11.1",
|
|
38
39
|
"@types/better-sqlite3": "^7.6.13"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { accountRepositoryConformance } from "@remit/data-ports/conformance";
|
|
2
|
+
import { NotFoundError } from "../error.js";
|
|
3
|
+
import { randomId } from "../id.js";
|
|
4
|
+
import { composeSqliteRepositories } from "../sqlite-repositories.js";
|
|
5
|
+
import { openD1TestStores } from "../test-d1.js";
|
|
6
|
+
|
|
7
|
+
let close: (() => Promise<void>) | undefined;
|
|
8
|
+
|
|
9
|
+
accountRepositoryConformance({
|
|
10
|
+
async createRepository() {
|
|
11
|
+
const opened = await openD1TestStores();
|
|
12
|
+
close = opened.close;
|
|
13
|
+
return composeSqliteRepositories(opened.stores).account;
|
|
14
|
+
},
|
|
15
|
+
teardown: async () => {
|
|
16
|
+
await close?.();
|
|
17
|
+
},
|
|
18
|
+
makeId: () => randomId(),
|
|
19
|
+
isNotFoundError: (error) => error instanceof NotFoundError,
|
|
20
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { calendarCollectionRepositoryConformance } from "@remit/data-ports/conformance";
|
|
2
|
+
import { NotFoundError } from "../error.js";
|
|
3
|
+
import { randomId } from "../id.js";
|
|
4
|
+
import { openD1TestStores } from "../test-d1.js";
|
|
5
|
+
import { CalendarCollectionRepo } from "./calendar-collection.js";
|
|
6
|
+
|
|
7
|
+
let close: (() => Promise<void>) | undefined;
|
|
8
|
+
|
|
9
|
+
calendarCollectionRepositoryConformance({
|
|
10
|
+
async createRepository() {
|
|
11
|
+
const opened = await openD1TestStores();
|
|
12
|
+
close = opened.close;
|
|
13
|
+
return new CalendarCollectionRepo(opened.stores.app.db);
|
|
14
|
+
},
|
|
15
|
+
teardown: async () => {
|
|
16
|
+
await close?.();
|
|
17
|
+
},
|
|
18
|
+
makeId: () => randomId(),
|
|
19
|
+
isNotFoundError: (error) => error instanceof NotFoundError,
|
|
20
|
+
});
|
|
@@ -1,77 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { provisionDefaultCalendar } from "@remit/calendar-service";
|
|
4
|
-
import type { ICalendarStore } from "@remit/data-ports";
|
|
5
|
-
import type { Db } from "../db.js";
|
|
6
|
-
import {
|
|
7
|
-
calendarEventIndexTable,
|
|
8
|
-
calendarObjectTable,
|
|
9
|
-
calendarTable,
|
|
10
|
-
} from "../schema.js";
|
|
11
|
-
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
12
|
-
import { createSqliteTestDb } from "../test-db-sqlite.js";
|
|
13
|
-
import { CalendarCollectionRepo } from "./calendar-collection.js";
|
|
1
|
+
import { openCalendarSqliteTestStores } from "../test-stores.js";
|
|
2
|
+
import { calendarDefaultSuite } from "./calendar-default.suite.js";
|
|
14
3
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
describe("provisioning the default calendar", () => {
|
|
18
|
-
let db: Db<Record<string, unknown>>;
|
|
19
|
-
let close: () => Promise<void>;
|
|
20
|
-
let store: ICalendarStore;
|
|
21
|
-
|
|
22
|
-
before(async () => {
|
|
23
|
-
const created = await createSqliteTestDb({
|
|
24
|
-
calendars: calendarTable,
|
|
25
|
-
calendarObjects: calendarObjectTable,
|
|
26
|
-
calendarEventIndexes: calendarEventIndexTable,
|
|
27
|
-
});
|
|
28
|
-
db = created.db as unknown as Db<Record<string, unknown>>;
|
|
29
|
-
close = created.close;
|
|
30
|
-
store = createSqliteRepositories({
|
|
31
|
-
app: db,
|
|
32
|
-
mail: db,
|
|
33
|
-
search: db,
|
|
34
|
-
}).calendarStore;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
after(() => close());
|
|
38
|
-
|
|
39
|
-
test("leaves one calendar behind when several first reads arrive together", async () => {
|
|
40
|
-
const provisioned = await Promise.all(
|
|
41
|
-
Array.from({ length: 8 }, () =>
|
|
42
|
-
provisionDefaultCalendar(store, ACCOUNT_CONFIG_ID),
|
|
43
|
-
),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
const ids = new Set(provisioned.map((collection) => collection.calendarId));
|
|
47
|
-
assert.equal(ids.size, 1, "every caller was handed the same calendar");
|
|
48
|
-
|
|
49
|
-
const rows = await new CalendarCollectionRepo(db).listByAccountConfig(
|
|
50
|
-
ACCOUNT_CONFIG_ID,
|
|
51
|
-
);
|
|
52
|
-
assert.equal(rows.length, 1);
|
|
53
|
-
assert.equal(rows[0]?.urlSegment, "default");
|
|
54
|
-
assert.equal(
|
|
55
|
-
rows[0]?.syncSequence,
|
|
56
|
-
0,
|
|
57
|
-
"a losing provision did not reset a collection the winner may already have written to",
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("a later provision returns the stored collection rather than resetting it", async () => {
|
|
62
|
-
const repo = new CalendarCollectionRepo(db);
|
|
63
|
-
const [before] = await repo.listByAccountConfig(ACCOUNT_CONFIG_ID);
|
|
64
|
-
assert.ok(before);
|
|
65
|
-
await repo.bumpSyncSequence(ACCOUNT_CONFIG_ID, before.calendarId);
|
|
66
|
-
|
|
67
|
-
const again = await provisionDefaultCalendar(
|
|
68
|
-
store,
|
|
69
|
-
ACCOUNT_CONFIG_ID,
|
|
70
|
-
"A different name",
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
assert.equal(again.calendarId, before.calendarId);
|
|
74
|
-
assert.equal(again.displayName, before.displayName);
|
|
75
|
-
assert.equal(again.syncSequence, 1);
|
|
76
|
-
});
|
|
77
|
-
});
|
|
4
|
+
calendarDefaultSuite(openCalendarSqliteTestStores);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { after, before, describe, test } from "node:test";
|
|
3
|
+
import { provisionDefaultCalendar } from "@remit/calendar-service";
|
|
4
|
+
import type { ICalendarStore } from "@remit/data-ports";
|
|
5
|
+
import type { RepositoryDb } from "../db.js";
|
|
6
|
+
import { composeSqliteRepositories } from "../sqlite-repositories.js";
|
|
7
|
+
import type { OpenTestStores } from "../test-stores.js";
|
|
8
|
+
import { CalendarCollectionRepo } from "./calendar-collection.js";
|
|
9
|
+
|
|
10
|
+
const ACCOUNT_CONFIG_ID = "account-config-default";
|
|
11
|
+
|
|
12
|
+
export const calendarDefaultSuite = (open: OpenTestStores): void => {
|
|
13
|
+
describe("provisioning the default calendar", () => {
|
|
14
|
+
let db: RepositoryDb<Record<string, unknown>>;
|
|
15
|
+
let close: () => Promise<void>;
|
|
16
|
+
let store: ICalendarStore;
|
|
17
|
+
|
|
18
|
+
before(async () => {
|
|
19
|
+
const opened = await open();
|
|
20
|
+
db = opened.stores.app.db;
|
|
21
|
+
close = opened.close;
|
|
22
|
+
store = composeSqliteRepositories(opened.stores).calendarStore;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
after(() => close());
|
|
26
|
+
|
|
27
|
+
test("leaves one calendar behind when several first reads arrive together", async () => {
|
|
28
|
+
const provisioned = await Promise.all(
|
|
29
|
+
Array.from({ length: 8 }, () =>
|
|
30
|
+
provisionDefaultCalendar(store, ACCOUNT_CONFIG_ID),
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const ids = new Set(
|
|
35
|
+
provisioned.map((collection) => collection.calendarId),
|
|
36
|
+
);
|
|
37
|
+
assert.equal(ids.size, 1, "every caller was handed the same calendar");
|
|
38
|
+
|
|
39
|
+
const rows = await new CalendarCollectionRepo(db).listByAccountConfig(
|
|
40
|
+
ACCOUNT_CONFIG_ID,
|
|
41
|
+
);
|
|
42
|
+
assert.equal(rows.length, 1);
|
|
43
|
+
assert.equal(rows[0]?.urlSegment, "default");
|
|
44
|
+
assert.equal(
|
|
45
|
+
rows[0]?.syncSequence,
|
|
46
|
+
0,
|
|
47
|
+
"a losing provision did not reset a collection the winner may already have written to",
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("a later provision returns the stored collection rather than resetting it", async () => {
|
|
52
|
+
const repo = new CalendarCollectionRepo(db);
|
|
53
|
+
const [before] = await repo.listByAccountConfig(ACCOUNT_CONFIG_ID);
|
|
54
|
+
assert.ok(before);
|
|
55
|
+
await repo.bumpSyncSequence(ACCOUNT_CONFIG_ID, before.calendarId);
|
|
56
|
+
|
|
57
|
+
const again = await provisionDefaultCalendar(
|
|
58
|
+
store,
|
|
59
|
+
ACCOUNT_CONFIG_ID,
|
|
60
|
+
"A different name",
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
assert.equal(again.calendarId, before.calendarId);
|
|
64
|
+
assert.equal(again.displayName, before.displayName);
|
|
65
|
+
assert.equal(again.syncSequence, 1);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { calendarEventIndexRepositoryConformance } from "@remit/data-ports/conformance";
|
|
2
|
+
import { NotFoundError } from "../error.js";
|
|
3
|
+
import { randomId } from "../id.js";
|
|
4
|
+
import { openD1TestStores } from "../test-d1.js";
|
|
5
|
+
import { CalendarEventIndexRepo } from "./calendar-event-index.js";
|
|
6
|
+
|
|
7
|
+
let close: (() => Promise<void>) | undefined;
|
|
8
|
+
|
|
9
|
+
calendarEventIndexRepositoryConformance({
|
|
10
|
+
async createRepository() {
|
|
11
|
+
const opened = await openD1TestStores();
|
|
12
|
+
close = opened.close;
|
|
13
|
+
return new CalendarEventIndexRepo(opened.stores.app.db);
|
|
14
|
+
},
|
|
15
|
+
teardown: async () => {
|
|
16
|
+
await close?.();
|
|
17
|
+
},
|
|
18
|
+
makeId: () => randomId(),
|
|
19
|
+
isNotFoundError: (error) => error instanceof NotFoundError,
|
|
20
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { calendarFeedTokenRepositoryConformance } from "@remit/data-ports/conformance";
|
|
2
|
+
import { NotFoundError } from "../error.js";
|
|
3
|
+
import { randomId } from "../id.js";
|
|
4
|
+
import { openD1TestStores } from "../test-d1.js";
|
|
5
|
+
import { CalendarFeedTokenRepo } from "./calendar-feed-token.js";
|
|
6
|
+
|
|
7
|
+
let close: (() => Promise<void>) | undefined;
|
|
8
|
+
|
|
9
|
+
calendarFeedTokenRepositoryConformance({
|
|
10
|
+
async createRepository() {
|
|
11
|
+
const opened = await openD1TestStores();
|
|
12
|
+
close = opened.close;
|
|
13
|
+
return new CalendarFeedTokenRepo(opened.stores.app.db);
|
|
14
|
+
},
|
|
15
|
+
teardown: async () => {
|
|
16
|
+
await close?.();
|
|
17
|
+
},
|
|
18
|
+
makeId: () => randomId(),
|
|
19
|
+
isNotFoundError: (error) => error instanceof NotFoundError,
|
|
20
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { calendarObjectRepositoryConformance } from "@remit/data-ports/conformance";
|
|
2
|
+
import { NotFoundError } from "../error.js";
|
|
3
|
+
import { randomId } from "../id.js";
|
|
4
|
+
import { openD1TestStores } from "../test-d1.js";
|
|
5
|
+
import { CalendarObjectRepo } from "./calendar-object.js";
|
|
6
|
+
|
|
7
|
+
let close: (() => Promise<void>) | undefined;
|
|
8
|
+
|
|
9
|
+
calendarObjectRepositoryConformance({
|
|
10
|
+
async createRepository() {
|
|
11
|
+
const opened = await openD1TestStores();
|
|
12
|
+
close = opened.close;
|
|
13
|
+
return new CalendarObjectRepo(opened.stores.app.db);
|
|
14
|
+
},
|
|
15
|
+
teardown: async () => {
|
|
16
|
+
await close?.();
|
|
17
|
+
},
|
|
18
|
+
makeId: () => randomId(),
|
|
19
|
+
isNotFoundError: (error) => error instanceof NotFoundError,
|
|
20
|
+
});
|
|
@@ -1,210 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
prepareCalendarObject,
|
|
5
|
-
provisionDefaultCalendar,
|
|
6
|
-
putCalendarObject,
|
|
7
|
-
} from "@remit/calendar-service";
|
|
8
|
-
import type {
|
|
9
|
-
CalendarRepositories,
|
|
10
|
-
CalendarWrite,
|
|
11
|
-
ICalendarStore,
|
|
12
|
-
} from "@remit/data-ports";
|
|
13
|
-
import type { Db } from "../db.js";
|
|
14
|
-
import {
|
|
15
|
-
calendarEventIndexTable,
|
|
16
|
-
calendarObjectTable,
|
|
17
|
-
calendarTable,
|
|
18
|
-
} from "../schema.js";
|
|
19
|
-
import { createSqliteRepositories } from "../sqlite-repositories.js";
|
|
20
|
-
import { createSqliteTestDb } from "../test-db-sqlite.js";
|
|
1
|
+
import { openCalendarSqliteTestStores } from "../test-stores.js";
|
|
2
|
+
import { calendarPutSuite } from "./calendar-put.suite.js";
|
|
21
3
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const RESOURCE = [
|
|
25
|
-
"BEGIN:VCALENDAR",
|
|
26
|
-
"VERSION:2.0",
|
|
27
|
-
"BEGIN:VEVENT",
|
|
28
|
-
"UID:atomic@example.com",
|
|
29
|
-
"DTSTART:20260826T090000Z",
|
|
30
|
-
"DTEND:20260826T100000Z",
|
|
31
|
-
"SUMMARY:Quarterly review",
|
|
32
|
-
"RRULE:FREQ=WEEKLY;COUNT=3",
|
|
33
|
-
"END:VEVENT",
|
|
34
|
-
"END:VCALENDAR",
|
|
35
|
-
"",
|
|
36
|
-
].join("\r\n");
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Appends a write that fails to every commit: a resource put into a collection
|
|
40
|
-
* that does not exist, which the store refuses at its last statement. Every
|
|
41
|
-
* statement of the real write set has run by then, so a failure there is the
|
|
42
|
-
* case that proves the boundary.
|
|
43
|
-
*/
|
|
44
|
-
class FailingLastWriteStore implements ICalendarStore {
|
|
45
|
-
constructor(private inner: ICalendarStore) {}
|
|
46
|
-
|
|
47
|
-
get repos(): CalendarRepositories {
|
|
48
|
-
return this.inner.repos;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async commit(writes: readonly CalendarWrite[]): Promise<void> {
|
|
52
|
-
const orphan = await prepareCalendarObject(
|
|
53
|
-
{ timezone: "" },
|
|
54
|
-
{
|
|
55
|
-
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
56
|
-
calendarId: "no-such-calendar",
|
|
57
|
-
resourceName: "orphan.ics",
|
|
58
|
-
icalData: RESOURCE,
|
|
59
|
-
},
|
|
60
|
-
);
|
|
61
|
-
assert.ok(orphan.ok);
|
|
62
|
-
await this.inner.commit([...writes, orphan.value]);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
describe("the calendar write path against sqlite", () => {
|
|
67
|
-
let db: Db<Record<string, unknown>>;
|
|
68
|
-
let close: () => Promise<void>;
|
|
69
|
-
let store: ICalendarStore;
|
|
70
|
-
let calendarId: string;
|
|
71
|
-
|
|
72
|
-
before(async () => {
|
|
73
|
-
const created = await createSqliteTestDb({
|
|
74
|
-
calendars: calendarTable,
|
|
75
|
-
calendarObjects: calendarObjectTable,
|
|
76
|
-
calendarEventIndexes: calendarEventIndexTable,
|
|
77
|
-
});
|
|
78
|
-
db = created.db as unknown as Db<Record<string, unknown>>;
|
|
79
|
-
close = created.close;
|
|
80
|
-
store = createSqliteRepositories({
|
|
81
|
-
app: db,
|
|
82
|
-
mail: db,
|
|
83
|
-
search: db,
|
|
84
|
-
}).calendarStore;
|
|
85
|
-
const collection = await provisionDefaultCalendar(store, ACCOUNT_CONFIG_ID);
|
|
86
|
-
calendarId = collection.calendarId;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
after(() => close());
|
|
90
|
-
|
|
91
|
-
const objects = () => db.select().from(calendarObjectTable);
|
|
92
|
-
const occurrences = () => db.select().from(calendarEventIndexTable);
|
|
93
|
-
|
|
94
|
-
test("writes the object, its occurrences and the sequence bump together", async () => {
|
|
95
|
-
const result = await putCalendarObject(store, {
|
|
96
|
-
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
97
|
-
calendarId,
|
|
98
|
-
resourceName: "review.ics",
|
|
99
|
-
icalData: RESOURCE,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
assert.ok(result.ok);
|
|
103
|
-
assert.equal(result.value.icalData, RESOURCE);
|
|
104
|
-
assert.equal(
|
|
105
|
-
(await objects())[0]?.icalData,
|
|
106
|
-
RESOURCE,
|
|
107
|
-
"the stored row holds the input bytes, CRLF and all",
|
|
108
|
-
);
|
|
109
|
-
assert.equal(result.value.syncSequence, 1);
|
|
110
|
-
assert.equal((await objects()).length, 1);
|
|
111
|
-
assert.equal(
|
|
112
|
-
(await occurrences()).filter(
|
|
113
|
-
(row) => row.calendarObjectId === result.value.calendarObjectId,
|
|
114
|
-
).length,
|
|
115
|
-
3,
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
test("a failing write in the batch leaves no object behind", async () => {
|
|
120
|
-
const before = await objects();
|
|
121
|
-
|
|
122
|
-
await assert.rejects(
|
|
123
|
-
putCalendarObject(new FailingLastWriteStore(store), {
|
|
124
|
-
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
125
|
-
calendarId,
|
|
126
|
-
resourceName: "doomed.ics",
|
|
127
|
-
icalData: RESOURCE,
|
|
128
|
-
}),
|
|
129
|
-
/NOT NULL constraint failed/,
|
|
130
|
-
);
|
|
131
|
-
|
|
132
|
-
const after = await objects();
|
|
133
|
-
assert.equal(after.length, before.length);
|
|
134
|
-
assert.equal(
|
|
135
|
-
after.some((row) => row.resourceName === "doomed.ics"),
|
|
136
|
-
false,
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("a failing write in the batch leaves the collection's sequence where it was", async () => {
|
|
141
|
-
const [before] = await db.select().from(calendarTable);
|
|
142
|
-
|
|
143
|
-
await assert.rejects(
|
|
144
|
-
putCalendarObject(new FailingLastWriteStore(store), {
|
|
145
|
-
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
146
|
-
calendarId,
|
|
147
|
-
resourceName: "doomed-again.ics",
|
|
148
|
-
icalData: RESOURCE,
|
|
149
|
-
}),
|
|
150
|
-
/NOT NULL constraint failed/,
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
const [after] = await db.select().from(calendarTable);
|
|
154
|
-
assert.equal(after?.syncSequence, before?.syncSequence);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("a refused resource writes nothing at all", async () => {
|
|
158
|
-
const before = await objects();
|
|
159
|
-
const [collectionBefore] = await db.select().from(calendarTable);
|
|
160
|
-
|
|
161
|
-
const result = await putCalendarObject(store, {
|
|
162
|
-
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
163
|
-
calendarId,
|
|
164
|
-
resourceName: "backwards.ics",
|
|
165
|
-
icalData: [
|
|
166
|
-
"BEGIN:VCALENDAR",
|
|
167
|
-
"VERSION:2.0",
|
|
168
|
-
"BEGIN:VEVENT",
|
|
169
|
-
"UID:backwards@example.com",
|
|
170
|
-
"DTSTART:20260826T100000Z",
|
|
171
|
-
"DTEND:20260826T090000Z",
|
|
172
|
-
"END:VEVENT",
|
|
173
|
-
"END:VCALENDAR",
|
|
174
|
-
"",
|
|
175
|
-
].join("\r\n"),
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
assert.ok(!result.ok);
|
|
179
|
-
assert.equal(result.error.code, "BackwardsEnd");
|
|
180
|
-
assert.equal((await objects()).length, before.length);
|
|
181
|
-
const [collectionAfter] = await db.select().from(calendarTable);
|
|
182
|
-
assert.equal(collectionAfter?.syncSequence, collectionBefore?.syncSequence);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("rewriting a resource replaces its occurrences rather than adding to them", async () => {
|
|
186
|
-
const first = await putCalendarObject(store, {
|
|
187
|
-
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
188
|
-
calendarId,
|
|
189
|
-
resourceName: "shrinking.ics",
|
|
190
|
-
icalData: RESOURCE,
|
|
191
|
-
});
|
|
192
|
-
assert.ok(first.ok);
|
|
193
|
-
|
|
194
|
-
const second = await putCalendarObject(store, {
|
|
195
|
-
accountConfigId: ACCOUNT_CONFIG_ID,
|
|
196
|
-
calendarId,
|
|
197
|
-
resourceName: "shrinking.ics",
|
|
198
|
-
icalData: RESOURCE.replace("COUNT=3", "COUNT=1"),
|
|
199
|
-
});
|
|
200
|
-
assert.ok(second.ok);
|
|
201
|
-
|
|
202
|
-
assert.equal(second.value.calendarObjectId, first.value.calendarObjectId);
|
|
203
|
-
assert.equal(
|
|
204
|
-
(await occurrences()).filter(
|
|
205
|
-
(row) => row.calendarObjectId === second.value.calendarObjectId,
|
|
206
|
-
).length,
|
|
207
|
-
1,
|
|
208
|
-
);
|
|
209
|
-
});
|
|
210
|
-
});
|
|
4
|
+
calendarPutSuite(openCalendarSqliteTestStores);
|