@mcp-abap-adt/adt-clients 37.0.0 → 37.1.0

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
@@ -157,7 +157,7 @@ npm install @mcp-abap-adt/adt-clients
157
157
  - Typed execution API based on `IExecutor`
158
158
  - Executors:
159
159
  - `getClassExecutor()` for `classrun`
160
- - `getClassTestRunner()` for a class's ABAP Unit tests: `run`, `getStatus`, `getResult`
160
+ - `getClassTestRunner()` for a class's ABAP Unit tests: `run`, `getStatus`, `getResult` (asynchronous, the default), or `runSync` (synchronous)
161
161
  - `getProgramTestRunner()` for a report's ABAP Unit tests, in its source or its includes
162
162
  - `getFunctionGroupTestRunner()` / `getFunctionModuleTestRunner()` for a function group's tests, or the ones exercising one module
163
163
  - `getProgramExecutor()` for `programrun` (on-premise systems)
@@ -440,6 +440,28 @@ const result = await runner.getResult(runId, { analyse: analyseUnitTest });
440
440
  `{ containerClass, testClass }`. On a legacy system the runner's `run` answers
441
441
  the finished result, and `getStatus`/`getResult` refuse without a request.
442
442
 
443
+ **The synchronous runner, `runSync`.** `run` starts a run on
444
+ `/sap/bc/adt/abapunit/runs`, which is then polled; that stays the default.
445
+ `runSync` is the other runner ADT has — one `POST /sap/bc/adt/abapunit/testruns`
446
+ answering the finished `aunit:runResult` — chosen explicitly by the caller.
447
+ Some systems have only this one (an on-prem S/4HANA 2023, SAP_BASIS 758, was
448
+ measured without `/abapunit/runs`); whether a system has `/runs` is in its
449
+ discovery, and reading that is the caller's — the library picks no runner by
450
+ release or by discovery.
451
+
452
+ ```typescript
453
+ const result = await runner.runSync('ZCL_UNDER_TEST', { analyse: analyseUnitTest });
454
+ ```
455
+
456
+ Its answer is read by the runner's `result` strategy, the one `getResult` uses:
457
+ on the measured systems (on premise and ABAP Cloud, 2026-10-22) both runners
458
+ answer the same document — `aunit:runResult` in `http://www.sap.com/adt/aunit`
459
+ — with the same methods and alerts. Two differences: the endpoint addresses
460
+ whole classes, so `{ containerClass, testClass }` targets run each distinct
461
+ container whole; and `title` and `context` are not sent. Scope, risk levels and
462
+ durations are, with `run`'s defaults. On a legacy system `runSync` is the
463
+ request `run` already sends there, read by `result`.
464
+
443
465
  A report's tests are local classes in its source or in an include it pulls in —
444
466
  written through `getProgram().update()` or `getInclude().update()` — and run
445
467
  through `executor.getProgramTestRunner().run(programName)`, with the same
@@ -6,6 +6,6 @@ export { AdtLocalDefinitions, type ILocalDefinitionsConfig, } from './AdtLocalDe
6
6
  export { AdtLocalMacros, type ILocalMacrosConfig } from './AdtLocalMacros';
7
7
  export { AdtLocalTestClass, type ILocalTestClassConfig, } from './AdtLocalTestClass';
8
8
  export { AdtLocalTypes, type ILocalTypesConfig } from './AdtLocalTypes';
9
- export { getClassUnitTestResult, getClassUnitTestStatus, IClassUnitTestDefinition, IClassUnitTestRunOptions, startClassUnitTestRun, startClassUnitTestRunByObject, } from './run';
9
+ export { getClassUnitTestResult, getClassUnitTestStatus, IClassUnitTestDefinition, IClassUnitTestRunOptions, startClassUnitTestRun, startClassUnitTestRunByObject, startClassUnitTestRunSync, } from './run';
10
10
  export * from './types';
11
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/class/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,mBAAmB,EACnB,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,OAAO,CAAC;AACf,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/class/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,mBAAmB,EACnB,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,GAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACxE,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,wBAAwB,EACxB,wBAAwB,EACxB,qBAAqB,EACrB,6BAA6B,EAC7B,yBAAyB,GAC1B,MAAM,OAAO,CAAC;AACf,cAAc,SAAS,CAAC"}
@@ -17,7 +17,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
17
17
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.startClassUnitTestRunByObject = exports.startClassUnitTestRun = exports.getClassUnitTestStatus = exports.getClassUnitTestResult = exports.AdtLocalTypes = exports.AdtLocalTestClass = exports.AdtLocalMacros = exports.AdtLocalDefinitions = exports.AdtClass = void 0;
20
+ exports.startClassUnitTestRunSync = exports.startClassUnitTestRunByObject = exports.startClassUnitTestRun = exports.getClassUnitTestStatus = exports.getClassUnitTestResult = exports.AdtLocalTypes = exports.AdtLocalTestClass = exports.AdtLocalMacros = exports.AdtLocalDefinitions = exports.AdtClass = void 0;
21
21
  var AdtClass_1 = require("./AdtClass");
