@prosopo/provider-mock 2.8.141 → 2.8.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.
Files changed (78) hide show
  1. package/.turbo/turbo-build$colon$cjs.log +8 -6
  2. package/.turbo/turbo-build$colon$tsc.log +26 -26
  3. package/.turbo/turbo-build.log +9 -7
  4. package/CHANGELOG.md +62 -0
  5. package/Dockerfile +11 -0
  6. package/dist/api.d.ts +35 -2
  7. package/dist/api.d.ts.map +1 -1
  8. package/dist/api.js +97 -67
  9. package/dist/api.js.map +1 -1
  10. package/dist/cjs/api.cjs +105 -66
  11. package/dist/cjs/db.cjs +16 -7
  12. package/dist/cjs/isMain.cjs +16 -0
  13. package/dist/cjs/start.cjs +59 -16
  14. package/dist/cjs/verify.cjs +39 -0
  15. package/dist/db.d.ts +8 -4
  16. package/dist/db.d.ts.map +1 -1
  17. package/dist/db.js +16 -7
  18. package/dist/db.js.map +1 -1
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +5 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/isMain.d.ts +2 -0
  24. package/dist/isMain.d.ts.map +1 -0
  25. package/dist/isMain.js +16 -0
  26. package/dist/isMain.js.map +1 -0
  27. package/dist/start.d.ts +17 -0
  28. package/dist/start.d.ts.map +1 -1
  29. package/dist/start.js +54 -17
  30. package/dist/start.js.map +1 -1
  31. package/dist/tests/db.unit.test.d.ts +2 -0
  32. package/dist/tests/db.unit.test.d.ts.map +1 -0
  33. package/dist/tests/db.unit.test.js +184 -0
  34. package/dist/tests/db.unit.test.js.map +1 -0
  35. package/dist/tests/fixtures.d.ts +33 -0
  36. package/dist/tests/fixtures.d.ts.map +1 -0
  37. package/dist/tests/fixtures.js +55 -0
  38. package/dist/tests/fixtures.js.map +1 -0
  39. package/dist/tests/handlers.unit.test.d.ts +2 -0
  40. package/dist/tests/handlers.unit.test.d.ts.map +1 -0
  41. package/dist/tests/handlers.unit.test.js +204 -0
  42. package/dist/tests/handlers.unit.test.js.map +1 -0
  43. package/dist/tests/providerMock.test-d.d.ts +2 -0
  44. package/dist/tests/providerMock.test-d.d.ts.map +1 -0
  45. package/dist/tests/providerMock.test-d.js +88 -0
  46. package/dist/tests/providerMock.test-d.js.map +1 -0
  47. package/dist/tests/start.unit.test.d.ts +2 -0
  48. package/dist/tests/start.unit.test.d.ts.map +1 -0
  49. package/dist/tests/start.unit.test.js +205 -0
  50. package/dist/tests/start.unit.test.js.map +1 -0
  51. package/dist/tests/verify.unit.test.d.ts +2 -0
  52. package/dist/tests/verify.unit.test.d.ts.map +1 -0
  53. package/dist/tests/verify.unit.test.js +63 -0
  54. package/dist/tests/verify.unit.test.js.map +1 -0
  55. package/dist/tests/wiring.unit.test.d.ts +2 -0
  56. package/dist/tests/wiring.unit.test.d.ts.map +1 -0
  57. package/dist/tests/wiring.unit.test.js +181 -0
  58. package/dist/tests/wiring.unit.test.js.map +1 -0
  59. package/dist/verify.d.ts +13 -0
  60. package/dist/verify.d.ts.map +1 -0
  61. package/dist/verify.js +34 -0
  62. package/dist/verify.js.map +1 -0
  63. package/package.json +19 -11
  64. package/src/api.ts +170 -79
  65. package/src/db.ts +39 -12
  66. package/src/index.ts +18 -0
  67. package/src/isMain.ts +37 -0
  68. package/src/start.ts +92 -18
  69. package/src/tests/db.unit.test.ts +287 -0
  70. package/src/tests/fixtures.ts +117 -0
  71. package/src/tests/handlers.unit.test.ts +325 -0
  72. package/src/tests/providerMock.test-d.ts +165 -0
  73. package/src/tests/start.unit.test.ts +291 -0
  74. package/src/tests/verify.unit.test.ts +143 -0
  75. package/src/tests/wiring.unit.test.ts +293 -0
  76. package/src/verify.ts +63 -0
  77. package/tsconfig.tsbuildinfo +1 -1
  78. package/vite.test.config.ts +32 -0
package/dist/start.js CHANGED
@@ -1,30 +1,67 @@
1
1
  import "./_virtual/_rolldown/runtime.js";
2
2
  import { prosopoRouter } from "./api.js";
