UNPKG

avo-inspector

Version:

[![npm version](https://badge.fury.io/js/avo-inspector.svg)](https://badge.fury.io/js/avo-inspector)

544 lines (543 loc) 26.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { 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; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var EventValidator_1 = require("../eventSpec/EventValidator"); function makeSpecResponse(events, branchId) { if (branchId === void 0) { branchId = "branch1"; } return { events: events, metadata: { schemaId: "schema1", branchId: branchId, latestActionId: "action1", }, }; } function makeEvent(id, props, variantIds) { if (variantIds === void 0) { variantIds = []; } return { branchId: "branch1", baseEventId: id, variantIds: variantIds, props: props, }; } describe("EventValidator", function () { describe("pinned values", function () { test("passes when value matches pinned value", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { method: { type: "string", required: true, pinnedValues: { email: ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ method: "email" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.method.failedEventIds).toBeUndefined(); expect(result.propertyResults.method.passedEventIds).toBeUndefined(); return [2 /*return*/]; } }); }); }); test("fails when value does not match pinned value", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { method: { type: "string", required: true, pinnedValues: { email: ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ method: "phone" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.method.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); }); describe("allowed values", function () { test("passes when value is in allowed list", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { status: { type: "string", required: true, allowedValues: { '["active","inactive"]': ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ status: "active" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.status.failedEventIds).toBeUndefined(); return [2 /*return*/]; } }); }); }); test("fails when value is not in allowed list", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { status: { type: "string", required: true, allowedValues: { '["active","inactive"]': ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ status: "deleted" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.status.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); }); describe("regex patterns", function () { test("passes when value matches regex", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { email: { type: "string", required: true, regexPatterns: { "^[^@]+@[^@]+$": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ email: "user@example.com" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.email.failedEventIds).toBeUndefined(); return [2 /*return*/]; } }); }); }); test("fails when value does not match regex", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { email: { type: "string", required: true, regexPatterns: { "^[^@]+@[^@]+$": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ email: "invalid" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.email.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); test("safe-regex2: unsafe regex patterns are skipped with warning", function () { return __awaiter(void 0, void 0, void 0, function () { var warnSpy, spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: warnSpy = jest.spyOn(console, "warn").mockImplementation(); spec = makeSpecResponse([ makeEvent("evt_1", { input: { type: "string", required: true, regexPatterns: { "(a+)+$": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ input: "aaaaaa" }, spec)]; case 1: result = _a.sent(); // Unsafe pattern should be skipped, not cause failure expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("[Avo Inspector] Warning: unsafe regex pattern skipped")); warnSpy.mockRestore(); return [2 /*return*/]; } }); }); }); test("non-string values fail all regex constraints", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { code: { type: "string", required: true, regexPatterns: { "^[0-9]+$": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ code: 12345 }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.code.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); }); describe("min/max ranges", function () { test("passes when value is within range", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { age: { type: "int", required: true, minMaxRanges: { "0,120": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ age: 25 }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.age.failedEventIds).toBeUndefined(); return [2 /*return*/]; } }); }); }); test("fails when value is below minimum", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { age: { type: "int", required: true, minMaxRanges: { "0,120": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ age: -1 }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.age.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); test("fails when value is above maximum", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { age: { type: "int", required: true, minMaxRanges: { "0,120": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ age: 150 }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.age.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); test("handles open-ended ranges (min only)", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { score: { type: "int", required: true, minMaxRanges: { "0,": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ score: 999999 }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.score.failedEventIds).toBeUndefined(); return [2 /*return*/]; } }); }); }); test("non-numeric values fail all min/max constraints", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { count: { type: "int", required: true, minMaxRanges: { "0,100": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ count: "fifty" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.count.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); }); describe("bandwidth optimization", function () { test("returns passedEventIds when strictly smaller than failedEventIds", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result, propResult; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { method: { type: "string", required: true, pinnedValues: { email: ["evt_1"], phone: ["evt_2"], sms: ["evt_3"], }, }, }, ["evt_2", "evt_3"]), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ method: "email" }, spec)]; case 1: result = _a.sent(); propResult = result.propertyResults.method; // evt_1 passes, evt_2 and evt_3 fail // passedEventIds=[evt_1] (len 1) < failedEventIds=[evt_2, evt_3] (len 2) // So passedEventIds should be returned expect(propResult.passedEventIds).toEqual(["evt_1"]); expect(propResult.failedEventIds).toBeUndefined(); return [2 /*return*/]; } }); }); }); }); describe("property not in spec", function () { test("returns empty result for unknown properties", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { method: { type: "string", required: true }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ method: "email", extraProp: "value" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.extraProp).toEqual({}); return [2 /*return*/]; } }); }); }); }); describe("null/undefined on non-required properties", function () { test("skips validation for null on non-required primitive property", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { nickname: { type: "string", required: false, pinnedValues: { "some-name": ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ nickname: null }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.nickname.failedEventIds).toBeUndefined(); return [2 /*return*/]; } }); }); }); }); describe("nested object properties", function () { test("validates children of object properties", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { address: { type: "object", required: true, children: { country: { type: "string", required: true, pinnedValues: { US: ["evt_1"] }, }, }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ address: { country: "UK" } }, spec)]; case 1: result = _b.sent(); expect((_a = result.propertyResults.address.children) === null || _a === void 0 ? void 0 : _a.country.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); }); describe("list properties", function () { test("validates each item in a list against constraints", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { tags: { type: "string", required: true, isList: true, allowedValues: { '["red","green","blue"]': ["evt_1"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ tags: ["red", "yellow"] }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.tags.failedEventIds).toContain("evt_1"); return [2 /*return*/]; } }); }); }); }); describe("metadata", function () { test("returns metadata from the spec response", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([makeEvent("evt_1", {})], "my-branch"); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ anything: "value" }, spec)]; case 1: result = _a.sent(); expect(result.metadata).toEqual({ schemaId: "schema1", branchId: "my-branch", latestActionId: "action1", }); return [2 /*return*/]; } }); }); }); }); describe("multiple events", function () { test("aggregates constraints from multiple events", function () { return __awaiter(void 0, void 0, void 0, function () { var spec, result; return __generator(this, function (_a) { switch (_a.label) { case 0: spec = makeSpecResponse([ makeEvent("evt_1", { method: { type: "string", required: true, pinnedValues: { email: ["evt_1"] }, }, }), makeEvent("evt_2", { method: { type: "string", required: true, pinnedValues: { phone: ["evt_2"] }, }, }), ]); return [4 /*yield*/, (0, EventValidator_1.validateEvent)({ method: "email" }, spec)]; case 1: result = _a.sent(); expect(result.propertyResults.method.failedEventIds).toContain("evt_2"); return [2 /*return*/]; } }); }); }); }); });