22
22
  Object.defineProperty(exports, "AdtClass", { enumerable: true, get: function () { return AdtClass_1.AdtClass; } });
23
23
  var AdtLocalDefinitions_1 = require("./AdtLocalDefinitions");
@@ -33,4 +33,5 @@ Object.defineProperty(exports, "getClassUnitTestResult", { enumerable: true, get
33
33
  Object.defineProperty(exports, "getClassUnitTestStatus", { enumerable: true, get: function () { return run_1.getClassUnitTestStatus; } });
34
34
  Object.defineProperty(exports, "startClassUnitTestRun", { enumerable: true, get: function () { return run_1.startClassUnitTestRun; } });
35
35
  Object.defineProperty(exports, "startClassUnitTestRunByObject", { enumerable: true, get: function () { return run_1.startClassUnitTestRunByObject; } });
36
+ Object.defineProperty(exports, "startClassUnitTestRunSync", { enumerable: true, get: function () { return run_1.startClassUnitTestRunSync; } });
36
37
  __exportStar(require("./types"), exports);
@@ -61,4 +61,39 @@ export declare const getClassUnitTestResult: typeof getUnitTestRunResult;
61
61
  * container runs without the caller naming one.
62
62
  */
63
63
  export declare function startClassUnitTestRunByObject(connection: IAbapConnection, className: string, options?: IClassUnitTestRunOptions): Promise<IAdtWireResponse>;
64
+ /**
65
+ * Run the ABAP Unit tests of whole classes and answer the finished result —
66
+ * `POST /sap/bc/adt/abapunit/testruns`, one request, no run to poll.
67
+ *
68
+ * The one implementation of this endpoint: the regular runner's `runSync` and
69
+ * the legacy runner's `run` both send it.
70
+ *
71
+ * **Measured:** synchronous on a modern and a legacy on-prem system, over HTTP
72
+ * and over RFC — `<aunit:runResult>` in the POST's answer, no `Location` — and
73
+ * the typed and `application/xml` bodies byte-identical; the legacy system
74
+ * ignores a typed `Accept` and answers `application/xml` either way. Advertised
75
+ * (2026-10-22) by an on-prem system and an ABAP Cloud system alike, beside
76
+ * `/abapunit/runs`; and on an on-prem S/4HANA 2023 that has no `/runs` at all
77
+ * (fr0ster/mcp-abap-adt#286).
78
+ *
79
+ * **Reading the result: `kind` carries the distinction, `severity` does not.**
80
+ * A failed assertion and an uncaught exception both come back
81
+ * `severity="critical"`, so a parser classifying on severity reports a short
82
+ * dump as a failed test. `kind` separates them: `failedAssertion`,
83
+ * `exception`, `noTestClasses`.
84
+ *
85
+ * The format differs from `/runs`:
86
+ * - root `aunit:runConfiguration` in `http://www.sap.com/adt/aunit` (not
87
+ * `aunit:run` in `http://www.sap.com/adt/api/aunit`);
88
+ * - objects by URI in `adtcore:objectReferences` — so a run covers whole
89
+ * classes: the format has no way to name one test class inside its
90
+ * container;
91
+ * - `application/xml` for `Content-Type` and `Accept`.
92
+ *
93
+ * `options` absent sends no `<options>` element — the body the legacy runner
94
+ * has always sent, unchanged. Given, the scope, risk levels and durations go in
95
+ * `<options>` with the same defaults `/runs` gets; `title` and `context` have
96
+ * no place in this format and are not sent.
97
+ */
98
+ export declare function startClassUnitTestRunSync(connection: IAbapConnection, classNames: string[], options?: IClassUnitTestRunOptions): Promise<IAdtWireResponse>;
64
99
  //# sourceMappingURL=run.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/core/class/run.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AAIjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,eAAe,EAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,OAAc,EACxB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AAOD,YAAY,EACV,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,8BAA8B,CAAC;AAMtC,OAAO,KAAK,EACV,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EAErB,MAAM,oBAAoB,CAAC;AAE5B,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,eAAe,EAC3B,KAAK,EAAE,wBAAwB,EAAE,EACjC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CA6C3B;AAED,mFAAmF;AACnF,eAAO,MAAM,sBAAsB,6BAAuB,CAAC;AAE3D,qEAAqE;AACrE,eAAO,MAAM,sBAAsB,6BAAuB,CAAC;AAE3D;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,eAAe,EAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CAM3B"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/core/class/run.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AAKjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAsB,QAAQ,CAC5B,UAAU,EAAE,eAAe,EAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,OAAc,EACxB,UAAU,CAAC,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,YAAY,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAoB3B;AAOD,YAAY,EACV,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,8BAA8B,CAAC;AAMtC,OAAO,KAAK,EACV,wBAAwB,EACxB,wBAAwB,EACzB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EAErB,MAAM,oBAAoB,CAAC;AAE5B,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,eAAe,EAC3B,KAAK,EAAE,wBAAwB,EAAE,EACjC,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CA6C3B;AAED,mFAAmF;AACnF,eAAO,MAAM,sBAAsB,6BAAuB,CAAC;AAE3D,qEAAqE;AACrE,eAAO,MAAM,sBAAsB,6BAAuB,CAAC;AAE3D;;;;GAIG;AACH,wBAAsB,6BAA6B,CACjD,UAAU,EAAE,eAAe,EAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CAM3B;AASD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,yBAAyB,CAC7C,UAAU,EAAE,eAAe,EAC3B,UAAU,EAAE,MAAM,EAAE,EACpB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,gBAAgB,CAAC,CA8B3B"}
@@ -7,6 +7,8 @@ exports.getClassUnitTestResult = exports.getClassUnitTestStatus = void 0;
7
7
  exports.runClass = runClass;
8
8
  exports.startClassUnitTestRun = startClassUnitTestRun;
9
9
  exports.startClassUnitTestRunByObject = startClassUnitTestRunByObject;
10
+ exports.startClassUnitTestRunSync = startClassUnitTestRunSync;
11
+ const objects_1 = require("../../endpoints/objects");
10
12
  const passport_1 = require("../../runtime/debugger/passport");
11
13
  const xml_1 = require("../../utils/xml");
12
14
  /**
@@ -126,3 +128,89 @@ exports.getClassUnitTestResult = abapUnit_1.getUnitTestRunResult;
126
128
  async function startClassUnitTestRunByObject(connection, className, options) {
127
129
  return (0, abapUnit_1.startUnitTestRunByObject)(connection, { name: className, type: 'CLAS' }, options);
128
130
  }
131
+ // =============================================================================
132
+ // The synchronous runner: /abapunit/testruns
133
+ // =============================================================================
134
+ const CT_TESTRUNS = 'application/xml';
135
+ const ACCEPT_TESTRUNS = 'application/xml';
136
+ /**
137
+ * Run the ABAP Unit tests of whole classes and answer the finished result —
138
+ * `POST /sap/bc/adt/abapunit/testruns`, one request, no run to poll.
139
+ *
140
+ * The one implementation of this endpoint: the regular runner's `runSync` and
141
+ * the legacy runner's `run` both send it.
142
+ *
143
+ * **Measured:** synchronous on a modern and a legacy on-prem system, over HTTP
144
+ * and over RFC — `<aunit:runResult>` in the POST's answer, no `Location` — and
145
+ * the typed and `application/xml` bodies byte-identical; the legacy system
146
+ * ignores a typed `Accept` and answers `application/xml` either way. Advertised
147
+ * (2026-10-22) by an on-prem system and an ABAP Cloud system alike, beside
148
+ * `/abapunit/runs`; and on an on-prem S/4HANA 2023 that has no `/runs` at all
149
+ * (fr0ster/mcp-abap-adt#286).
150
+ *
151
+ * **Reading the result: `kind` carries the distinction, `severity` does not.**
152
+ * A failed assertion and an uncaught exception both come back
153
+ * `severity="critical"`, so a parser classifying on severity reports a short
154
+ * dump as a failed test. `kind` separates them: `failedAssertion`,
155
+ * `exception`, `noTestClasses`.
156
+ *
157
+ * The format differs from `/runs`:
158
+ * - root `aunit:runConfiguration` in `http://www.sap.com/adt/aunit` (not
159
+ * `aunit:run` in `http://www.sap.com/adt/api/aunit`);
160
+ * - objects by URI in `adtcore:objectReferences` — so a run covers whole
161
+ * classes: the format has no way to name one test class inside its
162
+ * container;
163
+ * - `application/xml` for `Content-Type` and `Accept`.
164
+ *
165
+ * `options` absent sends no `<options>` element — the body the legacy runner
166
+ * has always sent, unchanged. Given, the scope, risk levels and durations go in
167
+ * `<options>` with the same defaults `/runs` gets; `title` and `context` have
168
+ * no place in this format and are not sent.
169
+ */
170
+ async function startClassUnitTestRunSync(connection, classNames, options) {
171
+ const objectRefs = classNames
172
+ .map((name) => {
173
+ return ` <adtcore:objectReference adtcore:uri="${(0, xml_1.escapeXmlAttr)(objects_1.CLASS.uri(name))}"/>`;
174
+ })
175
+ .join('\n');
176
+ const xml = `<?xml version="1.0" encoding="UTF-8"?><aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit">
177
+ <external>
178
+ <coverage active="false"/>
179
+ </external>
180
+ ${options ? testRunsOptions(options) : ''} <adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core">
181
+ <objectSet kind="inclusive">
182
+ <adtcore:objectReferences>
183
+ ${objectRefs}
184
+ </adtcore:objectReferences>
185
+ </objectSet>
186
+ </adtcore:objectSets>
187
+ </aunit:runConfiguration>`;
188
+ return connection.makeAdtRequest({
189
+ url: '/sap/bc/adt/abapunit/testruns',
190
+ method: 'POST',
191
+ timeout: 0,
192
+ data: xml,
193
+ headers: {
194
+ 'Content-Type': CT_TESTRUNS,
195
+ Accept: ACCEPT_TESTRUNS,
196
+ },
197
+ });
198
+ }
199
+ /**
200
+ * `<options>` of a `runConfiguration`, as Eclipse sends it. The scope's three
201
+ * flags are `testDeterminationStrategy`'s: own tests are `sameProgram`,
202
+ * foreign tests `assignedTests`, their preview `appendAssignedTestsPreview`.
203
+ */
204
+ function testRunsOptions(options) {
205
+ const scope = options.scope ?? {};
206
+ const risk = options.riskLevel ?? {};
207
+ const duration = options.duration ?? {};
208
+ return ` <options>
209
+ <uriType value="semantic"/>
210
+ <testDeterminationStrategy sameProgram="${boolAttr(scope.ownTests, true)}" assignedTests="${boolAttr(scope.foreignTests, false)}" appendAssignedTestsPreview="${boolAttr(scope.addForeignTestsAsPreview, true)}"/>
211
+ <testRiskLevels harmless="${boolAttr(risk.harmless, true)}" dangerous="${boolAttr(risk.dangerous, true)}" critical="${boolAttr(risk.critical, true)}"/>
212
+ <testDurations short="${boolAttr(duration.short, true)}" medium="${boolAttr(duration.medium, true)}" long="${boolAttr(duration.long, true)}"/>
213
+ <withNavigationUri enabled="true"/>
214
+ </options>
215
+ `;
216
+ }
@@ -27,7 +27,12 @@ import { AbapUnitRunner, type IAbapUnitRunnerResults } from '../abapUnitRunner';
27
27
  * whose every test class runs.
28
28
  */
29
29
  export type IClassTestRunTarget = IClassUnitTestDefinition[] | string;
30
- /** One strategy per distinct answer: starting a run, polling it, its result. */
30
+ /** The distinct containers a target names: what `/testruns` addresses. */
31
+ export declare function containersOf(target: IClassTestRunTarget): string[];
32
+ /**
33
+ * One strategy per distinct answer: starting a run, polling it, its result.
34
+ * `runSync` answers the result document itself, so it is read by `result`.
35
+ */
31
36
  export type IClassTestRunnerResults = IAbapUnitRunnerResults;
32
37
  /** The shipped default: documents as they arrived. */
33
38
  export declare const classTestRunnerDocuments: {
@@ -49,5 +54,26 @@ export declare class ClassTestRunner<R extends IClassTestRunnerResults = typeof
49
54
  * read as a failure.
50
55
  */
51
56
  run<E extends IAdtError = IAdtError>(target: IClassTestRunTarget, options?: IClassUnitTestRunOptions & IAdtAnalyseOptions<E>): Promise<IAdtResponse<ReturnType<R['run']>, E>>;
57
+ /**
58
+ * Run the tests and answer the finished result. One POST, to
59
+ * `/sap/bc/adt/abapunit/testruns` — the synchronous runner, beside the
60
+ * asynchronous `run` + `getStatus` + `getResult`, which stays the default.
61
+ *
62
+ * An explicit choice of the caller's: nothing here asks the system which
63
+ * runner it has. A system without `/abapunit/runs` (an on-prem S/4HANA 2023
64
+ * was measured with `/testruns` alone) needs this one; whether a system has
65
+ * it is in its discovery, which is the caller's to read.
66
+ *
67
+ * The answer is the `aunit:runResult` document, so it is read by this
68
+ * runner's `result` strategy — the one `getResult` uses — and a failing test
69
+ * is in it as an `alert`; whether that is a failure is `options.analyse`'s
70
+ * (`analyseUnitTest` from @mcp-abap-adt/adt-strategies reads it so).
71
+ *
72
+ * Differences from `run`: the endpoint addresses whole classes, so a target
73
+ * of test definitions runs each distinct container whole, and `title` and
74
+ * `context` are not sent; scope, risk levels and durations are, with the
75
+ * same defaults.
76
+ */
77
+ runSync<E extends IAdtError = IAdtError>(target: IClassTestRunTarget, options?: IClassUnitTestRunOptions & IAdtAnalyseOptions<E>): Promise<IAdtResponse<ReturnType<R['result']>, E>>;
52
78
  }
53
79
  //# sourceMappingURL=ClassTestRunner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ClassTestRunner.d.ts","sourceRoot":"","sources":["../../../src/executors/class/ClassTestRunner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAG9D,OAAO,EACL,cAAc,EAEd,KAAK,sBAAsB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,wBAAwB,EAAE,GAAG,MAAM,CAAC;AAEtE,gFAAgF;AAChF,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,sDAAsD;AACtD,eAAO,MAAM,wBAAwB;;;;CAA0B,CAAC;AAEhE,qBAAa,eAAe,CACxB,CAAC,SAAS,uBAAuB,GAAG,OAAO,wBAAwB,CAErE,SAAQ,cAAc,CAAC,CAAC,CACxB,YACE,YAAY,CACV,mBAAmB,EACnB,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EACpB,wBAAwB,CACzB;gBAGD,UAAU,EAAE,eAAe,EAC3B,MAAM,CAAC,EAAE,OAAO,EAEhB,OAAO,GAAE,CAA4C;IAKvD;;;;;;;;;;OAUG;IACG,GAAG,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EACvC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,CAAC,EAAE,wBAAwB,GAAG,kBAAkB,CAAC,CAAC,CAAC,GACzD,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAUlD"}
1
+ {"version":3,"file":"ClassTestRunner.d.ts","sourceRoot":"","sources":["../../../src/executors/class/ClassTestRunner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,wBAAwB,EACxB,wBAAwB,EACxB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAC/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAM9D,OAAO,EACL,cAAc,EAEd,KAAK,sBAAsB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,wBAAwB,EAAE,GAAG,MAAM,CAAC;AAEtE,0EAA0E;AAC1E,wBAAgB,YAAY,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAIlE;AAED;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D,sDAAsD;AACtD,eAAO,MAAM,wBAAwB;;;;CAA0B,CAAC;AAEhE,qBAAa,eAAe,CACxB,CAAC,SAAS,uBAAuB,GAAG,OAAO,wBAAwB,CAErE,SAAQ,cAAc,CAAC,CAAC,CACxB,YACE,YAAY,CACV,mBAAmB,EACnB,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EACpB,wBAAwB,CACzB;gBAGD,UAAU,EAAE,eAAe,EAC3B,MAAM,CAAC,EAAE,OAAO,EAEhB,OAAO,GAAE,CAA4C;IAKvD;;;;;;;;;;OAUG;IACG,GAAG,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EACvC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,CAAC,EAAE,wBAAwB,GAAG,kBAAkB,CAAC,CAAC,CAAC,GACzD,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAWjD;;;;;;;;;;;;;;;;;;;OAmBG;IACG,OAAO,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EAC3C,MAAM,EAAE,mBAAmB,EAC3B,OAAO,CAAC,EAAE,wBAAwB,GAAG,kBAAkB,CAAC,CAAC,CAAC,GACzD,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CAarD"}
@@ -21,9 +21,16 @@
21
21
  */
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
23
  exports.ClassTestRunner = exports.classTestRunnerDocuments = void 0;
24
+ exports.containersOf = containersOf;
24
25
  const run_1 = require("../../core/class/run");
25
26
  const adtResponse_1 = require("../../utils/adtResponse");
26
27
  const abapUnitRunner_1 = require("../abapUnitRunner");
28
+ /** The distinct containers a target names: what `/testruns` addresses. */
29
+ function containersOf(target) {
30
+ return typeof target === 'string'
31
+ ? [target]
32
+ : [...new Set(target.map((test) => test.containerClass))];
33
+ }
27
34
  /** The shipped default: documents as they arrived. */
28
35
  exports.classTestRunnerDocuments = abapUnitRunner_1.abapUnitRunnerDocuments;
29
36
  class ClassTestRunner extends abapUnitRunner_1.AbapUnitRunner {
@@ -49,5 +56,29 @@ class ClassTestRunner extends abapUnitRunner_1.AbapUnitRunner {
49
56
  }
50
57
  return (0, adtResponse_1.answering)(() => (0, run_1.startClassUnitTestRun)(this.connection, target, options), this.results.run, options?.analyse);
51
58
  }
59
+ /**
60
+ * Run the tests and answer the finished result. One POST, to
61
+ * `/sap/bc/adt/abapunit/testruns` — the synchronous runner, beside the
62
+ * asynchronous `run` + `getStatus` + `getResult`, which stays the default.
63
+ *
64
+ * An explicit choice of the caller's: nothing here asks the system which
65
+ * runner it has. A system without `/abapunit/runs` (an on-prem S/4HANA 2023
66
+ * was measured with `/testruns` alone) needs this one; whether a system has
67
+ * it is in its discovery, which is the caller's to read.
68
+ *
69
+ * The answer is the `aunit:runResult` document, so it is read by this
70
+ * runner's `result` strategy — the one `getResult` uses — and a failing test
71
+ * is in it as an `alert`; whether that is a failure is `options.analyse`'s
72
+ * (`analyseUnitTest` from @mcp-abap-adt/adt-strategies reads it so).
73
+ *
74
+ * Differences from `run`: the endpoint addresses whole classes, so a target
75
+ * of test definitions runs each distinct container whole, and `title` and
76
+ * `context` are not sent; scope, risk levels and durations are, with the
77
+ * same defaults.
78
+ */
79
+ async runSync(target, options) {
80
+ const { analyse, ...runOptions } = options ?? {};
81
+ return (0, adtResponse_1.answering)(() => (0, run_1.startClassUnitTestRunSync)(this.connection, containersOf(target), runOptions), this.results.result, analyse);
82
+ }
52
83
  }
53
84
  exports.ClassTestRunner = ClassTestRunner;
@@ -21,6 +21,12 @@ export declare class ClassTestRunnerLegacy<R extends IClassTestRunnerResults = t
21
21
  * result document itself, not an id.
22
22
  */
23
23
  run<E extends IAdtError = IAdtError>(target: IClassTestRunTarget, options?: IClassUnitTestRunOptions & IAdtAnalyseOptions<E>): Promise<IAdtResponse<ReturnType<R['run']>, E>>;
24
+ /**
25
+ * The same request as `run` — on a legacy system `run` is already the
26
+ * synchronous one — answered through the `result` strategy, as the regular
27
+ * runner's `runSync` is.
28
+ */
29
+ runSync<E extends IAdtError = IAdtError>(target: IClassTestRunTarget, options?: IClassUnitTestRunOptions & IAdtAnalyseOptions<E>): Promise<IAdtResponse<ReturnType<R['result']>, E>>;
24
30
  /**
25
31
  * Refused without a request: a legacy system finishes a run before it
26
32
  * answers and exposes no run to poll. `run`'s own answer is the result.
@@ -1 +1 @@
1
- {"version":3,"file":"ClassTestRunnerLegacy.d.ts","sourceRoot":"","sources":["../../../src/executors/class/ClassTestRunnerLegacy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,wBAAwB,EAEzB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EACL,eAAe,EACf,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACzB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,qBAAqB,CAChC,CAAC,SAAS,uBAAuB,GAAG,OAAO,wBAAwB,CACnE,SAAQ,eAAe,CAAC,CAAC,CAAC;IAC1B;;;;;OAKG;IACY,GAAG,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EAChD,MAAM,EAAE,mBAAmB,EAC3B,OAAO,CAAC,EAAE,wBAAwB,GAAG,kBAAkB,CAAC,CAAC,CAAC,GACzD,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAYjD;;;OAGG;IACY,SAAS,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,KAAK,OAAO,CAClE,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CACzC;IASD,0EAA0E;IAC3D,SAAS,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,KAAK,OAAO,CAClE,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CACzC;CAQF"}
1
+ {"version":3,"file":"ClassTestRunnerLegacy.d.ts","sourceRoot":"","sources":["../../../src/executors/class/ClassTestRunnerLegacy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,wBAAwB,EAEzB,MAAM,8BAA8B,CAAC;AAItC,OAAO,EACL,eAAe,EACf,KAAK,wBAAwB,EAE7B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACzB,MAAM,mBAAmB,CAAC;AAE3B,qBAAa,qBAAqB,CAChC,CAAC,SAAS,uBAAuB,GAAG,OAAO,wBAAwB,CACnE,SAAQ,eAAe,CAAC,CAAC,CAAC;IAC1B;;;;;OAKG;IACY,GAAG,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EAChD,MAAM,EAAE,mBAAmB,EAC3B,OAAO,CAAC,EAAE,wBAAwB,GAAG,kBAAkB,CAAC,CAAC,CAAC,GACzD,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAUjD;;;;OAIG;IACY,OAAO,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EACpD,MAAM,EAAE,mBAAmB,EAC3B,OAAO,CAAC,EAAE,wBAAwB,GAAG,kBAAkB,CAAC,CAAC,CAAC,GACzD,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAQpD;;;OAGG;IACY,SAAS,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,KAAK,OAAO,CAClE,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CACzC;IASD,0EAA0E;IAC3D,SAAS,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,KAAK,OAAO,CAClE,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CACzC;CAQF"}
@@ -15,7 +15,7 @@
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.ClassTestRunnerLegacy = void 0;
17
17
  const interfaces_adt_1 = require("@mcp-abap-adt/interfaces-adt");
18
- const runLegacy_1 = require("../../core/class/runLegacy");
18
+ const run_1 = require("../../core/class/run");
19
19
  const adtResponse_1 = require("../../utils/adtResponse");
20
20
  const ClassTestRunner_1 = require("./ClassTestRunner");
21
21
  class ClassTestRunnerLegacy extends ClassTestRunner_1.ClassTestRunner {
@@ -26,10 +26,17 @@ class ClassTestRunnerLegacy extends ClassTestRunner_1.ClassTestRunner {
26
26
  * result document itself, not an id.
27
27
  */
28
28
  async run(target, options) {
29
- const classNames = typeof target === 'string'
30
- ? [target]
31
- : [...new Set(target.map((test) => test.containerClass))];
32
- return (0, adtResponse_1.answering)(() => (0, runLegacy_1.startClassUnitTestRunLegacy)(this.connection, classNames), this.results.run, options?.analyse);
29
+ // No options: the body a legacy system has always been sent — the
30
+ // `<options>` element `runSync` sends is unmeasured there.
31
+ return (0, adtResponse_1.answering)(() => (0, run_1.startClassUnitTestRunSync)(this.connection, (0, ClassTestRunner_1.containersOf)(target)), this.results.run, options?.analyse);
32
+ }
33
+ /**
34
+ * The same request as `run` — on a legacy system `run` is already the
35
+ * synchronous one — answered through the `result` strategy, as the regular
36
+ * runner's `runSync` is.
37
+ */
38
+ async runSync(target, options) {
39
+ return (0, adtResponse_1.answering)(() => (0, run_1.startClassUnitTestRunSync)(this.connection, (0, ClassTestRunner_1.containersOf)(target)), this.results.result, options?.analyse);
33
40
  }
34
41
  /**
35
42
  * Refused without a request: a legacy system finishes a run before it
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-abap-adt/adt-clients",
3
- "version": "37.0.0",
3
+ "version": "37.1.0",
4
4
  "description": "ADT clients for SAP ABAP systems - AdtClient and AdtRuntimeClient",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,42 +0,0 @@
1
- /**
2
- * ABAP Unit test run operations for legacy systems (BASIS < 7.50)
3
- *
4
- * Legacy systems use:
5
- * - /sap/bc/adt/abapunit/testruns instead of /sap/bc/adt/abapunit/runs
6
- * - application/xml for Content-Type and Accept (not versioned vnd.sap.adt.api.abapunit.* types)
7
- */
8
- import type { IAbapConnection, IAdtWireResponse } from '@mcp-abap-adt/interfaces-adt-connection';
9
- /**
10
- * Start ABAP Unit test run on legacy systems
11
- * Uses /sap/bc/adt/abapunit/testruns endpoint with aunit:runConfiguration format
12
- *
13
- * There is only a start here. `getClassUnitTestStatusLegacy` and
14
- * `getClassUnitTestResultLegacy` polled `testruns/{runId}` and
15
- * `testruns/{runId}/results`, and were removed once the endpoint was measured:
16
- * the POST answers with the finished result, so there was never a run to poll.
17
- * Nothing called them — `ClassTestRunnerLegacy` serves both from the response the
18
- * POST returned — so they were an unmeasured contract kept alive by nobody
19
- * looking.
20
- *
21
- * **Reading the result: `kind` carries the distinction, `severity` does not.** A
22
- * failed assertion and an uncaught exception both come back
23
- * `severity="critical"`, measured on one release — so a parser classifying on
24
- * severity reports a short dump as a failed test. The field that separates them
25
- * is `kind`: `failedAssertion`, `exception`, `noTestClasses`.
26
- *
27
- * Also measured: this endpoint is **synchronous** on both a modern and a legacy
28
- * on-prem system, over HTTP and over RFC — `<aunit:runResult>` with no
29
- * `Location`, and the typed and `application/xml` bodies byte-identical. The
30
- * legacy system ignores the typed `Accept` altogether and answers
31
- * `application/xml` either way.
32
- *
33
- * Legacy format differs from modern:
34
- * - Root element: aunit:runConfiguration (not aunit:run)
35
- * - Namespace: http://www.sap.com/adt/aunit (not http://www.sap.com/adt/api/aunit)
36
- * - Objects via adtcore:objectReferences with URI (not aunit:tests with containerClass/class)
37
- * — so a run covers whole classes: this format has no way to name one test
38
- * class inside its container, and a caller's test class name is not sent.
39
- * - Content-Type/Accept: application/xml (not versioned vnd.sap.adt.api.abapunit.*)
40
- */
41
- export declare function startClassUnitTestRunLegacy(connection: IAbapConnection, classNames: string[]): Promise<IAdtWireResponse>;
42
- //# sourceMappingURL=runLegacy.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"runLegacy.d.ts","sourceRoot":"","sources":["../../../src/core/class/runLegacy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,yCAAyC,CAAC;AAMjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,2BAA2B,CAC/C,UAAU,EAAE,eAAe,EAC3B,UAAU,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC,gBAAgB,CAAC,CA8B3B"}
@@ -1,74 +0,0 @@
1
- "use strict";
2
- /**
3
- * ABAP Unit test run operations for legacy systems (BASIS < 7.50)
4
- *
5
- * Legacy systems use:
6
- * - /sap/bc/adt/abapunit/testruns instead of /sap/bc/adt/abapunit/runs
7
- * - application/xml for Content-Type and Accept (not versioned vnd.sap.adt.api.abapunit.* types)
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.startClassUnitTestRunLegacy = startClassUnitTestRunLegacy;
11
- const objects_1 = require("../../endpoints/objects");
12
- const CT_XML = 'application/xml';
13
- const ACCEPT_XML = 'application/xml';
14
- /**
15
- * Start ABAP Unit test run on legacy systems
16
- * Uses /sap/bc/adt/abapunit/testruns endpoint with aunit:runConfiguration format
17
- *
18
- * There is only a start here. `getClassUnitTestStatusLegacy` and
19
- * `getClassUnitTestResultLegacy` polled `testruns/{runId}` and
20
- * `testruns/{runId}/results`, and were removed once the endpoint was measured:
21
- * the POST answers with the finished result, so there was never a run to poll.
22
- * Nothing called them — `ClassTestRunnerLegacy` serves both from the response the
23
- * POST returned — so they were an unmeasured contract kept alive by nobody
24
- * looking.
25
- *
26
- * **Reading the result: `kind` carries the distinction, `severity` does not.** A
27
- * failed assertion and an uncaught exception both come back
28
- * `severity="critical"`, measured on one release — so a parser classifying on
29
- * severity reports a short dump as a failed test. The field that separates them
30
- * is `kind`: `failedAssertion`, `exception`, `noTestClasses`.
31
- *
32
- * Also measured: this endpoint is **synchronous** on both a modern and a legacy
33
- * on-prem system, over HTTP and over RFC — `<aunit:runResult>` with no
34
- * `Location`, and the typed and `application/xml` bodies byte-identical. The
35
- * legacy system ignores the typed `Accept` altogether and answers
36
- * `application/xml` either way.
37
- *
38
- * Legacy format differs from modern:
39
- * - Root element: aunit:runConfiguration (not aunit:run)
40
- * - Namespace: http://www.sap.com/adt/aunit (not http://www.sap.com/adt/api/aunit)
41
- * - Objects via adtcore:objectReferences with URI (not aunit:tests with containerClass/class)
42
- * — so a run covers whole classes: this format has no way to name one test
43
- * class inside its container, and a caller's test class name is not sent.
44
- * - Content-Type/Accept: application/xml (not versioned vnd.sap.adt.api.abapunit.*)
45
- */
46
- async function startClassUnitTestRunLegacy(connection, classNames) {
47
- const objectRefs = classNames
48
- .map((name) => {
49
- return ` <adtcore:objectReference adtcore:uri="${objects_1.CLASS.uri(name)}"/>`;
50
- })
51
- .join('\n');
52
- const xml = `<?xml version="1.0" encoding="UTF-8"?><aunit:runConfiguration xmlns:aunit="http://www.sap.com/adt/aunit">
53
- <external>
54
- <coverage active="false"/>
55
- </external>
56
- <adtcore:objectSets xmlns:adtcore="http://www.sap.com/adt/core">
57
- <objectSet kind="inclusive">
58
- <adtcore:objectReferences>
59
- ${objectRefs}
60
- </adtcore:objectReferences>
61
- </objectSet>
62
- </adtcore:objectSets>
63
- </aunit:runConfiguration>`;
64
- return connection.makeAdtRequest({
65
- url: '/sap/bc/adt/abapunit/testruns',
66
- method: 'POST',
67
- timeout: 0,
68
- data: xml,
69
- headers: {
70
- 'Content-Type': CT_XML,
71
- Accept: ACCEPT_XML,
72
- },
73
- });
74
- }