3
+ import { isMain } from "./isMain.js";
3
4
  import { handleErrors } from "@prosopo/api-express-router";
4
5
  import { i18nMiddleware } from "@prosopo/locale";
5
6
  import { LogLevel, getLogger } from "@prosopo/logger";
6
7
  import cors from "cors";
7
8
  import express from "express";
8
9
  //#region src/start.ts
9
- var logger = getLogger(LogLevel.enum.info, "prosopo:provider-mock:start.ts");
10
- async function startApi() {
11
- const apiApp = express();
12
- const apiPort = "9229";
10
+ var DEFAULT_API_PORT = 9229;
11
+ var defaultStartDeps = (env = process.env) => ({
12
+ createApp: express,
13
+ i18n: () => i18nMiddleware({}),
14
+ router: prosopoRouter,
15
+ logger: getLogger(LogLevel.enum.info, "prosopo:provider-mock:start.ts"),
16
+ port: readPort(env),
17
+ exit: (code) => {
18
+ process.exit(code);
19
+ }
20
+ });
21
+ /**
22
+ * The port to listen on.
23
+ *
24
+ * The port used to be the string "9229" with no way to change it, so the mock
25
+ * could not be run twice on one machine. A value that is not a usable port is
26
+ * ignored rather than passed to listen, where it would either throw or — for a
27
+ * number out of range — bind somewhere unexpected.
28
+ */
29
+ var readPort = (env) => {
30
+ const raw = env.PROVIDER_MOCK_PORT;
31
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_API_PORT;
32
+ const port = Number(raw);
33
+ if (!Number.isInteger(port) || port < 0 || port > 65535) return DEFAULT_API_PORT;
34
+ return port;
35
+ };
36
+ var startApi = async (deps = defaultStartDeps()) => {
37
+ const apiApp = deps.createApp();
13
38
  apiApp.use(cors());
14
39
  apiApp.use(express.json());
15
- apiApp.use(await i18nMiddleware({}));
16
- apiApp.use(prosopoRouter());
40
+ apiApp.use(await deps.i18n());
41
+ apiApp.use(deps.router());
17
42
  apiApp.use(handleErrors);
18
- apiApp.listen(apiPort, () => {
19
- logger.info(() => ({ msg: `Prosopo app listening at http://localhost:${apiPort}` }));
43
+ const server = apiApp.listen(deps.port);
44
+ await new Promise((resolvePromise, rejectPromise) => {
45
+ server.once("listening", () => {
46
+ server.removeListener("error", rejectPromise);
47
+ deps.logger.info(() => ({ msg: `Prosopo app listening at http://localhost:${deps.port}` }));
48
+ resolvePromise();
49
+ });
50
+ server.once("error", rejectPromise);
20
51
  });
21
- }
22
- startApi().catch((error) => {
23
- logger.error(() => ({
24
- err: error,
25
- msg: "Failed to start API"
26
- }));
27
- process.exit(1);
28
- });
52
+ return apiApp;
53
+ };
54
+ var main = async (deps = defaultStartDeps()) => {
55
+ try {
56
+ await startApi(deps);
57
+ } catch (error) {
58
+ deps.logger.error(() => ({
59
+ err: error instanceof Error ? error : new Error(String(error)),
60
+ msg: "Failed to start API"
61
+ }));
62
+ deps.exit(1);
63
+ }
64
+ };
65
+ if (isMain(import.meta.url)) main();
29
66
  //#endregion
30
- export {};
67
+ export { DEFAULT_API_PORT, defaultStartDeps, main, readPort, startApi };
package/dist/start.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"start.js","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,gCAAgC,CAAC,CAAC;AAE/E,KAAK,UAAU,QAAQ;IACtB,MAAM,MAAM,GAAG,OAAO,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,MAAM,CAAC;IAEvB,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACnB,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3B,MAAM,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,MAAM,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,CAAC;IAC5B,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAEzB,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE;QAC3B,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;YAClB,GAAG,EAAE,6CAA6C,OAAO,EAAE;SAC3D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC;IACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"start.js","sourceRoot":"","sources":["../src/start.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=db.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.unit.test.d.ts","sourceRoot":"","sources":["../../src/tests/db.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,184 @@
1
+ import { ProsopoDBError } from "@prosopo/common";
2
+ import { MongoDatabase } from "@prosopo/database";
3
+ import { afterEach, beforeEach, describe, expect, test, vi, } from "vitest";
4
+ import { JA4Database } from "../db.js";
5
+ const createConnectionMock = (table) => ({
6
+ model: vi.fn(() => table),
7
+ });
8
+ const createRecordMock = (observationCount) => ({
9
+ observation_count: observationCount,
10
+ save: vi.fn(async () => undefined),
11
+ });
12
+ const RECORD = {
13
+ ja4_fingerprint: "t13d1516h2_8daaf6152771_b0da82dd1658",
14
+ user_agent_string: "Mozilla/5.0",
15
+ };
16
+ let db;
17
+ let connectSpy;
18
+ let table;
19
+ const stubSuperConnect = (connection) => {
20
+ connectSpy = vi.fn(async () => {
21
+ db.connection = connection;
22
+ });
23
+ vi.spyOn(MongoDatabase.prototype, "connect").mockImplementation(connectSpy);
24
+ };
25
+ beforeEach(() => {
26
+ db = new JA4Database("mongodb://localhost:27017", "client", "admin");
27
+ const constructed = [];
28
+ const saved = vi.fn(async () => undefined);
29
+ function TableModel(doc) {
30
+ constructed.push(doc);
31
+ }
32
+ TableModel.prototype.save = saved;
33
+ table = Object.assign(TableModel, {
34
+ find: vi.fn(async () => []),
35
+ findOne: vi.fn(async () => null),
36
+ constructed,
37
+ saved,
38
+ });
39
+ });
40
+ afterEach(() => {
41
+ vi.restoreAllMocks();
42
+ });
43
+ describe("the constructor", () => {
44
+ test("builds a url from the parts it is given", () => {
45
+ expect(db.url).toContain("mongodb://localhost:27017");
46
+ expect(db.url).toContain("authSource=admin");
47
+ expect(db.dbname).toBe("client");
48
+ });
49
+ test("registers no tables before connecting", () => {
50
+ expect(db.tables).toEqual({});
51
+ });
52
+ test("works without a database name or auth source", () => {
53
+ const bare = new JA4Database("mongodb://localhost:27017");
54
+ expect(bare.url).toContain("localhost:27017");
55
+ expect(bare.tables).toEqual({});
56
+ });
57
+ });
58
+ describe("connect", () => {
59
+ test("registers the ja4 model on the open connection", async () => {
60
+ const connection = createConnectionMock(table);
61
+ stubSuperConnect(connection);
62
+ await db.connect();
63
+ expect(connection.model).toHaveBeenCalledOnce();
64
+ expect(connection.model.mock.calls[0]?.[0]).toBe("ja4");
65
+ expect(db.tables.ja4).toBeDefined();
66
+ });
67
+ test("opens the connection before registering anything", async () => {
68
+ stubSuperConnect(createConnectionMock(table));
69
+ await db.connect();
70
+ expect(connectSpy).toHaveBeenCalledOnce();
71
+ });
72
+ test("fails loudly when the base class left no connection", async () => {
73
+ stubSuperConnect(undefined);
74
+ await expect(db.connect()).rejects.toBeInstanceOf(ProsopoDBError);
75
+ expect(db.tables.ja4).toBeUndefined();
76
+ });
77
+ test("propagates a failure to connect", async () => {
78
+ vi.spyOn(MongoDatabase.prototype, "connect").mockRejectedValue(new Error("mongo is down"));
79
+ await expect(db.connect()).rejects.toThrow("mongo is down");
80
+ expect(db.tables.ja4).toBeUndefined();
81
+ });
82
+ test("connecting twice is harmless", async () => {
83
+ stubSuperConnect(createConnectionMock(table));
84
+ await db.connect();
85
+ await db.connect();
86
+ expect(db.tables.ja4).toBeDefined();
87
+ });
88
+ });
89
+ describe("getTables", () => {
90
+ test("throws when nothing has been registered yet", () => {
91
+ expect(() => db.getTables()).toThrow(ProsopoDBError);
92
+ });
93
+ test("returns the models once connected", async () => {
94
+ stubSuperConnect(createConnectionMock(table));
95
+ await db.connect();
96
+ expect(db.getTables().ja4).toBeDefined();
97
+ });
98
+ test("querying before connecting is a database error, not a TypeError", async () => {
99
+ await expect(db.getJA4Records()).rejects.toBeInstanceOf(ProsopoDBError);
100
+ await expect(db.getJA4RecordByFingerprintAndUserAgent("a", "b")).rejects.toBeInstanceOf(ProsopoDBError);
101
+ await expect(db.addOrUpdateJA4Record(RECORD)).rejects.toBeInstanceOf(ProsopoDBError);
102
+ });
103
+ });
104
+ describe("queries", () => {
105
+ beforeEach(async () => {
106
+ stubSuperConnect(createConnectionMock(table));
107
+ await db.connect();
108
+ });
109
+ test("getJA4Records asks for every record", async () => {
110
+ await db.getJA4Records();
111
+ expect(table.find).toHaveBeenCalledWith({});
112
+ });
113
+ test("getJA4Records passes an empty result through", async () => {
114
+ table.find.mockResolvedValue([]);
115
+ expect(await db.getJA4Records()).toEqual([]);
116
+ });
117
+ test("a lookup matches on both the fingerprint and the user agent", async () => {
118
+ await db.getJA4RecordByFingerprintAndUserAgent("fp", "ua");
119
+ expect(table.findOne).toHaveBeenCalledWith({
120
+ ja4_fingerprint: "fp",
121
+ user_agent_string: "ua",
122
+ });
123
+ });
124
+ test("a miss is null, not an error", async () => {
125
+ expect(await db.getJA4RecordByFingerprintAndUserAgent("fp", "ua")).toBeNull();
126
+ });
127
+ test("a failed query is propagated", async () => {
128
+ table.find.mockRejectedValue(new Error("connection reset"));
129
+ await expect(db.getJA4Records()).rejects.toThrow("connection reset");
130
+ });
131
+ });
132
+ describe("addOrUpdateJA4Record", () => {
133
+ beforeEach(async () => {
134
+ stubSuperConnect(createConnectionMock(table));
135
+ await db.connect();
136
+ });
137
+ test("inserts when there is no matching record", async () => {
138
+ await db.addOrUpdateJA4Record(RECORD);
139
+ expect(table.constructed).toEqual([RECORD]);
140
+ expect(table.saved).toHaveBeenCalledOnce();
141
+ });
142
+ test("increments the count when there is one", async () => {
143
+ const existing = createRecordMock(4);
144
+ table.findOne.mockResolvedValue(existing);
145
+ const result = await db.addOrUpdateJA4Record(RECORD);
146
+ expect(existing.observation_count).toBe(5);
147
+ expect(existing.save).toHaveBeenCalledOnce();
148
+ expect(result).toBe(existing);
149
+ expect(table.constructed).toEqual([]);
150
+ });
151
+ test("a record with no count so far starts from one", async () => {
152
+ const existing = createRecordMock(undefined);
153
+ table.findOne.mockResolvedValue(existing);
154
+ await db.addOrUpdateJA4Record(RECORD);
155
+ expect(existing.observation_count).toBe(1);
156
+ });
157
+ test("a count of zero is incremented, not treated as missing", async () => {
158
+ const existing = createRecordMock(0);
159
+ table.findOne.mockResolvedValue(existing);
160
+ await db.addOrUpdateJA4Record(RECORD);
161
+ expect(existing.observation_count).toBe(1);
162
+ });
163
+ test("looks up by the record's own fingerprint and user agent", async () => {
164
+ await db.addOrUpdateJA4Record(RECORD);
165
+ expect(table.findOne).toHaveBeenCalledWith({
166
+ ja4_fingerprint: RECORD.ja4_fingerprint,
167
+ user_agent_string: RECORD.user_agent_string,
168
+ });
169
+ });
170
+ test("an empty user agent is looked up as the empty string", async () => {
171
+ await db.addOrUpdateJA4Record({ ...RECORD, user_agent_string: "" });
172
+ expect(table.findOne).toHaveBeenCalledWith({
173
+ ja4_fingerprint: RECORD.ja4_fingerprint,
174
+ user_agent_string: "",
175
+ });
176
+ });
177
+ test("a failure to save is propagated", async () => {
178
+ const existing = createRecordMock(1);
179
+ existing.save.mockRejectedValue(new Error("duplicate key"));
180
+ table.findOne.mockResolvedValue(existing);
181
+ await expect(db.addOrUpdateJA4Record(RECORD)).rejects.toThrow("duplicate key");
182
+ });
183
+ });
184
+ //# sourceMappingURL=db.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.unit.test.js","sourceRoot":"","sources":["../../src/tests/db.unit.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { IncomingHttpHeaders } from "node:http";
2
+ import type { ProcaptchaOutput } from "@prosopo/types";
3
+ import { type Mock } from "vitest";
4
+ import type { ApiRequest, ApiResponse, RouterDeps } from "../api.js";
5
+ import type { JA4Store } from "../db.js";
6
+ export declare const createOutput: (overrides?: Partial<ProcaptchaOutput>) => ProcaptchaOutput;
7
+ export declare const createBody: (overrides?: Record<string, unknown>) => Record<string, unknown>;
8
+ export interface DatabaseMock extends JA4Store {
9
+ connect: Mock<() => Promise<void>>;
10
+ close: Mock<() => Promise<void>>;
11
+ addOrUpdateJA4Record: Mock<JA4Store["addOrUpdateJA4Record"]>;
12
+ }
13
+ export declare const createDatabaseMock: () => DatabaseMock;
14
+ export interface DepsMock {
15
+ deps: RouterDeps;
16
+ database: DatabaseMock;
17
+ getJA4: Mock<(headers: IncomingHttpHeaders) => Promise<{
18
+ ja4PlusFingerprint: string;
19
+ }>>;
20
+ decodeToken: Mock<(token: string) => ProcaptchaOutput>;
21
+ }
22
+ export declare const createDeps: (overrides?: Partial<RouterDeps>) => DepsMock;
23
+ export interface ResponseMock {
24
+ res: ApiResponse;
25
+ json: Mock<(body: object) => unknown>;
26
+ status: Mock<(code: number) => {
27
+ send: (body: string) => unknown;
28
+ }>;
29
+ send: Mock<(body: string) => unknown>;
30
+ }
31
+ export declare const createResponse: () => ResponseMock;
32
+ export declare const createRequest: (overrides?: Partial<ApiRequest>) => ApiRequest;
33
+ //# sourceMappingURL=fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../src/tests/fixtures.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAErD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,KAAK,IAAI,EAAM,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAa,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIpD,eAAO,MAAM,YAAY,eACb,OAAO,CAAC,gBAAgB,CAAC,KAClC,gBASD,CAAC;AAGH,eAAO,MAAM,UAAU,eACX,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAIvB,CAAC;AAEH,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC7C,OAAO,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACnC,KAAK,EAAE,IAAI,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACjC,oBAAoB,EAAE,IAAI,CAAC,QAAQ,CAAC,sBAAsB,CAAC,CAAC,CAAC;CAC7D;AAGD,eAAO,MAAM,kBAAkB,QAAO,YAMpC,CAAC;AAEH,MAAM,WAAW,QAAQ;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,IAAI,CACX,CAAC,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC;QAAE,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC,CACzE,CAAC;IACF,WAAW,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,gBAAgB,CAAC,CAAC;CACvD;AAED,eAAO,MAAM,UAAU,eAAe,OAAO,CAAC,UAAU,CAAC,KAAQ,QAsBhE,CAAC;AAEF,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,WAAW,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;IACtC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK;QAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;KAAE,CAAC,CAAC;IACpE,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC;CACtC;AAED,eAAO,MAAM,cAAc,QAAO,YAOjC,CAAC;AAEF,eAAO,MAAM,aAAa,eACd,OAAO,CAAC,UAAU,CAAC,KAC5B,UAKD,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { getLogger } from "@prosopo/logger";
2
+ import { vi } from "vitest";
3
+ import { TEST_ACCOUNT, TEST_DAPP } from "../verify.js";
4
+ export const createOutput = (overrides = {}) => ({
5
+ user: TEST_ACCOUNT,
6
+ dapp: TEST_DAPP,
7
+ timestamp: "1717171717",
8
+ signature: {
9
+ provider: { challenge: undefined, requestHash: undefined },
10
+ user: { timestamp: undefined, requestHash: undefined },
11
+ },
12
+ ...overrides,
13
+ });
14
+ export const createBody = (overrides = {}) => ({
15
+ token: "0xdeadbeef",
16
+ dappSignature: "0xsignature",
17
+ ...overrides,
18
+ });
19
+ export const createDatabaseMock = () => ({
20
+ connect: vi.fn(async () => undefined),
21
+ close: vi.fn(async () => undefined),
22
+ addOrUpdateJA4Record: vi.fn(async () => null),
23
+ });
24
+ export const createDeps = (overrides = {}) => {
25
+ const database = createDatabaseMock();
26
+ const getJA4 = vi.fn(async () => ({
27
+ ja4PlusFingerprint: "t13d1516h2_8daaf6152771_b0da82dd1658",
28
+ }));
29
+ const decodeToken = vi.fn(() => createOutput());
30
+ return {
31
+ database,
32
+ getJA4,
33
+ decodeToken,
34
+ deps: {
35
+ db: database,
36
+ getJA4,
37
+ decodeToken,
38
+ logger: getLogger("fatal", "provider-mock:test"),
39
+ ...overrides,
40
+ },
41
+ };
42
+ };
43
+ export const createResponse = () => {
44
+ const json = vi.fn(() => undefined);
45
+ const send = vi.fn(() => undefined);
46
+ const status = vi.fn(() => ({ send }));
47
+ return { res: { json, status }, json, status, send };
48
+ };
49
+ export const createRequest = (overrides = {}) => ({
50
+ body: createBody(),
51
+ headers: { "user-agent": "Mozilla/5.0" },
52
+ t: (key) => `translated:${key}`,
53
+ ...overrides,
54
+ });
55
+ //# sourceMappingURL=fixtures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixtures.js","sourceRoot":"","sources":["../../src/tests/fixtures.ts"],"names":[],"mappings":"AAeA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAa,EAAE,EAAE,MAAM,QAAQ,CAAC;AAGvC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGvD,MAAM,CAAC,MAAM,YAAY,GAAG,CAC3B,YAAuC,EAAE,EACtB,EAAE,CAAC,CAAC;IACvB,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,SAAS;IACf,SAAS,EAAE,YAAY;IACvB,SAAS,EAAE;QACV,QAAQ,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE;QAC1D,IAAI,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE;KACtD;IACD,GAAG,SAAS;CACZ,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,UAAU,GAAG,CACzB,YAAqC,EAAE,EACb,EAAE,CAAC,CAAC;IAC9B,KAAK,EAAE,YAAY;IACnB,aAAa,EAAE,aAAa;IAC5B,GAAG,SAAS;CACZ,CAAC,CAAC;AASH,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAiB,EAAE,CAAC,CAAC;IACtD,OAAO,EAAE,EAAE,CAAC,EAAE,CAAsB,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;IAC1D,KAAK,EAAE,EAAE,CAAC,EAAE,CAAsB,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC;IACxD,oBAAoB,EAAE,EAAE,CAAC,EAAE,CAC1B,KAAK,IAA+B,EAAE,CAAC,IAAI,CAC3C;CACD,CAAC,CAAC;AAWH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,YAAiC,EAAE,EAAY,EAAE;IAC3E,MAAM,QAAQ,GAAG,kBAAkB,EAAE,CAAC;IACtC,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CAElB,KAAK,IAAI,EAAE,CAAC,CAAC;QACd,kBAAkB,EAAE,sCAAsC;KAC1D,CAAC,CAAC,CAAC;IACJ,MAAM,WAAW,GAAG,EAAE,CAAC,EAAE,CAAsC,GAAG,EAAE,CACnE,YAAY,EAAE,CACd,CAAC;IACF,OAAO;QACN,QAAQ;QACR,MAAM;QACN,WAAW;QACX,IAAI,EAAE;YACL,EAAE,EAAE,QAAQ;YACZ,MAAM;YACN,WAAW;YACX,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,oBAAoB,CAAC;YAChD,GAAG,SAAS;SACZ;KACD,CAAC;AACH,CAAC,CAAC;AASF,MAAM,CAAC,MAAM,cAAc,GAAG,GAAiB,EAAE;IAChD,MAAM,IAAI,GAAG,EAAE,CAAC,EAAE,CAA4B,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/D,MAAM,IAAI,GAAG,EAAE,CAAC,EAAE,CAA4B,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC/D,MAAM,MAAM,GAAG,EAAE,CAAC,EAAE,CACnB,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAChB,CAAC;IACF,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACtD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAC5B,YAAiC,EAAE,EACtB,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,UAAU,EAAE;IAClB,OAAO,EAAE,EAAE,YAAY,EAAE,aAAa,EAAE;IACxC,CAAC,EAAE,CAAC,GAAW,EAAU,EAAE,CAAC,cAAc,GAAG,EAAE;IAC/C,GAAG,SAAS;CACZ,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=handlers.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.unit.test.d.ts","sourceRoot":"","sources":["../../src/tests/handlers.unit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,204 @@
1
+ import { ProsopoApiError } from "@prosopo/common";
2
+ import { beforeEach, describe, expect, test, vi } from "vitest";
3
+ import { createTestHandler, createVerifyHandler, } from "../api.js";
4
+ import { NOT_VERIFIED_MESSAGE, TEST_COMMITMENT_ID, VERIFIED_MESSAGE, } from "../verify.js";
5
+ import { createBody, createDeps, createOutput, createRequest, createResponse, } from "./fixtures.js";
6
+ let mocks;
7
+ let response;
8
+ let next;
9
+ let nextCalls;
10
+ beforeEach(() => {
11
+ mocks = createDeps();
12
+ response = createResponse();
13
+ nextCalls = [];
14
+ next = (error) => {
15
+ nextCalls.push(error);
16
+ };
17
+ });
18
+ const errorCode = (error) => error instanceof ProsopoApiError ? error.context?.code : undefined;
19
+ describe("the verify handler", () => {
20
+ test("approves a token carrying a fixture", async () => {
21
+ await createVerifyHandler(mocks.deps)(createRequest(), response.res, next);
22
+ expect(response.json).toHaveBeenCalledWith({
23
+ status: `translated:${VERIFIED_MESSAGE}`,
24
+ verified: true,
25
+ commitmentId: TEST_COMMITMENT_ID,
26
+ });
27
+ expect(nextCalls).toEqual([]);
28
+ });
29
+ test("rejects a token carrying nothing known", async () => {
30
+ mocks.decodeToken.mockReturnValue(createOutput({ user: "nobody", dapp: "nowhere" }));
31
+ await createVerifyHandler(mocks.deps)(createRequest(), response.res, next);
32
+ expect(response.json).toHaveBeenCalledWith({
33
+ status: `translated:${NOT_VERIFIED_MESSAGE}`,
34
+ verified: false,
35
+ });
36
+ });
37
+ test("a rejection response has no commitmentId key at all", async () => {
38
+ mocks.decodeToken.mockReturnValue(createOutput({ user: "nobody", dapp: "nowhere" }));
39
+ await createVerifyHandler(mocks.deps)(createRequest(), response.res, next);
40
+ const body = response.json.mock.calls[0]?.[0];
41
+ expect(body).toBeDefined();
42
+ expect(Object.keys(body)).toEqual(["status", "verified"]);
43
+ });
44
+ test("decodes the token that was sent, not some other field", async () => {
45
+ await createVerifyHandler(mocks.deps)(createRequest({ body: createBody({ token: "0xabc123" }) }), response.res, next);
46
+ expect(mocks.decodeToken).toHaveBeenCalledWith("0xabc123");
47
+ });
48
+ test("translates the status message through the request", async () => {
49
+ const t = vi.fn(() => "Vérifié");
50
+ await createVerifyHandler(mocks.deps)(createRequest({ t }), response.res, next);
51
+ expect(t).toHaveBeenCalledWith(VERIFIED_MESSAGE);
52
+ expect(response.json).toHaveBeenCalledWith(expect.objectContaining({ status: "Vérifié" }));
53
+ });
54
+ describe("a body that does not parse", () => {
55
+ const badBodies = [
56
+ ["missing entirely", undefined],
57
+ ["not an object", "token=0x1"],
58
+ ["null", null],
59
+ ["empty", {}],
60
+ ["missing the token", createBody({ token: undefined })],
61
+ ["missing the signature", createBody({ dappSignature: undefined })],
62
+ ["a token that is not hex", createBody({ token: "deadbeef" })],
63
+ ["a token that is the empty string", createBody({ token: "" })],
64
+ ["a token of the wrong type", createBody({ token: 42 })],
65
+ [
66
+ "a token longer than INPUT_LIMITS.TOKEN",
67
+ createBody({ token: `0x${"a".repeat(131072)}` }),
68
+ ],
69
+ ];
70
+ for (const [description, body] of badBodies) {
71
+ test(`is a 400 when it is ${description}`, async () => {
72
+ await createVerifyHandler(mocks.deps)(createRequest({ body }), response.res, next);
73
+ expect(nextCalls).toHaveLength(1);
74
+ expect(nextCalls[0]).toBeInstanceOf(ProsopoApiError);
75
+ expect(errorCode(nextCalls[0])).toBe(400);
76
+ expect(response.json).not.toHaveBeenCalled();
77
+ });
78
+ }
79
+ test("never reaches the decoder", async () => {
80
+ await createVerifyHandler(mocks.deps)(createRequest({ body: {} }), response.res, next);
81
+ expect(mocks.decodeToken).not.toHaveBeenCalled();
82
+ });
83
+ });
84
+ test("a token that cannot be decoded is a 500", async () => {
85
+ mocks.decodeToken.mockImplementation(() => {
86
+ throw new Error("bad codec");
87
+ });
88
+ await createVerifyHandler(mocks.deps)(createRequest(), response.res, next);
89
+ expect(errorCode(nextCalls[0])).toBe(500);
90
+ expect(response.json).not.toHaveBeenCalled();
91
+ });
92
+ test("a failure inside the translator is a 500, not a crash", async () => {
93
+ await createVerifyHandler(mocks.deps)(createRequest({
94
+ t: () => {
95
+ throw new Error("no i18n middleware");
96
+ },
97
+ }), response.res, next);
98
+ expect(errorCode(nextCalls[0])).toBe(500);
99
+ });
100
+ test("calls next exactly once, never alongside a response", async () => {
101
+ mocks.decodeToken.mockImplementation(() => {
102
+ throw new Error("bad codec");
103
+ });
104
+ await createVerifyHandler(mocks.deps)(createRequest(), response.res, next);
105
+ expect(nextCalls).toHaveLength(1);
106
+ expect(response.json).not.toHaveBeenCalled();
107
+ });
108
+ test("resolves rather than rejecting, whatever happens", async () => {
109
+ mocks.decodeToken.mockImplementation(() => {
110
+ throw new Error("bad codec");
111
+ });
112
+ await expect(createVerifyHandler(mocks.deps)(createRequest(), response.res, next)).resolves.toBeUndefined();
113
+ });
114
+ test("a thrown non-Error is still reported as a 500", async () => {
115
+ mocks.decodeToken.mockImplementation(() => {
116
+ throw "not an error";
117
+ });
118
+ await createVerifyHandler(mocks.deps)(createRequest(), response.res, next);
119
+ expect(errorCode(nextCalls[0])).toBe(500);
120
+ });
121
+ });
122
+ describe("the test handler", () => {
123
+ test("returns the fingerprint and the user agent", async () => {
124
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
125
+ expect(response.json).toHaveBeenCalledWith({
126
+ ja4: "t13d1516h2_8daaf6152771_b0da82dd1658",
127
+ ua: "Mozilla/5.0",
128
+ });
129
+ });
130
+ test("stores the fingerprint against the user agent", async () => {
131
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
132
+ expect(mocks.database.addOrUpdateJA4Record).toHaveBeenCalledWith({
133
+ ja4_fingerprint: "t13d1516h2_8daaf6152771_b0da82dd1658",
134
+ user_agent_string: "Mozilla/5.0",
135
+ });
136
+ });
137
+ test("connects before writing and closes afterwards", async () => {
138
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
139
+ expect(mocks.database.connect).toHaveBeenCalledOnce();
140
+ expect(mocks.database.close).toHaveBeenCalledOnce();
141
+ expect(mocks.database.connect.mock.invocationCallOrder[0]).toBeLessThan(mocks.database.addOrUpdateJA4Record.mock.invocationCallOrder[0] ?? 0);
142
+ });
143
+ test("a request with no user agent stores the empty string", async () => {
144
+ await createTestHandler(mocks.deps)(createRequest({ headers: {} }), response.res);
145
+ expect(mocks.database.addOrUpdateJA4Record).toHaveBeenCalledWith(expect.objectContaining({ user_agent_string: "" }));
146
+ });
147
+ test("an empty user agent header is stored as the empty string", async () => {
148
+ await createTestHandler(mocks.deps)(createRequest({ headers: { "user-agent": "" } }), response.res);
149
+ expect(mocks.database.addOrUpdateJA4Record).toHaveBeenCalledWith(expect.objectContaining({ user_agent_string: "" }));
150
+ });
151
+ test("closes the connection even when the write fails", async () => {
152
+ mocks.database.addOrUpdateJA4Record.mockRejectedValue(new Error("duplicate key"));
153
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
154
+ expect(mocks.database.close).toHaveBeenCalledOnce();
155
+ expect(response.status).toHaveBeenCalledWith(500);
156
+ });
157
+ test("closes the connection even when connecting fails", async () => {
158
+ mocks.database.connect.mockRejectedValue(new Error("mongo is down"));
159
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
160
+ expect(mocks.database.close).toHaveBeenCalledOnce();
161
+ expect(mocks.database.addOrUpdateJA4Record).not.toHaveBeenCalled();
162
+ expect(response.status).toHaveBeenCalledWith(500);
163
+ });
164
+ test("does not open a connection when fingerprinting fails", async () => {
165
+ mocks.getJA4.mockRejectedValue(new Error("no ClientHello"));
166
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
167
+ expect(mocks.database.connect).not.toHaveBeenCalled();
168
+ expect(mocks.database.close).not.toHaveBeenCalled();
169
+ expect(response.status).toHaveBeenCalledWith(500);
170
+ });
171
+ test("a failure to close is itself reported, not swallowed", async () => {
172
+ mocks.database.close.mockRejectedValue(new Error("close failed"));
173
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
174
+ expect(response.status).toHaveBeenCalledWith(500);
175
+ expect(response.json).not.toHaveBeenCalled();
176
+ });
177
+ test("sends a body with the 500, so the client sees a reason", async () => {
178
+ mocks.getJA4.mockRejectedValue(new Error("no ClientHello"));
179
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
180
+ expect(response.send).toHaveBeenCalledWith("Failed to record the caller's JA4 fingerprint.");
181
+ });
182
+ test("resolves rather than rejecting when the database is down", async () => {
183
+ mocks.database.connect.mockRejectedValue(new Error("mongo is down"));
184
+ await expect(createTestHandler(mocks.deps)(createRequest(), response.res)).resolves.toBeUndefined();
185
+ });
186
+ test("a thrown non-Error is still a 500", async () => {
187
+ mocks.getJA4.mockRejectedValue("not an error");
188
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
189
+ expect(response.status).toHaveBeenCalledWith(500);
190
+ });
191
+ test("passes the logger to the fingerprinter", async () => {
192
+ await createTestHandler(mocks.deps)(createRequest(), response.res);
193
+ expect(mocks.getJA4).toHaveBeenCalledWith(expect.objectContaining({ "user-agent": "Mozilla/5.0" }), mocks.deps.logger);
194
+ });
195
+ test("a duplicate write does not stop the next request working", async () => {
196
+ mocks.database.addOrUpdateJA4Record.mockRejectedValueOnce(new Error("duplicate key"));
197
+ const handler = createTestHandler(mocks.deps);
198
+ await handler(createRequest(), response.res);
199
+ const second = createResponse();
200
+ await handler(createRequest(), second.res);
201
+ expect(second.json).toHaveBeenCalledOnce();
202
+ });
203
+ });
204
+ //# sourceMappingURL=handlers.unit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.unit.test.js","sourceRoot":"","sources":["../../src/tests/handlers.unit.test.ts"],"names":[],"mappings":"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"}