From 76f4d45feef54072916c5d1a38aedb7236e2bc15 Mon Sep 17 00:00:00 2001 From: Charlie Lamb Date: Fri, 12 Jun 2026 13:53:33 +0100 Subject: [PATCH] chore: fix dockerfile and unit tests --- docker/Dockerfile | 2 +- .../migration/filters/filterRowTypes.test.ts | 374 ++++++++---------- 2 files changed, 165 insertions(+), 211 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9433c7e34..4a687db04 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -25,11 +25,11 @@ COPY apps/checkout/package.json apps/checkout/ COPY apps/docs/package.json apps/docs/ COPY apps/sdk-test/package.json apps/sdk-test/ COPY apps/website/package.json apps/website/ -COPY packages/ai-sdk/package.json packages/ai-sdk/ COPY packages/atmn/package.json packages/atmn/ COPY packages/atmn-tests/package.json packages/atmn-tests/ COPY packages/auth/package.json packages/auth/ COPY packages/autumn-js/package.json packages/autumn-js/ +COPY packages/gateway/package.json packages/gateway/ COPY packages/ksuid/package.json packages/ksuid/ COPY packages/logging/package.json packages/logging/ COPY packages/mcp/package.json packages/mcp/ diff --git a/vite/tests/views/migrations/migration/filters/filterRowTypes.test.ts b/vite/tests/views/migrations/migration/filters/filterRowTypes.test.ts index 74e64c9dd..49bba2dea 100644 --- a/vite/tests/views/migrations/migration/filters/filterRowTypes.test.ts +++ b/vite/tests/views/migrations/migration/filters/filterRowTypes.test.ts @@ -1,109 +1,11 @@ import { describe, expect, test } from "bun:test"; -import type { PlanFilter } from "@autumn/shared"; import { customerIdToStrings, - groupsToPlanFilter, planFilterToGroups, + ruleToStringMatcher, stringsToCustomerId, } from "@/views/migrations/migration/filters/filterRowTypes"; -describe("planFilterToGroups -> groupsToPlanFilter roundtrip", () => { - const roundtrip = (input: PlanFilter): PlanFilter => { - const groups = planFilterToGroups(input); - return groupsToPlanFilter(groups); - }; - - test("empty filter", () => { - expect(roundtrip({})).toEqual({}); - }); - - test("plan_id bare string", () => { - expect(roundtrip({ plan_id: "pro" })).toEqual({ plan_id: "pro" }); - }); - - test("plan_id $in", () => { - const input = { plan_id: { $in: ["a", "b"] } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("plan_id $nin", () => { - const input = { plan_id: { $nin: ["x"] } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("plan_id $ne", () => { - const input = { plan_id: { $ne: "old" } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("plan_id $regex", () => { - const input = { plan_id: { $regex: "^pro" } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("plan_id $startsWith", () => { - const input = { plan_id: { $startsWith: "pro" } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("paid: true", () => { - expect(roundtrip({ paid: true })).toEqual({ paid: true }); - }); - - test("paid: false", () => { - expect(roundtrip({ paid: false })).toEqual({ paid: false }); - }); - - test("recurring: true", () => { - expect(roundtrip({ recurring: true })).toEqual({ recurring: true }); - }); - - test("custom: true", () => { - expect(roundtrip({ custom: true })).toEqual({ custom: true }); - }); - - test("custom: false", () => { - expect(roundtrip({ custom: false })).toEqual({ custom: false }); - }); - - test("price: null (free plan)", () => { - expect(roundtrip({ price: null })).toEqual({ price: null }); - }); - - test("price: { $ne: null } (paid plan)", () => { - expect(roundtrip({ price: { $ne: null } })).toEqual({ - price: { $ne: null }, - }); - }); - - test("item with implicit $some (feature_id)", () => { - const input: PlanFilter = { item: { feature_id: "credits" } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("item unlimited boolean", () => { - const input: PlanFilter = { item: { unlimited: true } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("$or groups", () => { - const input: PlanFilter = { - $or: [{ plan_id: "enterprise" }, { plan_id: "team" }], - }; - expect(roundtrip(input)).toEqual(input); - }); - - test("combined filters", () => { - const input: PlanFilter = { - plan_id: "pro", - paid: true, - recurring: true, - item: { feature_id: "credits", unlimited: false }, - }; - expect(roundtrip(input)).toEqual(input); - }); -}); - describe("planFilterToGroups structure", () => { test("empty filter produces one group with no rules", () => { const groups = planFilterToGroups({}); @@ -111,40 +13,179 @@ describe("planFilterToGroups structure", () => { expect(groups[0].rules).toHaveLength(0); }); - test("$or produces multiple groups", () => { + test("$or of pure plan selections collapses to a single plan row", () => { const groups = planFilterToGroups({ $or: [{ plan_id: "a" }, { plan_id: "b" }], }); + expect(groups).toEqual([ + { rules: [{ field: "plan_id", operator: "in", values: ["a", "b"] }] }, + ]); + }); + + test("$or of non-selection filters produces multiple groups", () => { + const groups = planFilterToGroups({ + $or: [{ paid: true }, { recurring: true }], + }); expect(groups).toHaveLength(2); }); - }); -describe("groupsToPlanFilter", () => { - test("serializes visual OR groups as top-level $or branches", () => { - expect( - groupsToPlanFilter([ - { - rules: [{ field: "custom", operator: "is", values: ["true"] }], - }, - { - rules: [{ field: "custom", operator: "is", values: ["false"] }], - }, - ]), - ).toEqual({ - $or: [{ custom: true }, { custom: false }], - }); +describe("planFilterToGroups decodes matcher forms", () => { + test("bare plan_id string", () => { + expect(planFilterToGroups({ plan_id: "pro" })).toEqual([ + { rules: [{ field: "plan_id", operator: "is", values: ["pro"] }] }, + ]); }); - test("ignores empty OR groups", () => { + test("plan_id $in", () => { + expect(planFilterToGroups({ plan_id: { $in: ["a", "b"] } })).toEqual([ + { rules: [{ field: "plan_id", operator: "in", values: ["a", "b"] }] }, + ]); + }); + + test("plan_id $ne", () => { + expect(planFilterToGroups({ plan_id: { $ne: "old" } })).toEqual([ + { rules: [{ field: "plan_id", operator: "is_not", values: ["old"] }] }, + ]); + }); + + test("plan_id $nin", () => { + expect(planFilterToGroups({ plan_id: { $nin: ["x"] } })).toEqual([ + { rules: [{ field: "plan_id", operator: "not_in", values: ["x"] }] }, + ]); + }); + + test("plan_id $regex", () => { + expect(planFilterToGroups({ plan_id: { $regex: "^pro" } })).toEqual([ + { rules: [{ field: "plan_id", operator: "regex", values: ["^pro"] }] }, + ]); + }); + + test("plan_id $startsWith", () => { + expect(planFilterToGroups({ plan_id: { $startsWith: "pro" } })).toEqual([ + { + rules: [{ field: "plan_id", operator: "starts_with", values: ["pro"] }], + }, + ]); + }); + + test("booleans decode to is rules", () => { + expect(planFilterToGroups({ paid: true })).toEqual([ + { rules: [{ field: "paid", operator: "is", values: ["true"] }] }, + ]); + expect(planFilterToGroups({ custom: false })).toEqual([ + { rules: [{ field: "custom", operator: "is", values: ["false"] }] }, + ]); + }); + + test("price null decodes to not_exists, $ne null to exists", () => { + expect(planFilterToGroups({ price: null })).toEqual([ + { rules: [{ field: "price", operator: "not_exists", values: [] }] }, + ]); + expect(planFilterToGroups({ price: { $ne: null } })).toEqual([ + { rules: [{ field: "price", operator: "exists", values: [] }] }, + ]); + }); + + test("item filters decode to item_* rules", () => { expect( - groupsToPlanFilter([ - { - rules: [{ field: "custom", operator: "is", values: ["true"] }], - }, - { rules: [{ field: "plan_id", operator: "is", values: [] }] }, - ]), - ).toEqual({ custom: true }); + planFilterToGroups({ item: { feature_id: "credits", unlimited: false } }), + ).toEqual([ + { + rules: [ + { field: "item_feature_id", operator: "is", values: ["credits"] }, + { field: "item_unlimited", operator: "is", values: ["false"] }, + ], + }, + ]); + }); + + test("combined filters share one group", () => { + const groups = planFilterToGroups({ + plan_id: { $regex: "^pro" }, + paid: true, + recurring: true, + }); + expect(groups).toHaveLength(1); + expect(groups[0].rules.map((rule) => rule.field)).toEqual([ + "plan_id", + "paid", + "recurring", + ]); + }); +}); + +describe("ruleToStringMatcher", () => { + test("is with one value returns a bare string", () => { + expect( + ruleToStringMatcher({ field: "plan_id", operator: "is", values: ["x"] }), + ).toBe("x"); + }); + + test("is with no values returns an empty string", () => { + expect( + ruleToStringMatcher({ field: "plan_id", operator: "is", values: [] }), + ).toBe(""); + }); + + test("is with multiple values widens to $in", () => { + expect( + ruleToStringMatcher({ + field: "plan_id", + operator: "is", + values: ["a", "b"], + }), + ).toEqual({ $in: ["a", "b"] }); + }); + + test("is_not returns $ne", () => { + expect( + ruleToStringMatcher({ + field: "plan_id", + operator: "is_not", + values: ["x"], + }), + ).toEqual({ $ne: "x" }); + }); + + test("in returns $in", () => { + expect( + ruleToStringMatcher({ + field: "plan_id", + operator: "in", + values: ["a", "b"], + }), + ).toEqual({ $in: ["a", "b"] }); + }); + + test("not_in returns $nin", () => { + expect( + ruleToStringMatcher({ + field: "plan_id", + operator: "not_in", + values: ["x"], + }), + ).toEqual({ $nin: ["x"] }); + }); + + test("regex returns $regex", () => { + expect( + ruleToStringMatcher({ + field: "plan_id", + operator: "regex", + values: ["^pro"], + }), + ).toEqual({ $regex: "^pro" }); + }); + + test("starts_with returns $startsWith", () => { + expect( + ruleToStringMatcher({ + field: "plan_id", + operator: "starts_with", + values: ["pro"], + }), + ).toEqual({ $startsWith: "pro" }); }); }); @@ -225,90 +266,3 @@ describe("customerIdToStrings -> stringsToCustomerId roundtrip", () => { expect(stringsToCustomerId(customerIdToStrings(undefined))).toBeUndefined(); }); }); - -describe("planFilterToGroups -> groupsToPlanFilter: version (NumberMatcher)", () => { - const roundtrip = (input: PlanFilter): PlanFilter => { - const groups = planFilterToGroups(input); - return groupsToPlanFilter(groups); - }; - - test("bare number roundtrips", () => { - expect(roundtrip({ version: 2 })).toEqual({ version: 2 }); - }); - - test("explicit null roundtrips", () => { - // Regression: previously the UI dropped `version: null` because - // ruleToNumberMatcher returned undefined for an empty values array. - expect(roundtrip({ version: null })).toEqual({ version: null }); - }); - - test("$eq number roundtrips (simplified to bare value)", () => { - expect(roundtrip({ version: { $eq: 3 } })).toEqual({ version: 3 }); - }); - - test("$ne number roundtrips", () => { - const input = { version: { $ne: 1 } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("$in roundtrips", () => { - const input = { version: { $in: [1, 2, 3] } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("$nin roundtrips", () => { - const input = { version: { $nin: [4, 5] } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("$gte roundtrips", () => { - const input = { version: { $gte: 2 } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("$lt roundtrips", () => { - const input = { version: { $lt: 5 } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("$gte + $lte (range) preserves BOTH constraints", () => { - // Regression: previously emitted only the first operator, silently - // broadening "versions 2–4" to "versions ≥ 2". - const input = { version: { $gte: 2, $lte: 4 } }; - expect(roundtrip(input)).toEqual(input); - }); - - test("$gt + $lt (open range) preserves BOTH constraints", () => { - const input = { version: { $gt: 1, $lt: 10 } }; - expect(roundtrip(input)).toEqual(input); - }); -}); - -describe("planFilterToGroups: version emits multiple rules for combined matchers", () => { - test("single operator → single rule", () => { - const groups = planFilterToGroups({ version: { $gte: 2 } }); - const versionRules = groups[0].rules.filter((r) => r.field === "version"); - expect(versionRules.length).toBe(1); - expect(versionRules[0]).toEqual({ - field: "version", - operator: "gte", - values: ["2"], - }); - }); - - test("$gte + $lte → two rules in the same group", () => { - const groups = planFilterToGroups({ version: { $gte: 2, $lte: 4 } }); - const versionRules = groups[0].rules.filter((r) => r.field === "version"); - expect(versionRules.length).toBe(2); - const operators = versionRules.map((r) => r.operator).sort(); - expect(operators).toEqual(["gte", "lte"]); - }); - - test("null → single empty `is` rule", () => { - const groups = planFilterToGroups({ version: null }); - const versionRules = groups[0].rules.filter((r) => r.field === "version"); - expect(versionRules).toEqual([ - { field: "version", operator: "is", values: [] }, - ]); - }); -});