diff --git a/.github/scripts/validate-should-fail.sh b/.github/scripts/validate-should-fail.sh new file mode 100755 index 000000000..c66b282b3 --- /dev/null +++ b/.github/scripts/validate-should-fail.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# Negative examples: documents that MUST be rejected by the schema. +# +# Each case is "|". We assert BEHAVIOUR (the +# document is rejected, and its output contains the declared substring) - not a +# specific constraint name, and each case carries its own clause. +# +# All cases share ONE schema and are validated in a SINGLE xmllint call (one +# compile for many files) to stay fast. Fails closed: accepted, wrong reason, or +# xmllint not running all count as failures. +# +# CI uses the vendored 2025 Linux x86-64 xmllint ("Temporary xmllint master", +# https://github.com/TransmodelEcosystem/NeTEx/pull/915); set XMLLINT_BIN to a local +# xmllint to run this on any other OS or CPU architecture (macOS, Windows, ARM, ...). + +set -u +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +ROOT_DIR=$( cd -- "${SCRIPT_DIR}/../.." &> /dev/null && pwd ) +XMLLINT="${XMLLINT_BIN:-${SCRIPT_DIR}/xmllint}" +cd "${ROOT_DIR}" + +SCHEMA="xsd/NeTEx_publication.xsd" + +# "|" +CASES=( + "examples/should-fail/duplicate-GroupOfLinkSequences.xml|Duplicate key-sequence" + "examples/should-fail/duplicate-CalendarDate.xml|Duplicate key-sequence" + "examples/should-fail/duplicate-ValidBetween.xml|Duplicate key-sequence" + "examples/should-fail/duplicate-ValidityPeriod.xml|Duplicate key-sequence" +) + +files=() +for c in "${CASES[@]}"; do files+=("${c%%|*}"); done +out=$("${XMLLINT}" --noout --schema "${SCHEMA}" "${files[@]}" 2>&1) + +fail=0 +echo "Checking NeTEx 'should-fail' negative examples ..." +for c in "${CASES[@]}"; do + f="${c%%|*}"; expected="${c#*|}" + if printf '%s\n' "${out}" | grep -Fqx "${f} validates"; then + echo "SHOULD HAVE FAILED ${f} — accepted (must be rejected)"; fail=1 + elif printf '%s\n' "${out}" | grep -F "${f}:" | grep -q "${expected}"; then + echo "OK ${f}" + else + echo "ERROR ${f} — rejected, but not matching '${expected}'"; fail=1 + fi +done + +exit "${fail}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abef8b4f9..8b05dcd9d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,9 @@ jobs: - name: Validate NeTEx XML examples run: ./.github/scripts/validate-examples.sh + - name: Validate negative examples (must-fail) + run: ./.github/scripts/validate-should-fail.sh + - name: Commit changes uses: EndBug/add-and-commit@v9 # https://github.com/marketplace/actions/add-commit with: diff --git a/examples/should-fail/README.md b/examples/should-fail/README.md new file mode 100644 index 000000000..35110f047 --- /dev/null +++ b/examples/should-fail/README.md @@ -0,0 +1,7 @@ +# Negative examples (must-fail) + +Each file carries one deliberate defect and must therefore be **rejected** by the +schema for a specific, expected reason. The cases — file, schema and expected +constraint — are declared in `.github/scripts/validate-should-fail.sh` (run in CI). + +Run locally: `XMLLINT_BIN=$(which xmllint) ./.github/scripts/validate-should-fail.sh` diff --git a/examples/should-fail/duplicate-CalendarDate.xml b/examples/should-fail/duplicate-CalendarDate.xml new file mode 100644 index 000000000..ac82ae569 --- /dev/null +++ b/examples/should-fail/duplicate-CalendarDate.xml @@ -0,0 +1,17 @@ + + + 2020-01-01T12:00:00Z + TEST + + + + + 2020-01-01 + + + 2020-01-01 + + + + + diff --git a/examples/should-fail/duplicate-GroupOfLinkSequences.xml b/examples/should-fail/duplicate-GroupOfLinkSequences.xml new file mode 100644 index 000000000..40bb4cb59 --- /dev/null +++ b/examples/should-fail/duplicate-GroupOfLinkSequences.xml @@ -0,0 +1,13 @@ + + + 2020-01-01T12:00:00Z + TEST + + + + + + + + + diff --git a/examples/should-fail/duplicate-ValidBetween.xml b/examples/should-fail/duplicate-ValidBetween.xml new file mode 100644 index 000000000..44eb0c8f2 --- /dev/null +++ b/examples/should-fail/duplicate-ValidBetween.xml @@ -0,0 +1,17 @@ + + + 2020-01-01T12:00:00Z + TEST + + + + 2020-01-01T00:00:00Z + + + + + 2020-01-01T00:00:00Z + + + + diff --git a/examples/should-fail/duplicate-ValidityPeriod.xml b/examples/should-fail/duplicate-ValidityPeriod.xml new file mode 100644 index 000000000..e7f49a0d9 --- /dev/null +++ b/examples/should-fail/duplicate-ValidityPeriod.xml @@ -0,0 +1,23 @@ + + + 2020-01-01T12:00:00Z + TEST + + + + + A + + 2020-01-01T00:00:00Z + + + + B + + 2020-01-01T00:00:00Z + + + + + + diff --git a/xsd/NeTEx_publication.xsd b/xsd/NeTEx_publication.xsd index d39608582..fe127fa5d 100644 --- a/xsd/NeTEx_publication.xsd +++ b/xsd/NeTEx_publication.xsd @@ -979,6 +979,15 @@ + + + + Every [GroupOfLinkSequences Id + Version] must be unique within document. + + + + + @@ -2191,6 +2200,15 @@ + + + + Each date is only allowed once per calendar. + + + + + @@ -2239,6 +2257,15 @@ + + + + Every [ValidityCondition Id + Version] must be unique within document. + + + + + @@ -6488,6 +6515,15 @@ + + + + Every [UsageValidityPeriod Id + Version] must be unique within document. + + + + +