@remit/backend 0.0.93 → 0.0.95

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.93",
3
+ "version": "0.0.95",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -14,6 +14,10 @@ import type { AccountConfigItem, MailboxItem } from "@remit/data-ports";
14
14
  import { ConfigNotEmptyError, NotFoundError } from "@remit/data-ports/errors";
15
15
  import type { CanonicalMailboxRoleValue } from "@remit/data-ports/folder-role";
16
16
  import { logger } from "@remit/logger-lambda";
17
+ import {
18
+ EMBEDDING_PROVIDER_OFF,
19
+ readEmbeddingProviderFromEnv,
20
+ } from "@remit/search-service/from-env";
17
21
  import type { APIGatewayProxyEvent } from "aws-lambda";
18
22
  import { env } from "expect-env";
19
23
  import type { Context } from "openapi-backend";
@@ -138,9 +142,22 @@ const emptyConfigResponse = (
138
142
  updatedAt: now,
139
143
  },
140
144
  accounts: [],
145
+ semanticSearchEnabled: semanticSearchEnabled(),
141
146
  };
142
147
  };
143
148
 
149
+ /**
150
+ * Whether this instance embeds anything, read from the same
151
+ * `SEARCH_EMBEDDING_PROVIDER` the search-index worker and the `remit` wrapper
152
+ * read. It rides GET /config because the semantic surfaces need it before they
153
+ * have a query to send: an off instance stores no vectors, so the Organize
154
+ * widen and semantic filters have nothing to read, and a client that learns
155
+ * that only from an empty result cannot tell it from a mailbox with nothing
156
+ * similar in it (#1068).
157
+ */
158
+ const semanticSearchEnabled = (): boolean =>
159
+ readEmbeddingProviderFromEnv() !== EMBEDDING_PROVIDER_OFF;
160
+
144
161
  export const ConfigOperations: Record<
145
162
  ConfigOperationIds,
146
163
  OperationHandler<ConfigOperationIds>
@@ -220,6 +237,7 @@ export const ConfigOperations: Record<
220
237
 
221
238
  return {
222
239
  accountConfig: toAccountConfigResponse(accountConfig),
240
+ semanticSearchEnabled: semanticSearchEnabled(),
223
241
  ...(pendingImport ? { pendingImport } : {}),
224
242
  accounts: activeAccounts.map((acc) =>
225
243
  toAccountResponse(
@@ -10,8 +10,9 @@ import {
10
10
  import { join } from "node:path";
11
11
  import { after, afterEach, before, beforeEach, describe, it } from "node:test";
12
12
  import { fileURLToPath } from "node:url";
13
- import type { APIGatewayProxyEvent } from "aws-lambda";
13
+ import type { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda";
14
14
  import type { Context } from "openapi-backend";
15
+ import { normalizeRequest } from "../request.js";
15
16
  import { SystemOperations } from "./system-update.js";
16
17
 
17
18
  const tmpRoot = join(
@@ -415,3 +416,35 @@ describe("POST /system/update", () => {
415
416
  assert.ok(stamped >= before - 1000 && stamped <= after + 1000);
416
417
  });
417
418
  });
419
+
420
+ describe("GET /system/update through the whole request pipeline", () => {
421
+ it("records the check for ?refresh=true off the wire", async () => {
422
+ // The rest of this file hands the handler a query the validator has already
423
+ // coerced. Here the query arrives as API Gateway delivers it — every value a
424
+ // string — and goes through the real built spec, which is where the press
425
+ // was being answered with 400 instead of reaching the handler at all.
426
+ writeState(okState);
427
+ const { api } = await import("../index.js");
428
+
429
+ const event = {
430
+ httpMethod: "GET",
431
+ path: "/system/update",
432
+ queryStringParameters: { refresh: "true" },
433
+ headers: {},
434
+ requestContext: { authorizer: { claims: { sub: USER } } },
435
+ } as unknown as APIGatewayProxyEvent;
436
+
437
+ const result = (await api.handleRequest(
438
+ normalizeRequest(event),
439
+ event,
440
+ {} as never,
441
+ )) as APIGatewayProxyResult;
442
+
443
+ assert.equal(result.statusCode, 200);
444
+ assert.deepEqual(
445
+ JSON.parse(readFileSync(join(controlDir, "check-request.json"), "utf8")),
446
+ {},
447
+ );
448
+ assert.deepEqual(JSON.parse(result.body), okState);
449
+ });
450
+ });
@@ -0,0 +1,44 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+ import { OpenAPISpec } from "./index.js";
4
+
5
+ type Parameter = {
6
+ name: string;
7
+ in: string;
8
+ explode?: boolean;
9
+ };
10
+
11
+ const queryParameters = (): Array<{
12
+ operationId: string;
13
+ param: Parameter;
14
+ }> => {
15
+ const found: Array<{ operationId: string; param: Parameter }> = [];
16
+ for (const item of Object.values(OpenAPISpec.paths ?? {})) {
17
+ for (const operation of Object.values(item ?? {})) {
18
+ const { operationId, parameters } = operation as {
19
+ operationId?: string;
20
+ parameters?: Parameter[];
21
+ };
22
+ if (!operationId || !parameters) continue;
23
+ for (const param of parameters) {
24
+ if (param.in === "query") found.push({ operationId, param });
25
+ }
26
+ }
27
+ }
28
+ return found;
29
+ };
30
+
31
+ describe("the built OpenAPI spec", () => {
32
+ it("declares every query parameter exploded", () => {
33
+ // openapi-backend splits a non-exploded query value on commas before it
34
+ // validates, so `?refresh=true` arrives at the validator as `["true"]` and
35
+ // is refused as "must be boolean" — the handler never runs. Exploded is the
36
+ // wire-identical form for a scalar, so every `@query` in main.tsp carries
37
+ // `#{ explode: true }`.
38
+ const offenders = queryParameters()
39
+ .filter(({ param }) => param.explode === false)
40
+ .map(({ operationId, param }) => `${operationId}.${param.name}`);
41
+
42
+ assert.deepEqual(offenders, []);
43
+ });
44
+ });