From d2caf970fb00305cec06ba41cc3f805ed3b2e162 Mon Sep 17 00:00:00 2001 From: Helen Kwok <107752876+helenkwok@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:09:30 +1030 Subject: [PATCH] fix(ids): match patterns against the whole value and apply every length limit --- .../IDSSpecifications/src/facets/Facet.ts | 18 ++++--- .../src/facets/facet.test.ts | 48 +++++++++++++++++++ 2 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 packages/core/src/openbim/IDSSpecifications/src/facets/facet.test.ts diff --git a/packages/core/src/openbim/IDSSpecifications/src/facets/Facet.ts b/packages/core/src/openbim/IDSSpecifications/src/facets/Facet.ts index c4b7f43d6..bb0d1af8a 100644 --- a/packages/core/src/openbim/IDSSpecifications/src/facets/Facet.ts +++ b/packages/core/src/openbim/IDSSpecifications/src/facets/Facet.ts @@ -62,21 +62,19 @@ export abstract class IDSFacet { } if (facetParameter.type === "pattern") { - const regex = new RegExp(facetParameter.parameter); + // XSD patterns match the whole value, not a substring. + const regex = new RegExp(`^(?:${facetParameter.parameter})$`); pass = regex.test(String(value)); } if (facetParameter.type === "length") { + // Every given limit must hold, not just the last one checked. const { min, length, max } = facetParameter.parameter; - if (length !== undefined) { - pass = String(value).length === length; - } - if (min !== undefined) { - pass = String(value).length >= min; - } - if (max !== undefined) { - pass = String(value).length <= max; - } + const valueLength = String(value).length; + pass = + (length === undefined || valueLength === length) && + (min === undefined || valueLength >= min) && + (max === undefined || valueLength <= max); } if (facetParameter.type === "bounds" && typeof value === "number") { diff --git a/packages/core/src/openbim/IDSSpecifications/src/facets/facet.test.ts b/packages/core/src/openbim/IDSSpecifications/src/facets/facet.test.ts new file mode 100644 index 000000000..7cc2840c5 --- /dev/null +++ b/packages/core/src/openbim/IDSSpecifications/src/facets/facet.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, it } from "vitest"; +import { IDSFacet } from "./Facet"; +import type { IDSFacetParameter } from "../types"; + +// evalRequirement is shared by every facet; a minimal facet exposes it. +class TestFacet extends IDSFacet { + facetType = "Attribute" as const; + async getEntities() {} + async test() {} + serialize() { + return ""; + } + matches( + value: string | number | boolean | null, + parameter: IDSFacetParameter, + ) { + return this.evalRequirement(value, parameter, "Value"); + } +} + +const facet = new TestFacet(undefined as never); +const pattern = (parameter: string): IDSFacetParameter => ({ + type: "pattern", + parameter, +}); +const length = (parameter: { + min?: number; + length?: number; + max?: number; +}): IDSFacetParameter => ({ type: "length", parameter }) as IDSFacetParameter; + +describe("IDSFacet.evalRequirement", () => { + it("matches a pattern against the whole value, as XSD does", () => { + expect(facet.matches("EW-01", pattern("EW-[0-9]{2}"))).toBe(true); + expect(facet.matches("XEW-01", pattern("EW-[0-9]{2}"))).toBe(false); + expect(facet.matches("EW-012", pattern("EW-[0-9]{2}"))).toBe(false); + expect(facet.matches("B", pattern("A|B"))).toBe(true); + expect(facet.matches("AB", pattern("A|B"))).toBe(false); + }); + + it("applies every length limit, not just the last one", () => { + expect(facet.matches("abc", length({ min: 2, max: 4 }))).toBe(true); + expect(facet.matches("a", length({ min: 2, max: 4 }))).toBe(false); + expect(facet.matches("abcde", length({ min: 2, max: 4 }))).toBe(false); + expect(facet.matches("abcd", length({ length: 3, max: 10 }))).toBe(false); + expect(facet.matches("abc", length({ length: 3 }))).toBe(true); + }); +});