diff --git a/.github/workflows/end2end.yaml b/.github/workflows/end2end.yaml index a679727446..4140afecd9 100644 --- a/.github/workflows/end2end.yaml +++ b/.github/workflows/end2end.yaml @@ -356,6 +356,9 @@ jobs: - name: Check for unused step definitions working-directory: tests/functional run: yarn unused-steps + - name: Check that all scenarios have a version tag + working-directory: tests/functional + run: yarn ensure-version-tags end2end-pra: needs: [build-kafka, build-mongodb] diff --git a/tests/functional/ctst/common/hooks.ts b/tests/functional/ctst/common/hooks.ts index ca266e2ca1..9f195b56c7 100644 --- a/tests/functional/ctst/common/hooks.ts +++ b/tests/functional/ctst/common/hooks.ts @@ -23,7 +23,6 @@ import { startDLQConsumer, stopDLQConsumer } from 'steps/utils/kafka'; import 'cli-testing/hooks/KeycloakSetup'; import 'cli-testing/hooks/Logger'; -import 'cli-testing/hooks/versionTags'; // HTTPS should not cause any error for CTST process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; diff --git a/tests/functional/ctst/scripts/ensureVersionTags.ts b/tests/functional/ctst/scripts/ensureVersionTags.ts new file mode 100644 index 0000000000..3be3382a52 --- /dev/null +++ b/tests/functional/ctst/scripts/ensureVersionTags.ts @@ -0,0 +1,75 @@ +/** + * Verifies that every Scenario in the ctst feature files + * has a semver version tag (e.g. @2.6.0), either on the Feature, + * an enclosing Rule, or directly on the scenario. + * + * Exits with code 1 and lists offenders if any are missing. + */ + +/* eslint-disable no-console */ + +import { Parser, AstBuilder, GherkinClassicTokenMatcher } from '@cucumber/gherkin'; +import { IdGenerator } from '@cucumber/messages'; +import * as fs from 'fs'; +import * as path from 'path'; + +const VERSION_TAG_REGEX = /^@\d+\.\d+\.\d+$/; +const FEATURES_DIR = path.resolve(__dirname, '../features'); + +function collectFeatureFiles(dir: string): string[] { + const files: string[] = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) { + files.push(...collectFeatureFiles(full)); + } else if (entry.name.endsWith('.feature')) { + files.push(full); + } + } + return files; +} + +let totalScenarios = 0; +const offenders: string[] = []; +const parser = new Parser(new AstBuilder(IdGenerator.incrementing()), new GherkinClassicTokenMatcher()); + +for (const file of collectFeatureFiles(FEATURES_DIR)) { + const rel = path.relative(FEATURES_DIR, file); + const ast = parser.parse(fs.readFileSync(file, 'utf8')); + const feature = ast.feature; + if (!feature) { continue; } + + const featureHasVersion = feature.tags.some(t => VERSION_TAG_REGEX.test(t.name)); + + for (const child of feature.children) { + if (child.scenario) { + totalScenarios++; + const hasVersion = featureHasVersion || child.scenario.tags.some(t => VERSION_TAG_REGEX.test(t.name)); + if (!hasVersion) { + offenders.push(` ${rel}:${child.scenario.location.line} — "${child.scenario.name}"`); + } + } else if (child.rule) { + // Version tag can be inherited from Feature or Rule + const ruleHasVersion = child.rule.tags.some(t => VERSION_TAG_REGEX.test(t.name)); + for (const ruleChild of child.rule.children) { + if (!ruleChild.scenario) { continue; } // skip Background inside Rule + totalScenarios++; + const hasVersion = featureHasVersion || ruleHasVersion + || ruleChild.scenario.tags.some(t => VERSION_TAG_REGEX.test(t.name)); + if (!hasVersion) { + offenders.push(` ${rel}:${ruleChild.scenario.location.line} — "${ruleChild.scenario.name}"`); + } + } + } + // Background nodes are intentionally ignored + } +} + +if (offenders.length > 0) { + console.error(`\n${offenders.length} scenario(s) missing a version tag (@X.Y.Z):\n`); + offenders.forEach(o => console.error(o)); + console.error('\nAdd a version tag (e.g. @2.15.0) to each scenario or its parent Feature.\n'); + process.exit(1); +} + +console.log(`All ${totalScenarios} scenarios have a version tag.`); diff --git a/tests/functional/package.json b/tests/functional/package.json index 51f4e05f83..9b6fcdb33f 100644 --- a/tests/functional/package.json +++ b/tests/functional/package.json @@ -7,7 +7,7 @@ "node": ">=24" }, "dependencies": { - "@cucumber/cucumber": "^12.7.0", + "@cucumber/cucumber": "^12.9.0", "@kubernetes/client-node": "^1.4.0", "@platformatic/kafka": "^1.30.0", "@smithy/node-http-handler": "^4.0.0", @@ -33,6 +33,8 @@ "@aws-sdk/client-iam": "^3.901.0", "@aws-sdk/client-s3": "^3.931.0", "@aws-sdk/client-sts": "^3.901.0", + "@cucumber/gherkin": "^38.0.0", + "@cucumber/messages": "^32.3.1", "@eslint/compat": "^1.1.1", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.9.1", @@ -50,6 +52,7 @@ "scripts": { "build:cucumber": "tsc --build tsconfig.json", "unused-steps": "! yarn cucumber-js --config ctst/cucumber.config.cjs --dry-run --format usage 2>&1 | grep UNUSED", + "ensure-version-tags": "ts-node ctst/scripts/ensureVersionTags.ts", "test:aws_crr": "mocha --config mocha/.mocharc.js -t 10000 --reporter-options configFile=mocha/mocha-reporter.json,cmrOutput=mocha-junit-reporter+testsuitesTitle+test_aws_crr mocha/backbeat/tests/crr/awsBackend.js", "test:azure_crr": "mocha --config mocha/.mocharc.js -t 10000 --reporter-options configFile=mocha/mocha-reporter.json,cmrOutput=mocha-junit-reporter+testsuitesTitle+test_azure_crr mocha/backbeat/tests/crr/azureBackend.js", "test:gcp_crr": "mocha --config mocha/.mocharc.js -t 10000 --reporter-options configFile=mocha/mocha-reporter.json,cmrOutput=mocha-junit-reporter+testsuitesTitle+test_gcp_crr mocha/backbeat/tests/crr/gcpBackend.js", diff --git a/tests/functional/yarn.lock b/tests/functional/yarn.lock index 8b6bb4b338..7d191cc699 100644 --- a/tests/functional/yarn.lock +++ b/tests/functional/yarn.lock @@ -871,20 +871,20 @@ dependencies: regexp-match-indices "1.0.2" -"@cucumber/cucumber@^12.7.0": - version "12.8.1" - resolved "https://registry.yarnpkg.com/@cucumber/cucumber/-/cucumber-12.8.1.tgz#7873a1f7f60141d6077755bbbdc819e739fa6d4a" - integrity sha512-hCXxiStjbZsRVZlV+CMywkqBtJ6RZTQeXSBZGPHm1YoIOI6YB8pCo0KlnJMmxfKfoeUKagtQMNPnpJBXwhkUjQ== +"@cucumber/cucumber@^12.9.0": + version "12.9.0" + resolved "https://registry.yarnpkg.com/@cucumber/cucumber/-/cucumber-12.9.0.tgz#23ec33efd561e34a4355320cb8abc8ec41824ea0" + integrity sha512-QbgEo/DcKFMRGL+yULh8Kw6peEfdPJjhYjpKp0dYc+6Dv1Bmp6hvxIdTi2CIinYBCXhvCZzNO1Ct/n6Dk1yAtA== dependencies: "@cucumber/ci-environment" "13.0.0" "@cucumber/cucumber-expressions" "19.0.0" "@cucumber/gherkin" "38.0.0" "@cucumber/gherkin-streams" "6.0.0" "@cucumber/gherkin-utils" "11.0.0" - "@cucumber/html-formatter" "23.0.0" + "@cucumber/html-formatter" "23.1.0" "@cucumber/junit-xml-formatter" "0.13.3" "@cucumber/message-streams" "4.1.1" - "@cucumber/messages" "32.2.0" + "@cucumber/messages" "32.3.1" "@cucumber/pretty-formatter" "1.0.1" "@cucumber/tag-expressions" "9.1.0" assertion-error-formatter "^3.0.0" @@ -942,10 +942,10 @@ dependencies: "@cucumber/messages" ">=31.0.0 <33" -"@cucumber/html-formatter@23.0.0": - version "23.0.0" - resolved "https://registry.yarnpkg.com/@cucumber/html-formatter/-/html-formatter-23.0.0.tgz#066f548f55274b58b67b4930836bd73579a9bf07" - integrity sha512-WwcRzdM8Ixy4e53j+Frm3fKM5rNuIyWUfy4HajEN+Xk/YcjA6yW0ACGTFDReB++VDZz/iUtwYdTlPRY36NbqJg== +"@cucumber/html-formatter@23.1.0": + version "23.1.0" + resolved "https://registry.yarnpkg.com/@cucumber/html-formatter/-/html-formatter-23.1.0.tgz#6b9f759f9d50355b0cb28edde3ad3580d91f7081" + integrity sha512-DcCSFoGs6jbwzXPgX1CwgJKEE+ZMcIEzq/0Memg0o24maNn9NJizBFHmoFWG4iv/OxHza+mvc+56cTHetfHndw== "@cucumber/junit-xml-formatter@0.13.3": version "0.13.3" @@ -964,15 +964,7 @@ dependencies: mime "^3.0.0" -"@cucumber/messages@32.2.0": - version "32.2.0" - resolved "https://registry.yarnpkg.com/@cucumber/messages/-/messages-32.2.0.tgz#a6cff1646366af60e0202e934d6f43f8ccce877f" - integrity sha512-oYp1dgL2TByYWL51Z+rNm+/mFtJhiPU9WS03goes9EALb8d9GFcXRbG1JluFLFaChF1YDqIzLac0kkC3tv1DjQ== - dependencies: - class-transformer "0.5.1" - reflect-metadata "0.2.2" - -"@cucumber/messages@>=31.0.0 <33", "@cucumber/messages@^32.0.0": +"@cucumber/messages@32.3.1", "@cucumber/messages@>=31.0.0 <33", "@cucumber/messages@^32.0.0", "@cucumber/messages@^32.3.1": version "32.3.1" resolved "https://registry.yarnpkg.com/@cucumber/messages/-/messages-32.3.1.tgz#8c6990554c35fb9bcff0b7f09fe52ddfd479dbce" integrity sha512-yNQq1KoXRYaEKrWMFmpUQX7TdeQuU9jeGgJAZ3dArTsC/T4NpJ6DnqaJIIgwPnz/wtQIQTNX7/h0rOuF5xY4qQ==