@remit/backend 0.0.142 → 0.0.144
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/dist/dev-server/content-handler.d.ts +38 -0
- package/dist/dev-server/content-handler.js +46 -0
- package/dist/src/api.d.ts +7 -0
- package/dist/src/api.js +144 -0
- package/dist/src/auth.d.ts +30 -0
- package/dist/src/auth.js +73 -0
- package/dist/src/config/msoauth.d.ts +30 -0
- package/dist/src/config/msoauth.js +30 -0
- package/{src/data-backend.ts → dist/src/data-backend.d.ts} +2 -4
- package/dist/src/data-backend.js +12 -0
- package/{src/derive/autoMoved.ts → dist/src/derive/autoMoved.d.ts} +1 -29
- package/dist/src/derive/autoMoved.js +49 -0
- package/dist/src/derive/contentSignature.d.ts +41 -0
- package/{src/derive/contentSignature.ts → dist/src/derive/contentSignature.js} +27 -72
- package/dist/src/derive/contentUrl.d.ts +46 -0
- package/dist/src/derive/contentUrl.js +46 -0
- package/{src/derive/deriveMuted.ts → dist/src/derive/deriveMuted.d.ts} +1 -3
- package/dist/src/derive/deriveMuted.js +7 -0
- package/dist/src/derive/enrichThreadRows.d.ts +118 -0
- package/dist/src/derive/enrichThreadRows.js +151 -0
- package/dist/src/derive/filterThreadCriteria.d.ts +23 -0
- package/dist/src/derive/filterThreadCriteria.js +24 -0
- package/{src/derive/pendingMoveCounts.ts → dist/src/derive/pendingMoveCounts.d.ts} +5 -53
- package/dist/src/derive/pendingMoveCounts.js +53 -0
- package/{src/derive/senderTrust.ts → dist/src/derive/senderTrust.d.ts} +1 -9
- package/dist/src/derive/senderTrust.js +19 -0
- package/dist/src/error.d.ts +21 -0
- package/dist/src/error.js +85 -0
- package/dist/src/handlers/account-guards.d.ts +36 -0
- package/dist/src/handlers/account-guards.js +107 -0
- package/dist/src/handlers/account-oauth.d.ts +14 -0
- package/dist/src/handlers/account-oauth.js +387 -0
- package/dist/src/handlers/account-overrides.d.ts +53 -0
- package/dist/src/handlers/account-overrides.js +233 -0
- package/dist/src/handlers/account-signature.d.ts +28 -0
- package/dist/src/handlers/account-signature.js +85 -0
- package/dist/src/handlers/account.d.ts +6 -0
- package/dist/src/handlers/account.js +350 -0
- package/dist/src/handlers/address.d.ts +35 -0
- package/dist/src/handlers/address.js +141 -0
- package/dist/src/handlers/calendar-event.d.ts +70 -0
- package/dist/src/handlers/calendar-event.js +425 -0
- package/dist/src/handlers/calendar-feed.d.ts +47 -0
- package/dist/src/handlers/calendar-feed.js +192 -0
- package/dist/src/handlers/calendar-suggestion.d.ts +47 -0
- package/dist/src/handlers/calendar-suggestion.js +212 -0
- package/dist/src/handlers/calendar.d.ts +116 -0
- package/dist/src/handlers/calendar.js +330 -0
- package/dist/src/handlers/config.d.ts +43 -0
- package/dist/src/handlers/config.js +176 -0
- package/dist/src/handlers/ensure-account-config.d.ts +9 -0
- package/dist/src/handlers/ensure-account-config.js +27 -0
- package/dist/src/handlers/filter.d.ts +3 -0
- package/dist/src/handlers/filter.js +103 -0
- package/dist/src/handlers/folder-role-appointments.d.ts +68 -0
- package/dist/src/handlers/folder-role-appointments.js +168 -0
- package/dist/src/handlers/folder-role-labels.d.ts +26 -0
- package/dist/src/handlers/folder-role-labels.js +40 -0
- package/dist/src/handlers/folder-role.d.ts +9 -0
- package/dist/src/handlers/folder-role.js +40 -0
- package/dist/src/handlers/index.d.ts +2 -0
- package/dist/src/handlers/index.js +60 -0
- package/dist/src/handlers/label.d.ts +43 -0
- package/dist/src/handlers/label.js +114 -0
- package/dist/src/handlers/mailbox-bindings.d.ts +41 -0
- package/dist/src/handlers/mailbox-bindings.js +61 -0
- package/dist/src/handlers/mailbox.d.ts +23 -0
- package/dist/src/handlers/mailbox.js +219 -0
- package/dist/src/handlers/me.d.ts +2 -0
- package/dist/src/handlers/me.js +138 -0
- package/dist/src/handlers/message.d.ts +84 -0
- package/dist/src/handlers/message.js +607 -0
- package/dist/src/handlers/organize.d.ts +10 -0
- package/dist/src/handlers/organize.js +112 -0
- package/dist/src/handlers/outbox-attachment.d.ts +23 -0
- package/dist/src/handlers/outbox-attachment.js +81 -0
- package/dist/src/handlers/outbox.d.ts +4 -0
- package/dist/src/handlers/outbox.js +144 -0
- package/dist/src/handlers/sync-progress.d.ts +46 -0
- package/dist/src/handlers/sync-progress.js +63 -0
- package/dist/src/handlers/sync.d.ts +28 -0
- package/dist/src/handlers/sync.js +82 -0
- package/dist/src/handlers/system-update.d.ts +2 -0
- package/dist/src/handlers/system-update.js +172 -0
- package/dist/src/handlers/thread.d.ts +148 -0
- package/dist/src/handlers/thread.js +233 -0
- package/dist/src/handlers/unified-threads.d.ts +247 -0
- package/dist/src/handlers/unified-threads.js +409 -0
- package/{src/handlers/vip-suggestions.ts → dist/src/handlers/vip-suggestions.d.ts} +1 -13
- package/dist/src/handlers/vip-suggestions.js +16 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +37 -0
- package/{src/json.ts → dist/src/json.d.ts} +1 -4
- package/dist/src/json.js +10 -0
- package/dist/src/jwt-auth.d.ts +18 -0
- package/dist/src/jwt-auth.js +97 -0
- package/dist/src/request-context.d.ts +9 -0
- package/dist/src/request-context.js +22 -0
- package/dist/src/request.d.ts +3 -0
- package/dist/src/request.js +7 -0
- package/dist/src/response.d.ts +27 -0
- package/dist/src/response.js +87 -0
- package/dist/src/service/account-deletion.d.ts +22 -0
- package/dist/src/service/account-deletion.js +39 -0
- package/dist/src/service/compose-sqlite.d.ts +2 -0
- package/dist/src/service/compose-sqlite.js +11 -0
- package/dist/src/service/create-remit-client.d.ts +95 -0
- package/dist/src/service/create-remit-client.js +207 -0
- package/dist/src/service/data-client.d.ts +12 -0
- package/dist/src/service/data-client.js +44 -0
- package/dist/src/service/export-identity.d.ts +3 -0
- package/dist/src/service/export-identity.js +35 -0
- package/dist/src/service/fire-and-forget.d.ts +54 -0
- package/dist/src/service/fire-and-forget.js +38 -0
- package/dist/src/service/organize.d.ts +184 -0
- package/dist/src/service/organize.js +384 -0
- package/dist/src/service/refusal.d.ts +4 -0
- package/dist/src/service/refusal.js +20 -0
- package/dist/src/service/sender-category.d.ts +51 -0
- package/{src/service/sender-category.ts → dist/src/service/sender-category.js} +40 -81
- package/dist/src/service/sqs.d.ts +1 -0
- package/{src/service/sqs.ts → dist/src/service/sqs.js} +0 -1
- package/dist/src/service/trigger-sync.d.ts +51 -0
- package/dist/src/service/trigger-sync.js +39 -0
- package/dist/src/types.d.ts +49 -0
- package/dist/src/types.js +1 -0
- package/package.json +113 -98
- package/.env.test +0 -4
- package/dev-server/content-auth.test.ts +0 -113
- package/dev-server/content-auth.ts +0 -54
- package/dev-server/content-handler.test.ts +0 -105
- package/dev-server/content-handler.ts +0 -79
- package/dev-server/content-path.test.ts +0 -37
- package/dev-server/content-path.ts +0 -27
- package/dev-server/cors.test.ts +0 -48
- package/dev-server/cors.ts +0 -25
- package/dev-server/keep-alive.test.ts +0 -24
- package/dev-server/lambda-helpers.ts +0 -69
- package/dev-server/log-output.test.ts +0 -173
- package/dev-server/oauth-callback-route.test.ts +0 -66
- package/dev-server/relational-health.test.ts +0 -83
- package/dev-server/relational-health.ts +0 -39
- package/dev-server/server.ts +0 -384
- package/dev-server/sync-age.test.ts +0 -91
- package/dev-server/sync-age.ts +0 -55
- package/dev-server/upload-handler.ts +0 -132
- package/dev-server/upload-route.test.ts +0 -196
- package/scripts/backfill-classification.ts +0 -91
- package/scripts/backfill-list-id.ts +0 -83
- package/scripts/config-save.ts +0 -128
- package/src/auth.test.ts +0 -12
- package/src/auth.ts +0 -92
- package/src/config/msoauth.test.ts +0 -48
- package/src/config/msoauth.ts +0 -48
- package/src/data-backend.test.ts +0 -23
- package/src/derive/autoMoved.test.ts +0 -104
- package/src/derive/contentSignature.test.ts +0 -161
- package/src/derive/contentUrl.test.ts +0 -172
- package/src/derive/contentUrl.ts +0 -86
- package/src/derive/deriveMuted.test.ts +0 -34
- package/src/derive/enrichThreadRows.test.ts +0 -360
- package/src/derive/enrichThreadRows.ts +0 -234
- package/src/derive/filterThreadCriteria.test.ts +0 -126
- package/src/derive/filterThreadCriteria.ts +0 -51
- package/src/derive/senderTrust.test.ts +0 -67
- package/src/error.test.ts +0 -201
- package/src/error.ts +0 -123
- package/src/handlers/account-guards.test.ts +0 -67
- package/src/handlers/account-guards.ts +0 -169
- package/src/handlers/account-oauth-callback.test.ts +0 -155
- package/src/handlers/account-oauth-consent.test.ts +0 -336
- package/src/handlers/account-oauth-start.test.ts +0 -191
- package/src/handlers/account-oauth.test.ts +0 -439
- package/src/handlers/account-oauth.ts +0 -635
- package/src/handlers/account-overrides.test.ts +0 -88
- package/src/handlers/account-overrides.ts +0 -323
- package/src/handlers/account-ownership.ts +0 -25
- package/src/handlers/account-signature.ts +0 -117
- package/src/handlers/account-synced-services.test.ts +0 -245
- package/src/handlers/account.ts +0 -552
- package/src/handlers/address.test.ts +0 -381
- package/src/handlers/address.ts +0 -200
- package/src/handlers/calendar-event.test.ts +0 -685
- package/src/handlers/calendar-event.ts +0 -738
- package/src/handlers/calendar-feed-validators.test.ts +0 -228
- package/src/handlers/calendar-feed.test.ts +0 -439
- package/src/handlers/calendar-feed.ts +0 -291
- package/src/handlers/calendar-sqlite-fixture.ts +0 -36
- package/src/handlers/calendar-suggestion.test.ts +0 -1269
- package/src/handlers/calendar-suggestion.ts +0 -425
- package/src/handlers/calendar.test.ts +0 -1080
- package/src/handlers/calendar.ts +0 -517
- package/src/handlers/config.test.ts +0 -245
- package/src/handlers/config.ts +0 -332
- package/src/handlers/ensure-account-config.ts +0 -30
- package/src/handlers/filter.test.ts +0 -644
- package/src/handlers/filter.ts +0 -518
- package/src/handlers/folder-role-appointments.test.ts +0 -438
- package/src/handlers/folder-role-appointments.ts +0 -249
- package/src/handlers/folder-role-labels.ts +0 -63
- package/src/handlers/folder-role.test.ts +0 -209
- package/src/handlers/folder-role.ts +0 -80
- package/src/handlers/index.ts +0 -83
- package/src/handlers/label.test.ts +0 -112
- package/src/handlers/label.ts +0 -210
- package/src/handlers/mailbox-bindings.ts +0 -111
- package/src/handlers/mailbox-rename-refusal.test.ts +0 -247
- package/src/handlers/mailbox-settled-target.test.ts +0 -251
- package/src/handlers/mailbox.test.ts +0 -280
- package/src/handlers/mailbox.ts +0 -581
- package/src/handlers/me.test.ts +0 -81
- package/src/handlers/me.ts +0 -203
- package/src/handlers/message-read-backfill.test.ts +0 -103
- package/src/handlers/message.test.ts +0 -131
- package/src/handlers/message.ts +0 -1131
- package/src/handlers/organize.test.ts +0 -277
- package/src/handlers/organize.ts +0 -197
- package/src/handlers/outbox-attachment.test.ts +0 -765
- package/src/handlers/outbox-attachment.ts +0 -126
- package/src/handlers/outbox-list.test.ts +0 -179
- package/src/handlers/outbox.test.ts +0 -772
- package/src/handlers/outbox.ts +0 -258
- package/src/handlers/search.test.ts +0 -161
- package/src/handlers/search.ts +0 -105
- package/src/handlers/sync-progress.test.ts +0 -205
- package/src/handlers/sync-progress.ts +0 -99
- package/src/handlers/sync.test.ts +0 -168
- package/src/handlers/sync.ts +0 -118
- package/src/handlers/system-update.test.ts +0 -497
- package/src/handlers/system-update.ts +0 -230
- package/src/handlers/thread.test.ts +0 -396
- package/src/handlers/thread.ts +0 -427
- package/src/handlers/unified-threads-mail-sources.sqlite.test.ts +0 -197
- package/src/handlers/unified-threads.test.ts +0 -785
- package/src/handlers/unified-threads.ts +0 -669
- package/src/index.test.ts +0 -83
- package/src/index.ts +0 -228
- package/src/jwt-auth.test.ts +0 -152
- package/src/jwt-auth.ts +0 -132
- package/src/log-redaction.test.ts +0 -144
- package/src/openapi-spec.test.ts +0 -118
- package/src/request-context.test.ts +0 -61
- package/src/request-context.ts +0 -33
- package/src/request-pipeline.test.ts +0 -88
- package/src/request.ts +0 -10
- package/src/response.test.ts +0 -130
- package/src/response.ts +0 -144
- package/src/service/compose-sqlite.ts +0 -98
- package/src/service/config-import.ts +0 -39
- package/src/service/create-remit-client.ts +0 -521
- package/src/service/data-client.test.ts +0 -97
- package/src/service/data-client.ts +0 -58
- package/src/service/export-identity.ts +0 -39
- package/src/service/filter.ts +0 -40
- package/src/service/fire-and-forget.test.ts +0 -126
- package/src/service/fire-and-forget.ts +0 -79
- package/src/service/localhost-env-config.test.ts +0 -36
- package/src/service/organize-queue.ts +0 -98
- package/src/service/organize.test.ts +0 -1451
- package/src/service/organize.ts +0 -1025
- package/src/service/read-path-filter-config.test.ts +0 -243
- package/src/service/semantic-capability.test.ts +0 -78
- package/src/service/semantic-capability.ts +0 -95
- package/src/service/sender-category.test.ts +0 -235
- package/src/service/trigger-sync.test.ts +0 -205
- package/src/service/trigger-sync.ts +0 -100
- package/src/stdout-capture-fixture.ts +0 -40
- package/src/types.ts +0 -271
- package/tsconfig.json +0 -7
|
@@ -1,497 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
mkdtempSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
rmSync,
|
|
8
|
-
writeFileSync,
|
|
9
|
-
} from "node:fs";
|
|
10
|
-
import { join } from "node:path";
|
|
11
|
-
import { after, afterEach, before, beforeEach, describe, it } from "node:test";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
-
import type { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
|
|
14
|
-
import type { Context } from "openapi-backend";
|
|
15
|
-
import { normalizeRequest } from "../request.js";
|
|
16
|
-
import { formatResponse } from "../response.js";
|
|
17
|
-
import { SystemOperations } from "./system-update.js";
|
|
18
|
-
|
|
19
|
-
const tmpRoot = join(
|
|
20
|
-
fileURLToPath(new URL(".", import.meta.url)),
|
|
21
|
-
"..",
|
|
22
|
-
"..",
|
|
23
|
-
".tmp",
|
|
24
|
-
"system-update",
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const USER = "any-signed-in-user";
|
|
28
|
-
const MANIFEST_URL = "https://updates.example.com/stable.json";
|
|
29
|
-
|
|
30
|
-
let controlDir: string;
|
|
31
|
-
|
|
32
|
-
const okState = {
|
|
33
|
-
currentVersion: "v1.4.1",
|
|
34
|
-
check: {
|
|
35
|
-
status: "ok",
|
|
36
|
-
lastCheckedAt: "2026-07-20T08:00:00Z",
|
|
37
|
-
latestVersion: "v1.5.0",
|
|
38
|
-
publishedAt: "2026-07-18T09:00:00Z",
|
|
39
|
-
summary: "Faster search.",
|
|
40
|
-
releaseNotesUrl: "https://example.com/notes",
|
|
41
|
-
updateAvailable: true,
|
|
42
|
-
},
|
|
43
|
-
run: null,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const writeState = (state: unknown): void => {
|
|
47
|
-
writeFileSync(join(controlDir, "state.json"), JSON.stringify(state));
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
before(() => {
|
|
51
|
-
mkdirSync(tmpRoot, { recursive: true });
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
after(() => {
|
|
55
|
-
rmSync(tmpRoot, { recursive: true, force: true });
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
beforeEach(() => {
|
|
59
|
-
controlDir = mkdtempSync(join(tmpRoot, "control-"));
|
|
60
|
-
process.env.REMIT_UPDATE_CONTROL_DIR = controlDir;
|
|
61
|
-
process.env.REMIT_UPDATE_MANIFEST_URL = MANIFEST_URL;
|
|
62
|
-
process.env.REMIT_TAG = "v1.4.1";
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
afterEach(() => {
|
|
66
|
-
delete process.env.REMIT_UPDATE_MANIFEST_URL;
|
|
67
|
-
delete process.env.REMIT_UPDATE_CONTROL_DIR;
|
|
68
|
-
delete process.env.REMIT_TAG;
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const buildEvent = (sub?: string): APIGatewayProxyEvent =>
|
|
72
|
-
({
|
|
73
|
-
headers: {},
|
|
74
|
-
requestContext: sub ? { authorizer: { claims: { sub } } } : {},
|
|
75
|
-
}) as unknown as APIGatewayProxyEvent;
|
|
76
|
-
|
|
77
|
-
// The query as openapi-backend hands it over: validated and coerced against the
|
|
78
|
-
// spec, so `refresh` reaches the handler as a boolean or not at all.
|
|
79
|
-
const getContext = (query: Record<string, unknown> = {}): Context =>
|
|
80
|
-
({ request: { query } }) as unknown as Context;
|
|
81
|
-
|
|
82
|
-
const postContext = (targetVersion: string): Context =>
|
|
83
|
-
({ request: { requestBody: { targetVersion } } }) as unknown as Context;
|
|
84
|
-
|
|
85
|
-
const getSystemUpdate =
|
|
86
|
-
SystemOperations.SystemOperations_getSystemUpdate as unknown as (
|
|
87
|
-
context: Context,
|
|
88
|
-
event: APIGatewayProxyEvent,
|
|
89
|
-
) => Promise<unknown>;
|
|
90
|
-
|
|
91
|
-
const applySystemUpdate =
|
|
92
|
-
SystemOperations.SystemOperations_applySystemUpdate as unknown as (
|
|
93
|
-
context: Context,
|
|
94
|
-
event: APIGatewayProxyEvent,
|
|
95
|
-
) => Promise<unknown>;
|
|
96
|
-
|
|
97
|
-
const getUpdate = (
|
|
98
|
-
event: APIGatewayProxyEvent,
|
|
99
|
-
query: Record<string, unknown> = {},
|
|
100
|
-
) => getSystemUpdate(getContext(query), event);
|
|
101
|
-
|
|
102
|
-
const applyUpdate = (targetVersion: string, event: APIGatewayProxyEvent) =>
|
|
103
|
-
applySystemUpdate(postContext(targetVersion), event);
|
|
104
|
-
|
|
105
|
-
const hasStatus = (value: unknown, code: number): boolean =>
|
|
106
|
-
typeof value === "object" &&
|
|
107
|
-
value !== null &&
|
|
108
|
-
(value as { statusCode?: number }).statusCode === code;
|
|
109
|
-
|
|
110
|
-
describe("GET /system/update", () => {
|
|
111
|
-
it("returns 404 when no manifest URL is configured", async () => {
|
|
112
|
-
delete process.env.REMIT_UPDATE_MANIFEST_URL;
|
|
113
|
-
|
|
114
|
-
const result = await getUpdate(buildEvent(USER));
|
|
115
|
-
|
|
116
|
-
assert.ok(hasStatus(result, 404));
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("returns 401 when the caller is not authenticated", async () => {
|
|
120
|
-
writeState(okState);
|
|
121
|
-
|
|
122
|
-
const result = await getUpdate(buildEvent());
|
|
123
|
-
|
|
124
|
-
assert.ok(hasStatus(result, 401));
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it("returns 200 for any authenticated caller", async () => {
|
|
128
|
-
writeState(okState);
|
|
129
|
-
|
|
130
|
-
const result = await getUpdate(buildEvent("another-signed-in-user"));
|
|
131
|
-
|
|
132
|
-
assert.deepEqual(result, okState);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it("returns 200 with the state file verbatim", async () => {
|
|
136
|
-
writeState(okState);
|
|
137
|
-
|
|
138
|
-
const result = await getUpdate(buildEvent(USER));
|
|
139
|
-
|
|
140
|
-
assert.deepEqual(result, okState);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("records a check request on the control volume when refresh is set (#599)", async () => {
|
|
144
|
-
writeState(okState);
|
|
145
|
-
|
|
146
|
-
const result = await getUpdate(buildEvent(USER), { refresh: true });
|
|
147
|
-
|
|
148
|
-
// The press reaches the updater through the seam, and the answer is the
|
|
149
|
-
// stored state — lastCheckedAt included, so the panel can say how old the
|
|
150
|
-
// verdict it is still showing is.
|
|
151
|
-
const file = readFileSync(join(controlDir, "check-request.json"), "utf8");
|
|
152
|
-
assert.deepEqual(JSON.parse(file), {});
|
|
153
|
-
assert.deepEqual(result, okState);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
it("records a check request over an unknown version when no state exists yet", async () => {
|
|
157
|
-
const result = await getUpdate(buildEvent(USER), { refresh: true });
|
|
158
|
-
|
|
159
|
-
assert.equal(existsSync(join(controlDir, "check-request.json")), true);
|
|
160
|
-
assert.deepEqual(result, {
|
|
161
|
-
currentVersion: "unknown",
|
|
162
|
-
check: { status: "disabled" },
|
|
163
|
-
run: null,
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("records nothing when the query carries no refresh", async () => {
|
|
168
|
-
// `?refresh=1` is rejected by the spec before it reaches here, so the only
|
|
169
|
-
// value that records a request is the boolean the validator produced.
|
|
170
|
-
writeState(okState);
|
|
171
|
-
|
|
172
|
-
await getUpdate(buildEvent(USER));
|
|
173
|
-
|
|
174
|
-
assert.equal(existsSync(join(controlDir, "check-request.json")), false);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("returns 401 and records nothing when a refresh is not authenticated", async () => {
|
|
178
|
-
writeState(okState);
|
|
179
|
-
|
|
180
|
-
const result = await getUpdate(buildEvent(), { refresh: true });
|
|
181
|
-
|
|
182
|
-
assert.ok(hasStatus(result, 401));
|
|
183
|
-
assert.equal(existsSync(join(controlDir, "check-request.json")), false);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it("reports an unknown version, not its own process env, when no state file exists", async () => {
|
|
187
|
-
// The updater owns the running version and writes it into state.json; with
|
|
188
|
-
// no state file the backend has nothing authoritative, so it says unknown
|
|
189
|
-
// rather than surfacing REMIT_TAG, which drifts from the real running tag.
|
|
190
|
-
const result = await getUpdate(buildEvent(USER));
|
|
191
|
-
|
|
192
|
-
assert.deepEqual(result, {
|
|
193
|
-
currentVersion: "unknown",
|
|
194
|
-
check: { status: "disabled" },
|
|
195
|
-
run: null,
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("returns 200 with the run block untouched when the check has failed", async () => {
|
|
200
|
-
const run = {
|
|
201
|
-
runId: "run-1",
|
|
202
|
-
fromVersion: "v1.3.0",
|
|
203
|
-
targetVersion: "v1.4.1",
|
|
204
|
-
phase: "committing",
|
|
205
|
-
outcome: "succeeded",
|
|
206
|
-
startedAt: "2026-07-19T08:00:00Z",
|
|
207
|
-
updatedAt: "2026-07-19T08:05:00Z",
|
|
208
|
-
message: "Update installed.",
|
|
209
|
-
logCommand: "remit logs backend",
|
|
210
|
-
};
|
|
211
|
-
writeState({
|
|
212
|
-
currentVersion: "v1.4.1",
|
|
213
|
-
check: {
|
|
214
|
-
status: "failed",
|
|
215
|
-
lastCheckedAt: "2026-07-20T08:00:00Z",
|
|
216
|
-
error: "The update manifest could not be fetched.",
|
|
217
|
-
},
|
|
218
|
-
run,
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
const result = (await getUpdate(buildEvent(USER))) as {
|
|
222
|
-
check: { status: string };
|
|
223
|
-
run: unknown;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
assert.equal(result.check.status, "failed");
|
|
227
|
-
assert.deepEqual(result.run, run);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it("passes both schema-version fields through when the state file carries them", async () => {
|
|
231
|
-
writeState({
|
|
232
|
-
currentVersion: "v1.4.1",
|
|
233
|
-
currentSchemaVersion: 5,
|
|
234
|
-
check: { ...okState.check, schemaVersion: 6 },
|
|
235
|
-
run: null,
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const result = (await getUpdate(buildEvent(USER))) as {
|
|
239
|
-
currentSchemaVersion?: number;
|
|
240
|
-
check: { schemaVersion?: number };
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
assert.equal(result.currentSchemaVersion, 5);
|
|
244
|
-
assert.equal(result.check.schemaVersion, 6);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("omits both schema-version fields when the state file predates them", async () => {
|
|
248
|
-
writeState(okState);
|
|
249
|
-
|
|
250
|
-
const result = (await getUpdate(buildEvent(USER))) as {
|
|
251
|
-
currentSchemaVersion?: number;
|
|
252
|
-
check: { schemaVersion?: number };
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
assert.ok(!("currentSchemaVersion" in result));
|
|
256
|
-
assert.ok(!("schemaVersion" in result.check));
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it("carries currentSchemaVersion alone when the check has no schema version", async () => {
|
|
260
|
-
writeState({
|
|
261
|
-
currentVersion: "v1.4.1",
|
|
262
|
-
currentSchemaVersion: 5,
|
|
263
|
-
check: okState.check,
|
|
264
|
-
run: null,
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
const result = (await getUpdate(buildEvent(USER))) as {
|
|
268
|
-
currentSchemaVersion?: number;
|
|
269
|
-
check: { schemaVersion?: number };
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
assert.equal(result.currentSchemaVersion, 5);
|
|
273
|
-
assert.ok(!("schemaVersion" in result.check));
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it("carries check.schemaVersion alone when currentSchemaVersion is absent", async () => {
|
|
277
|
-
writeState({
|
|
278
|
-
currentVersion: "v1.4.1",
|
|
279
|
-
check: { ...okState.check, schemaVersion: 6 },
|
|
280
|
-
run: null,
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
const result = (await getUpdate(buildEvent(USER))) as {
|
|
284
|
-
currentSchemaVersion?: number;
|
|
285
|
-
check: { schemaVersion?: number };
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
assert.ok(!("currentSchemaVersion" in result));
|
|
289
|
-
assert.equal(result.check.schemaVersion, 6);
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
describe("POST /system/update", () => {
|
|
294
|
-
it("returns 404 when no manifest URL is configured", async () => {
|
|
295
|
-
delete process.env.REMIT_UPDATE_MANIFEST_URL;
|
|
296
|
-
|
|
297
|
-
const result = await applyUpdate("v1.5.0", buildEvent(USER));
|
|
298
|
-
|
|
299
|
-
assert.ok(hasStatus(result, 404));
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
it("returns 401 without writing request.json when the caller is not authenticated", async () => {
|
|
303
|
-
writeState(okState);
|
|
304
|
-
|
|
305
|
-
const result = await applyUpdate("v1.5.0", buildEvent());
|
|
306
|
-
|
|
307
|
-
assert.ok(hasStatus(result, 401));
|
|
308
|
-
assert.throws(() => readFileSync(join(controlDir, "request.json"), "utf8"));
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("returns 202 for any authenticated caller and records that identity", async () => {
|
|
312
|
-
writeState(okState);
|
|
313
|
-
|
|
314
|
-
const result = (await applyUpdate(
|
|
315
|
-
"v1.5.0",
|
|
316
|
-
buildEvent("another-signed-in-user"),
|
|
317
|
-
)) as { statusCode: number };
|
|
318
|
-
|
|
319
|
-
assert.equal(result.statusCode, 202);
|
|
320
|
-
|
|
321
|
-
const request = JSON.parse(
|
|
322
|
-
readFileSync(join(controlDir, "request.json"), "utf8"),
|
|
323
|
-
);
|
|
324
|
-
assert.equal(request.requestedBy, "another-signed-in-user");
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
it("returns 409 when a run is already in flight", async () => {
|
|
328
|
-
writeState({
|
|
329
|
-
...okState,
|
|
330
|
-
run: {
|
|
331
|
-
runId: "run-9",
|
|
332
|
-
fromVersion: "v1.4.1",
|
|
333
|
-
targetVersion: "v1.5.0",
|
|
334
|
-
phase: "verifying",
|
|
335
|
-
outcome: null,
|
|
336
|
-
startedAt: "2026-07-20T08:01:00Z",
|
|
337
|
-
updatedAt: "2026-07-20T08:04:00Z",
|
|
338
|
-
message: "Waiting for the new version to answer.",
|
|
339
|
-
logCommand: "remit logs backend",
|
|
340
|
-
},
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
await assert.rejects(applyUpdate("v1.5.0", buildEvent(USER)), {
|
|
344
|
-
statusCode: 409,
|
|
345
|
-
});
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it("returns 422 when the target is not the checked version", async () => {
|
|
349
|
-
writeState(okState);
|
|
350
|
-
|
|
351
|
-
await assert.rejects(applyUpdate("v9.9.9", buildEvent(USER)), {
|
|
352
|
-
statusCode: 422,
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
it("returns 422 when no check has ever succeeded", async () => {
|
|
357
|
-
await assert.rejects(applyUpdate("v1.5.0", buildEvent(USER)), {
|
|
358
|
-
statusCode: 422,
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it("returns 202 with a run block carrying a fresh runId", async () => {
|
|
363
|
-
writeState(okState);
|
|
364
|
-
|
|
365
|
-
const result = (await applyUpdate("v1.5.0", buildEvent(USER))) as {
|
|
366
|
-
statusCode: number;
|
|
367
|
-
body: { run: { runId: string; targetVersion: string; outcome: null } };
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
assert.equal(result.statusCode, 202);
|
|
371
|
-
assert.equal(result.body.run.targetVersion, "v1.5.0");
|
|
372
|
-
assert.equal(result.body.run.outcome, null);
|
|
373
|
-
assert.ok(result.body.run.runId.length > 0);
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it("writes request.json with exactly the four seam fields and nothing else", async () => {
|
|
377
|
-
writeState(okState);
|
|
378
|
-
|
|
379
|
-
await applyUpdate("v1.5.0", buildEvent(USER));
|
|
380
|
-
|
|
381
|
-
const request = JSON.parse(
|
|
382
|
-
readFileSync(join(controlDir, "request.json"), "utf8"),
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
assert.deepEqual(Object.keys(request).sort(), [
|
|
386
|
-
"requestedAt",
|
|
387
|
-
"requestedBy",
|
|
388
|
-
"runId",
|
|
389
|
-
"targetVersion",
|
|
390
|
-
]);
|
|
391
|
-
assert.equal(request.targetVersion, "v1.5.0");
|
|
392
|
-
assert.equal(request.requestedBy, USER);
|
|
393
|
-
for (const forbidden of ["registry", "image", "digest"]) {
|
|
394
|
-
assert.ok(!(forbidden in request));
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it("stamps requestedAt with the moment of the request, in UTC", async () => {
|
|
399
|
-
// The updater installs a request only while it is still current (#587), so
|
|
400
|
-
// requestedAt is the field that decides it. A missing, fixed or non-UTC
|
|
401
|
-
// stamp would make every request expire on arrival.
|
|
402
|
-
writeState(okState);
|
|
403
|
-
const before = Date.now();
|
|
404
|
-
|
|
405
|
-
await applyUpdate("v1.5.0", buildEvent(USER));
|
|
406
|
-
|
|
407
|
-
const after = Date.now();
|
|
408
|
-
const request = JSON.parse(
|
|
409
|
-
readFileSync(join(controlDir, "request.json"), "utf8"),
|
|
410
|
-
) as { requestedAt: string };
|
|
411
|
-
|
|
412
|
-
assert.match(
|
|
413
|
-
request.requestedAt,
|
|
414
|
-
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/,
|
|
415
|
-
);
|
|
416
|
-
const stamped = Date.parse(request.requestedAt);
|
|
417
|
-
assert.ok(stamped >= before - 1000 && stamped <= after + 1000);
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
describe("GET /system/update through the whole request pipeline", () => {
|
|
422
|
-
it("records the check for ?refresh=true off the wire", async () => {
|
|
423
|
-
// The rest of this file hands the handler a query the validator has already
|
|
424
|
-
// coerced. Here the query arrives as API Gateway delivers it — every value a
|
|
425
|
-
// string — and goes through the real built spec, which is where the press
|
|
426
|
-
// was being answered with 400 instead of reaching the handler at all.
|
|
427
|
-
writeState(okState);
|
|
428
|
-
const { api } = await import("../index.js");
|
|
429
|
-
|
|
430
|
-
const event = {
|
|
431
|
-
httpMethod: "GET",
|
|
432
|
-
path: "/system/update",
|
|
433
|
-
queryStringParameters: { refresh: "true" },
|
|
434
|
-
headers: {},
|
|
435
|
-
requestContext: { authorizer: { claims: { sub: USER } } },
|
|
436
|
-
} as unknown as APIGatewayProxyEvent;
|
|
437
|
-
|
|
438
|
-
const result = (await api.handleRequest(
|
|
439
|
-
normalizeRequest(event),
|
|
440
|
-
event,
|
|
441
|
-
{} as never,
|
|
442
|
-
)) as APIGatewayProxyResult;
|
|
443
|
-
|
|
444
|
-
assert.equal(result.statusCode, 200);
|
|
445
|
-
assert.deepEqual(
|
|
446
|
-
JSON.parse(readFileSync(join(controlDir, "check-request.json"), "utf8")),
|
|
447
|
-
{},
|
|
448
|
-
);
|
|
449
|
-
assert.deepEqual(JSON.parse(result.body), okState);
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// A refusal here is the object `formatResponse` unwraps, never one that has
|
|
454
|
-
// already been serialized: handing it a `body` string publishes the envelope
|
|
455
|
-
// itself — `{"statusCode":…,"headers":…,"body":"{…}"}` — and what the caller
|
|
456
|
-
// then parses is not an ApiError at all. Only the status was ever asserted, so
|
|
457
|
-
// the shape went unguarded (issue #371).
|
|
458
|
-
describe("the wire body of a self-update refusal", () => {
|
|
459
|
-
it("answers an unconfigured seam with a flat not_found", async () => {
|
|
460
|
-
delete process.env.REMIT_UPDATE_MANIFEST_URL;
|
|
461
|
-
|
|
462
|
-
const result = await getUpdate(buildEvent(USER));
|
|
463
|
-
const wire = formatResponse(result as Record<string, unknown>);
|
|
464
|
-
|
|
465
|
-
assert.equal(wire.statusCode, 404);
|
|
466
|
-
assert.deepEqual(JSON.parse(wire.body), {
|
|
467
|
-
code: "not_found",
|
|
468
|
-
message: "Not found",
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
it("answers an unauthenticated caller with a flat unauthorized", async () => {
|
|
473
|
-
writeState(okState);
|
|
474
|
-
|
|
475
|
-
const result = await getUpdate(buildEvent());
|
|
476
|
-
const wire = formatResponse(result as Record<string, unknown>);
|
|
477
|
-
|
|
478
|
-
assert.equal(wire.statusCode, 401);
|
|
479
|
-
assert.deepEqual(JSON.parse(wire.body), {
|
|
480
|
-
code: "unauthorized",
|
|
481
|
-
message: "Unauthorized",
|
|
482
|
-
});
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
it("answers an unauthenticated install request the same way", async () => {
|
|
486
|
-
writeState(okState);
|
|
487
|
-
|
|
488
|
-
const result = await applyUpdate("v1.5.0", buildEvent());
|
|
489
|
-
const wire = formatResponse(result as Record<string, unknown>);
|
|
490
|
-
|
|
491
|
-
assert.equal(wire.statusCode, 401);
|
|
492
|
-
assert.deepEqual(JSON.parse(wire.body), {
|
|
493
|
-
code: "unauthorized",
|
|
494
|
-
message: "Unauthorized",
|
|
495
|
-
});
|
|
496
|
-
});
|
|
497
|
-
});
|