diff --git a/packages/adt-contracts/tests/contracts/coverage.test.ts b/packages/adt-contracts/tests/contracts/coverage.test.ts
index ea81e518f..716b1dd53 100644
--- a/packages/adt-contracts/tests/contracts/coverage.test.ts
+++ b/packages/adt-contracts/tests/contracts/coverage.test.ts
@@ -20,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';
@@ -140,47 +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');
- });
-});
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',
+ );
+ });
+});
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 | {