+
+
Siemens API Linter Report
+
+
+
+
+
+
+
Total Rules Count: {{ this.enabledCount }}
+
+
+
+
+
+
+ Severity
+ Problems
+
+
+
+
+ Errors
+ {{ this.errorCount }}
+
+
+ Warnings
+ {{ this.warnCount }}
+
+
+ Info
+ {{ this.infoCount }}
+
+
+ Hints
+ {{ this.hintCount }}
+
+
+
+
+
+
+
+
+
+
+
Detailed report
+
+
+
+
+
+
+ {{#each this.data}}
+
+
+ {{#if hasDetails}}
+
+
+
+
+ {{/if}}
+
+
+ {{ ruleName }}
+ {{ ruleDescription }}
+ {{#if hasDetails}}
+ {{ message }}
+ {{else}}
+ {{ message }}
+ {{/if}}
+
+
+
+ {{#if hasDetails}}
+
+
+
+ #
+ Line
+ Severity
+ Path
+ message
+
+
+
+ {{#each details}}
+
+ {{inc @index}}
+ {{ line }}
+ {{ severity }}
+ {{ path }}
+ {{ message }}
+
+ {{/each}}
+
+
+ {{/if}}
+
+
+
+ {{/each}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api-linter/test/api-commonops.test.js b/api-linter/test/api-commonops.test.js
new file mode 100644
index 0000000..2027278
--- /dev/null
+++ b/api-linter/test/api-commonops.test.js
@@ -0,0 +1,131 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const { retrieveDocument, setupSpectral } = require('@jamietanna/spectral-test-harness');
+const {
+ resultsForSeverity,
+ getSpecFilePath,
+ assertOnlyErrors,
+ assertOnlyInfos
+} = require('./base.js');
+
+const path = 'commonops/';
+const rulesetPath = 'test/testdata/commonops/.spectral.yml';
+
+test('A Successful fetch of individual resources MUST return the status code 200', async () => {
+ const specFilePath = getSpecFilePath(path, '800-1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[800.1]');
+ //Problem in line:29
+ assertOnlyErrors(results);
+ });
+});
+
+test('A successful fetch of resource collection request MUST return a 2xx status code', async () => {
+ const specFilePath = getSpecFilePath(path, '800-2.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[800.2]');
+ //Problem in line:37
+ assertOnlyErrors(results);
+ });
+});
+
+test('An API Provider MUST support fetching resource data for provided links', async () => {
+ const specFilePath = getSpecFilePath(path, '800-4.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[800.4]');
+ //Problem in line:50
+ assertOnlyErrors(results);
+ });
+});
+
+test('The POST request MUST include a single resource object as primary data', async () => {
+ const specFilePath = getSpecFilePath(path, '801.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[801]');
+ //Problem in line:45-47
+ assertOnlyErrors(results);
+ });
+});
+
+test('API MUST respond to successful POST creation request with 2xx success status code', async () => {
+ const specFilePath = getSpecFilePath(path, '801-4.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[801.4]');
+ //Problem in line:34
+ assertOnlyErrors(results);
+ });
+});
+
+test('API MUST respond to successful PATCH update request with 2xx success status code', async () => {
+ const specFilePath = getSpecFilePath(path, '802-6.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[802.6]');
+ //Problem in line:42
+ assertOnlyErrors(results);
+ });
+});
+
+test('API MUST respond to successful PUT update request with 2xx success status code', async () => {
+ const specFilePath = getSpecFilePath(path, '803-2.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[803.2]');
+ //Problem in line:42
+ assertOnlyErrors(results);
+ });
+});
+
+test('API MUST respond to successful DELETE request with 2xx success status code', async () => {
+ const specFilePath = getSpecFilePath(path, '804-1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[804.1]');
+ //Problem in line:37
+ assertOnlyErrors(results);
+ });
+});
+
+test('The API consumer MAY use conditional header information to solve concurrent update requests', async () => {
+ const specFilePath = getSpecFilePath(path, '803-6.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[803.6]');
+ //Problem in line:37-40
+ assertOnlyInfos(results);
+ });
+});
diff --git a/api-linter/test/api-errors-refs.test.js b/api-linter/test/api-errors-refs.test.js
new file mode 100644
index 0000000..533b04a
--- /dev/null
+++ b/api-linter/test/api-errors-refs.test.js
@@ -0,0 +1,41 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const fs = require('fs');
+const { Document } = require('@stoplight/spectral-core');
+const Parsers = require('@stoplight/spectral-parsers');
+
+// 👇 RE-ADD setupSpectral here
+const { setupSpectral } = require('@jamietanna/spectral-test-harness');
+
+const { resultsForSeverity } = require('./base.js');
+const { resolver } = require('@stoplight/spectral-ref-resolver');
+const path = require('path');
+
+const rulesetPath = 'test/testdata/refs/.spectral.yml';
+const specFilePath = path.join(__dirname, 'testdata/refs', '300.yml'); // ABSOLUTE
+
+test('API MUST use official HTTP status codes as intended', async () => {
+ // ───────────── arrange ─────────────
+ /* ── Spectral with resolver ── */
+ const spectral = await setupSpectral(rulesetPath, { resolver });
+
+ /* ── Document with correct source (real path!) ── */
+ const raw = fs.readFileSync(specFilePath, 'utf8');
+ const document = new Document(raw, Parsers.Yaml, specFilePath);
+ // … but give it the real filename so $ref resolution works
+ document.source = specFilePath;
+
+ // ───────────── act ─────────────
+ const results = await spectral.run(document, { resolve: { external: true } });
+
+ // ───────────── assert ─────────────
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[300]');
+ //Problem in line:35-40
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[301]');
+});
diff --git a/api-linter/test/api-errors.test.js b/api-linter/test/api-errors.test.js
new file mode 100644
index 0000000..49954b6
--- /dev/null
+++ b/api-linter/test/api-errors.test.js
@@ -0,0 +1,95 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const { retrieveDocument, setupSpectral } = require('@jamietanna/spectral-test-harness');
+const {
+ resultsForSeverity,
+ getSpecFilePath,
+ assertOnlyErrors,
+ assertOnlyWarnings,
+ assertOnlyInfos
+} = require('./base.js');
+
+const path = 'error/';
+const rulesetPath = 'test/testdata/error/.spectral.yml';
+
+test('API MUST use official HTTP status codes as intended', async () => {
+ const specFilePath = getSpecFilePath(path, '300.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[300]');
+ //Problem in line:35-40
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[301]');
+ });
+});
+
+test('SHOULD only use most common HTTP status codes', async () => {
+ const specFilePath = getSpecFilePath(path, '301.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ //Problem in line:35-40
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[301]');
+ assertOnlyWarnings(results);
+ });
+});
+
+test('SHOULD use the most specific HTTP status code when returning errors', async () => {
+ const specFilePath = getSpecFilePath(path, '302.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ //Problem in line:130
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[302]');
+ assertOnlyWarnings(results);
+ });
+});
+
+test('Error object SHOULD be represented according to the defined structure fields', async () => {
+ const specFilePath = getSpecFilePath(path, '305.1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ //Problem in line:110
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[305]-1');
+ assertOnlyErrors(results);
+ });
+});
+
+test('The error links resource object MAY contain the members: about, type', async () => {
+ const specFilePath = getSpecFilePath(path, '305.2.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ //Problem in line:114-117
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[305]-2');
+ assertOnlyInfos(results);
+ });
+});
+
+test('The source object SHOULD include one of the members or be omitted: pointer, parameter, header', async () => {
+ const specFilePath = getSpecFilePath(path, '305.3.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ //Problem in line:114-117
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[305]-3');
+ assertOnlyWarnings(results);
+ });
+});
diff --git a/api-linter/test/api-filtering.test.js b/api-linter/test/api-filtering.test.js
new file mode 100644
index 0000000..ac4d027
--- /dev/null
+++ b/api-linter/test/api-filtering.test.js
@@ -0,0 +1,39 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const { retrieveDocument, setupSpectral } = require('@jamietanna/spectral-test-harness');
+const {
+ resultsForSeverity,
+ getSpecFilePath,
+ assertOnlyInfos,
+ assertOnlyHints
+} = require('./base.js');
+
+const path = 'filtering/';
+const rulesetPath = 'test/testdata/filtering/.spectral.yml';
+
+test('An API provider MAY NOT support more than 4 parameters at once for selecting resources', async () => {
+ const specFilePath = getSpecFilePath(path, '400.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const hints = resultsForSeverity(results, 'Hint');
+ expect(hints).toHaveLength(2);
+ expect(hints[0].code).toBe('Siemens-API-[400]');
+ expect(hints[1].code).toBe('Siemens-API-[401]');
+ //Problem in line:29-62
+ assertOnlyHints(results);
+ });
+});
+
+test('The query parameter filter SHOULD be used to filter or query data', async () => {
+ const specFilePath = getSpecFilePath(path, '401.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(0);
+ assertOnlyInfos(results);
+ });
+});
diff --git a/api-linter/test/api-linter-cli.test.js b/api-linter/test/api-linter-cli.test.js
new file mode 100644
index 0000000..7666569
--- /dev/null
+++ b/api-linter/test/api-linter-cli.test.js
@@ -0,0 +1,68 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const path = require('path');
+const fs = require('fs');
+const { spawnSync } = require('child_process');
+
+/* ─────────── paths used by both tests ─────────── */
+const cli = path.join(__dirname, '..', 'src', 'index.js');
+const ruleset = path.join(__dirname, 'testdata', 'refs', '.spectral.yml');
+const spec = path.join(__dirname, 'testdata', 'refs', '300.yml');
+
+/* handy wrapper to invoke the CLI */
+function runLinter({ resolveFlag, reportFile }) {
+ const args = [
+ cli,
+ '--specPath',
+ spec,
+ '--rulesetPath',
+ ruleset,
+ '--failSeverity',
+ 'error',
+ '--outputFilename',
+ reportFile
+ ];
+ if (resolveFlag) args.push('--resolve');
+
+ return spawnSync('node', args, { encoding: 'utf8' });
+}
+
+describe('api-linter CLI external $ref behaviour', () => {
+ const reportWith = path.join(__dirname, 'tmp-report-resolve.html');
+ const reportWithout = path.join(__dirname, 'tmp-report-no-resolve.html');
+
+ /* clean-up generated HTML files after the suite finished */
+ afterAll(() => {
+ [reportWith, reportWithout].forEach(f => {
+ if (fs.existsSync(f)) fs.unlinkSync(f);
+ });
+ });
+
+ test('with --resolve follows external refs (no invalid-ref)', () => {
+ const { status, stdout } = runLinter({
+ resolveFlag: true,
+ reportFile: reportWith
+ });
+
+ /* exit-code still 1 because the real rule fires */
+ expect(status).toBe(1);
+ expect(stdout).toMatch(/Siemens-API-\[300]/); // rule present
+ expect(stdout).not.toMatch(/invalid-ref/); // resolver worked
+ expect(fs.existsSync(reportWith)).toBe(true); // report created
+ });
+
+ test('without --resolve produces invalid-ref error', () => {
+ const { status, stdout } = runLinter({
+ resolveFlag: false,
+ reportFile: reportWithout
+ });
+
+ expect(status).toBe(1);
+ expect(stdout).toMatch(/Siemens-API-\[300]/); // rule present
+ expect(stdout).toMatch(/Siemens-API-\[301]/); // rule present
+ expect(stdout).not.toMatch(/invalid-ref/); // Spectral never tried to search for external references
+ expect(fs.existsSync(reportWithout)).toBe(true);
+ });
+});
diff --git a/api-linter/test/api-mediatype.test.js b/api-linter/test/api-mediatype.test.js
new file mode 100644
index 0000000..66b7801
--- /dev/null
+++ b/api-linter/test/api-mediatype.test.js
@@ -0,0 +1,121 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const { retrieveDocument, setupSpectral } = require('@jamietanna/spectral-test-harness');
+const {
+ resultsForSeverity,
+ getSpecFilePath,
+ assertOnlyErrors,
+ assertOnlyWarnings,
+ assertOnlyInfos
+} = require('./base.js');
+
+const path = 'mediatype/';
+const rulesetPath = 'test/testdata/mediatype/.spectral.yml';
+
+test('Siemens Xcelerator recommends the media type application/json for exchanging data.', async () => {
+ const specFilePath = getSpecFilePath(path, '100.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[100]');
+ //Problem in line:32
+ assertOnlyInfos(results);
+ });
+});
+
+test('The root of every document SHOULD contain a JSON object', async () => {
+ const specFilePath = getSpecFilePath(path, '101-1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[101.1]');
+ //Problem in line:38
+ assertOnlyWarnings(results);
+ });
+});
+
+test('A response document SHOULD contain at least one of the following:data,error,meta,links.', async () => {
+ const specFilePath = getSpecFilePath(path, '101-2.1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[101.2]-1');
+ //Problem in line:40-42, properties should contain at least one of data,error,meta,links
+ assertOnlyWarnings(results);
+ });
+});
+
+test('A request document SHOULD contain at least data', async () => {
+ const specFilePath = getSpecFilePath(path, '101-2.2.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[101.2]-2');
+ //Problem in line:45-47, properties should contain at least data
+ assertOnlyWarnings(results);
+ });
+});
+
+test('The data type MUST be compliant with the types defined', async () => {
+ const specFilePath = getSpecFilePath(path, '101-4-1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[101.4.1]');
+ //Problem in line:87, should be one of boolean|object|array|integer|number|string
+ assertOnlyErrors(results);
+ });
+});
+
+test('A meta object SHOULD be used to represent meta information as a JSON object', async () => {
+ const specFilePath = getSpecFilePath(path, '101-7-2.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[101.7.2]');
+ //Problem in line:77, should be type: object
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[101.7]');
+ });
+});
+
+test('Field names SHOULD use lower camel case', async () => {
+ const specFilePath = getSpecFilePath(path, '101-8.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[101.8]');
+ //Problem in line:68, should be addressDetail
+ assertOnlyWarnings(results);
+ });
+});
+
+test('Resource names SHOULD be in lowercase with hyphen', async () => {
+ const specFilePath = getSpecFilePath(path, '101-9.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[101.9]');
+ //Problem in line:20, should be room-details
+ assertOnlyWarnings(results);
+ });
+});
diff --git a/api-linter/test/api-pagination.test.js b/api-linter/test/api-pagination.test.js
new file mode 100644
index 0000000..38863cc
--- /dev/null
+++ b/api-linter/test/api-pagination.test.js
@@ -0,0 +1,111 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const { retrieveDocument, setupSpectral } = require('@jamietanna/spectral-test-harness');
+const {
+ resultsForSeverity,
+ getSpecFilePath,
+ assertOnlyInfos,
+ assertOnlyHints
+} = require('./base.js');
+
+const path = 'pagination/';
+const rulesetPath = 'test/testdata/pagination/.spectral.yml';
+
+test('A server MAY provide links to traverse a paginated data set', async () => {
+ const specFilePath = getSpecFilePath(path, '600.1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[600]-1');
+ assertOnlyInfos(results);
+ });
+});
+
+test('Pagination links SHOULD appear in the top-level links object.', async () => {
+ const specFilePath = getSpecFilePath(path, '600.2.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[600]-2');
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[600]-1');
+ });
+});
+
+test('Naming convention MUST be used for pagination keys.', async () => {
+ const specFilePath = getSpecFilePath(path, '600.3.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const hints = resultsForSeverity(results, 'Hint');
+ expect(hints).toHaveLength(1);
+ expect(hints[0].code).toBe('Siemens-API-[600]-3');
+ assertOnlyHints(results);
+ });
+});
+
+test('Pagination SHOULD be implemented using query parameters.', async () => {
+ const specFilePath = getSpecFilePath(path, '601.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const warnings = resultsForSeverity(results, 'Warning');
+ expect(warnings).toHaveLength(1);
+ expect(warnings[0].code).toBe('Siemens-API-[601]');
+ });
+});
+
+test('Server MAY provide pagination meta information to the client [601.1.1].', async () => {
+ const specFilePath = getSpecFilePath(path, '601-1-1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[601.1.1]');
+ assertOnlyInfos(results);
+ });
+});
+
+test('Server MAY provide pagination meta information to the client [601.2.1].', async () => {
+ const specFilePath = getSpecFilePath(path, '601-2-1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[601.2.1]');
+ assertOnlyInfos(results);
+ });
+});
+
+test('Server MAY provide pagination meta information to the client [601.3.1].', async () => {
+ const specFilePath = getSpecFilePath(path, '601-3-1.1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[601.3.1]-1');
+ assertOnlyInfos(results);
+ });
+});
+
+test('Server MAY provide pagination meta information to the client [601.3.1].', async () => {
+ const specFilePath = getSpecFilePath(path, '601-3-1.2.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[601.3.1]-2');
+ assertOnlyInfos(results);
+ });
+});
diff --git a/api-linter/test/api-security.test.js b/api-linter/test/api-security.test.js
new file mode 100644
index 0000000..04021f9
--- /dev/null
+++ b/api-linter/test/api-security.test.js
@@ -0,0 +1,53 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const { retrieveDocument, setupSpectral } = require('@jamietanna/spectral-test-harness');
+const { resultsForSeverity, getSpecFilePath, assertOnlyErrors } = require('./base.js');
+
+const path = 'security/';
+const rulesetPath = 'test/testdata/security/.spectral.yml';
+
+test('No security problem test 1', async () => {
+ global.apiSecurity = 'y';
+ const specFilePath = getSpecFilePath(path, 'valid.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ expect(results).toHaveLength(0);
+ });
+});
+
+test('No security problem test 2', async () => {
+ global.apiSecurity = 'n';
+ const specFilePath = getSpecFilePath(path, 'invalid.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ expect(results).toHaveLength(0);
+ });
+});
+
+test('No security problem test 3', async () => {
+ global.apiSecurity = 'n';
+ const specFilePath = getSpecFilePath(path, 'invalid.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ expect(results).toHaveLength(0);
+ });
+});
+
+test('Has security problem test', async () => {
+ global.apiSecurity = 'y';
+ const specFilePath = getSpecFilePath(path, 'invalid.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ expect(results).toHaveLength(1);
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-Security');
+ assertOnlyErrors(results);
+ });
+});
diff --git a/api-linter/test/api-sparse-fieldsets.test.js b/api-linter/test/api-sparse-fieldsets.test.js
new file mode 100644
index 0000000..889dac4
--- /dev/null
+++ b/api-linter/test/api-sparse-fieldsets.test.js
@@ -0,0 +1,21 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const { retrieveDocument, setupSpectral } = require('@jamietanna/spectral-test-harness');
+const { resultsForSeverity, getSpecFilePath, assertOnlyInfos } = require('./base.js');
+
+const path = 'fields/';
+const rulesetPath = 'test/testdata/fields/.spectral.yml';
+
+test('API MAY provide projection of fieldset in response', async () => {
+ const specFilePath = getSpecFilePath(path, '500.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const infos = resultsForSeverity(results, 'Information');
+ expect(infos).toHaveLength(1);
+ expect(infos[0].code).toBe('Siemens-API-[500]');
+ assertOnlyInfos(results);
+ });
+});
diff --git a/api-linter/test/api-versioning.test.js b/api-linter/test/api-versioning.test.js
new file mode 100644
index 0000000..e442f25
--- /dev/null
+++ b/api-linter/test/api-versioning.test.js
@@ -0,0 +1,70 @@
+/**
+ * Copyright Siemens 2026.
+ * SPDX-License-Identifier: MIT
+ */
+const { retrieveDocument, setupSpectral } = require('@jamietanna/spectral-test-harness');
+const {
+ resultsForSeverity,
+ getSpecFilePath,
+ assertOnlyErrors,
+ assertOnlyWarnings
+} = require('./base.js');
+
+const path = 'versioning/';
+const rulesetPath = 'test/testdata/versioning/.spectral.yml';
+
+test('Semantic versioning MUST be used to version individual APIs', async () => {
+ global.apiVersioning = 'url';
+ const specFilePath = getSpecFilePath(path, 'semantic-versioning.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Semantic-Versioning-[2.0.0]');
+ //Problem in line:35-40
+ assertOnlyErrors(results);
+ });
+});
+
+test('The Major version number MUST be specified in the URI as a path segment', async () => {
+ global.apiVersioning = 'url';
+ const specFilePath = getSpecFilePath(path, '200-1.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[200.1]');
+ //Problem in line:13
+ assertOnlyErrors(results);
+ });
+});
+
+test('A request HTTP header e.g., Api-Version MUST be supported to allow client to specify the version.', async () => {
+ global.apiVersioning = 'header';
+ const specFilePath = getSpecFilePath(path, '200-2.yml');
+ const spectral = await setupSpectral(rulesetPath);
+ const document = retrieveDocument(specFilePath);
+ spectral.run(document).then(results => {
+ const errors = resultsForSeverity(results, 'Error');
+ expect(errors).toHaveLength(1);
+ expect(errors[0].code).toBe('Siemens-API-[200.2]');
+ //Problem in line:29
+ assertOnlyErrors(results);
+ });
+});
+
+test("A header with full semantic version value SHOULD be returned in the response with 'Api-Version: