avo-inspector 3.1.0-alpha.2 → 3.2.0

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 (52) hide show
  1. package/README.md +6 -13
  2. package/dist/AvoBatcher.js +52 -44
  3. package/dist/AvoDeduplicator.js +46 -45
  4. package/dist/AvoEncryption.js +147 -253
  5. package/dist/AvoInspector.js +295 -334
  6. package/dist/AvoNetworkCallsHandler.d.ts +6 -0
  7. package/dist/AvoNetworkCallsHandler.js +180 -136
  8. package/dist/AvoSchemaParser.js +34 -71
  9. package/dist/AvoStreamId.js +67 -129
  10. package/dist/__mocks__/xhr.js +14 -0
  11. package/dist/__mocks__/xhrError.js +14 -0
  12. package/dist/__tests__/AvoEncryption.d.ts +1 -0
  13. package/dist/__tests__/AvoEncryption.js +272 -0
  14. package/dist/__tests__/AvoEncryptionIntegration.d.ts +1 -0
  15. package/dist/__tests__/AvoEncryptionIntegration.js +191 -0
  16. package/dist/__tests__/AvoEventSpecFetcher.d.ts +1 -0
  17. package/dist/__tests__/AvoEventSpecFetcher.js +377 -0
  18. package/dist/__tests__/AvoInspector.d.ts +1 -0
  19. package/dist/__tests__/AvoInspector.js +335 -0
  20. package/dist/__tests__/Batcher.d.ts +1 -0
  21. package/dist/__tests__/Batcher.js +214 -0
  22. package/dist/__tests__/BatcherPublic.d.ts +1 -0
  23. package/dist/__tests__/BatcherPublic.js +139 -0
  24. package/dist/__tests__/Deduplicator.d.ts +1 -0
  25. package/dist/__tests__/Deduplicator.js +190 -0
  26. package/dist/__tests__/EventSpecCache.d.ts +1 -0
  27. package/dist/__tests__/EventSpecCache.js +143 -0
  28. package/dist/__tests__/EventValidator.d.ts +1 -0
  29. package/dist/__tests__/EventValidator.js +543 -0
  30. package/dist/__tests__/InstallationId.d.ts +1 -0
  31. package/dist/__tests__/InstallationId.js +60 -0
  32. package/dist/__tests__/NetworkCallsHandler.d.ts +1 -0
  33. package/dist/__tests__/NetworkCallsHandler.js +222 -0
  34. package/dist/__tests__/NetworkCallsHandlerGzip_test.d.ts +1 -0
  35. package/dist/__tests__/NetworkCallsHandlerModelA.d.ts +1 -0
  36. package/dist/__tests__/NetworkCallsHandlerModelA.js +206 -0
  37. package/dist/__tests__/Parsing.d.ts +1 -0
  38. package/dist/__tests__/Parsing.js +115 -0
  39. package/dist/__tests__/Sessions.d.ts +1 -0
  40. package/dist/__tests__/Sessions.js +10 -0
  41. package/dist/__tests__/Storage.d.ts +1 -0
  42. package/dist/__tests__/Storage.js +173 -0
  43. package/dist/__tests__/StreamId.d.ts +1 -0
  44. package/dist/__tests__/StreamId.js +153 -0
  45. package/dist/__tests__/constants.js +48 -0
  46. package/dist/eventSpec/AvoEventSpecCache.js +94 -82
  47. package/dist/eventSpec/AvoEventSpecFetcher.js +200 -117
  48. package/dist/eventSpec/EventValidator.js +460 -304
  49. package/dist/index.js +1 -1
  50. package/dist/lite/AvoNetworkCallsHandlerLite.d.ts +6 -0
  51. package/dist/lite/AvoNetworkCallsHandlerLite.js +110 -1
  52. package/package.json +2 -2
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ /**
40
+ * Integration tests for encryption in AvoNetworkCallsHandler.
41
+ *
42
+ * Covers:
43
+ * - publicEncryptionKey in base body when non-null and non-empty
44
+ * - Prod negative test: no encryptedPropertyValue in prod env payload
45
+ * - Dev positive test: encryptedPropertyValue present in dev env payload
46
+ */
47
+ var AvoGuid_1 = require("../AvoGuid");
48
+ var AvoStreamId_1 = require("../AvoStreamId");
49
+ var AvoNetworkCallsHandler_1 = require("../AvoNetworkCallsHandler");
50
+ var p256_1 = require("@noble/curves/p256");
51
+ var constants_1 = require("./constants");
52
+ function bytesToHex(bytes) {
53
+ return Array.from(bytes)
54
+ .map(function (b) { return b.toString(16).padStart(2, "0"); })
55
+ .join("");
56
+ }
57
+ function generateTestKeyPair() {
58
+ var privateKeyBytes = p256_1.p256.utils.randomPrivateKey();
59
+ var publicKeyBytes = p256_1.p256.getPublicKey(privateKeyBytes, false);
60
+ return {
61
+ privateKey: bytesToHex(privateKeyBytes).padStart(64, "0"),
62
+ publicKey: bytesToHex(publicKeyBytes),
63
+ };
64
+ }
65
+ var keyPair = generateTestKeyPair();
66
+ describe("AvoNetworkCallsHandler encryption integration", function () {
67
+ var now = new Date();
68
+ beforeAll(function () {
69
+ // @ts-ignore
70
+ jest.spyOn(global, "Date").mockImplementation(function () { return now; });
71
+ jest
72
+ .spyOn(AvoStreamId_1.AvoStreamId, "initialize")
73
+ .mockResolvedValue(constants_1.mockedReturns.ANONYMOUS_ID);
74
+ jest
75
+ .spyOn(AvoGuid_1.default, "newGuid")
76
+ .mockImplementation(function () { return constants_1.mockedReturns.GUID; });
77
+ });
78
+ afterAll(function () {
79
+ jest.restoreAllMocks();
80
+ });
81
+ test("publicEncryptionKey is included in base body when non-null and non-empty", function () { return __awaiter(void 0, void 0, void 0, function () {
82
+ var handler, body;
83
+ return __generator(this, function (_a) {
84
+ switch (_a.label) {
85
+ case 0:
86
+ handler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler("test-api-key", "dev", "TestApp", "1.0.0", "1.0.0", keyPair.publicKey);
87
+ return [4 /*yield*/, handler.bodyForEventSchemaCall("testEvent", [{ propertyName: "name", propertyType: "string" }], null, null, { name: "Alice" })];
88
+ case 1:
89
+ body = _a.sent();
90
+ expect(body.publicEncryptionKey).toBe(keyPair.publicKey);
91
+ return [2 /*return*/];
92
+ }
93
+ });
94
+ }); });
95
+ test("publicEncryptionKey is NOT included in base body when null", function () { return __awaiter(void 0, void 0, void 0, function () {
96
+ var handler, body;
97
+ return __generator(this, function (_a) {
98
+ switch (_a.label) {
99
+ case 0:
100
+ handler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler("test-api-key", "dev", "TestApp", "1.0.0", "1.0.0");
101
+ return [4 /*yield*/, handler.bodyForEventSchemaCall("testEvent", [{ propertyName: "name", propertyType: "string" }], null, null)];
102
+ case 1:
103
+ body = _a.sent();
104
+ expect(body.publicEncryptionKey).toBeUndefined();
105
+ return [2 /*return*/];
106
+ }
107
+ });
108
+ }); });
109
+ test("publicEncryptionKey is NOT included in base body when empty string", function () { return __awaiter(void 0, void 0, void 0, function () {
110
+ var handler, body;
111
+ return __generator(this, function (_a) {
112
+ switch (_a.label) {
113
+ case 0:
114
+ handler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler("test-api-key", "dev", "TestApp", "1.0.0", "1.0.0", "");
115
+ return [4 /*yield*/, handler.bodyForEventSchemaCall("testEvent", [{ propertyName: "name", propertyType: "string" }], null, null)];
116
+ case 1:
117
+ body = _a.sent();
118
+ expect(body.publicEncryptionKey).toBeUndefined();
119
+ return [2 /*return*/];
120
+ }
121
+ });
122
+ }); });
123
+ test("Prod negative test: no encryptedPropertyValue in prod env payload", function () { return __awaiter(void 0, void 0, void 0, function () {
124
+ var handler, body, _i, _a, prop;
125
+ return __generator(this, function (_b) {
126
+ switch (_b.label) {
127
+ case 0:
128
+ handler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler("test-api-key", "prod", "TestApp", "1.0.0", "1.0.0", keyPair.publicKey);
129
+ return [4 /*yield*/, handler.bodyForEventSchemaCall("testEvent", [{ propertyName: "name", propertyType: "string" }], null, null, { name: "Alice" })];
130
+ case 1:
131
+ body = _b.sent();
132
+ // In prod, shouldEncrypt returns false, so no encryption
133
+ for (_i = 0, _a = body.eventProperties; _i < _a.length; _i++) {
134
+ prop = _a[_i];
135
+ expect(prop.encryptedPropertyValue).toBeUndefined();
136
+ }
137
+ return [2 /*return*/];
138
+ }
139
+ });
140
+ }); });
141
+ test("Dev positive test: encryptedPropertyValue present in dev env payload", function () { return __awaiter(void 0, void 0, void 0, function () {
142
+ var handler, body;
143
+ return __generator(this, function (_a) {
144
+ switch (_a.label) {
145
+ case 0:
146
+ handler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler("test-api-key", "dev", "TestApp", "1.0.0", "1.0.0", keyPair.publicKey);
147
+ return [4 /*yield*/, handler.bodyForEventSchemaCall("testEvent", [{ propertyName: "name", propertyType: "string" }], null, null, { name: "Alice" })];
148
+ case 1:
149
+ body = _a.sent();
150
+ expect(body.eventProperties).toHaveLength(1);
151
+ expect(body.eventProperties[0].encryptedPropertyValue).toBeDefined();
152
+ expect(typeof body.eventProperties[0].encryptedPropertyValue).toBe("string");
153
+ return [2 /*return*/];
154
+ }
155
+ });
156
+ }); });
157
+ test("Staging positive test: encryptedPropertyValue present in staging env payload", function () { return __awaiter(void 0, void 0, void 0, function () {
158
+ var handler, body;
159
+ return __generator(this, function (_a) {
160
+ switch (_a.label) {
161
+ case 0:
162
+ handler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler("test-api-key", "staging", "TestApp", "1.0.0", "1.0.0", keyPair.publicKey);
163
+ return [4 /*yield*/, handler.bodyForEventSchemaCall("testEvent", [{ propertyName: "price", propertyType: "float" }], null, null, { price: 9.99 })];
164
+ case 1:
165
+ body = _a.sent();
166
+ expect(body.eventProperties).toHaveLength(1);
167
+ expect(body.eventProperties[0].encryptedPropertyValue).toBeDefined();
168
+ return [2 /*return*/];
169
+ }
170
+ });
171
+ }); });
172
+ test("List-type properties are omitted in encrypted payload", function () { return __awaiter(void 0, void 0, void 0, function () {
173
+ var handler, body;
174
+ return __generator(this, function (_a) {
175
+ switch (_a.label) {
176
+ case 0:
177
+ handler = new AvoNetworkCallsHandler_1.AvoNetworkCallsHandler("test-api-key", "dev", "TestApp", "1.0.0", "1.0.0", keyPair.publicKey);
178
+ return [4 /*yield*/, handler.bodyForEventSchemaCall("testEvent", [
179
+ { propertyName: "tags", propertyType: "list", children: ["string"] },
180
+ { propertyName: "name", propertyType: "string" },
181
+ ], null, null, { tags: ["a", "b"], name: "Alice" })];
182
+ case 1:
183
+ body = _a.sent();
184
+ // list property should be omitted
185
+ expect(body.eventProperties).toHaveLength(1);
186
+ expect(body.eventProperties[0].propertyName).toBe("name");
187
+ return [2 /*return*/];
188
+ }
189
+ });
190
+ }); });
191
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,377 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ var AvoEventSpecFetcher_1 = require("../eventSpec/AvoEventSpecFetcher");
40
+ // Mock global fetch
41
+ var mockFetch = jest.fn();
42
+ global.fetch = mockFetch;
43
+ function makeValidWireResponse() {
44
+ return {
45
+ events: [
46
+ {
47
+ b: "branch1",
48
+ id: "evt_1",
49
+ vids: ["var_1"],
50
+ p: {
51
+ method: { t: "string", r: true, p: { email: ["evt_1"] } },
52
+ },
53
+ },
54
+ ],
55
+ metadata: {
56
+ schemaId: "schema1",
57
+ branchId: "branch1",
58
+ latestActionId: "action1",
59
+ },
60
+ };
61
+ }
62
+ describe("AvoEventSpecFetcher", function () {
63
+ var fetcher;
64
+ beforeEach(function () {
65
+ mockFetch.mockReset();
66
+ fetcher = new AvoEventSpecFetcher_1.AvoEventSpecFetcher(2000, false, "dev", "https://api.test.com");
67
+ });
68
+ test("returns null in production environment", function () { return __awaiter(void 0, void 0, void 0, function () {
69
+ var prodFetcher, result;
70
+ return __generator(this, function (_a) {
71
+ switch (_a.label) {
72
+ case 0:
73
+ prodFetcher = new AvoEventSpecFetcher_1.AvoEventSpecFetcher(2000, false, "prod");
74
+ return [4 /*yield*/, prodFetcher.fetch({
75
+ apiKey: "key",
76
+ streamId: "stream",
77
+ eventName: "test",
78
+ })];
79
+ case 1:
80
+ result = _a.sent();
81
+ expect(result).toBeNull();
82
+ expect(mockFetch).not.toHaveBeenCalled();
83
+ return [2 /*return*/];
84
+ }
85
+ });
86
+ }); });
87
+ test("fetches and parses wire format correctly", function () { return __awaiter(void 0, void 0, void 0, function () {
88
+ var wireResponse, result;
89
+ return __generator(this, function (_a) {
90
+ switch (_a.label) {
91
+ case 0:
92
+ wireResponse = makeValidWireResponse();
93
+ mockFetch.mockResolvedValue({
94
+ status: 200,
95
+ json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
96
+ return [2 /*return*/, wireResponse];
97
+ }); }); },
98
+ });
99
+ return [4 /*yield*/, fetcher.fetch({
100
+ apiKey: "key",
101
+ streamId: "stream",
102
+ eventName: "test",
103
+ })];
104
+ case 1:
105
+ result = _a.sent();
106
+ expect(result).not.toBeNull();
107
+ expect(result.events[0].branchId).toBe("branch1");
108
+ expect(result.events[0].baseEventId).toBe("evt_1");
109
+ expect(result.events[0].variantIds).toEqual(["var_1"]);
110
+ expect(result.events[0].props.method.type).toBe("string");
111
+ expect(result.events[0].props.method.required).toBe(true);
112
+ expect(result.events[0].props.method.pinnedValues).toEqual({
113
+ email: ["evt_1"],
114
+ });
115
+ expect(result.metadata.schemaId).toBe("schema1");
116
+ return [2 /*return*/];
117
+ }
118
+ });
119
+ }); });
120
+ test("builds correct URL with query parameters", function () { return __awaiter(void 0, void 0, void 0, function () {
121
+ var url;
122
+ return __generator(this, function (_a) {
123
+ switch (_a.label) {
124
+ case 0:
125
+ mockFetch.mockResolvedValue({
126
+ status: 200,
127
+ json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
128
+ return [2 /*return*/, makeValidWireResponse()];
129
+ }); }); },
130
+ });
131
+ return [4 /*yield*/, fetcher.fetch({
132
+ apiKey: "my-key",
133
+ streamId: "my-stream",
134
+ eventName: "Sign Up",
135
+ })];
136
+ case 1:
137
+ _a.sent();
138
+ expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("https://api.test.com/trackingPlan/eventSpec?"), expect.objectContaining({ method: "GET" }));
139
+ url = mockFetch.mock.calls[0][0];
140
+ expect(url).toContain("apiKey=my-key");
141
+ expect(url).toContain("streamId=my-stream");
142
+ expect(url).toContain("eventName=Sign+Up");
143
+ return [2 /*return*/];
144
+ }
145
+ });
146
+ }); });
147
+ test("returns null on non-200 status", function () { return __awaiter(void 0, void 0, void 0, function () {
148
+ var result;
149
+ return __generator(this, function (_a) {
150
+ switch (_a.label) {
151
+ case 0:
152
+ mockFetch.mockResolvedValue({
153
+ status: 404,
154
+ json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
155
+ return [2 /*return*/, ({})];
156
+ }); }); },
157
+ });
158
+ return [4 /*yield*/, fetcher.fetch({
159
+ apiKey: "key",
160
+ streamId: "stream",
161
+ eventName: "test",
162
+ })];
163
+ case 1:
164
+ result = _a.sent();
165
+ expect(result).toBeNull();
166
+ return [2 /*return*/];
167
+ }
168
+ });
169
+ }); });
170
+ test("returns null on invalid response shape", function () { return __awaiter(void 0, void 0, void 0, function () {
171
+ var result;
172
+ return __generator(this, function (_a) {
173
+ switch (_a.label) {
174
+ case 0:
175
+ mockFetch.mockResolvedValue({
176
+ status: 200,
177
+ json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
178
+ return [2 /*return*/, ({ invalid: true })];
179
+ }); }); },
180
+ });
181
+ return [4 /*yield*/, fetcher.fetch({
182
+ apiKey: "key",
183
+ streamId: "stream",
184
+ eventName: "test",
185
+ })];
186
+ case 1:
187
+ result = _a.sent();
188
+ expect(result).toBeNull();
189
+ return [2 /*return*/];
190
+ }
191
+ });
192
+ }); });
193
+ test("returns null on network error", function () { return __awaiter(void 0, void 0, void 0, function () {
194
+ var result;
195
+ return __generator(this, function (_a) {
196
+ switch (_a.label) {
197
+ case 0:
198
+ mockFetch.mockRejectedValue(new Error("Network error"));
199
+ return [4 /*yield*/, fetcher.fetch({
200
+ apiKey: "key",
201
+ streamId: "stream",
202
+ eventName: "test",
203
+ })];
204
+ case 1:
205
+ result = _a.sent();
206
+ expect(result).toBeNull();
207
+ return [2 /*return*/];
208
+ }
209
+ });
210
+ }); });
211
+ test("returns null on JSON parse error", function () { return __awaiter(void 0, void 0, void 0, function () {
212
+ var result;
213
+ return __generator(this, function (_a) {
214
+ switch (_a.label) {
215
+ case 0:
216
+ mockFetch.mockResolvedValue({
217
+ status: 200,
218
+ json: function () { return __awaiter(void 0, void 0, void 0, function () {
219
+ return __generator(this, function (_a) {
220
+ throw new Error("Invalid JSON");
221
+ });
222
+ }); },
223
+ });
224
+ return [4 /*yield*/, fetcher.fetch({
225
+ apiKey: "key",
226
+ streamId: "stream",
227
+ eventName: "test",
228
+ })];
229
+ case 1:
230
+ result = _a.sent();
231
+ expect(result).toBeNull();
232
+ return [2 /*return*/];
233
+ }
234
+ });
235
+ }); });
236
+ test("in-flight de-duplication: exactly ONE fetch for concurrent same-key events", function () { return __awaiter(void 0, void 0, void 0, function () {
237
+ var resolveJson, jsonPromise, params, promise1, promise2, _a, result1, result2;
238
+ return __generator(this, function (_b) {
239
+ switch (_b.label) {
240
+ case 0:
241
+ jsonPromise = new Promise(function (resolve) {
242
+ resolveJson = resolve;
243
+ });
244
+ mockFetch.mockReturnValue(Promise.resolve({
245
+ status: 200,
246
+ json: function () { return jsonPromise; },
247
+ }));
248
+ params = { apiKey: "key", streamId: "stream", eventName: "test" };
249
+ promise1 = fetcher.fetch(params);
250
+ promise2 = fetcher.fetch(params);
251
+ // Resolve the JSON
252
+ resolveJson(makeValidWireResponse());
253
+ return [4 /*yield*/, Promise.all([promise1, promise2])];
254
+ case 1:
255
+ _a = _b.sent(), result1 = _a[0], result2 = _a[1];
256
+ // Both should get the same result
257
+ expect(result1).toEqual(result2);
258
+ // Only one fetch call should have been made
259
+ expect(mockFetch).toHaveBeenCalledTimes(1);
260
+ return [2 /*return*/];
261
+ }
262
+ });
263
+ }); });
264
+ test("in-flight failure: all queued callbacks resolved with null on fetch failure", function () { return __awaiter(void 0, void 0, void 0, function () {
265
+ var rejectFetch, fetchPromise, params, promise1, promise2, _a, result1, result2;
266
+ return __generator(this, function (_b) {
267
+ switch (_b.label) {
268
+ case 0:
269
+ fetchPromise = new Promise(function (_resolve, reject) {
270
+ rejectFetch = reject;
271
+ });
272
+ mockFetch.mockReturnValue(fetchPromise);
273
+ params = { apiKey: "key", streamId: "stream", eventName: "test" };
274
+ promise1 = fetcher.fetch(params);
275
+ promise2 = fetcher.fetch(params);
276
+ rejectFetch(new Error("Network failure"));
277
+ return [4 /*yield*/, Promise.all([promise1, promise2])];
278
+ case 1:
279
+ _a = _b.sent(), result1 = _a[0], result2 = _a[1];
280
+ expect(result1).toBeNull();
281
+ expect(result2).toBeNull();
282
+ return [2 /*return*/];
283
+ }
284
+ });
285
+ }); });
286
+ test("works in staging environment", function () { return __awaiter(void 0, void 0, void 0, function () {
287
+ var stagingFetcher, result;
288
+ return __generator(this, function (_a) {
289
+ switch (_a.label) {
290
+ case 0:
291
+ stagingFetcher = new AvoEventSpecFetcher_1.AvoEventSpecFetcher(2000, false, "staging", "https://api.test.com");
292
+ mockFetch.mockResolvedValue({
293
+ status: 200,
294
+ json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
295
+ return [2 /*return*/, makeValidWireResponse()];
296
+ }); }); },
297
+ });
298
+ return [4 /*yield*/, stagingFetcher.fetch({
299
+ apiKey: "key",
300
+ streamId: "stream",
301
+ eventName: "test",
302
+ })];
303
+ case 1:
304
+ result = _a.sent();
305
+ expect(result).not.toBeNull();
306
+ return [2 /*return*/];
307
+ }
308
+ });
309
+ }); });
310
+ test("parses property constraints including children, lists, regex, allowedValues, minmax", function () { return __awaiter(void 0, void 0, void 0, function () {
311
+ var wireResponse, result, props;
312
+ return __generator(this, function (_a) {
313
+ switch (_a.label) {
314
+ case 0:
315
+ wireResponse = {
316
+ events: [
317
+ {
318
+ b: "branch1",
319
+ id: "evt_1",
320
+ vids: [],
321
+ p: {
322
+ name: {
323
+ t: "string",
324
+ r: true,
325
+ rx: { "^[A-Z]": ["evt_1"] },
326
+ v: { '["option1","option2"]': ["evt_1"] },
327
+ },
328
+ age: {
329
+ t: "int",
330
+ r: false,
331
+ minmax: { "0,120": ["evt_1"] },
332
+ },
333
+ items: {
334
+ t: "object",
335
+ r: false,
336
+ l: true,
337
+ children: {
338
+ id: { t: "string", r: true },
339
+ },
340
+ },
341
+ },
342
+ },
343
+ ],
344
+ metadata: {
345
+ schemaId: "schema1",
346
+ branchId: "branch1",
347
+ latestActionId: "action1",
348
+ },
349
+ };
350
+ mockFetch.mockResolvedValue({
351
+ status: 200,
352
+ json: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
353
+ return [2 /*return*/, wireResponse];
354
+ }); }); },
355
+ });
356
+ return [4 /*yield*/, fetcher.fetch({
357
+ apiKey: "key",
358
+ streamId: "stream",
359
+ eventName: "test",
360
+ })];
361
+ case 1:
362
+ result = _a.sent();
363
+ expect(result).not.toBeNull();
364
+ props = result.events[0].props;
365
+ expect(props.name.regexPatterns).toEqual({ "^[A-Z]": ["evt_1"] });
366
+ expect(props.name.allowedValues).toEqual({
367
+ '["option1","option2"]': ["evt_1"],
368
+ });
369
+ expect(props.age.minMaxRanges).toEqual({ "0,120": ["evt_1"] });
370
+ expect(props.items.isList).toBe(true);
371
+ expect(props.items.children).toBeDefined();
372
+ expect(props.items.children.id.type).toBe("string");
373
+ return [2 /*return*/];
374
+ }
375
+ });
376
+ }); });
377
+ });
@@ -0,0 +1 @@
1
+ export {};