@remit/backend 0.0.78 → 0.0.79

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/backend",
3
- "version": "0.0.78",
3
+ "version": "0.0.79",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -1,5 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import {
3
+ existsSync,
3
4
  mkdirSync,
4
5
  mkdtempSync,
5
6
  readFileSync,
@@ -71,6 +72,11 @@ const buildEvent = (sub?: string): APIGatewayProxyEvent =>
71
72
  requestContext: sub ? { authorizer: { claims: { sub } } } : {},
72
73
  }) as unknown as APIGatewayProxyEvent;
73
74
 
75
+ // The query as openapi-backend hands it over: validated and coerced against the
76
+ // spec, so `refresh` reaches the handler as a boolean or not at all.
77
+ const getContext = (query: Record<string, unknown> = {}): Context =>
78
+ ({ request: { query } }) as unknown as Context;
79
+
74
80
  const postContext = (targetVersion: string): Context =>
75
81
  ({ request: { requestBody: { targetVersion } } }) as unknown as Context;
76
82
 
@@ -86,8 +92,10 @@ const applySystemUpdate =
86
92
  event: APIGatewayProxyEvent,
87
93
  ) => Promise<unknown>;
88
94
 
89
- const getUpdate = (event: APIGatewayProxyEvent) =>
90
- getSystemUpdate({} as unknown as Context, event);
95
+ const getUpdate = (
96
+ event: APIGatewayProxyEvent,
97
+ query: Record<string, unknown> = {},
98
+ ) => getSystemUpdate(getContext(query), event);
91
99
 
92
100
  const applyUpdate = (targetVersion: string, event: APIGatewayProxyEvent) =>
93
101
  applySystemUpdate(postContext(targetVersion), event);
@@ -130,6 +138,49 @@ describe("GET /system/update", () => {
130
138
  assert.deepEqual(result, okState);
131
139
  });
132
140
 
141
+ it("records a check request on the control volume when refresh is set (#599)", async () => {
142
+ writeState(okState);
143
+
144
+ const result = await getUpdate(buildEvent(USER), { refresh: true });
145
+
146
+ // The press reaches the updater through the seam, and the answer is the
147
+ // stored state — lastCheckedAt included, so the panel can say how old the
148
+ // verdict it is still showing is.
149
+ const file = readFileSync(join(controlDir, "check-request.json"), "utf8");
150
+ assert.deepEqual(JSON.parse(file), {});
151
+ assert.deepEqual(result, okState);
152
+ });
153
+
154
+ it("records a check request over an unknown version when no state exists yet", async () => {
155
+ const result = await getUpdate(buildEvent(USER), { refresh: true });
156
+
157
+ assert.equal(existsSync(join(controlDir, "check-request.json")), true);
158
+ assert.deepEqual(result, {
159
+ currentVersion: "unknown",
160
+ check: { status: "disabled" },
161
+ run: null,
162
+ });
163
+ });
164
+
165
+ it("records nothing when the query carries no refresh", async () => {
166
+ // `?refresh=1` is rejected by the spec before it reaches here, so the only
167
+ // value that records a request is the boolean the validator produced.
168
+ writeState(okState);
169
+
170
+ await getUpdate(buildEvent(USER));
171
+
172
+ assert.equal(existsSync(join(controlDir, "check-request.json")), false);
173
+ });
174
+
175
+ it("returns 401 and records nothing when a refresh is not authenticated", async () => {
176
+ writeState(okState);
177
+
178
+ const result = await getUpdate(buildEvent(), { refresh: true });
179
+
180
+ assert.ok(hasStatus(result, 401));
181
+ assert.equal(existsSync(join(controlDir, "check-request.json")), false);
182
+ });
183
+
133
184
  it("reports an unknown version, not its own process env, when no state file exists", async () => {
134
185
  // The updater owns the running version and writes it into state.json; with
135
186
  // no state file the backend has nothing authoritative, so it says unknown
@@ -112,6 +112,20 @@ const writeRequest = (request: {
112
112
  renameSync(tmp, join(dir, "request.json"));
113
113
  };
114
114
 
115
+ /**
116
+ * Record a check request on the control seam (#599). The updater consumes it on
117
+ * its watch loop and runs a manifest check immediately, so a press of check in
118
+ * the panel — not just the updater's own cadence — moves `lastCheckedAt`. Like
119
+ * request.json it is written atomically and carries no authority: its presence
120
+ * is the whole message, so it is empty.
121
+ */
122
+ const writeCheckRequest = (): void => {
123
+ const dir = controlDir();
124
+ const tmp = join(dir, `.check-request.json.tmp`);
125
+ writeFileSync(tmp, JSON.stringify({}), { mode: 0o644 });
126
+ renameSync(tmp, join(dir, "check-request.json"));
127
+ };
128
+
115
129
  /**
116
130
  * The resource returned by the POST. The updater has not yet written the
117
131
  * authoritative run — it polls the seam — so this bootstraps the run block with
@@ -148,7 +162,7 @@ export const SystemOperations: Record<
148
162
  OperationHandler<SystemOperationIds>
149
163
  > = {
150
164
  SystemOperations_getSystemUpdate: async (
151
- _context: Context,
165
+ context: Context,
152
166
  ...args: unknown[]
153
167
  ): Promise<SystemUpdateResponse | APIGatewayProxyResult> => {
154
168
  const offSurface = guardManifestConfigured();
@@ -157,6 +171,14 @@ export const SystemOperations: Record<
157
171
  const event = args[0] as APIGatewayProxyEvent;
158
172
  if (!getSubFromEvent(event)) return unauthorized();
159
173
 
174
+ // A refresh asks for a fresh answer, and only the updater can fetch one, so
175
+ // the request goes on the control seam for its watch loop to pick up (#599).
176
+ // The answer is the stored state as it stands, `lastCheckedAt` included: the
177
+ // caller holds its own press and watches that timestamp move, so the
178
+ // response never has to claim a verdict the updater has not reached.
179
+ const { refresh } = context.request.query as { refresh?: boolean };
180
+ if (refresh === true) writeCheckRequest();
181
+
160
182
  return readState() ?? emptyResource();
161
183
  },
162
184