@abapify/adt-aunit 0.4.15 → 0.4.18

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.
@@ -2,7 +2,7 @@ import { createAbapGitCoverageSourceResolver, toJacocoXml, toSonarGenericCoverag
2
2
  import { buildCoverageQuery, buildStatementsBulkRequest, extractCoverageMeasurementId, extractCoverageStatementUris } from "@abapify/adt-contracts";
3
3
  import { writeFile } from "node:fs/promises";
4
4
  import { writeFileSync } from "node:fs";
5
- //#region src/formatters/junit.ts
5
+ // src/formatters/junit.ts
6
6
  /**
7
7
  * JUnit XML Formatter
8
8
  *
@@ -95,8 +95,7 @@ async function outputJunitReport(result, outputFile) {
95
95
  console.log(`\n📄 JUnit XML report written to: ${outputFile}`);
96
96
  console.log(`📊 ${result.totalTests} tests (${result.passCount} passed, ${result.failCount} failed, ${result.errorCount} errors)`);
97
97
  }
98
- //#endregion
99
- //#region src/formatters/sonar.ts
98
+ // src/formatters/sonar.ts
100
99
  /**
101
100
  * AUnit Sonar Generic Execution Format Formatter
102
101
  *
@@ -174,8 +173,7 @@ function outputSonarReport(result, filePath) {
174
173
  const xml = toSonarXml(result);
175
174
  writeFileSync(filePath, xml, "utf-8");
176
175
  }
177
- //#endregion
178
- //#region src/commands/aunit.ts
176
+ // src/commands/aunit.ts
179
177
  const ansi = {
180
178
  red: (s) => `\x1b[31m${s}\x1b[0m`,
181
179
  green: (s) => `\x1b[32m${s}\x1b[0m`,
@@ -540,7 +538,6 @@ const aunitCommand = {
540
538
  if (failures > 0) process.exit(failures > 125 ? 125 : failures);
541
539
  }
542
540
  };
543
- //#endregion
544
541
  export { outputJunitReport as n, toJunitXml as r, aunitCommand as t };
545
542
 
546
- //# sourceMappingURL=aunit-9mhc0LYi.mjs.map
543
+ //# sourceMappingURL=aunit-DPZ_v7tc.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"aunit-9mhc0LYi.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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"}
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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"}
@@ -1,9 +1,7 @@
1
1
  import { CliCommandPlugin } from "@abapify/adt-plugin";
2
- //#region src/commands/aunit.d.ts
3
2
  /**
4
3
  * AUnit Command Plugin
5
4
  */
6
5
  declare const aunitCommand: CliCommandPlugin;
7
- //#endregion
8
6
  export { aunitCommand, aunitCommand as default };
9
7
  //# 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":";;;;cAwgBa,cAAc"}
@@ -1,2 +1 @@
1
- import { t as aunitCommand } from "../aunit-9mhc0LYi.mjs";
2
- export { aunitCommand, aunitCommand as default };
1
+ export { t as aunitCommand, t as default } from "../aunit-DPZ_v7tc.mjs";
@@ -1,2 +1,2 @@
1
- import { a as outputSonarGenericCoverageReport, i as outputJacocoReport, n as JacocoInput, o as toJacocoXml, r as createAbapGitCoverageSourceResolver, s as toSonarGenericCoverageXml, t as CoverageSourcePathResolver } from "../jacoco-BNRO5NVU.mjs";
1
+ import { a as outputSonarGenericCoverageReport, i as outputJacocoReport, n as JacocoInput, o as toJacocoXml, r as createAbapGitCoverageSourceResolver, s as toSonarGenericCoverageXml, t as CoverageSourcePathResolver } from "../jacoco-5Mn8hzTC.mjs";
2
2
  export { CoverageSourcePathResolver, JacocoInput, createAbapGitCoverageSourceResolver, outputJacocoReport, outputSonarGenericCoverageReport, toJacocoXml, toSonarGenericCoverageXml };
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, join, relative, sep } from "node:path";
3
3
  import { adtUriToAbapGitPath } from "@abapify/adt-plugin-abapgit";
4
- //#region src/formatters/jacoco.ts
4
+ // src/formatters/jacoco.ts
5
5
  /**
6
6
  * JaCoCo XML coverage report formatter.
7
7
  *
@@ -258,7 +258,6 @@ function toSonarGenericCoverageXml(input) {
258
258
  function outputSonarGenericCoverageReport(input, filePath) {
259
259
  writeFileSync(filePath, toSonarGenericCoverageXml(input), "utf-8");
260
260
  }
261
- //#endregion
262
261
  export { createAbapGitCoverageSourceResolver, outputJacocoReport, outputSonarGenericCoverageReport, toJacocoXml, toSonarGenericCoverageXml };
263
262
 
264
263
  //# sourceMappingURL=jacoco.mjs.map
package/dist/index.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { aunitCommand } from "./commands/aunit.mjs";
2
- import "./jacoco-BNRO5NVU.mjs";
3
- //#region src/types.d.ts
2
+ import "./jacoco-5Mn8hzTC.mjs";
4
3
  /**
5
4
  * AUnit Types
6
5
  *
@@ -92,8 +91,6 @@ interface AunitResult {
92
91
  * Output format options
93
92
  */
