@abapify/adt-aunit 0.4.33 → 0.4.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,19 @@
1
1
  # @abapify/adt-aunit
2
2
 
3
- ABAP Unit Test CLI plugin for [`adt-cli`](https://github.com/abapify/adt-cli).
4
- Runs AUnit test runs against an SAP system via ADT and prints results to the
5
- console or serialises them as JUnit XML (for GitLab CI / Jenkins),
6
- SonarQube generic test reports, or JaCoCo / sonar-generic coverage.
3
+ [![npm](https://img.shields.io/npm/v/%40abapify%2Fadt-aunit)](https://www.npmjs.com/package/@abapify/adt-aunit)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/abapify/adt-cli/blob/main/LICENSE)
7
5
 
8
- [![npm](https://img.shields.io/npm/v/%40abapify%2Fadt-aunit.svg)](https://www.npmjs.com/package/@abapify/adt-aunit)
6
+ ABAP Unit Test CLI plugin for adt-cli with JUnit XML output for GitLab CI.
9
7
 
10
8
  ## Install
11
9
 
12
10
  ```bash
13
- npm i @abapify/adt-aunit
14
- # or
15
- bun add @abapify/adt-aunit
11
+ npm install @abapify/adt-aunit @abapify/adt-cli
16
12
  ```
17
13
 
18
14
  ## Usage
19
15
 
20
- Register the command in your `adt.config.ts` so `adt-cli` picks it up:
16
+ Register the command plugin in your project config:
21
17
 
22
18
  ```ts
23
19
  // adt.config.ts
@@ -26,49 +22,22 @@ export default {
26
22
  };
27
23
  ```
28
24
 
29
- Then run tests against a package, class, transport, or arbitrary object URI:
25
+ Then:
30
26
 
31
27
  ```bash
32
- adt aunit -p ZMY_PACKAGE
33
- adt aunit -c ZCL_MY_CLASS
34
- adt aunit --transport NPLK900042 --format junit --output aunit-report.xml
28
+ adt aunit --package ZMY_PKG --format junit --output aunit.xml
35
29
  ```
36
30
 
37
- Example GitLab CI job:
31
+ ## Features
38
32
 
39
- ```yaml
40
- abap-unit:
41
- script:
42
- - npx adt aunit -p $PACKAGE --format junit --output aunit-report.xml
43
- artifacts:
44
- when: always
45
- reports:
46
- junit: aunit-report.xml
47
- ```
48
-
49
- The package also exports helpers for embedding AUnit in custom tooling:
50
-
51
- ```ts
52
- import { toJunitXml, type AunitResult } from '@abapify/adt-aunit';
53
-
54
- const xml = toJunitXml(result satisfies AunitResult);
55
- ```
56
-
57
- ## Role in the monorepo
58
-
59
- - CLI plugin loaded by `@abapify/adt-cli` via the `commands` entry in
60
- `adt.config.ts`; ships both the `aunit` command and result formatters.
61
- - Depends on `@abapify/adt-contracts` / `@abapify/adt-schemas` for the
62
- AUnit ADT endpoint and on `@abapify/adt-plugin-abapgit` to resolve object
63
- names discovered from transports and packages.
64
- - Focused on AUnit only; ATC checks, coverage orchestration outside AUnit,
65
- and generic test runners live elsewhere.
33
+ - Run ABAP Unit tests from the terminal or CI
34
+ - JUnit and Sonar test reports for CI
35
+ - ABAP code coverage as JaCoCo or Sonar generic format
66
36
 
67
- ## Related
37
+ ## Documentation
68
38
 
69
- - [abapify/adt-cli monorepo](https://github.com/abapify/adt-cli)
70
- - [Full docs](https://adt-cli.netlify.app)
39
+ **[adt-cli.netlify.app/sdk/packages/adt-aunit](https://adt-cli.netlify.app/sdk/packages/adt-aunit)**
71
40
 
72
- ## License
41
+ ---
73
42
 
74
- MIT
43
+ Part of [**abapify**](https://github.com/abapify/adt-cli) — open, typed tooling for SAP ABAP Development Tools (ADT): CLI, MCP server, and SDK.
@@ -113,7 +113,7 @@ async function outputJunitReport(result, outputFile) {
113
113
  * </testExecutions>
114
114
  */
115
115
  function escapeXml(s) {
116
- return s.replaceAll(/&/g, "&amp;").replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;").replaceAll(/"/g, "&quot;").replaceAll(/'/g, "&apos;");
116
+ return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&apos;");
117
117
  }
118
118
  /**
119
119
  * Build a file path for the test object.
@@ -414,9 +414,54 @@ async function resolveTargets(options) {
414
414
  targetName: options.object
415
415
  };
416
416
  }
417
+ /** Emit the result in the requested output format. */
418
+ async function outputResults(result, options, ctx) {
419
+ if (options.format === "json") console.log(JSON.stringify(result, null, 2));
420
+ else if (options.format === "junit" && options.output) {
421
+ await outputJunitReport(result, options.output);
422
+ displayResults(result, ctx.adtSystemName);
423
+ } else if (options.format === "sonar" && options.output) {
424
+ outputSonarReport(result, options.output);
425
+ displayResults(result, ctx.adtSystemName);
426
+ } else displayResults(result, ctx.adtSystemName);
427
+ }
417
428
  /**
418
- * AUnit Command Plugin
429
+ * Coverage reporting — fetch the measurement, render it in the requested
430
+ * format, and write it to `--coverage-output` (or stdout).
419
431
  */
432
+ async function reportCoverage(response, client, targetUris, options, ctx) {
433
+ const measurementId = extractCoverageMeasurementId(response);
434
+ if (!measurementId) {
435
+ ctx.logger.warn?.("⚠️ Coverage requested but SAP returned no measurement link. The system may not have coverage collection enabled for this user/package, or additional system configuration is required.");
436
+ return;
437
+ }
438
+ if (!client.adt.runtime) {
439
+ ctx.logger.warn?.("⚠️ Coverage link present but the runtime/traces contract is not available on this client.");
440
+ return;
441
+ }
442
+ try {
443
+ const cov = client.adt.runtime.traces.coverage;
444
+ const { measurements, statements } = await fetchCoverageData(cov, measurementId, targetUris);
445
+ const format = options.coverageFormat ?? "jacoco";
446
+ const sourcePathResolver = createAbapGitCoverageSourceResolver();
447
+ const xml = format === "sonar-generic" ? toSonarGenericCoverageXml({
448
+ measurements,
449
+ statements,
450
+ sourcePathResolver
451
+ }) : toJacocoXml({
452
+ measurements,
453
+ statements,
454
+ sourcePathResolver
455
+ });
456
+ if (options.coverageOutput) {
457
+ const { writeFileSync } = await import("node:fs");
458
+ writeFileSync(options.coverageOutput, xml, "utf-8");
459
+ ctx.logger.info(`📊 Coverage report (${format}) written to ${options.coverageOutput}`);
460
+ } else console.log(xml);
461
+ } catch (err) {
462
+ ctx.logger.error(`❌ Coverage collection failed: ${err instanceof Error ? err.message : String(err)}`);
463
+ }
464
+ }
420
465
  const aunitCommand = {
421
466
  name: "aunit",
422
467
  description: "Run ABAP Unit tests",
@@ -499,45 +544,12 @@ const aunitCommand = {
499
544
  const body = buildRunConfiguration(targetUris, options.coverage === true);
500
545
  const response = await client.adt.aunit.testruns.post(body);
501
546
  const result = convertResponse(response);
502
- if (options.format === "json") console.log(JSON.stringify(result, null, 2));
503
- else if (options.format === "junit" && options.output) {
504
- await outputJunitReport(result, options.output);
505
- displayResults(result, ctx.adtSystemName);
506
- } else if (options.format === "sonar" && options.output) {
507
- outputSonarReport(result, options.output);
508
- displayResults(result, ctx.adtSystemName);
509
- } else displayResults(result, ctx.adtSystemName);
510
- if (options.coverage) {
511
- const measurementId = extractCoverageMeasurementId(response);
512
- if (!measurementId) ctx.logger.warn?.("⚠️ Coverage requested but SAP returned no measurement link. The system may not have coverage collection enabled for this user/package, or additional system configuration is required.");
513
- else if (!client.adt.runtime) ctx.logger.warn?.("⚠️ Coverage link present but the runtime/traces contract is not available on this client.");
514
- else try {
515
- const cov = client.adt.runtime.traces.coverage;
516
- const { measurements, statements } = await fetchCoverageData(cov, measurementId, targetUris);
517
- const format = options.coverageFormat ?? "jacoco";
518
- const sourcePathResolver = createAbapGitCoverageSourceResolver();
519
- const xml = format === "sonar-generic" ? toSonarGenericCoverageXml({
520
- measurements,
521
- statements,
522
- sourcePathResolver
523
- }) : toJacocoXml({
524
- measurements,
525
- statements,
526
- sourcePathResolver
527
- });
528
- if (options.coverageOutput) {
529
- const { writeFileSync } = await import("node:fs");
530
- writeFileSync(options.coverageOutput, xml, "utf-8");
531
- ctx.logger.info(`📊 Coverage report (${format}) written to ${options.coverageOutput}`);
532
- } else console.log(xml);
533
- } catch (err) {
534
- ctx.logger.error(`❌ Coverage collection failed: ${err instanceof Error ? err.message : String(err)}`);
535
- }
536
- }
547
+ await outputResults(result, options, ctx);
548
+ if (options.coverage) await reportCoverage(response, client, targetUris, options, ctx);
537
549
  const failures = result.failCount + result.errorCount;
538
- if (failures > 0) process.exit(failures > 125 ? 125 : failures);
550
+ if (failures > 0) process.exit(Math.min(failures, 125));
539
551
  }
540
552
  };
541
553
  export { outputJunitReport as n, toJunitXml as r, aunitCommand as t };
542
554
 
543
- //# sourceMappingURL=aunit-DPZ_v7tc.mjs.map
555
+ //# sourceMappingURL=aunit-ijY1RFbR.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aunit-ijY1RFbR.mjs","names":["escapeXml"],"sources":["../src/formatters/junit.ts","../src/formatters/sonar.ts","../src/commands/aunit.ts"],"sourcesContent":["/**\n * JUnit XML Formatter\n *\n * Outputs AUnit results in JUnit XML format compatible with\n * GitLab CI unit_test_reports.\n *\n * @see https://docs.gitlab.com/ci/testing/unit_test_reports/\n *\n * GitLab parses:\n * - testsuites > testsuite (name, time)\n * - testsuite > testcase (classname, name, file, time)\n * - testcase > failure, error, skipped\n * - testcase > system-out, system-err\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AunitResult, AunitTestMethod } from '../types';\n\n/**\n * Escape XML special characters\n */\nfunction escapeXml(str: string): string {\n return str\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;');\n}\n\n/**\n * Build failure/error message from alerts\n */\nfunction buildAlertMessage(method: AunitTestMethod): string {\n return method.alerts\n .map((alert) => {\n const lines: string[] = [];\n lines.push(alert.title);\n for (const detail of alert.details) {\n lines.push(` ${detail}`);\n }\n if (alert.stack.length > 0) {\n lines.push('Stack:');\n for (const entry of alert.stack) {\n const desc = entry.description || entry.name || '';\n const uriPart = entry.uri ? ` (${entry.uri})` : '';\n lines.push(` at ${desc}${uriPart}`);\n }\n }\n return lines.join('\\n');\n })\n .join('\\n---\\n');\n}\n\n/**\n * Build testcase XML lines for a single test method\n */\nfunction buildTestCaseXml(\n method: AunitTestMethod,\n classname: string,\n): string[] {\n const caseLines: string[] = [\n ` <testcase classname=\"${escapeXml(classname)}\" name=\"${escapeXml(method.name)}\" time=\"${method.executionTime.toFixed(3)}\">`,\n ];\n\n if (method.status === 'fail') {\n const message = method.alerts[0]?.title || 'Assertion failed';\n const body = buildAlertMessage(method);\n caseLines.push(\n ` <failure message=\"${escapeXml(message)}\">${escapeXml(body)}</failure>`,\n );\n } else if (method.status === 'error') {\n const message = method.alerts[0]?.title || 'Error';\n const body = buildAlertMessage(method);\n caseLines.push(\n ` <error message=\"${escapeXml(message)}\">${escapeXml(body)}</error>`,\n );\n } else if (method.status === 'skip') {\n caseLines.push(' <skipped/>');\n }\n\n if (method.uri) {\n caseLines.push(` <system-out>${escapeXml(method.uri)}</system-out>`);\n }\n\n caseLines.push(' </testcase>');\n return caseLines;\n}\n\n/**\n * Convert AUnit result to JUnit XML string\n */\nexport function toJunitXml(result: AunitResult): string {\n // Collect all test suites (one per test class)\n const suites: string[] = [];\n\n for (const program of result.programs) {\n for (const testClass of program.testClasses) {\n const tests = testClass.methods.length;\n const failures = testClass.methods.filter(\n (m) => m.status === 'fail',\n ).length;\n const errors = testClass.methods.filter(\n (m) => m.status === 'error',\n ).length;\n const skipped = testClass.methods.filter(\n (m) => m.status === 'skip',\n ).length;\n const time = testClass.methods.reduce(\n (sum, m) => sum + m.executionTime,\n 0,\n );\n\n const suiteLines: string[] = [\n ` <testsuite name=\"${escapeXml(program.name)}.${escapeXml(testClass.name)}\" tests=\"${tests}\" failures=\"${failures}\" errors=\"${errors}\" skipped=\"${skipped}\" time=\"${time.toFixed(3)}\">`,\n ];\n\n for (const method of testClass.methods) {\n const classname = `${program.name}.${testClass.name}`;\n suiteLines.push(...buildTestCaseXml(method, classname));\n }\n\n suiteLines.push(' </testsuite>');\n suites.push(suiteLines.join('\\n'));\n }\n }\n\n const header = `<testsuites tests=\"${result.totalTests}\" failures=\"${result.failCount}\" errors=\"${result.errorCount}\" skipped=\"${result.skipCount}\" time=\"${result.totalTime.toFixed(3)}\">`;\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n header,\n ...suites,\n '</testsuites>',\n ].join('\\n');\n}\n\n/**\n * Write JUnit XML report to file\n */\nexport async function outputJunitReport(\n result: AunitResult,\n outputFile: string,\n): Promise<void> {\n const xml = toJunitXml(result);\n await writeFile(outputFile, xml, 'utf-8');\n console.log(`\\n📄 JUnit XML report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalTests} tests (${result.passCount} passed, ${result.failCount} failed, ${result.errorCount} errors)`,\n );\n}\n","/**\n * AUnit Sonar Generic Execution Format Formatter\n *\n * Converts AUnit test results to Sonar Generic Execution format.\n * @see https://docs.sonarsource.com/sonarqube/latest/analyzing-source-code/test-coverage/generic-test-data/#generic-execution\n *\n * Output structure:\n * <testExecutions version=\"1\">\n * <file path=\"...\">\n * <testCase name=\"...\" duration=\"...\" />\n * <testCase name=\"...\">\n * <failure message=\"...\">stack trace</failure>\n * </testCase>\n * </file>\n * </testExecutions>\n */\n\nimport { writeFileSync } from 'node:fs';\nimport type {\n AunitResult,\n AunitProgram,\n AunitTestClass,\n AunitTestMethod,\n} from '../types';\n\nfunction escapeXml(s: string): string {\n return s\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;');\n}\n\n/**\n * Build a file path for the test object.\n * Mirrors sapcli behaviour: uses the object type and name as a pseudo-path\n * so that Sonar can link findings to source files in a checkout.\n */\nfunction objectPath(program: AunitProgram): string {\n const type = (program.type ?? '').replace('/', '_');\n const name = program.name.toLowerCase();\n if (type.startsWith('CLAS')) {\n return `src/${name}.clas.abap`;\n }\n if (type.startsWith('PROG')) {\n return `src/${name}.prog.abap`;\n }\n if (type.startsWith('INTF')) {\n return `src/${name}.intf.abap`;\n }\n return `src/${name}.abap`;\n}\n\nfunction methodElement(method: AunitTestMethod, cls: AunitTestClass): string {\n const durationMs = Math.round(method.executionTime * 1000);\n const testName = escapeXml(`${cls.name}.${method.name}`);\n\n if (method.status === 'pass' || method.status === 'skip') {\n return ` <testCase name=\"${testName}\" duration=\"${durationMs}\" />`;\n }\n\n const kind = method.status === 'fail' ? 'failure' : 'error';\n const messages = method.alerts\n .map((a) => {\n const detail = [a.title, ...a.details].join('\\n');\n return detail;\n })\n .join('\\n---\\n');\n\n const stack = method.alerts\n .flatMap((a) => a.stack.map((s) => s.description ?? s.name ?? ''))\n .filter(Boolean)\n .join('\\n');\n\n const msgAttr = escapeXml(method.alerts[0]?.title ?? method.status);\n const body = escapeXml([messages, stack].filter(Boolean).join('\\n\\n'));\n\n return [\n ` <testCase name=\"${testName}\" duration=\"${durationMs}\">`,\n ` <${kind} message=\"${msgAttr}\">${body}</${kind}>`,\n ` </testCase>`,\n ].join('\\n');\n}\n\nfunction programElement(program: AunitProgram): string {\n const path = escapeXml(objectPath(program));\n const methods = program.testClasses.flatMap((cls) =>\n cls.methods.map((m) => methodElement(m, cls)),\n );\n if (methods.length === 0) return '';\n return [` <file path=\"${path}\">`, ...methods, ` </file>`].join('\\n');\n}\n\n/**\n * Convert AUnit result to Sonar Generic Execution XML string\n */\nexport function toSonarXml(result: AunitResult): string {\n const files = result.programs.map(programElement).filter(Boolean).join('\\n');\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<testExecutions version=\"1\">',\n files,\n '</testExecutions>',\n ].join('\\n');\n}\n\n/**\n * Write Sonar Generic Execution XML report to file\n */\nexport function outputSonarReport(result: AunitResult, filePath: string): void {\n const xml = toSonarXml(result);\n writeFileSync(filePath, xml, 'utf-8');\n}\n","/**\n * AUnit Command Plugin\n *\n * CLI-agnostic command for running ABAP Unit tests.\n * Uses the CliContext.getAdtClient() factory for API access.\n *\n * Supports JUnit XML output for GitLab CI integration:\n * @see https://docs.gitlab.com/ci/testing/unit_test_reports/\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\nimport {\n buildCoverageQuery,\n buildStatementsBulkRequest,\n extractCoverageMeasurementId,\n extractCoverageStatementUris,\n} from '@abapify/adt-contracts';\nimport {\n acoverageResult,\n acoverageStatements,\n type InferTypedSchema,\n} from '@abapify/adt-schemas';\n\ntype AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;\ntype AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;\n// Simple ANSI color helpers (no external dependency)\nconst ansi = {\n red: (s: string) => `\\x1b[31m${s}\\x1b[0m`,\n green: (s: string) => `\\x1b[32m${s}\\x1b[0m`,\n yellow: (s: string) => `\\x1b[33m${s}\\x1b[0m`,\n cyan: (s: string) => `\\x1b[36m${s}\\x1b[0m`,\n dim: (s: string) => `\\x1b[2m${s}\\x1b[0m`,\n};\nimport { outputJunitReport, outputSonarReport } from '../formatters';\nimport {\n createAbapGitCoverageSourceResolver,\n toJacocoXml,\n toSonarGenericCoverageXml,\n} from '../formatters/jacoco';\nimport type {\n AunitResult,\n AunitProgram,\n AunitTestClass,\n AunitTestMethod,\n AunitAlert,\n OutputFormat,\n} from '../types';\n\n// Client type - the plugin receives a typed client from context\n// The client.adt.aunit namespace provides the testruns operation\ninterface AdtClient {\n adt: {\n aunit: {\n testruns: {\n post: (body: RunConfigurationBody) => Promise<RunResultResponse>;\n };\n };\n runtime?: {\n traces: {\n coverage: {\n measurements: {\n post: (id: string, body: string) => Promise<AcoverageResultSchema>;\n };\n statements: {\n post: (\n id: string,\n body: string,\n ) => Promise<AcoverageStatementsSchema>;\n };\n };\n };\n };\n };\n}\n\ntype CoverageClient = NonNullable<\n AdtClient['adt']['runtime']\n>['traces']['coverage'];\n\nasync function fetchCoverageData(\n coverage: CoverageClient,\n measurementId: string,\n targetUris: string[],\n): Promise<{\n measurements: AcoverageResultSchema;\n statements: AcoverageStatementsSchema | undefined;\n}> {\n const measurements = await coverage.measurements.post(\n measurementId,\n buildCoverageQuery(targetUris),\n );\n const statementUris = extractCoverageStatementUris(measurements);\n if (statementUris.length === 0)\n return { measurements, statements: undefined };\n const statements = await coverage.statements.post(\n measurementId,\n buildStatementsBulkRequest(statementUris),\n );\n return { measurements, statements };\n}\n\n// Request body shape (matches aunitRun schema)\ninterface RunConfigurationBody {\n runConfiguration: {\n external?: {\n coverage?: { active?: string };\n };\n options?: {\n uriType?: { value?: string };\n testDeterminationStrategy?: {\n sameProgram?: string;\n assignedTests?: string;\n appendAssignedTestsPreview?: string;\n };\n testRiskLevels?: {\n harmless?: string;\n dangerous?: string;\n critical?: string;\n };\n testDurations?: {\n short?: string;\n medium?: string;\n long?: string;\n };\n withNavigationUri?: { enabled?: string };\n };\n objectSets: {\n objectSet: Array<{\n kind: string;\n objectReferences?: {\n objectReference: Array<{ uri: string }>;\n };\n }>;\n };\n };\n}\n\n// Response shape (matches aunitResult schema)\ninterface RunResultResponse {\n runResult: {\n program?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n uriType?: string;\n testClasses?: {\n testClass?: Array<{\n uri?: string;\n name?: string;\n uriType?: string;\n durationCategory?: string;\n riskLevel?: string;\n testMethods?: {\n testMethod?: Array<{\n uri?: string;\n name?: string;\n executionTime?: string;\n uriType?: string;\n unit?: string;\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: {\n detail?: Array<{ text?: string }>;\n };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>;\n };\n }>;\n };\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: {\n detail?: Array<{ text?: string }>;\n };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>;\n };\n }>;\n };\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n }>;\n };\n }>;\n };\n}\n\n/**\n * Build the default runConfiguration request body.\n * Set `coverage` to enable the `<external><coverage active=\"true\"/></external>`\n * block – SAP will then include an atom:link to the coverage\n * measurement on the runResult.\n */\nfunction buildRunConfiguration(\n targetUris: string[],\n coverage = false,\n): RunConfigurationBody {\n return {\n runConfiguration: {\n external: {\n coverage: { active: coverage ? 'true' : 'false' },\n },\n options: {\n uriType: { value: 'semantic' },\n testDeterminationStrategy: {\n sameProgram: 'true',\n assignedTests: 'false',\n appendAssignedTestsPreview: 'true',\n },\n testRiskLevels: {\n harmless: 'true',\n dangerous: 'true',\n critical: 'true',\n },\n testDurations: {\n short: 'true',\n medium: 'true',\n long: 'true',\n },\n withNavigationUri: { enabled: 'false' },\n },\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n}\n\n/**\n * Convert a single test method alert to an AunitAlert, updating the status.\n */\nfunction convertAlerts(\n rawAlerts: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: { detail?: Array<{ text?: string }> };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>,\n): { alerts: AunitAlert[]; status: AunitTestMethod['status'] } {\n const alerts: AunitAlert[] = [];\n let status: AunitTestMethod['status'] = 'pass';\n\n for (const alert of rawAlerts) {\n const details = (alert.details?.detail ?? []).map((d) => d.text || '');\n const stack = (alert.stack?.stackEntry ?? []).map((s) => ({\n uri: s.uri,\n type: s.type,\n name: s.name,\n description: s.description,\n }));\n\n alerts.push({\n kind: alert.kind || 'unknown',\n severity: alert.severity || 'unknown',\n title: alert.title || '',\n details,\n stack,\n });\n\n if (alert.severity === 'critical' || alert.kind === 'failedAssertion') {\n status = 'fail';\n } else if (alert.severity === 'fatal' || alert.kind === 'error') {\n status = 'error';\n }\n }\n\n return { alerts, status };\n}\n\n/**\n * Convert SAP AUnit response to our normalized AunitResult\n */\nfunction convertResponse(response: RunResultResponse): AunitResult {\n const programs: AunitProgram[] = [];\n let totalTests = 0;\n let passCount = 0;\n let failCount = 0;\n let errorCount = 0;\n let skipCount = 0;\n let totalTime = 0;\n\n for (const prog of response.runResult.program || []) {\n const testClasses: AunitTestClass[] = [];\n\n for (const tc of prog.testClasses?.testClass || []) {\n const methods: AunitTestMethod[] = [];\n\n for (const tm of tc.testMethods?.testMethod || []) {\n const execTime = Number.parseFloat(tm.executionTime || '0');\n totalTime += execTime;\n\n const { alerts, status } = convertAlerts(tm.alerts?.alert || []);\n\n totalTests++;\n if (status === 'pass') passCount++;\n else if (status === 'fail') failCount++;\n else if (status === 'error') errorCount++;\n else if (status === 'skip') skipCount++;\n\n methods.push({\n name: tm.name || 'UNKNOWN',\n uri: tm.uri,\n executionTime: execTime,\n status,\n alerts,\n });\n }\n\n testClasses.push({\n name: tc.name || 'UNKNOWN',\n uri: tc.uri,\n riskLevel: tc.riskLevel,\n durationCategory: tc.durationCategory,\n methods,\n });\n }\n\n programs.push({\n name: prog.name || 'UNKNOWN',\n type: prog.type,\n uri: prog.uri,\n testClasses,\n });\n }\n\n return {\n programs,\n totalTests,\n passCount,\n failCount,\n errorCount,\n skipCount,\n totalTime,\n };\n}\n\n/**\n * Create OSC 8 hyperlink for terminal\n */\nfunction hyperlink(text: string, url: string): string {\n const OSC = '\\x1b]';\n const BEL = '\\x07';\n const SEP = ';';\n return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;\n}\n\n/**\n * Create ADT Eclipse link\n */\nfunction adtLink(name: string, uri: string, systemName?: string): string {\n if (!systemName || !name || !uri) {\n return name ? ansi.cyan(name) : '';\n }\n const path = uri.startsWith('/sap/bc/adt') ? uri : `/sap/bc/adt${uri}`;\n const url = `adt://${systemName}${path}`;\n return hyperlink(ansi.cyan(name), url);\n}\n\n/**\n * Display a single failed/errored test method in console\n */\nfunction displayFailedMethod(method: AunitTestMethod): void {\n const icon = method.status === 'fail' ? ansi.red('✗') : ansi.red('⚠');\n console.log(` ${icon} ${method.name} (${method.executionTime}s)`);\n for (const alert of method.alerts) {\n console.log(` ${ansi.dim(alert.title)}`);\n for (const detail of alert.details) {\n const dimDetail = ansi.dim(` ${detail}`);\n console.log(` ${dimDetail}`);\n }\n }\n}\n\n/**\n * Display AUnit results summary in console\n */\nfunction displaySummary(result: AunitResult): void {\n const allPassed = result.failCount === 0 && result.errorCount === 0;\n const statusIcon = allPassed ? '✅' : '❌';\n console.log(`\\n${statusIcon} ABAP Unit Test Results:`);\n console.log(\n ` 📋 Total: ${result.totalTests} tests in ${result.totalTime.toFixed(3)}s`,\n );\n if (result.passCount > 0) {\n const passLine = `✓ ${result.passCount} passed`;\n console.log(` ${ansi.green(passLine)}`);\n }\n if (result.failCount > 0) {\n const failLine = `✗ ${result.failCount} failed`;\n console.log(` ${ansi.red(failLine)}`);\n }\n if (result.errorCount > 0) {\n const errorLine = `⚠ ${result.errorCount} errors`;\n console.log(` ${ansi.red(errorLine)}`);\n }\n if (result.skipCount > 0) {\n const skipLine = `○ ${result.skipCount} skipped`;\n console.log(` ${ansi.yellow(skipLine)}`);\n }\n}\n\n/**\n * Display AUnit results in console\n */\nfunction displayResults(result: AunitResult, systemName?: string): void {\n if (result.totalTests === 0) {\n console.log(`\\n⚠️ No tests found`);\n return;\n }\n\n displaySummary(result);\n\n // Show failed tests\n for (const prog of result.programs) {\n for (const tc of prog.testClasses) {\n const failedMethods = tc.methods.filter(\n (m) => m.status === 'fail' || m.status === 'error',\n );\n if (failedMethods.length === 0) continue;\n\n const classLink = adtLink(\n `${prog.name} → ${tc.name}`,\n tc.uri || prog.uri || '',\n systemName,\n );\n console.log(`\\n ${classLink}`);\n\n for (const method of failedMethods) {\n displayFailedMethod(method);\n }\n }\n }\n}\n\n/**\n * Resolve target URIs and name from command options\n */\nasync function resolveTargets(options: {\n fromFile?: string;\n transport?: string;\n class?: string;\n package?: string;\n object?: string;\n}): Promise<{ targetUris: string[]; targetName: string }> {\n if (options.fromFile) {\n const { readFileSync } = await import('node:fs');\n const content = readFileSync(options.fromFile, 'utf-8');\n const targetUris = content\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#'));\n return {\n targetUris,\n targetName: `${targetUris.length} objects from ${options.fromFile}`,\n };\n }\n if (options.transport) {\n return {\n targetUris: [\n `/sap/bc/adt/cts/transportrequests/${options.transport.toUpperCase()}`,\n ],\n targetName: `Transport ${options.transport.toUpperCase()}`,\n };\n }\n if (options.class) {\n return {\n targetUris: [`/sap/bc/adt/oo/classes/${options.class.toLowerCase()}`],\n targetName: `Class ${options.class.toUpperCase()}`,\n };\n }\n if (options.package) {\n return {\n targetUris: [`/sap/bc/adt/packages/${options.package.toUpperCase()}`],\n targetName: `Package ${options.package.toUpperCase()}`,\n };\n }\n return { targetUris: [options.object!], targetName: options.object! };\n}\n\n/**\n * AUnit Command Plugin\n */\n/** CLI options parsed for the `aunit` command. */\ninterface AunitCliOptions {\n package?: string;\n object?: string;\n class?: string;\n transport?: string;\n fromFile?: string;\n format?: OutputFormat;\n output?: string;\n coverage?: boolean;\n coverageOutput?: string;\n coverageFormat?: 'jacoco' | 'sonar-generic';\n}\n\n/** Emit the result in the requested output format. */\nasync function outputResults(\n result: AunitResult,\n options: Pick<AunitCliOptions, 'format' | 'output'>,\n ctx: CliContext,\n): Promise<void> {\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'junit' && options.output) {\n await outputJunitReport(result, options.output);\n // Also print summary to console\n displayResults(result, ctx.adtSystemName);\n } else if (options.format === 'sonar' && options.output) {\n outputSonarReport(result, options.output);\n // Also print summary to console\n displayResults(result, ctx.adtSystemName);\n } else {\n displayResults(result, ctx.adtSystemName);\n }\n}\n\n/**\n * Coverage reporting — fetch the measurement, render it in the requested\n * format, and write it to `--coverage-output` (or stdout).\n */\nasync function reportCoverage(\n response: RunResultResponse,\n client: AdtClient,\n targetUris: string[],\n options: Pick<AunitCliOptions, 'coverageFormat' | 'coverageOutput'>,\n ctx: CliContext,\n): Promise<void> {\n const measurementId = extractCoverageMeasurementId(response);\n if (!measurementId) {\n ctx.logger.warn?.(\n '⚠️ Coverage requested but SAP returned no measurement link. ' +\n 'The system may not have coverage collection enabled for this user/package, ' +\n 'or additional system configuration is required.',\n );\n return;\n }\n if (!client.adt.runtime) {\n ctx.logger.warn?.(\n '⚠️ Coverage link present but the runtime/traces contract is not available on this client.',\n );\n return;\n }\n\n try {\n const cov = client.adt.runtime.traces.coverage;\n const { measurements, statements } = await fetchCoverageData(\n cov,\n measurementId,\n targetUris,\n );\n const format = options.coverageFormat ?? 'jacoco';\n const sourcePathResolver = createAbapGitCoverageSourceResolver();\n const xml =\n format === 'sonar-generic'\n ? toSonarGenericCoverageXml({\n measurements,\n statements,\n sourcePathResolver,\n })\n : toJacocoXml({\n measurements,\n statements,\n sourcePathResolver,\n });\n if (options.coverageOutput) {\n const { writeFileSync } = await import('node:fs');\n writeFileSync(options.coverageOutput, xml, 'utf-8');\n ctx.logger.info(\n `📊 Coverage report (${format}) written to ${options.coverageOutput}`,\n );\n } else {\n console.log(xml);\n }\n } catch (err) {\n ctx.logger.error(\n `❌ Coverage collection failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n}\n\nexport const aunitCommand: CliCommandPlugin = {\n name: 'aunit',\n description: 'Run ABAP Unit tests',\n\n options: [\n {\n flags: '-p, --package <package>',\n description: 'Run tests on package',\n },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run tests on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-c, --class <name>',\n description: 'Run tests on ABAP class (e.g., ZCL_MY_CLASS)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run tests on transport request (e.g., NPLK900042)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run tests on objects listed in file (one URI per line)',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, junit, sonar',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description:\n 'Output file (required for junit/sonar format, e.g., aunit-report.xml)',\n },\n {\n flags: '--coverage',\n description:\n 'Request ABAP code coverage with the test run and follow the resulting measurement link',\n },\n {\n flags: '--coverage-output <file>',\n description:\n 'Write the coverage report to this file (default: stdout for jacoco/sonar-generic)',\n },\n {\n flags: '--coverage-format <format>',\n description: 'Coverage format: jacoco | sonar-generic',\n default: 'jacoco',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as AunitCliOptions;\n\n // Validate: at least one target\n const targetCount = [\n options.package,\n options.object,\n options.class,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --class, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --class, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for junit/sonar format\n if (\n (options.format === 'junit' || options.format === 'sonar') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client\n if (!ctx.getAdtClient) {\n ctx.logger.error('❌ ADT client not available. Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n // Determine targets\n const { targetUris, targetName } = await resolveTargets(options);\n\n if (options.fromFile && targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n\n ctx.logger.info(`🧪 Running ABAP Unit tests on ${targetName}...`);\n\n // Execute test run\n const body = buildRunConfiguration(targetUris, options.coverage === true);\n const response = await client.adt.aunit.testruns.post(body);\n\n // Convert to normalized result\n const result = convertResponse(response);\n\n // Output\n await outputResults(result, options, ctx);\n\n // Coverage reporting – only if the user explicitly asked for it.\n if (options.coverage) {\n await reportCoverage(response, client, targetUris, options, ctx);\n }\n\n // Exit with non-zero code equal to number of failures+errors (sapcli convention)\n const failures = result.failCount + result.errorCount;\n if (failures > 0) {\n process.exit(Math.min(failures, 125));\n }\n },\n};\n\nexport default aunitCommand;\n"],"mappings":"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"}
@@ -1,7 +1,4 @@
1
1
  import { CliCommandPlugin } from "@abapify/adt-plugin";
2
- /**
3
- * AUnit Command Plugin
4
- */
5
2
  declare const aunitCommand: CliCommandPlugin;
6
3
  export { aunitCommand, aunitCommand as default };
7
4
  //# sourceMappingURL=aunit.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"aunit.d.mts","names":[],"sources":["../../src/commands/aunit.ts"],"mappings":";;;;cAwgBa,cAAc"}
1
+ {"version":3,"file":"aunit.d.mts","names":[],"sources":["../../src/commands/aunit.ts"],"mappings":";cA2mBa,cAAc"}
@@ -1 +1 @@
1
- export { t as aunitCommand, t as default } from "../aunit-DPZ_v7tc.mjs";
1
+ export { t as aunitCommand, t as default } from "../aunit-ijY1RFbR.mjs";
@@ -45,7 +45,7 @@ const COUNTER_TYPE_MAPPING = {
45
45
  statement: "INSTRUCTION"
46
46
  };
47
47
  function escapeAttr(s) {
48
- return s.replaceAll(/&/g, "&amp;").replaceAll(/</g, "&lt;").replaceAll(/>/g, "&gt;").replaceAll(/"/g, "&quot;");
48
+ return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;");
49
49
  }
50
50
  function indent(level) {
51
51
  return " ".repeat(level);
@@ -1 +1 @@
1
- {"version":3,"file":"jacoco.mjs","names":[],"sources":["../../src/formatters/jacoco.ts"],"sourcesContent":["/**\n * JaCoCo XML coverage report formatter.\n *\n * Consumes parsed coverage data from the adt-contracts runtime endpoints:\n *\n * measurements: client.adt.runtime.traces.coverage.measurements.post(id, query)\n * → AcoverageResultSchema (tree of DEVC → CLAS → methods)\n * statements: client.adt.runtime.traces.coverage.statements.post(id, bulkRequest)\n * → AcoverageStatementsSchema (per-method line hits/misses)\n *\n * Emits JaCoCo 1.1 XML:\n *\n * <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n * <!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">\n * <report name=\"ABAP Coverage\">\n * <package name=\"TEST_PACKAGE\">\n * <class name=\"CL_FOO\" sourcefilename=\"cl_foo.clas.abap\">\n * <method name=\"METHOD_A\" desc=\"\" line=\"52\">\n * <counter type=\"BRANCH\" missed=\"…\" covered=\"…\"/>\n * <counter type=\"METHOD\" missed=\"…\" covered=\"…\"/>\n * <counter type=\"INSTRUCTION\" missed=\"…\" covered=\"…\"/>\n * </method>\n * <counter type=\"…\" …/>\n * </class>\n * <sourcefile name=\"cl_foo.clas.abap\">\n * <line nr=\"N\" mi=\"0|1\" ci=\"1|0\" mb=\"0\" cb=\"0\"/>\n * </sourcefile>\n * <counter …/>\n * </package>\n * <counter …/>\n * </report>\n *\n * Improvement over sapcli: we use adtUriToAbapGitPath() and an optional\n * repository resolver so JaCoCo package + sourcefile names reconstruct the\n * tracked abapGit path. GitLab can then match changed files for MR diff\n * annotations, while standard JaCoCo consumers retain the same path.\n */\n\nimport { existsSync, readdirSync, writeFileSync, type Dirent } from 'node:fs';\nimport { basename, dirname, join, relative, sep } from 'node:path';\nimport {\n acoverageResult,\n acoverageStatements,\n type InferTypedSchema,\n} from '@abapify/adt-schemas';\nimport { adtUriToAbapGitPath } from '@abapify/adt-plugin-abapgit';\n\ntype AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;\ntype AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;\n\n// ─── Counter mapping ──────────────────────────────────────────────────\n\nconst COUNTER_TYPE_MAPPING: Record<string, string> = {\n branch: 'BRANCH',\n procedure: 'METHOD',\n statement: 'INSTRUCTION',\n};\n\n// ─── Narrow, recursive views of the schema types ──────────────────────\n//\n// The generated types mark deep recursion as `unknown`; we cast once\n// here so the rest of the formatter stays type-safe.\n\ninterface Coverage {\n type?: string;\n total?: number;\n executed?: number;\n}\n\ninterface ObjectRef {\n uri?: string;\n type?: string;\n name?: string;\n}\n\ninterface CoverageNode {\n objectReference?: ObjectRef;\n coverages?: { coverage?: Coverage[] };\n nodes?: { node?: CoverageNode[] };\n}\n\ninterface StatementEntry {\n objectReference?: ObjectRef;\n executed?: number;\n}\n\ninterface StatementResponse {\n name?: string;\n procedure?: StatementEntry[];\n statement?: StatementEntry[];\n}\n\n// ─── XML helpers ──────────────────────────────────────────────────────\n\nfunction escapeAttr(s: string): string {\n return s\n .replaceAll(/&/g, '&amp;')\n .replaceAll(/</g, '&lt;')\n .replaceAll(/>/g, '&gt;')\n .replaceAll(/\"/g, '&quot;');\n}\n\nfunction indent(level: number): string {\n return ' '.repeat(level);\n}\n\nfunction toPosix(filePath: string): string {\n return sep === '/' ? filePath : filePath.split(sep).join('/');\n}\n\nfunction collectAbapSources(root: string): string[] {\n const sources: string[] = [];\n const pending = [root];\n while (pending.length > 0) {\n const directory = pending.pop()!;\n let entries: Dirent[];\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n const candidate = join(directory, entry.name);\n if (entry.isDirectory()) pending.push(candidate);\n else if (entry.isFile() && entry.name.endsWith('.abap')) {\n sources.push(candidate);\n }\n }\n }\n return sources;\n}\n\nexport type CoverageSourcePathResolver = (\n reportedPath: string,\n) => string | null;\n\n/** Resolve an abapGit basename to a unique repository-relative source path. */\nexport function createAbapGitCoverageSourceResolver(\n sourceRoot = 'src',\n repositoryRoot = process.cwd(),\n): CoverageSourcePathResolver {\n const absoluteSourceRoot = join(repositoryRoot, sourceRoot);\n const byBasename = new Map<string, string[]>();\n if (existsSync(absoluteSourceRoot)) {\n for (const source of collectAbapSources(absoluteSourceRoot)) {\n const name = basename(source);\n const matches = byBasename.get(name) ?? [];\n matches.push(toPosix(relative(repositoryRoot, source)));\n byBasename.set(name, matches);\n }\n }\n\n return (reportedPath: string): string | null => {\n const matches = byBasename.get(basename(reportedPath)) ?? [];\n if (matches.length > 1) {\n throw new Error(\n `Ambiguous ABAP coverage source ${basename(reportedPath)}: ${matches.join(', ')}`,\n );\n }\n return matches[0] ?? null;\n };\n}\n\n/**\n * Parse a `#start=L,C` fragment out of an ADT URI. Returns `null`\n * if the URI has no fragment or the fragment is not well-formed.\n */\nfunction parseStartLine(uri: string | undefined): number | null {\n if (!uri) return null;\n const m = /#start=(\\d+)/.exec(uri);\n return m ? Number.parseInt(m[1], 10) : null;\n}\n\n/**\n * For a given statement entry, return the start line encoded in its\n * `adtcore:uri` fragment (1-based). Returns `null` when unavailable.\n */\nfunction statementLine(entry: StatementEntry): number | null {\n return parseStartLine(entry.objectReference?.uri);\n}\n\n// ─── Mapping: (class_name, method_name) → lines ───────────────────────\n\ntype MethodKey = string;\n\nfunction methodKey(cls: string, method: string): MethodKey {\n return `${cls}.${method}`;\n}\n\ninterface LineHit {\n line: number;\n covered: boolean;\n}\n\nfunction buildMethodLinesMapping(\n statements: AcoverageStatementsSchema | undefined,\n): Map<MethodKey, LineHit[]> {\n const result = new Map<MethodKey, LineHit[]>();\n const responses: StatementResponse[] =\n (statements?.statementsBulkResponse\n ?.statementsResponse as unknown as StatementResponse[]) ?? [];\n\n for (const response of responses) {\n const parts = (response.name ?? '').split('.');\n if (parts.length < 2) continue;\n const methodName = parts.at(-1)!;\n const className = parts.at(-2)!;\n const key = methodKey(className, methodName);\n const bucket = result.get(key) ?? [];\n for (const stmt of response.statement ?? []) {\n const line = statementLine(stmt);\n if (line == null) continue;\n bucket.push({ line, covered: (stmt.executed ?? 0) > 0 });\n }\n result.set(key, bucket);\n }\n return result;\n}\n\n// ─── Emit <counter …/> rollups ────────────────────────────────────────\n\nfunction emitCounters(\n node: CoverageNode,\n indentLevel: number,\n out: string[],\n): void {\n for (const c of node.coverages?.coverage ?? []) {\n if (!c.type) continue;\n const jacocoType = COUNTER_TYPE_MAPPING[c.type];\n if (!jacocoType) continue;\n const total = c.total ?? 0;\n const executed = c.executed ?? 0;\n const missed = Math.max(0, total - executed);\n out.push(\n `${indent(indentLevel)}<counter type=\"${jacocoType}\" missed=\"${missed}\" covered=\"${executed}\"/>`,\n );\n }\n}\n\n// ─── Emit <class>, <sourcefile> ───────────────────────────────────────\n\nfunction sourcefilePathFor(\n ref: ObjectRef | undefined,\n resolver: CoverageSourcePathResolver | undefined,\n): string {\n const uri = ref?.uri;\n const abapgit = uri ? adtUriToAbapGitPath(uri) : null;\n if (abapgit) {\n return resolver?.(abapgit) ?? abapgit;\n }\n // Fallback: raw name\n return (ref?.name ?? 'UNKNOWN').toLowerCase();\n}\n\nfunction emitClass(\n classNode: CoverageNode,\n sourcePath: string,\n lineMap: Map<MethodKey, LineHit[]>,\n indentLevel: number,\n out: string[],\n): void {\n const ref = classNode.objectReference;\n const className = ref?.name ?? 'UNKNOWN';\n const sourcefile = basename(sourcePath);\n\n out.push(\n `${indent(indentLevel)}<class name=\"${escapeAttr(className)}\" sourcefilename=\"${escapeAttr(sourcefile)}\">`,\n );\n\n const classLines: LineHit[] = [];\n const methods = classNode.nodes?.node ?? [];\n\n for (const method of methods) {\n const methodRef = method.objectReference;\n const methodName = methodRef?.name ?? 'UNKNOWN';\n const line = parseStartLine(methodRef?.uri) ?? 0;\n\n out.push(\n `${indent(indentLevel + 1)}<method name=\"${escapeAttr(methodName)}\" desc=\"\" line=\"${line}\">`,\n );\n emitCounters(method, indentLevel + 2, out);\n out.push(`${indent(indentLevel + 1)}</method>`);\n\n // Gather line data\n const key = methodKey(className, methodName);\n const hits = lineMap.get(key);\n if (hits) classLines.push(...hits);\n }\n\n emitCounters(classNode, indentLevel + 1, out);\n out.push(`${indent(indentLevel)}</class>`);\n\n if (classLines.length > 0) {\n out.push(\n `${indent(indentLevel)}<sourcefile name=\"${escapeAttr(sourcefile)}\">`,\n );\n for (const { line, covered } of classLines) {\n const ci = covered ? 1 : 0;\n const mi = covered ? 0 : 1;\n out.push(\n `${indent(indentLevel + 1)}<line nr=\"${line}\" mi=\"${mi}\" ci=\"${ci}\" mb=\"0\" cb=\"0\"/>`,\n );\n }\n out.push(`${indent(indentLevel)}</sourcefile>`);\n }\n}\n\n// ─── Emit <package> ───────────────────────────────────────────────────\n\nfunction emitPackage(\n packageNode: CoverageNode,\n lineMap: Map<MethodKey, LineHit[]>,\n resolver: CoverageSourcePathResolver | undefined,\n indentLevel: number,\n out: string[],\n): void {\n const classNodes = packageNode.nodes?.node ?? [];\n const byDirectory = new Map<\n string,\n Array<{ node: CoverageNode; sourcePath: string }>\n >();\n for (const node of classNodes) {\n const sourcePath = sourcefilePathFor(node.objectReference, resolver);\n const directory = dirname(sourcePath);\n const group = byDirectory.get(directory) ?? [];\n group.push({ node, sourcePath });\n byDirectory.set(directory, group);\n }\n\n for (const [directory, classes] of byDirectory) {\n out.push(`${indent(indentLevel)}<package name=\"${escapeAttr(directory)}\">`);\n for (const { node, sourcePath } of classes) {\n emitClass(node, sourcePath, lineMap, indentLevel + 1, out);\n }\n // Repository-path resolution can split one SAP package across multiple\n // JaCoCo packages. Roll up the class nodes actually emitted in this\n // directory so each package counter remains consistent with its children.\n for (const counterType of Object.keys(COUNTER_TYPE_MAPPING)) {\n let total = 0;\n let executed = 0;\n for (const { node } of classes) {\n const counter = node.coverages?.coverage?.find(\n (coverage) => coverage.type === counterType,\n );\n total += counter?.total ?? 0;\n executed += counter?.executed ?? 0;\n }\n if (total === 0 && executed === 0) continue;\n out.push(\n `${indent(indentLevel + 1)}<counter type=\"${COUNTER_TYPE_MAPPING[counterType]}\" missed=\"${Math.max(0, total - executed)}\" covered=\"${executed}\"/>`,\n );\n }\n out.push(`${indent(indentLevel)}</package>`);\n }\n}\n\n// ─── Public API ───────────────────────────────────────────────────────\n\nexport interface JacocoInput {\n measurements: AcoverageResultSchema;\n statements?: AcoverageStatementsSchema;\n reportName?: string;\n sourcePathResolver?: CoverageSourcePathResolver;\n}\n\nexport function toJacocoXml(input: JacocoInput): string {\n const root = input.measurements?.result;\n const lineMap = buildMethodLinesMapping(input.statements);\n const out: string[] = [];\n\n out.push(\n '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>',\n '<!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">',\n `<report name=\"${escapeAttr(input.reportName ?? 'ABAP Coverage')}\">`,\n );\n\n if (root) {\n const packages = (root.nodes?.node as unknown as CoverageNode[]) ?? [];\n for (const pkg of packages) {\n emitPackage(pkg, lineMap, input.sourcePathResolver, 1, out);\n }\n emitCounters(root as unknown as CoverageNode, 1, out);\n }\n\n out.push('</report>');\n return out.join('\\n');\n}\n\nexport function outputJacocoReport(input: JacocoInput, filePath: string): void {\n writeFileSync(filePath, toJacocoXml(input), 'utf-8');\n}\n\n// ─── Alt: Sonar Generic Coverage format ───────────────────────────────\n//\n// Simpler line-based format:\n// <coverage version=\"1\">\n// <file path=\"src/zcl_foo.clas.abap\">\n// <lineToCover lineNumber=\"42\" covered=\"true\"/>\n// </file>\n// </coverage>\n\nexport function toSonarGenericCoverageXml(input: JacocoInput): string {\n const lineMap = buildMethodLinesMapping(input.statements);\n const root = input.measurements?.result;\n\n // Build file → lines map by walking measurements tree for class refs,\n // then looking up lines via their method keys.\n const fileLines = new Map<string, LineHit[]>();\n\n function walk(node: CoverageNode): void {\n const ref = node.objectReference;\n if (ref?.uri && ref.type?.startsWith('CLAS')) {\n const reportedPath = adtUriToAbapGitPath(ref.uri);\n const filePath = reportedPath\n ? (input.sourcePathResolver?.(reportedPath) ?? reportedPath)\n : null;\n if (filePath) {\n const className = ref.name ?? '';\n const methods = node.nodes?.node ?? [];\n for (const m of methods) {\n const methodName = m.objectReference?.name ?? '';\n const lines = lineMap.get(methodKey(className, methodName)) ?? [];\n const bucket = fileLines.get(filePath) ?? [];\n bucket.push(...lines);\n fileLines.set(filePath, bucket);\n }\n }\n }\n for (const child of node.nodes?.node ?? []) walk(child);\n }\n if (root) walk(root as unknown as CoverageNode);\n\n const out: string[] = [];\n out.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>', '<coverage version=\"1\">');\n for (const [file, lines] of fileLines) {\n if (lines.length === 0) continue;\n out.push(` <file path=\"${escapeAttr(file)}\">`);\n for (const { line, covered } of lines) {\n out.push(\n ` <lineToCover lineNumber=\"${line}\" covered=\"${covered ? 'true' : 'false'}\"/>`,\n );\n }\n out.push(' </file>');\n }\n out.push('</coverage>');\n return out.join('\\n');\n}\n\nexport function outputSonarGenericCoverageReport(\n input: JacocoInput,\n filePath: string,\n): void {\n writeFileSync(filePath, toSonarGenericCoverageXml(input), 'utf-8');\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"jacoco.mjs","names":[],"sources":["../../src/formatters/jacoco.ts"],"sourcesContent":["/**\n * JaCoCo XML coverage report formatter.\n *\n * Consumes parsed coverage data from the adt-contracts runtime endpoints:\n *\n * measurements: client.adt.runtime.traces.coverage.measurements.post(id, query)\n * → AcoverageResultSchema (tree of DEVC → CLAS → methods)\n * statements: client.adt.runtime.traces.coverage.statements.post(id, bulkRequest)\n * → AcoverageStatementsSchema (per-method line hits/misses)\n *\n * Emits JaCoCo 1.1 XML:\n *\n * <?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n * <!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">\n * <report name=\"ABAP Coverage\">\n * <package name=\"TEST_PACKAGE\">\n * <class name=\"CL_FOO\" sourcefilename=\"cl_foo.clas.abap\">\n * <method name=\"METHOD_A\" desc=\"\" line=\"52\">\n * <counter type=\"BRANCH\" missed=\"…\" covered=\"…\"/>\n * <counter type=\"METHOD\" missed=\"…\" covered=\"…\"/>\n * <counter type=\"INSTRUCTION\" missed=\"…\" covered=\"…\"/>\n * </method>\n * <counter type=\"…\" …/>\n * </class>\n * <sourcefile name=\"cl_foo.clas.abap\">\n * <line nr=\"N\" mi=\"0|1\" ci=\"1|0\" mb=\"0\" cb=\"0\"/>\n * </sourcefile>\n * <counter …/>\n * </package>\n * <counter …/>\n * </report>\n *\n * Improvement over sapcli: we use adtUriToAbapGitPath() and an optional\n * repository resolver so JaCoCo package + sourcefile names reconstruct the\n * tracked abapGit path. GitLab can then match changed files for MR diff\n * annotations, while standard JaCoCo consumers retain the same path.\n */\n\nimport { existsSync, readdirSync, writeFileSync, type Dirent } from 'node:fs';\nimport { basename, dirname, join, relative, sep } from 'node:path';\nimport {\n acoverageResult,\n acoverageStatements,\n type InferTypedSchema,\n} from '@abapify/adt-schemas';\nimport { adtUriToAbapGitPath } from '@abapify/adt-plugin-abapgit';\n\ntype AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;\ntype AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;\n\n// ─── Counter mapping ──────────────────────────────────────────────────\n\nconst COUNTER_TYPE_MAPPING: Record<string, string> = {\n branch: 'BRANCH',\n procedure: 'METHOD',\n statement: 'INSTRUCTION',\n};\n\n// ─── Narrow, recursive views of the schema types ──────────────────────\n//\n// The generated types mark deep recursion as `unknown`; we cast once\n// here so the rest of the formatter stays type-safe.\n\ninterface Coverage {\n type?: string;\n total?: number;\n executed?: number;\n}\n\ninterface ObjectRef {\n uri?: string;\n type?: string;\n name?: string;\n}\n\ninterface CoverageNode {\n objectReference?: ObjectRef;\n coverages?: { coverage?: Coverage[] };\n nodes?: { node?: CoverageNode[] };\n}\n\ninterface StatementEntry {\n objectReference?: ObjectRef;\n executed?: number;\n}\n\ninterface StatementResponse {\n name?: string;\n procedure?: StatementEntry[];\n statement?: StatementEntry[];\n}\n\n// ─── XML helpers ──────────────────────────────────────────────────────\n\nfunction escapeAttr(s: string): string {\n return s\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;');\n}\n\nfunction indent(level: number): string {\n return ' '.repeat(level);\n}\n\nfunction toPosix(filePath: string): string {\n return sep === '/' ? filePath : filePath.split(sep).join('/');\n}\n\nfunction collectAbapSources(root: string): string[] {\n const sources: string[] = [];\n const pending = [root];\n while (pending.length > 0) {\n const directory = pending.pop()!;\n let entries: Dirent[];\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n const candidate = join(directory, entry.name);\n if (entry.isDirectory()) pending.push(candidate);\n else if (entry.isFile() && entry.name.endsWith('.abap')) {\n sources.push(candidate);\n }\n }\n }\n return sources;\n}\n\nexport type CoverageSourcePathResolver = (\n reportedPath: string,\n) => string | null;\n\n/** Resolve an abapGit basename to a unique repository-relative source path. */\nexport function createAbapGitCoverageSourceResolver(\n sourceRoot = 'src',\n repositoryRoot = process.cwd(),\n): CoverageSourcePathResolver {\n const absoluteSourceRoot = join(repositoryRoot, sourceRoot);\n const byBasename = new Map<string, string[]>();\n if (existsSync(absoluteSourceRoot)) {\n for (const source of collectAbapSources(absoluteSourceRoot)) {\n const name = basename(source);\n const matches = byBasename.get(name) ?? [];\n matches.push(toPosix(relative(repositoryRoot, source)));\n byBasename.set(name, matches);\n }\n }\n\n return (reportedPath: string): string | null => {\n const matches = byBasename.get(basename(reportedPath)) ?? [];\n if (matches.length > 1) {\n throw new Error(\n `Ambiguous ABAP coverage source ${basename(reportedPath)}: ${matches.join(', ')}`,\n );\n }\n return matches[0] ?? null;\n };\n}\n\n/**\n * Parse a `#start=L,C` fragment out of an ADT URI. Returns `null`\n * if the URI has no fragment or the fragment is not well-formed.\n */\nfunction parseStartLine(uri: string | undefined): number | null {\n if (!uri) return null;\n const m = /#start=(\\d+)/.exec(uri);\n return m ? Number.parseInt(m[1], 10) : null;\n}\n\n/**\n * For a given statement entry, return the start line encoded in its\n * `adtcore:uri` fragment (1-based). Returns `null` when unavailable.\n */\nfunction statementLine(entry: StatementEntry): number | null {\n return parseStartLine(entry.objectReference?.uri);\n}\n\n// ─── Mapping: (class_name, method_name) → lines ───────────────────────\n\nfunction methodKey(cls: string, method: string): string {\n return `${cls}.${method}`;\n}\n\ninterface LineHit {\n line: number;\n covered: boolean;\n}\n\nfunction buildMethodLinesMapping(\n statements: AcoverageStatementsSchema | undefined,\n): Map<string, LineHit[]> {\n const result = new Map<string, LineHit[]>();\n const responses: StatementResponse[] =\n (statements?.statementsBulkResponse\n ?.statementsResponse as unknown as StatementResponse[]) ?? [];\n\n for (const response of responses) {\n const parts = (response.name ?? '').split('.');\n if (parts.length < 2) continue;\n const methodName = parts.at(-1)!;\n const className = parts.at(-2)!;\n const key = methodKey(className, methodName);\n const bucket = result.get(key) ?? [];\n for (const stmt of response.statement ?? []) {\n const line = statementLine(stmt);\n if (line == null) continue;\n bucket.push({ line, covered: (stmt.executed ?? 0) > 0 });\n }\n result.set(key, bucket);\n }\n return result;\n}\n\n// ─── Emit <counter …/> rollups ────────────────────────────────────────\n\nfunction emitCounters(\n node: CoverageNode,\n indentLevel: number,\n out: string[],\n): void {\n for (const c of node.coverages?.coverage ?? []) {\n if (!c.type) continue;\n const jacocoType = COUNTER_TYPE_MAPPING[c.type];\n if (!jacocoType) continue;\n const total = c.total ?? 0;\n const executed = c.executed ?? 0;\n const missed = Math.max(0, total - executed);\n out.push(\n `${indent(indentLevel)}<counter type=\"${jacocoType}\" missed=\"${missed}\" covered=\"${executed}\"/>`,\n );\n }\n}\n\n// ─── Emit <class>, <sourcefile> ───────────────────────────────────────\n\nfunction sourcefilePathFor(\n ref: ObjectRef | undefined,\n resolver: CoverageSourcePathResolver | undefined,\n): string {\n const uri = ref?.uri;\n const abapgit = uri ? adtUriToAbapGitPath(uri) : null;\n if (abapgit) {\n return resolver?.(abapgit) ?? abapgit;\n }\n // Fallback: raw name\n return (ref?.name ?? 'UNKNOWN').toLowerCase();\n}\n\nfunction emitClass(\n classNode: CoverageNode,\n sourcePath: string,\n lineMap: Map<string, LineHit[]>,\n indentLevel: number,\n out: string[],\n): void {\n const ref = classNode.objectReference;\n const className = ref?.name ?? 'UNKNOWN';\n const sourcefile = basename(sourcePath);\n\n out.push(\n `${indent(indentLevel)}<class name=\"${escapeAttr(className)}\" sourcefilename=\"${escapeAttr(sourcefile)}\">`,\n );\n\n const classLines: LineHit[] = [];\n const methods = classNode.nodes?.node ?? [];\n\n for (const method of methods) {\n const methodRef = method.objectReference;\n const methodName = methodRef?.name ?? 'UNKNOWN';\n const line = parseStartLine(methodRef?.uri) ?? 0;\n\n out.push(\n `${indent(indentLevel + 1)}<method name=\"${escapeAttr(methodName)}\" desc=\"\" line=\"${line}\">`,\n );\n emitCounters(method, indentLevel + 2, out);\n out.push(`${indent(indentLevel + 1)}</method>`);\n\n // Gather line data\n const key = methodKey(className, methodName);\n const hits = lineMap.get(key);\n if (hits) classLines.push(...hits);\n }\n\n emitCounters(classNode, indentLevel + 1, out);\n out.push(`${indent(indentLevel)}</class>`);\n\n if (classLines.length > 0) {\n out.push(\n `${indent(indentLevel)}<sourcefile name=\"${escapeAttr(sourcefile)}\">`,\n );\n for (const { line, covered } of classLines) {\n const ci = covered ? 1 : 0;\n const mi = covered ? 0 : 1;\n out.push(\n `${indent(indentLevel + 1)}<line nr=\"${line}\" mi=\"${mi}\" ci=\"${ci}\" mb=\"0\" cb=\"0\"/>`,\n );\n }\n out.push(`${indent(indentLevel)}</sourcefile>`);\n }\n}\n\n// ─── Emit <package> ───────────────────────────────────────────────────\n\nfunction emitPackage(\n packageNode: CoverageNode,\n lineMap: Map<string, LineHit[]>,\n resolver: CoverageSourcePathResolver | undefined,\n indentLevel: number,\n out: string[],\n): void {\n const classNodes = packageNode.nodes?.node ?? [];\n const byDirectory = new Map<\n string,\n Array<{ node: CoverageNode; sourcePath: string }>\n >();\n for (const node of classNodes) {\n const sourcePath = sourcefilePathFor(node.objectReference, resolver);\n const directory = dirname(sourcePath);\n const group = byDirectory.get(directory) ?? [];\n group.push({ node, sourcePath });\n byDirectory.set(directory, group);\n }\n\n for (const [directory, classes] of byDirectory) {\n out.push(`${indent(indentLevel)}<package name=\"${escapeAttr(directory)}\">`);\n for (const { node, sourcePath } of classes) {\n emitClass(node, sourcePath, lineMap, indentLevel + 1, out);\n }\n // Repository-path resolution can split one SAP package across multiple\n // JaCoCo packages. Roll up the class nodes actually emitted in this\n // directory so each package counter remains consistent with its children.\n for (const counterType of Object.keys(COUNTER_TYPE_MAPPING)) {\n let total = 0;\n let executed = 0;\n for (const { node } of classes) {\n const counter = node.coverages?.coverage?.find(\n (coverage) => coverage.type === counterType,\n );\n total += counter?.total ?? 0;\n executed += counter?.executed ?? 0;\n }\n if (total === 0 && executed === 0) continue;\n out.push(\n `${indent(indentLevel + 1)}<counter type=\"${COUNTER_TYPE_MAPPING[counterType]}\" missed=\"${Math.max(0, total - executed)}\" covered=\"${executed}\"/>`,\n );\n }\n out.push(`${indent(indentLevel)}</package>`);\n }\n}\n\n// ─── Public API ───────────────────────────────────────────────────────\n\nexport interface JacocoInput {\n measurements: AcoverageResultSchema;\n statements?: AcoverageStatementsSchema;\n reportName?: string;\n sourcePathResolver?: CoverageSourcePathResolver;\n}\n\nexport function toJacocoXml(input: JacocoInput): string {\n const root = input.measurements?.result;\n const lineMap = buildMethodLinesMapping(input.statements);\n const out: string[] = [];\n\n out.push(\n '<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>',\n '<!DOCTYPE report PUBLIC \"-//JACOCO//DTD Report 1.1//EN\" \"report.dtd\">',\n `<report name=\"${escapeAttr(input.reportName ?? 'ABAP Coverage')}\">`,\n );\n\n if (root) {\n const packages = (root.nodes?.node as unknown as CoverageNode[]) ?? [];\n for (const pkg of packages) {\n emitPackage(pkg, lineMap, input.sourcePathResolver, 1, out);\n }\n emitCounters(root as unknown as CoverageNode, 1, out);\n }\n\n out.push('</report>');\n return out.join('\\n');\n}\n\nexport function outputJacocoReport(input: JacocoInput, filePath: string): void {\n writeFileSync(filePath, toJacocoXml(input), 'utf-8');\n}\n\n// ─── Alt: Sonar Generic Coverage format ───────────────────────────────\n//\n// Simpler line-based format:\n// <coverage version=\"1\">\n// <file path=\"src/zcl_foo.clas.abap\">\n// <lineToCover lineNumber=\"42\" covered=\"true\"/>\n// </file>\n// </coverage>\n\nexport function toSonarGenericCoverageXml(input: JacocoInput): string {\n const lineMap = buildMethodLinesMapping(input.statements);\n const root = input.measurements?.result;\n\n // Build file → lines map by walking measurements tree for class refs,\n // then looking up lines via their method keys.\n const fileLines = new Map<string, LineHit[]>();\n\n function walk(node: CoverageNode): void {\n const ref = node.objectReference;\n if (ref?.uri && ref.type?.startsWith('CLAS')) {\n const reportedPath = adtUriToAbapGitPath(ref.uri);\n const filePath = reportedPath\n ? (input.sourcePathResolver?.(reportedPath) ?? reportedPath)\n : null;\n if (filePath) {\n const className = ref.name ?? '';\n const methods = node.nodes?.node ?? [];\n for (const m of methods) {\n const methodName = m.objectReference?.name ?? '';\n const lines = lineMap.get(methodKey(className, methodName)) ?? [];\n const bucket = fileLines.get(filePath) ?? [];\n bucket.push(...lines);\n fileLines.set(filePath, bucket);\n }\n }\n }\n for (const child of node.nodes?.node ?? []) walk(child);\n }\n if (root) walk(root as unknown as CoverageNode);\n\n const out: string[] = [];\n out.push('<?xml version=\"1.0\" encoding=\"UTF-8\"?>', '<coverage version=\"1\">');\n for (const [file, lines] of fileLines) {\n if (lines.length === 0) continue;\n out.push(` <file path=\"${escapeAttr(file)}\">`);\n for (const { line, covered } of lines) {\n out.push(\n ` <lineToCover lineNumber=\"${line}\" covered=\"${covered ? 'true' : 'false'}\"/>`,\n );\n }\n out.push(' </file>');\n }\n out.push('</coverage>');\n return out.join('\\n');\n}\n\nexport function outputSonarGenericCoverageReport(\n input: JacocoInput,\n filePath: string,\n): void {\n writeFileSync(filePath, toSonarGenericCoverageXml(input), 'utf-8');\n}\n"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- export { t as aunitCommand, n as outputJunitReport, r as toJunitXml } from "./aunit-DPZ_v7tc.mjs";
1
+ export { t as aunitCommand, n as outputJunitReport, r as toJunitXml } from "./aunit-ijY1RFbR.mjs";
@@ -1 +1 @@
1
- {"version":3,"file":"jacoco-5Mn8hzTC.d.mts","names":[],"sources":["../src/formatters/jacoco.ts"],"mappings":";KA+CK,wBAAwB,wBAAwB;KAChD,4BAA4B,wBAAwB;KAoF7C,8BACV;;iBAIc,oCACd,qBACA,0BACC;UA0Nc;EACf,cAAc;EACd,aAAa;EACb;EACA,qBAAqB;;iBAGP,YAAY,OAAO;iBAuBnB,mBAAmB,OAAO,aAAa;iBAavC,0BAA0B,OAAO;iBA+CjC,iCACd,OAAO,aACP"}
1
+ {"version":3,"file":"jacoco-5Mn8hzTC.d.mts","names":[],"sources":["../src/formatters/jacoco.ts"],"mappings":";KA+CK,wBAAwB,wBAAwB;KAChD,4BAA4B,wBAAwB;KAoF7C,8BACV;;iBAIc,oCACd,qBACA,0BACC;UAwNc;EACf,cAAc;EACd,aAAa;EACb;EACA,qBAAqB;;iBAGP,YAAY,OAAO;iBAuBnB,mBAAmB,OAAO,aAAa;iBAavC,0BAA0B,OAAO;iBA+CjC,iCACd,OAAO,aACP"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/adt-aunit",
3
- "version": "0.4.33",
3
+ "version": "0.4.36",
4
4
  "description": "ABAP Unit Test CLI plugin for adt-cli with JUnit XML output for GitLab CI",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -29,10 +29,10 @@
29
29
  "gitlab-ci"
30
30
  ],
31
31
  "dependencies": {
32
- "@abapify/adt-plugin": "0.4.33",
33
- "@abapify/adt-schemas": "0.4.33",
34
- "@abapify/adt-contracts": "0.4.33",
35
- "@abapify/adt-plugin-abapgit": "0.4.33"
32
+ "@abapify/adt-plugin": "0.4.36",
33
+ "@abapify/adt-schemas": "0.4.36",
34
+ "@abapify/adt-contracts": "0.4.36",
35
+ "@abapify/adt-plugin-abapgit": "0.4.36"
36
36
  },
37
37
  "module": "./dist/index.mjs",
38
38
  "repository": {
@@ -45,6 +45,6 @@
45
45
  "url": "https://github.com/abapify/adt-cli/issues"
46
46
  },
47
47
  "devDependencies": {
48
- "@abapify/adt-fixtures": "0.4.33"
48
+ "@abapify/adt-fixtures": "0.4.36"
49
49
  }
50
50
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"aunit-DPZ_v7tc.mjs","names":["escapeXml"],"sources":["../src/formatters/junit.ts","../src/formatters/sonar.ts","../src/commands/aunit.ts"],"sourcesContent":["/**\n * JUnit XML Formatter\n *\n * Outputs AUnit results in JUnit XML format compatible with\n * GitLab CI unit_test_reports.\n *\n * @see https://docs.gitlab.com/ci/testing/unit_test_reports/\n *\n * GitLab parses:\n * - testsuites > testsuite (name, time)\n * - testsuite > testcase (classname, name, file, time)\n * - testcase > failure, error, skipped\n * - testcase > system-out, system-err\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport type { AunitResult, AunitTestMethod } from '../types';\n\n/**\n * Escape XML special characters\n */\nfunction escapeXml(str: string): string {\n return str\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&apos;');\n}\n\n/**\n * Build failure/error message from alerts\n */\nfunction buildAlertMessage(method: AunitTestMethod): string {\n return method.alerts\n .map((alert) => {\n const lines: string[] = [];\n lines.push(alert.title);\n for (const detail of alert.details) {\n lines.push(` ${detail}`);\n }\n if (alert.stack.length > 0) {\n lines.push('Stack:');\n for (const entry of alert.stack) {\n const desc = entry.description || entry.name || '';\n const uriPart = entry.uri ? ` (${entry.uri})` : '';\n lines.push(` at ${desc}${uriPart}`);\n }\n }\n return lines.join('\\n');\n })\n .join('\\n---\\n');\n}\n\n/**\n * Build testcase XML lines for a single test method\n */\nfunction buildTestCaseXml(\n method: AunitTestMethod,\n classname: string,\n): string[] {\n const caseLines: string[] = [\n ` <testcase classname=\"${escapeXml(classname)}\" name=\"${escapeXml(method.name)}\" time=\"${method.executionTime.toFixed(3)}\">`,\n ];\n\n if (method.status === 'fail') {\n const message = method.alerts[0]?.title || 'Assertion failed';\n const body = buildAlertMessage(method);\n caseLines.push(\n ` <failure message=\"${escapeXml(message)}\">${escapeXml(body)}</failure>`,\n );\n } else if (method.status === 'error') {\n const message = method.alerts[0]?.title || 'Error';\n const body = buildAlertMessage(method);\n caseLines.push(\n ` <error message=\"${escapeXml(message)}\">${escapeXml(body)}</error>`,\n );\n } else if (method.status === 'skip') {\n caseLines.push(' <skipped/>');\n }\n\n if (method.uri) {\n caseLines.push(` <system-out>${escapeXml(method.uri)}</system-out>`);\n }\n\n caseLines.push(' </testcase>');\n return caseLines;\n}\n\n/**\n * Convert AUnit result to JUnit XML string\n */\nexport function toJunitXml(result: AunitResult): string {\n // Collect all test suites (one per test class)\n const suites: string[] = [];\n\n for (const program of result.programs) {\n for (const testClass of program.testClasses) {\n const tests = testClass.methods.length;\n const failures = testClass.methods.filter(\n (m) => m.status === 'fail',\n ).length;\n const errors = testClass.methods.filter(\n (m) => m.status === 'error',\n ).length;\n const skipped = testClass.methods.filter(\n (m) => m.status === 'skip',\n ).length;\n const time = testClass.methods.reduce(\n (sum, m) => sum + m.executionTime,\n 0,\n );\n\n const suiteLines: string[] = [\n ` <testsuite name=\"${escapeXml(program.name)}.${escapeXml(testClass.name)}\" tests=\"${tests}\" failures=\"${failures}\" errors=\"${errors}\" skipped=\"${skipped}\" time=\"${time.toFixed(3)}\">`,\n ];\n\n for (const method of testClass.methods) {\n const classname = `${program.name}.${testClass.name}`;\n suiteLines.push(...buildTestCaseXml(method, classname));\n }\n\n suiteLines.push(' </testsuite>');\n suites.push(suiteLines.join('\\n'));\n }\n }\n\n const header = `<testsuites tests=\"${result.totalTests}\" failures=\"${result.failCount}\" errors=\"${result.errorCount}\" skipped=\"${result.skipCount}\" time=\"${result.totalTime.toFixed(3)}\">`;\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n header,\n ...suites,\n '</testsuites>',\n ].join('\\n');\n}\n\n/**\n * Write JUnit XML report to file\n */\nexport async function outputJunitReport(\n result: AunitResult,\n outputFile: string,\n): Promise<void> {\n const xml = toJunitXml(result);\n await writeFile(outputFile, xml, 'utf-8');\n console.log(`\\n📄 JUnit XML report written to: ${outputFile}`);\n console.log(\n `📊 ${result.totalTests} tests (${result.passCount} passed, ${result.failCount} failed, ${result.errorCount} errors)`,\n );\n}\n","/**\n * AUnit Sonar Generic Execution Format Formatter\n *\n * Converts AUnit test results to Sonar Generic Execution format.\n * @see https://docs.sonarsource.com/sonarqube/latest/analyzing-source-code/test-coverage/generic-test-data/#generic-execution\n *\n * Output structure:\n * <testExecutions version=\"1\">\n * <file path=\"...\">\n * <testCase name=\"...\" duration=\"...\" />\n * <testCase name=\"...\">\n * <failure message=\"...\">stack trace</failure>\n * </testCase>\n * </file>\n * </testExecutions>\n */\n\nimport { writeFileSync } from 'node:fs';\nimport type {\n AunitResult,\n AunitProgram,\n AunitTestClass,\n AunitTestMethod,\n} from '../types';\n\nfunction escapeXml(s: string): string {\n return s\n .replaceAll(/&/g, '&amp;')\n .replaceAll(/</g, '&lt;')\n .replaceAll(/>/g, '&gt;')\n .replaceAll(/\"/g, '&quot;')\n .replaceAll(/'/g, '&apos;');\n}\n\n/**\n * Build a file path for the test object.\n * Mirrors sapcli behaviour: uses the object type and name as a pseudo-path\n * so that Sonar can link findings to source files in a checkout.\n */\nfunction objectPath(program: AunitProgram): string {\n const type = (program.type ?? '').replace('/', '_');\n const name = program.name.toLowerCase();\n if (type.startsWith('CLAS')) {\n return `src/${name}.clas.abap`;\n }\n if (type.startsWith('PROG')) {\n return `src/${name}.prog.abap`;\n }\n if (type.startsWith('INTF')) {\n return `src/${name}.intf.abap`;\n }\n return `src/${name}.abap`;\n}\n\nfunction methodElement(method: AunitTestMethod, cls: AunitTestClass): string {\n const durationMs = Math.round(method.executionTime * 1000);\n const testName = escapeXml(`${cls.name}.${method.name}`);\n\n if (method.status === 'pass' || method.status === 'skip') {\n return ` <testCase name=\"${testName}\" duration=\"${durationMs}\" />`;\n }\n\n const kind = method.status === 'fail' ? 'failure' : 'error';\n const messages = method.alerts\n .map((a) => {\n const detail = [a.title, ...a.details].join('\\n');\n return detail;\n })\n .join('\\n---\\n');\n\n const stack = method.alerts\n .flatMap((a) => a.stack.map((s) => s.description ?? s.name ?? ''))\n .filter(Boolean)\n .join('\\n');\n\n const msgAttr = escapeXml(method.alerts[0]?.title ?? method.status);\n const body = escapeXml([messages, stack].filter(Boolean).join('\\n\\n'));\n\n return [\n ` <testCase name=\"${testName}\" duration=\"${durationMs}\">`,\n ` <${kind} message=\"${msgAttr}\">${body}</${kind}>`,\n ` </testCase>`,\n ].join('\\n');\n}\n\nfunction programElement(program: AunitProgram): string {\n const path = escapeXml(objectPath(program));\n const methods = program.testClasses.flatMap((cls) =>\n cls.methods.map((m) => methodElement(m, cls)),\n );\n if (methods.length === 0) return '';\n return [` <file path=\"${path}\">`, ...methods, ` </file>`].join('\\n');\n}\n\n/**\n * Convert AUnit result to Sonar Generic Execution XML string\n */\nexport function toSonarXml(result: AunitResult): string {\n const files = result.programs.map(programElement).filter(Boolean).join('\\n');\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<testExecutions version=\"1\">',\n files,\n '</testExecutions>',\n ].join('\\n');\n}\n\n/**\n * Write Sonar Generic Execution XML report to file\n */\nexport function outputSonarReport(result: AunitResult, filePath: string): void {\n const xml = toSonarXml(result);\n writeFileSync(filePath, xml, 'utf-8');\n}\n","/**\n * AUnit Command Plugin\n *\n * CLI-agnostic command for running ABAP Unit tests.\n * Uses the CliContext.getAdtClient() factory for API access.\n *\n * Supports JUnit XML output for GitLab CI integration:\n * @see https://docs.gitlab.com/ci/testing/unit_test_reports/\n */\n\nimport type { CliCommandPlugin, CliContext } from '@abapify/adt-plugin';\nimport {\n buildCoverageQuery,\n buildStatementsBulkRequest,\n extractCoverageMeasurementId,\n extractCoverageStatementUris,\n} from '@abapify/adt-contracts';\nimport {\n acoverageResult,\n acoverageStatements,\n type InferTypedSchema,\n} from '@abapify/adt-schemas';\n\ntype AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;\ntype AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;\n// Simple ANSI color helpers (no external dependency)\nconst ansi = {\n red: (s: string) => `\\x1b[31m${s}\\x1b[0m`,\n green: (s: string) => `\\x1b[32m${s}\\x1b[0m`,\n yellow: (s: string) => `\\x1b[33m${s}\\x1b[0m`,\n cyan: (s: string) => `\\x1b[36m${s}\\x1b[0m`,\n dim: (s: string) => `\\x1b[2m${s}\\x1b[0m`,\n};\nimport { outputJunitReport, outputSonarReport } from '../formatters';\nimport {\n createAbapGitCoverageSourceResolver,\n toJacocoXml,\n toSonarGenericCoverageXml,\n} from '../formatters/jacoco';\nimport type {\n AunitResult,\n AunitProgram,\n AunitTestClass,\n AunitTestMethod,\n AunitAlert,\n OutputFormat,\n} from '../types';\n\n// Client type - the plugin receives a typed client from context\n// The client.adt.aunit namespace provides the testruns operation\ninterface AdtClient {\n adt: {\n aunit: {\n testruns: {\n post: (body: RunConfigurationBody) => Promise<RunResultResponse>;\n };\n };\n runtime?: {\n traces: {\n coverage: {\n measurements: {\n post: (id: string, body: string) => Promise<AcoverageResultSchema>;\n };\n statements: {\n post: (\n id: string,\n body: string,\n ) => Promise<AcoverageStatementsSchema>;\n };\n };\n };\n };\n };\n}\n\ntype CoverageClient = NonNullable<\n AdtClient['adt']['runtime']\n>['traces']['coverage'];\n\nasync function fetchCoverageData(\n coverage: CoverageClient,\n measurementId: string,\n targetUris: string[],\n): Promise<{\n measurements: AcoverageResultSchema;\n statements: AcoverageStatementsSchema | undefined;\n}> {\n const measurements = await coverage.measurements.post(\n measurementId,\n buildCoverageQuery(targetUris),\n );\n const statementUris = extractCoverageStatementUris(measurements);\n if (statementUris.length === 0)\n return { measurements, statements: undefined };\n const statements = await coverage.statements.post(\n measurementId,\n buildStatementsBulkRequest(statementUris),\n );\n return { measurements, statements };\n}\n\n// Request body shape (matches aunitRun schema)\ninterface RunConfigurationBody {\n runConfiguration: {\n external?: {\n coverage?: { active?: string };\n };\n options?: {\n uriType?: { value?: string };\n testDeterminationStrategy?: {\n sameProgram?: string;\n assignedTests?: string;\n appendAssignedTestsPreview?: string;\n };\n testRiskLevels?: {\n harmless?: string;\n dangerous?: string;\n critical?: string;\n };\n testDurations?: {\n short?: string;\n medium?: string;\n long?: string;\n };\n withNavigationUri?: { enabled?: string };\n };\n objectSets: {\n objectSet: Array<{\n kind: string;\n objectReferences?: {\n objectReference: Array<{ uri: string }>;\n };\n }>;\n };\n };\n}\n\n// Response shape (matches aunitResult schema)\ninterface RunResultResponse {\n runResult: {\n program?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n uriType?: string;\n testClasses?: {\n testClass?: Array<{\n uri?: string;\n name?: string;\n uriType?: string;\n durationCategory?: string;\n riskLevel?: string;\n testMethods?: {\n testMethod?: Array<{\n uri?: string;\n name?: string;\n executionTime?: string;\n uriType?: string;\n unit?: string;\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: {\n detail?: Array<{ text?: string }>;\n };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>;\n };\n }>;\n };\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: {\n detail?: Array<{ text?: string }>;\n };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>;\n };\n }>;\n };\n alerts?: {\n alert?: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n }>;\n };\n }>;\n };\n}\n\n/**\n * Build the default runConfiguration request body.\n * Set `coverage` to enable the `<external><coverage active=\"true\"/></external>`\n * block – SAP will then include an atom:link to the coverage\n * measurement on the runResult.\n */\nfunction buildRunConfiguration(\n targetUris: string[],\n coverage = false,\n): RunConfigurationBody {\n return {\n runConfiguration: {\n external: {\n coverage: { active: coverage ? 'true' : 'false' },\n },\n options: {\n uriType: { value: 'semantic' },\n testDeterminationStrategy: {\n sameProgram: 'true',\n assignedTests: 'false',\n appendAssignedTestsPreview: 'true',\n },\n testRiskLevels: {\n harmless: 'true',\n dangerous: 'true',\n critical: 'true',\n },\n testDurations: {\n short: 'true',\n medium: 'true',\n long: 'true',\n },\n withNavigationUri: { enabled: 'false' },\n },\n objectSets: {\n objectSet: [\n {\n kind: 'inclusive',\n objectReferences: {\n objectReference: targetUris.map((uri) => ({ uri })),\n },\n },\n ],\n },\n },\n };\n}\n\n/**\n * Convert a single test method alert to an AunitAlert, updating the status.\n */\nfunction convertAlerts(\n rawAlerts: Array<{\n kind?: string;\n severity?: string;\n title?: string;\n details?: { detail?: Array<{ text?: string }> };\n stack?: {\n stackEntry?: Array<{\n uri?: string;\n type?: string;\n name?: string;\n description?: string;\n }>;\n };\n }>,\n): { alerts: AunitAlert[]; status: AunitTestMethod['status'] } {\n const alerts: AunitAlert[] = [];\n let status: AunitTestMethod['status'] = 'pass';\n\n for (const alert of rawAlerts) {\n const details = (alert.details?.detail ?? []).map((d) => d.text || '');\n const stack = (alert.stack?.stackEntry ?? []).map((s) => ({\n uri: s.uri,\n type: s.type,\n name: s.name,\n description: s.description,\n }));\n\n alerts.push({\n kind: alert.kind || 'unknown',\n severity: alert.severity || 'unknown',\n title: alert.title || '',\n details,\n stack,\n });\n\n if (alert.severity === 'critical' || alert.kind === 'failedAssertion') {\n status = 'fail';\n } else if (alert.severity === 'fatal' || alert.kind === 'error') {\n status = 'error';\n }\n }\n\n return { alerts, status };\n}\n\n/**\n * Convert SAP AUnit response to our normalized AunitResult\n */\nfunction convertResponse(response: RunResultResponse): AunitResult {\n const programs: AunitProgram[] = [];\n let totalTests = 0;\n let passCount = 0;\n let failCount = 0;\n let errorCount = 0;\n let skipCount = 0;\n let totalTime = 0;\n\n for (const prog of response.runResult.program || []) {\n const testClasses: AunitTestClass[] = [];\n\n for (const tc of prog.testClasses?.testClass || []) {\n const methods: AunitTestMethod[] = [];\n\n for (const tm of tc.testMethods?.testMethod || []) {\n const execTime = Number.parseFloat(tm.executionTime || '0');\n totalTime += execTime;\n\n const { alerts, status } = convertAlerts(tm.alerts?.alert || []);\n\n totalTests++;\n if (status === 'pass') passCount++;\n else if (status === 'fail') failCount++;\n else if (status === 'error') errorCount++;\n else if (status === 'skip') skipCount++;\n\n methods.push({\n name: tm.name || 'UNKNOWN',\n uri: tm.uri,\n executionTime: execTime,\n status,\n alerts,\n });\n }\n\n testClasses.push({\n name: tc.name || 'UNKNOWN',\n uri: tc.uri,\n riskLevel: tc.riskLevel,\n durationCategory: tc.durationCategory,\n methods,\n });\n }\n\n programs.push({\n name: prog.name || 'UNKNOWN',\n type: prog.type,\n uri: prog.uri,\n testClasses,\n });\n }\n\n return {\n programs,\n totalTests,\n passCount,\n failCount,\n errorCount,\n skipCount,\n totalTime,\n };\n}\n\n/**\n * Create OSC 8 hyperlink for terminal\n */\nfunction hyperlink(text: string, url: string): string {\n const OSC = '\\x1b]';\n const BEL = '\\x07';\n const SEP = ';';\n return `${OSC}8${SEP}${SEP}${url}${BEL}${text}${OSC}8${SEP}${SEP}${BEL}`;\n}\n\n/**\n * Create ADT Eclipse link\n */\nfunction adtLink(name: string, uri: string, systemName?: string): string {\n if (!systemName || !name || !uri) {\n return name ? ansi.cyan(name) : '';\n }\n const path = uri.startsWith('/sap/bc/adt') ? uri : `/sap/bc/adt${uri}`;\n const url = `adt://${systemName}${path}`;\n return hyperlink(ansi.cyan(name), url);\n}\n\n/**\n * Display a single failed/errored test method in console\n */\nfunction displayFailedMethod(method: AunitTestMethod): void {\n const icon = method.status === 'fail' ? ansi.red('✗') : ansi.red('⚠');\n console.log(` ${icon} ${method.name} (${method.executionTime}s)`);\n for (const alert of method.alerts) {\n console.log(` ${ansi.dim(alert.title)}`);\n for (const detail of alert.details) {\n const dimDetail = ansi.dim(` ${detail}`);\n console.log(` ${dimDetail}`);\n }\n }\n}\n\n/**\n * Display AUnit results summary in console\n */\nfunction displaySummary(result: AunitResult): void {\n const allPassed = result.failCount === 0 && result.errorCount === 0;\n const statusIcon = allPassed ? '✅' : '❌';\n console.log(`\\n${statusIcon} ABAP Unit Test Results:`);\n console.log(\n ` 📋 Total: ${result.totalTests} tests in ${result.totalTime.toFixed(3)}s`,\n );\n if (result.passCount > 0) {\n const passLine = `✓ ${result.passCount} passed`;\n console.log(` ${ansi.green(passLine)}`);\n }\n if (result.failCount > 0) {\n const failLine = `✗ ${result.failCount} failed`;\n console.log(` ${ansi.red(failLine)}`);\n }\n if (result.errorCount > 0) {\n const errorLine = `⚠ ${result.errorCount} errors`;\n console.log(` ${ansi.red(errorLine)}`);\n }\n if (result.skipCount > 0) {\n const skipLine = `○ ${result.skipCount} skipped`;\n console.log(` ${ansi.yellow(skipLine)}`);\n }\n}\n\n/**\n * Display AUnit results in console\n */\nfunction displayResults(result: AunitResult, systemName?: string): void {\n if (result.totalTests === 0) {\n console.log(`\\n⚠️ No tests found`);\n return;\n }\n\n displaySummary(result);\n\n // Show failed tests\n for (const prog of result.programs) {\n for (const tc of prog.testClasses) {\n const failedMethods = tc.methods.filter(\n (m) => m.status === 'fail' || m.status === 'error',\n );\n if (failedMethods.length === 0) continue;\n\n const classLink = adtLink(\n `${prog.name} → ${tc.name}`,\n tc.uri || prog.uri || '',\n systemName,\n );\n console.log(`\\n ${classLink}`);\n\n for (const method of failedMethods) {\n displayFailedMethod(method);\n }\n }\n }\n}\n\n/**\n * Resolve target URIs and name from command options\n */\nasync function resolveTargets(options: {\n fromFile?: string;\n transport?: string;\n class?: string;\n package?: string;\n object?: string;\n}): Promise<{ targetUris: string[]; targetName: string }> {\n if (options.fromFile) {\n const { readFileSync } = await import('node:fs');\n const content = readFileSync(options.fromFile, 'utf-8');\n const targetUris = content\n .split('\\n')\n .map((line) => line.trim())\n .filter((line) => line && !line.startsWith('#'));\n return {\n targetUris,\n targetName: `${targetUris.length} objects from ${options.fromFile}`,\n };\n }\n if (options.transport) {\n return {\n targetUris: [\n `/sap/bc/adt/cts/transportrequests/${options.transport.toUpperCase()}`,\n ],\n targetName: `Transport ${options.transport.toUpperCase()}`,\n };\n }\n if (options.class) {\n return {\n targetUris: [`/sap/bc/adt/oo/classes/${options.class.toLowerCase()}`],\n targetName: `Class ${options.class.toUpperCase()}`,\n };\n }\n if (options.package) {\n return {\n targetUris: [`/sap/bc/adt/packages/${options.package.toUpperCase()}`],\n targetName: `Package ${options.package.toUpperCase()}`,\n };\n }\n return { targetUris: [options.object!], targetName: options.object! };\n}\n\n/**\n * AUnit Command Plugin\n */\nexport const aunitCommand: CliCommandPlugin = {\n name: 'aunit',\n description: 'Run ABAP Unit tests',\n\n options: [\n {\n flags: '-p, --package <package>',\n description: 'Run tests on package',\n },\n {\n flags: '-o, --object <uri>',\n description:\n 'Run tests on specific object (e.g., /sap/bc/adt/oo/classes/zcl_my_class)',\n },\n {\n flags: '-c, --class <name>',\n description: 'Run tests on ABAP class (e.g., ZCL_MY_CLASS)',\n },\n {\n flags: '-t, --transport <transport>',\n description: 'Run tests on transport request (e.g., NPLK900042)',\n },\n {\n flags: '-f, --from-file <file>',\n description: 'Run tests on objects listed in file (one URI per line)',\n },\n {\n flags: '--format <format>',\n description: 'Output format: console, json, junit, sonar',\n default: 'console',\n },\n {\n flags: '--output <file>',\n description:\n 'Output file (required for junit/sonar format, e.g., aunit-report.xml)',\n },\n {\n flags: '--coverage',\n description:\n 'Request ABAP code coverage with the test run and follow the resulting measurement link',\n },\n {\n flags: '--coverage-output <file>',\n description:\n 'Write the coverage report to this file (default: stdout for jacoco/sonar-generic)',\n },\n {\n flags: '--coverage-format <format>',\n description: 'Coverage format: jacoco | sonar-generic',\n default: 'jacoco',\n },\n ],\n\n async execute(args, ctx: CliContext) {\n const options = args as {\n package?: string;\n object?: string;\n class?: string;\n transport?: string;\n fromFile?: string;\n format?: OutputFormat;\n output?: string;\n coverage?: boolean;\n coverageOutput?: string;\n coverageFormat?: 'jacoco' | 'sonar-generic';\n };\n\n // Validate: at least one target\n const targetCount = [\n options.package,\n options.object,\n options.class,\n options.transport,\n options.fromFile,\n ].filter(Boolean).length;\n\n if (targetCount === 0) {\n ctx.logger.error(\n '❌ One of --package, --object, --class, --transport, or --from-file is required',\n );\n process.exit(1);\n }\n\n if (targetCount > 1) {\n ctx.logger.error(\n '❌ Only one of --package, --object, --class, --transport, or --from-file can be specified',\n );\n process.exit(1);\n }\n\n // Validate output file for junit/sonar format\n if (\n (options.format === 'junit' || options.format === 'sonar') &&\n !options.output\n ) {\n ctx.logger.error(`❌ --output is required for ${options.format} format`);\n process.exit(1);\n }\n\n // Get ADT client\n if (!ctx.getAdtClient) {\n ctx.logger.error('❌ ADT client not available. Run: adt auth login');\n process.exit(1);\n }\n\n const client = (await ctx.getAdtClient()) as AdtClient;\n\n // Determine targets\n const { targetUris, targetName } = await resolveTargets(options);\n\n if (options.fromFile && targetUris.length === 0) {\n ctx.logger.error(`❌ No objects found in ${options.fromFile}`);\n process.exit(1);\n }\n\n ctx.logger.info(`🧪 Running ABAP Unit tests on ${targetName}...`);\n\n // Execute test run\n const body = buildRunConfiguration(targetUris, options.coverage === true);\n const response = await client.adt.aunit.testruns.post(body);\n\n // Convert to normalized result\n const result = convertResponse(response);\n\n // Output\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else if (options.format === 'junit' && options.output) {\n await outputJunitReport(result, options.output);\n // Also print summary to console\n displayResults(result, ctx.adtSystemName);\n } else if (options.format === 'sonar' && options.output) {\n outputSonarReport(result, options.output);\n // Also print summary to console\n displayResults(result, ctx.adtSystemName);\n } else {\n displayResults(result, ctx.adtSystemName);\n }\n\n // Coverage reporting – only if the user explicitly asked for it.\n if (options.coverage) {\n const measurementId = extractCoverageMeasurementId(response);\n if (!measurementId) {\n ctx.logger.warn?.(\n '⚠️ Coverage requested but SAP returned no measurement link. ' +\n 'The system may not have coverage collection enabled for this user/package, ' +\n 'or additional system configuration is required.',\n );\n } else if (!client.adt.runtime) {\n ctx.logger.warn?.(\n '⚠️ Coverage link present but the runtime/traces contract is not available on this client.',\n );\n } else {\n try {\n const cov = client.adt.runtime.traces.coverage;\n const { measurements, statements } = await fetchCoverageData(\n cov,\n measurementId,\n targetUris,\n );\n const format = options.coverageFormat ?? 'jacoco';\n const sourcePathResolver = createAbapGitCoverageSourceResolver();\n const xml =\n format === 'sonar-generic'\n ? toSonarGenericCoverageXml({\n measurements,\n statements,\n sourcePathResolver,\n })\n : toJacocoXml({\n measurements,\n statements,\n sourcePathResolver,\n });\n if (options.coverageOutput) {\n const { writeFileSync } = await import('node:fs');\n writeFileSync(options.coverageOutput, xml, 'utf-8');\n ctx.logger.info(\n `📊 Coverage report (${format}) written to ${options.coverageOutput}`,\n );\n } else {\n console.log(xml);\n }\n } catch (err) {\n ctx.logger.error(\n `❌ Coverage collection failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n\n // Exit with non-zero code equal to number of failures+errors (sapcli convention)\n const failures = result.failCount + result.errorCount;\n if (failures > 0) {\n process.exit(failures > 125 ? 125 : failures);\n }\n },\n};\n\nexport default aunitCommand;\n"],"mappings":"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"}