@remit/data-ports 0.0.52 → 0.0.53
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
|
@@ -59,6 +59,29 @@ export function calendarCollectionRepositoryConformance(
|
|
|
59
59
|
assert.equal(all.length, 1);
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
+
test("createExclusive refuses a segment the account already uses", async () => {
|
|
63
|
+
const accountConfigId = harness.makeId();
|
|
64
|
+
|
|
65
|
+
const first = await repo.createExclusive({
|
|
66
|
+
accountConfigId,
|
|
67
|
+
urlSegment: "work",
|
|
68
|
+
displayName: "Work",
|
|
69
|
+
});
|
|
70
|
+
const second = await repo.createExclusive({
|
|
71
|
+
accountConfigId,
|
|
72
|
+
urlSegment: "work",
|
|
73
|
+
displayName: "Work again",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
assert.ok(first);
|
|
77
|
+
assert.equal(second, null);
|
|
78
|
+
assert.equal(
|
|
79
|
+
(await repo.get(accountConfigId, first.calendarId)).displayName,
|
|
80
|
+
"Work",
|
|
81
|
+
"the loser never overwrote the calendar the winner made",
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
62
85
|
test("get throws a not-found error for a missing collection", async () => {
|
|
63
86
|
await assert.rejects(
|
|
64
87
|
repo.get(harness.makeId(), harness.makeId()),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { after, before, describe, test } from "node:test";
|
|
3
|
+
import { CalendarEventStatus, CalendarTransparency } from "@remit/domain-enums";
|
|
3
4
|
import type { ICalendarEventIndexRepository } from "../interfaces/calendar-event-index.js";
|
|
4
5
|
import type { CalendarOccurrenceInput } from "../types.js";
|
|
5
6
|
import type { RepositoryConformanceHarness } from "./harness.js";
|
|
@@ -8,11 +9,16 @@ const occurrence = (
|
|
|
8
9
|
recurrenceId: string,
|
|
9
10
|
startAt: string,
|
|
10
11
|
endAt: string,
|
|
12
|
+
overrides: Partial<CalendarOccurrenceInput> = {},
|
|
11
13
|
): CalendarOccurrenceInput => ({
|
|
12
14
|
recurrenceId,
|
|
13
15
|
startAt,
|
|
14
16
|
endAt,
|
|
15
17
|
allDay: false,
|
|
18
|
+
summary: "Stand-up",
|
|
19
|
+
status: CalendarEventStatus.Confirmed,
|
|
20
|
+
transparency: CalendarTransparency.Opaque,
|
|
21
|
+
...overrides,
|
|
16
22
|
});
|
|
17
23
|
|
|
18
24
|
export function calendarEventIndexRepositoryConformance(
|
|
@@ -139,6 +145,46 @@ export function calendarEventIndexRepositoryConformance(
|
|
|
139
145
|
assert.equal(rows.length, 1);
|
|
140
146
|
});
|
|
141
147
|
|
|
148
|
+
test("an occurrence keeps its own summary, status and transparency", async () => {
|
|
149
|
+
const calendarId = harness.makeId();
|
|
150
|
+
const calendarObjectId = harness.makeId();
|
|
151
|
+
|
|
152
|
+
await repo.replaceForObject(calendarId, calendarObjectId, [
|
|
153
|
+
occurrence(
|
|
154
|
+
"2026-08-26T07:00:00Z",
|
|
155
|
+
"2026-08-26T07:00:00Z",
|
|
156
|
+
"2026-08-26T08:00:00Z",
|
|
157
|
+
),
|
|
158
|
+
occurrence(
|
|
159
|
+
"2026-09-02T07:00:00Z",
|
|
160
|
+
"2026-09-02T07:00:00Z",
|
|
161
|
+
"2026-09-02T08:00:00Z",
|
|
162
|
+
{
|
|
163
|
+
summary: "Stand-up (moved)",
|
|
164
|
+
status: CalendarEventStatus.Cancelled,
|
|
165
|
+
transparency: CalendarTransparency.Transparent,
|
|
166
|
+
},
|
|
167
|
+
),
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const rows = await repo.listForObject(calendarId, calendarObjectId);
|
|
171
|
+
assert.deepEqual(
|
|
172
|
+
rows.map((row) => [row.summary, row.status, row.transparency]),
|
|
173
|
+
[
|
|
174
|
+
[
|
|
175
|
+
"Stand-up",
|
|
176
|
+
CalendarEventStatus.Confirmed,
|
|
177
|
+
CalendarTransparency.Opaque,
|
|
178
|
+
],
|
|
179
|
+
[
|
|
180
|
+
"Stand-up (moved)",
|
|
181
|
+
CalendarEventStatus.Cancelled,
|
|
182
|
+
CalendarTransparency.Transparent,
|
|
183
|
+
],
|
|
184
|
+
],
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
142
188
|
test("deleteForObject removes every occurrence of one resource", async () => {
|
|
143
189
|
const calendarId = harness.makeId();
|
|
144
190
|
const calendarObjectId = harness.makeId();
|
|
@@ -107,6 +107,49 @@ export function calendarObjectRepositoryConformance(
|
|
|
107
107
|
);
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
test("find answers with null where get throws", async () => {
|
|
111
|
+
const calendarId = harness.makeId();
|
|
112
|
+
const object = await repo.put(objectInput(calendarId));
|
|
113
|
+
|
|
114
|
+
assert.equal(
|
|
115
|
+
(await repo.find(calendarId, object.calendarObjectId))?.icalData,
|
|
116
|
+
ICAL_DATA,
|
|
117
|
+
);
|
|
118
|
+
assert.equal(await repo.find(calendarId, harness.makeId()), null);
|
|
119
|
+
assert.equal(
|
|
120
|
+
await repo.find(harness.makeId(), object.calendarObjectId),
|
|
121
|
+
null,
|
|
122
|
+
"a resource is only found through the collection that holds it",
|
|
123
|
+
);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("listIncompleteExpansions returns only the series whose index stops short", async () => {
|
|
127
|
+
const calendarId = harness.makeId();
|
|
128
|
+
await repo.put(objectInput(calendarId, { resourceName: "complete.ics" }));
|
|
129
|
+
const incomplete = await repo.put(
|
|
130
|
+
objectInput(calendarId, {
|
|
131
|
+
resourceName: "incomplete.ics",
|
|
132
|
+
expandedThrough: "2026-01-01T00:00:00Z",
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
assert.deepEqual(
|
|
137
|
+
(
|
|
138
|
+
await repo.listIncompleteExpansions(
|
|
139
|
+
calendarId,
|
|
140
|
+
"2026-06-01T00:00:00Z",
|
|
141
|
+
)
|
|
142
|
+
).map((object) => object.calendarObjectId),
|
|
143
|
+
[incomplete.calendarObjectId],
|
|
144
|
+
"a resource with no expandedThrough is complete and never appears",
|
|
145
|
+
);
|
|
146
|
+
assert.deepEqual(
|
|
147
|
+
await repo.listIncompleteExpansions(calendarId, "2025-06-01T00:00:00Z"),
|
|
148
|
+
[],
|
|
149
|
+
"and one whose index already reaches the window does not either",
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
110
153
|
test("findByResourceName returns null for a name the collection does not hold", async () => {
|
|
111
154
|
assert.equal(
|
|
112
155
|
await repo.findByResourceName(harness.makeId(), "absent.ics"),
|
|
@@ -12,6 +12,18 @@ export interface ICalendarCollectionRepository {
|
|
|
12
12
|
* request without a prior read.
|
|
13
13
|
*/
|
|
14
14
|
create(input: CreateCalendarCollectionInput): Promise<CalendarCollectionItem>;
|
|
15
|
+
/**
|
|
16
|
+
* Writes the collection, or answers `null` when its `urlSegment` is already
|
|
17
|
+
* taken in this account config.
|
|
18
|
+
*
|
|
19
|
+
* The distinction from `create` is who asked. Provisioning wants the row
|
|
20
|
+
* whoever got there first wrote; a person naming a calendar wants to be told
|
|
21
|
+
* the name is taken, and handing them somebody else's calendar instead is a
|
|
22
|
+
* silent merge of two things they meant to keep apart.
|
|
23
|
+
*/
|
|
24
|
+
createExclusive(
|
|
25
|
+
input: CreateCalendarCollectionInput,
|
|
26
|
+
): Promise<CalendarCollectionItem | null>;
|
|
15
27
|
get(
|
|
16
28
|
accountConfigId: string,
|
|
17
29
|
calendarId: string,
|
|
@@ -6,6 +6,16 @@ export interface ICalendarObjectRepository {
|
|
|
6
6
|
calendarId: string,
|
|
7
7
|
calendarObjectId: string,
|
|
8
8
|
): Promise<CalendarObjectItem>;
|
|
9
|
+
/**
|
|
10
|
+
* The resource under an id, or `null` when the collection does not hold it.
|
|
11
|
+
* `get` throws instead, which is right for a caller that already knows the
|
|
12
|
+
* resource exists; a request naming one is asking whether it does, and that
|
|
13
|
+
* is an answer rather than a fault.
|
|
14
|
+
*/
|
|
15
|
+
find(
|
|
16
|
+
calendarId: string,
|
|
17
|
+
calendarObjectId: string,
|
|
18
|
+
): Promise<CalendarObjectItem | null>;
|
|
9
19
|
delete(calendarId: string, calendarObjectId: string): Promise<void>;
|
|
10
20
|
findByResourceName(
|
|
11
21
|
calendarId: string,
|
|
@@ -21,6 +31,17 @@ export interface ICalendarObjectRepository {
|
|
|
21
31
|
icalUid: string,
|
|
22
32
|
): Promise<CalendarObjectItem | null>;
|
|
23
33
|
listByCalendar(calendarId: string): Promise<CalendarObjectItem[]>;
|
|
34
|
+
/**
|
|
35
|
+
* The resources whose occurrence rows stop before `instant` — the series a
|
|
36
|
+
* range read has to expand for itself because the index does not reach that
|
|
37
|
+
* far. Narrow by construction: a resource that fits inside the expansion
|
|
38
|
+
* horizon carries no `expandedThrough` and never appears here, so a view of
|
|
39
|
+
* an ordinary calendar reads no iCalendar text at all.
|
|
40
|
+
*/
|
|
41
|
+
listIncompleteExpansions(
|
|
42
|
+
calendarId: string,
|
|
43
|
+
instant: string,
|
|
44
|
+
): Promise<CalendarObjectItem[]>;
|
|
24
45
|
/**
|
|
25
46
|
* Everything written to the collection after `syncSequence`, in change
|
|
26
47
|
* order — the read behind a WebDAV-Sync report. Exclusive of the token
|