94
93
  type OutputFormat = 'console' | 'json' | 'junit' | 'sonar';
95
- //#endregion
96
- //#region src/formatters/junit.d.ts
97
94
  /**
98
95
  * Convert AUnit result to JUnit XML string
99
96
  */
@@ -102,6 +99,5 @@ declare function toJunitXml(result: AunitResult): string;
102
99
  * Write JUnit XML report to file
103
100
  */
104
101
  declare function outputJunitReport(result: AunitResult, outputFile: string): Promise<void>;
105
- //#endregion
106
102
  export { type AunitAlert, type AunitProgram, type AunitResult, type AunitStackEntry, type AunitTestClass, type AunitTestMethod, type OutputFormat, aunitCommand, outputJunitReport, toJunitXml };
107
103
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/formatters/junit.ts"],"mappings":";;;;;;;;;;;UASiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,QAAQ;;;;;UAMO;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,SAAS;;;;;UAMM;;EAEf;;EAEA;;EAEA;;EAEA,aAAa;;;;;UAME;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,OAAO;;;;;UAMQ;EACf;EACA;EACA;EACA;;;;;UAMe;;EAEf,UAAU;;EAEV;EACA;EACA;EACA;EACA;;EAEA;;;;;KAMU;;;;;;iBCLI,WAAW,QAAQ;;;;iBA+Cb,kBACpB,QAAQ,aACR,qBACC"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/formatters/junit.ts"],"mappings":";;;;;;;;;;UASiB;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,QAAQ;;;;;UAMO;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,SAAS;;;;;UAMM;;EAEf;;EAEA;;EAEA;;EAEA,aAAa;;;;;UAME;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA,OAAO;;;;;UAMQ;EACf;EACA;EACA;EACA;;;;;UAMe;;EAEf,UAAU;;EAEV;EACA;EACA;EACA;EACA;;EAEA;;;;;KAMU;;;;iBCLI,WAAW,QAAQ;;;;iBA+Cb,kBACpB,QAAQ,aACR,qBACC"}
package/dist/index.mjs CHANGED
@@ -1,2 +1 @@
1
- import { n as outputJunitReport, r as toJunitXml, t as aunitCommand } from "./aunit-9mhc0LYi.mjs";
2
- export { aunitCommand, outputJunitReport, toJunitXml };
1
+ export { t as aunitCommand, n as outputJunitReport, r as toJunitXml } from "./aunit-DPZ_v7tc.mjs";
@@ -1,5 +1,4 @@
1
1
  import { InferTypedSchema, acoverageResult, acoverageStatements } from "@abapify/adt-schemas";
2
- //#region src/formatters/jacoco.d.ts
3
2
  type AcoverageResultSchema = InferTypedSchema<typeof acoverageResult>;
4
3
  type AcoverageStatementsSchema = InferTypedSchema<typeof acoverageStatements>;
5
4
  type CoverageSourcePathResolver = (reportedPath: string) => string | null;
@@ -15,6 +14,5 @@ declare function toJacocoXml(input: JacocoInput): string;
15
14
  declare function outputJacocoReport(input: JacocoInput, filePath: string): void;
16
15
  declare function toSonarGenericCoverageXml(input: JacocoInput): string;
17
16
  declare function outputSonarGenericCoverageReport(input: JacocoInput, filePath: string): void;
18
- //#endregion
19
17
  export { outputSonarGenericCoverageReport as a, outputJacocoReport as i, JacocoInput as n, toJacocoXml as o, createAbapGitCoverageSourceResolver as r, toSonarGenericCoverageXml as s, CoverageSourcePathResolver as t };
20
- //# sourceMappingURL=jacoco-BNRO5NVU.d.mts.map
18
+ //# sourceMappingURL=jacoco-5Mn8hzTC.d.mts.map
@@ -0,0 +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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/adt-aunit",
3
- "version": "0.4.15",
3
+ "version": "0.4.18",
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.15",
33
- "@abapify/adt-schemas": "0.4.15",
34
- "@abapify/adt-contracts": "0.4.15",
35
- "@abapify/adt-plugin-abapgit": "0.4.15"
32
+ "@abapify/adt-plugin": "0.4.18",
33
+ "@abapify/adt-schemas": "0.4.18",
34
+ "@abapify/adt-contracts": "0.4.18",
35
+ "@abapify/adt-plugin-abapgit": "0.4.18"
36
36
  },
37
37
  "module": "./dist/index.mjs",
38
38
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"jacoco-BNRO5NVU.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"}