From 699c174ae3aa2fe73e7dddef7987f9b8af37d404 Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Thu, 20 Aug 2026 17:38:31 +0200 Subject: [PATCH 1/3] fix(aunit): preserve coverage measurement links --- packages/adt-contracts/tests/contracts/aunit.test.ts | 11 +++++++++++ .../src/fixtures/aunit/run-result-coverage-link.xml | 4 ++++ packages/adt-fixtures/src/fixtures/registry.ts | 1 + packages/adt-schemas/.xsd/custom/aunitResult.xsd | 4 ++++ .../schemas/generated/schemas/custom/aunitResult.ts | 11 +++++++++++ .../generated/types/custom/aunitResult.types.ts | 10 ++++++++++ 6 files changed, 41 insertions(+) create mode 100644 packages/adt-fixtures/src/fixtures/aunit/run-result-coverage-link.xml diff --git a/packages/adt-contracts/tests/contracts/aunit.test.ts b/packages/adt-contracts/tests/contracts/aunit.test.ts index 4a400b888..329c6c3d4 100644 --- a/packages/adt-contracts/tests/contracts/aunit.test.ts +++ b/packages/adt-contracts/tests/contracts/aunit.test.ts @@ -9,11 +9,13 @@ */ import { fixtures } from '@abapify/adt-fixtures'; +import { expect, it } from 'vitest'; import { aunitRun, aunitResult } from '../../src/schemas'; import { ContractScenario, runScenario, type ContractOperation } from './base'; // Import contracts import { aunitContract } from '../../src/adt/aunit'; +import { extractCoverageMeasurementId } from '../../src/adt/aunit/coverage-link'; // ============================================================================= // Contract Definition Tests @@ -51,3 +53,12 @@ class AunitTestrunsScenario extends ContractScenario { // ============================================================================= runScenario(new AunitTestrunsScenario()); + +it('preserves the Atom coverage measurement link from an AUnit result', async () => { + const xml = await fixtures.aunit.runResultCoverageLink.load(); + const parsed = aunitResult.parse(xml); + + expect(extractCoverageMeasurementId(parsed)).toBe( + '6D664D9B46CB1FE1859107ADE8729541', + ); +}); diff --git a/packages/adt-fixtures/src/fixtures/aunit/run-result-coverage-link.xml b/packages/adt-fixtures/src/fixtures/aunit/run-result-coverage-link.xml new file mode 100644 index 000000000..69202d08d --- /dev/null +++ b/packages/adt-fixtures/src/fixtures/aunit/run-result-coverage-link.xml @@ -0,0 +1,4 @@ + + + + diff --git a/packages/adt-fixtures/src/fixtures/registry.ts b/packages/adt-fixtures/src/fixtures/registry.ts index 03c7726fb..28d4db190 100644 --- a/packages/adt-fixtures/src/fixtures/registry.ts +++ b/packages/adt-fixtures/src/fixtures/registry.ts @@ -26,6 +26,7 @@ export const registry = { aunit: { runRequest: 'aunit/run-request.xml', runResult: 'aunit/run-result.xml', + runResultCoverageLink: 'aunit/run-result-coverage-link.xml', // Sourced from jfilak/sapcli test fixtures (fixtures_adt_acoverage / // fixtures_adt_coverage). Real sanitized SAP responses for the // /runtime/traces/coverage endpoints. diff --git a/packages/adt-schemas/.xsd/custom/aunitResult.xsd b/packages/adt-schemas/.xsd/custom/aunitResult.xsd index ca70fb581..5f18b126f 100644 --- a/packages/adt-schemas/.xsd/custom/aunitResult.xsd +++ b/packages/adt-schemas/.xsd/custom/aunitResult.xsd @@ -33,16 +33,20 @@ --> + + + diff --git a/packages/adt-schemas/src/schemas/generated/schemas/custom/aunitResult.ts b/packages/adt-schemas/src/schemas/generated/schemas/custom/aunitResult.ts index 2795f37de..bf6a1e7ec 100644 --- a/packages/adt-schemas/src/schemas/generated/schemas/custom/aunitResult.ts +++ b/packages/adt-schemas/src/schemas/generated/schemas/custom/aunitResult.ts @@ -5,11 +5,17 @@ * Source: xsd/custom/aunitResult.xsd */ +import atom from '../sap/atom'; + export default { $xmlns: { xsd: 'http://www.w3.org/2001/XMLSchema', aunit: 'http://www.sap.com/adt/aunit', + atom: 'http://www.w3.org/2005/Atom', }, + $imports: [ + atom, + ], targetNamespace: 'http://www.sap.com/adt/aunit', attributeFormDefault: 'unqualified', elementFormDefault: 'qualified', @@ -24,6 +30,11 @@ export default { name: 'AunitRunResult', sequence: { element: [ + { + ref: 'atom:link', + minOccurs: '0', + maxOccurs: 'unbounded', + }, { name: 'program', type: 'aunit:AunitProgram', diff --git a/packages/adt-schemas/src/schemas/generated/types/custom/aunitResult.types.ts b/packages/adt-schemas/src/schemas/generated/types/custom/aunitResult.types.ts index 2268ad236..9f873c1a7 100644 --- a/packages/adt-schemas/src/schemas/generated/types/custom/aunitResult.types.ts +++ b/packages/adt-schemas/src/schemas/generated/types/custom/aunitResult.types.ts @@ -7,6 +7,16 @@ export type AunitResultSchema = { runResult: { + link?: undefined | { + href: string; + rel?: string | undefined; + type?: string | undefined; + hreflang?: string | undefined; + title?: string | undefined; + length?: number | undefined; + etag?: string | undefined; + _text?: string | undefined; + }[]; program?: undefined | { testClasses?: undefined | { testClass?: undefined | { From 820c5bdd7ef886d43b2de1127f77a6b8de2708d6 Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Fri, 21 Aug 2026 08:25:23 +0000 Subject: [PATCH 2/3] test(aunit): move coverage-link fixture test to coverage suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standalone `it()` in aunit.test.ts bypassed ContractScenario / ContractOperation and tested no HTTP operation — it was a fixture- parsing test for extractCoverageMeasurementId. Relocate it to coverage.test.ts, co-locating it with the existing extractCoverageMeasurementId unit tests. Resolves CodeRabbit review comment on PR #175. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../adt-contracts/tests/contracts/aunit.test.ts | 11 ----------- .../tests/contracts/coverage.test.ts | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/adt-contracts/tests/contracts/aunit.test.ts b/packages/adt-contracts/tests/contracts/aunit.test.ts index 329c6c3d4..4a400b888 100644 --- a/packages/adt-contracts/tests/contracts/aunit.test.ts +++ b/packages/adt-contracts/tests/contracts/aunit.test.ts @@ -9,13 +9,11 @@ */ import { fixtures } from '@abapify/adt-fixtures'; -import { expect, it } from 'vitest'; import { aunitRun, aunitResult } from '../../src/schemas'; import { ContractScenario, runScenario, type ContractOperation } from './base'; // Import contracts import { aunitContract } from '../../src/adt/aunit'; -import { extractCoverageMeasurementId } from '../../src/adt/aunit/coverage-link'; // ============================================================================= // Contract Definition Tests @@ -53,12 +51,3 @@ class AunitTestrunsScenario extends ContractScenario { // ============================================================================= runScenario(new AunitTestrunsScenario()); - -it('preserves the Atom coverage measurement link from an AUnit result', async () => { - const xml = await fixtures.aunit.runResultCoverageLink.load(); - const parsed = aunitResult.parse(xml); - - expect(extractCoverageMeasurementId(parsed)).toBe( - '6D664D9B46CB1FE1859107ADE8729541', - ); -}); diff --git a/packages/adt-contracts/tests/contracts/coverage.test.ts b/packages/adt-contracts/tests/contracts/coverage.test.ts index ea81e518f..65688d3d3 100644 --- a/packages/adt-contracts/tests/contracts/coverage.test.ts +++ b/packages/adt-contracts/tests/contracts/coverage.test.ts @@ -12,7 +12,11 @@ import { describe, it, expect } from 'vitest'; import { fixtures } from '@abapify/adt-fixtures'; -import { acoverageResult, acoverageStatements } from '../../src/schemas'; +import { + acoverageResult, + acoverageStatements, + aunitResult, +} from '../../src/schemas'; import { coverageContract, measurements, @@ -183,4 +187,13 @@ describe('extractCoverageMeasurementId', () => { }); expect(id).toBe('ABCDEF012345'); }); + + it('preserves the Atom coverage measurement link from an AUnit result', async () => { + const xml = await fixtures.aunit.runResultCoverageLink.load(); + const parsed = aunitResult.parse(xml); + + expect(extractCoverageMeasurementId(parsed)).toBe( + '6D664D9B46CB1FE1859107ADE8729541', + ); + }); }); From f7d4d8a79a7abcfee694b1be985244a4a4035302 Mon Sep 17 00:00:00 2001 From: Petr Plenkov Date: Fri, 21 Aug 2026 08:43:25 +0000 Subject: [PATCH 3/3] test(coverage): move parser-only assertions out of contracts directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit flagged that the extractCoverageMeasurementId describe block in coverage.test.ts (under tests/contracts/) bypasses the ContractScenario / ContractOperation harness — it's parser-only unit testing, not contract testing. Moved the entire block to a new tests/coverage-link.test.ts file outside tests/contracts/, keeping the contracts directory purely structural. Resolves CodeRabbit review comment on PR #175 (iteration 2). Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../tests/contracts/coverage.test.ts | 60 +----------------- .../adt-contracts/tests/coverage-link.test.ts | 61 +++++++++++++++++++ 2 files changed, 62 insertions(+), 59 deletions(-) create mode 100644 packages/adt-contracts/tests/coverage-link.test.ts diff --git a/packages/adt-contracts/tests/contracts/coverage.test.ts b/packages/adt-contracts/tests/contracts/coverage.test.ts index 65688d3d3..716b1dd53 100644 --- a/packages/adt-contracts/tests/contracts/coverage.test.ts +++ b/packages/adt-contracts/tests/contracts/coverage.test.ts @@ -12,11 +12,7 @@ import { describe, it, expect } from 'vitest'; import { fixtures } from '@abapify/adt-fixtures'; -import { - acoverageResult, - acoverageStatements, - aunitResult, -} from '../../src/schemas'; +import { acoverageResult, acoverageStatements } from '../../src/schemas'; import { coverageContract, measurements, @@ -24,7 +20,6 @@ import { } from '../../src/adt/runtime/traces/coverage'; import { ContractScenario, runScenario, type ContractOperation } from './base'; import { TypedContractScenario, runTypedScenario } from './base/typed-scenario'; -import { extractCoverageMeasurementId } from '../../src/adt/aunit/coverage-link'; const SCOV_CONTENT_TYPE = 'application/xml+scov'; @@ -144,56 +139,3 @@ class StatementsTypedScenario extends TypedContractScenario< } runTypedScenario(new StatementsTypedScenario()); - -// ───────────────────────────────────────────────────────────── -// 4. Coverage link helper -// ───────────────────────────────────────────────────────────── - -describe('extractCoverageMeasurementId', () => { - it('returns undefined when no coverage link is present', () => { - expect(extractCoverageMeasurementId({})).toBeUndefined(); - expect(extractCoverageMeasurementId(null)).toBeUndefined(); - }); - - it('finds the measurement id from a flat link array', () => { - // Atom link `rel` is an opaque relation-type URI per RFC 5988, not a - // network URL. Must match SAP wire format byte-for-byte. - const rel = - 'http://www.sap.com/adt/relations/runtime/traces/coverage/measurements/coveredobjects'; // NOSONAR: link-relation identifier (not a URL) - const id = extractCoverageMeasurementId({ - link: [ - { - href: '/sap/bc/adt/runtime/traces/coverage/measurements/6D664D9B46CB1FE1859107ADE8729541/coveredobjects', - rel, - }, - ], - }); - expect(id).toBe('6D664D9B46CB1FE1859107ADE8729541'); - }); - - it('finds the measurement id by walking nested nodes', () => { - const id = extractCoverageMeasurementId({ - program: { - testClasses: { - testClass: { - link: [ - { - href: '/sap/bc/adt/runtime/traces/coverage/measurements/ABCDEF012345/statements', - }, - ], - }, - }, - }, - }); - expect(id).toBe('ABCDEF012345'); - }); - - it('preserves the Atom coverage measurement link from an AUnit result', async () => { - const xml = await fixtures.aunit.runResultCoverageLink.load(); - const parsed = aunitResult.parse(xml); - - expect(extractCoverageMeasurementId(parsed)).toBe( - '6D664D9B46CB1FE1859107ADE8729541', - ); - }); -}); diff --git a/packages/adt-contracts/tests/coverage-link.test.ts b/packages/adt-contracts/tests/coverage-link.test.ts new file mode 100644 index 000000000..6241c2ca5 --- /dev/null +++ b/packages/adt-contracts/tests/coverage-link.test.ts @@ -0,0 +1,61 @@ +/** + * Unit tests for the coverage-link helper. + * + * These are parser-only assertions (no HTTP contract), so they live + * outside tests/contracts/ which is reserved for ContractScenario / + * ContractOperation definitions. + */ + +import { describe, it, expect } from 'vitest'; +import { fixtures } from '@abapify/adt-fixtures'; +import { aunitResult } from '../src/schemas'; +import { extractCoverageMeasurementId } from '../src/adt/aunit/coverage-link'; + +describe('extractCoverageMeasurementId', () => { + it('returns undefined when no coverage link is present', () => { + expect(extractCoverageMeasurementId({})).toBeUndefined(); + expect(extractCoverageMeasurementId(null)).toBeUndefined(); + }); + + it('finds the measurement id from a flat link array', () => { + // Atom link `rel` is an opaque relation-type URI per RFC 5988, not a + // network URL. Must match SAP wire format byte-for-byte. + const rel = + 'http://www.sap.com/adt/relations/runtime/traces/coverage/measurements/coveredobjects'; // NOSONAR: link-relation identifier (not a URL) + const id = extractCoverageMeasurementId({ + link: [ + { + href: '/sap/bc/adt/runtime/traces/coverage/measurements/6D664D9B46CB1FE1859107ADE8729541/coveredobjects', + rel, + }, + ], + }); + expect(id).toBe('6D664D9B46CB1FE1859107ADE8729541'); + }); + + it('finds the measurement id by walking nested nodes', () => { + const id = extractCoverageMeasurementId({ + program: { + testClasses: { + testClass: { + link: [ + { + href: '/sap/bc/adt/runtime/traces/coverage/measurements/ABCDEF012345/statements', + }, + ], + }, + }, + }, + }); + expect(id).toBe('ABCDEF012345'); + }); + + it('preserves the Atom coverage measurement link from an AUnit result', async () => { + const xml = await fixtures.aunit.runResultCoverageLink.load(); + const parsed = aunitResult.parse(xml); + + expect(extractCoverageMeasurementId(parsed)).toBe( + '6D664D9B46CB1FE1859107ADE8729541', + ); + }); +});