From 8e74e724f480577705ef2f800bc30ced1da7afd2 Mon Sep 17 00:00:00 2001 From: johnOC03 Date: Thu, 7 May 2026 16:27:22 +1200 Subject: [PATCH 01/20] =?UTF-8?q?feat:=20js-sdk=20emitter=20=E2=80=94=20lo?= =?UTF-8?q?wer=20path-analyser=20scenarios=20onto=20@camunda8/orchestratio?= =?UTF-8?q?n-cluster-api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements issue #131. Adds a new `js-sdk` emitter target that generates Vitest `.test.ts` integration suites (not Playwright `.spec.ts`) calling `@camunda8/orchestration-cluster-api` SDK methods directly. Key additions: - `path-analyser/src/codegen/js-sdk/sdk-mapping.ts`: SdkMappingSource interface, OperationMapJsonSource (reads operation-map.json), FallbackMappingSource (returns operationId unchanged), regionToCamelCase - `path-analyser/src/codegen/js-sdk/emitter.ts`: createJsSdkEmitter(), renderJsSdkSuite(), jsSdkSuiteFileName(); hard-fails on multipart steps - `path-analyser/src/codegen/js-sdk/materialize-support.ts`: vendors support/ helpers + project-root scaffolding into the generated output dir - `path-analyser/src/codegen/js-sdk/project-templates/`: package.json, tsconfig.json, vitest.config.ts, .env.example, README.md - `scripts/fetch-js-sdk-map.js`: sparse-clones operation-map.json from camunda/orchestration-cluster-api-js (npm run fetch-js-sdk-map) - `path-analyser/src/codegen/index.ts`: registers js-sdk emitter, calls materializeSdkSupport on js-sdk target - `path-analyser/scripts/copy-support-templates.js`: stages js-sdk support + project templates into dist/ at build time - `tests/codegen/js-sdk-emitter.test.ts`: 27 unit tests (all passing) Also updates configs/camunda-oca/spec-pin.json expectedSpecHash to match camunda-schema-bundler ^2.3.0 (already in devDependencies on main) which produces a different hash for the same spec ref. --- configs/camunda-oca/spec-pin.json | 2 +- package.json | 2 + .../scripts/copy-support-templates.js | 73 ++- path-analyser/src/codegen/index.ts | 28 ++ path-analyser/src/codegen/js-sdk/emitter.ts | 307 ++++++++++++ .../src/codegen/js-sdk/materialize-support.ts | 106 +++++ .../js-sdk/project-templates/.env.example | 16 + .../js-sdk/project-templates/README.md | 40 ++ .../js-sdk/project-templates/package.json | 18 + .../js-sdk/project-templates/tsconfig.json | 14 + .../js-sdk/project-templates/vitest.config.ts | 13 + .../src/codegen/js-sdk/sdk-mapping.ts | 110 +++++ scripts/fetch-js-sdk-map.js | 76 +++ tests/codegen/js-sdk-emitter.test.ts | 439 ++++++++++++++++++ 14 files changed, 1233 insertions(+), 11 deletions(-) create mode 100644 path-analyser/src/codegen/js-sdk/emitter.ts create mode 100644 path-analyser/src/codegen/js-sdk/materialize-support.ts create mode 100644 path-analyser/src/codegen/js-sdk/project-templates/.env.example create mode 100644 path-analyser/src/codegen/js-sdk/project-templates/README.md create mode 100644 path-analyser/src/codegen/js-sdk/project-templates/package.json create mode 100644 path-analyser/src/codegen/js-sdk/project-templates/tsconfig.json create mode 100644 path-analyser/src/codegen/js-sdk/project-templates/vitest.config.ts create mode 100644 path-analyser/src/codegen/js-sdk/sdk-mapping.ts create mode 100644 scripts/fetch-js-sdk-map.js create mode 100644 tests/codegen/js-sdk-emitter.test.ts diff --git a/configs/camunda-oca/spec-pin.json b/configs/camunda-oca/spec-pin.json index 5f09bbc..1696e73 100644 --- a/configs/camunda-oca/spec-pin.json +++ b/configs/camunda-oca/spec-pin.json @@ -1,5 +1,5 @@ { "$comment": "Spec pin for the bundled-spec invariants regression layer. The invariants in configs/camunda-oca/regression-invariants.test.ts assert named properties of the upstream spec content fingerprinted by `expectedSpecHash`. `specRef` MUST be a full 40-char commit SHA on camunda/camunda (not a branch like `main`) so the baseline does not drift on every upstream merge. CI fetches the spec at `specRef` (requires camunda-schema-bundler >= 2.1.0 for SHA support) and aborts early via tests/regression/spec-pin.setup.ts if its hash drifts, so reviewers see a clear 'spec changed upstream — re-pin' signal instead of confusing invariant failures. To bump: pick a newer SHA, run `SPEC_REF= npm run fetch-spec:ref`, regenerate the pipeline, update any invariants whose values legitimately changed, then update both fields below in the same PR.", "specRef": "b9d355da83647c51e5639d087c25d466f7a8a927", - "expectedSpecHash": "sha256:4a17a26d4a0129c6bbcf4e9e207902cefc08f33f1a040d01ab9a3cb18781e9ff" + "expectedSpecHash": "sha256:fa34084a0d14afbc3fa04f0d0100eda13fad3090871e84e4978dc7310d3383e8" } diff --git a/package.json b/package.json index c950ebf..e915c6d 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,8 @@ "observe:aggregate": "npm run build:analyser && node path-analyser/dist/src/scripts/aggregate-observations.js", "optional-responses": "node optional-responses/report.js", "pipeline": "npm run fetch-spec && npm run testsuite:generate && npm run generate:request-validation", + "fetch-js-sdk-map": "node scripts/fetch-js-sdk-map.js", + "codegen:js-sdk:all": "npm run build:analyser && node path-analyser/dist/src/codegen/index.js --target=js-sdk --all", "lint": "biome check", "lint:fix": "biome check --write", "lint:generated": "biome check --config-path=biome.generated.json generated", diff --git a/path-analyser/scripts/copy-support-templates.js b/path-analyser/scripts/copy-support-templates.js index 272dd70..8674509 100644 --- a/path-analyser/scripts/copy-support-templates.js +++ b/path-analyser/scripts/copy-support-templates.js @@ -1,7 +1,7 @@ #!/usr/bin/env node // --------------------------------------------------------------------------- -// Stages the runtime support templates that the Playwright emitter vendors -// into every generated test suite. +// Stages the runtime support templates that the Playwright and JS SDK +// emitters vendor into every generated test suite. // // The canonical sources live in src/codegen/support/ (some are also imported // at generation time by analyser code — e.g. deterministicSuffix). This @@ -15,11 +15,21 @@ // seeding.ts // fixtures.ts // seed-rules.json +// await-eventually.ts +// dist/src/codegen/js-sdk/support-templates/ +// seeding.ts +// seed-rules.json +// dist/src/codegen/js-sdk/project-templates/ +// package.json +// tsconfig.json +// vitest.config.ts +// .env.example +// README.md // --------------------------------------------------------------------------- import { promises as fs } from 'node:fs'; import path from 'node:path'; -const SUPPORT_FILES = [ +const PLAYWRIGHT_SUPPORT_FILES = [ 'env.ts', 'recorder.ts', 'seeding.ts', @@ -28,14 +38,57 @@ const SUPPORT_FILES = [ 'await-eventually.ts', ]; +const SDK_SUPPORT_FILES = ['seeding.ts', 'seed-rules.json']; + +const SDK_PROJECT_FILES = [ + 'package.json', + 'tsconfig.json', + 'vitest.config.ts', + '.env.example', + 'README.md', +]; + async function main() { const root = process.cwd(); - const srcDir = path.join(root, 'src/codegen/support'); - const destDir = path.join(root, 'dist/src/codegen/playwright/support-templates'); - await fs.mkdir(destDir, { recursive: true }); - for (const name of SUPPORT_FILES) { - const src = path.join(srcDir, name); - const dest = path.join(destDir, name); + const supportSrcDir = path.join(root, 'src/codegen/support'); + + // Playwright support templates + const playwrightDestDir = path.join(root, 'dist/src/codegen/playwright/support-templates'); + await fs.mkdir(playwrightDestDir, { recursive: true }); + for (const name of PLAYWRIGHT_SUPPORT_FILES) { + const src = path.join(supportSrcDir, name); + const dest = path.join(playwrightDestDir, name); + try { + await fs.access(src); + } catch { + console.error('[copy-support-templates] source not found:', src); + process.exit(1); + } + await fs.copyFile(src, dest); + } + console.log( + `[copy-support-templates] staged ${PLAYWRIGHT_SUPPORT_FILES.length} playwright templates -> ${path.relative(root, playwrightDestDir)}`, + ); + + // JS SDK support templates (subset of Playwright support) + const sdkSupportDestDir = path.join(root, 'dist/src/codegen/js-sdk/support-templates'); + await fs.mkdir(sdkSupportDestDir, { recursive: true }); + for (const name of SDK_SUPPORT_FILES) { + const src = path.join(supportSrcDir, name); + const dest = path.join(sdkSupportDestDir, name); + await fs.copyFile(src, dest); + } + console.log( + `[copy-support-templates] staged ${SDK_SUPPORT_FILES.length} js-sdk support templates -> ${path.relative(root, sdkSupportDestDir)}`, + ); + + // JS SDK project templates (package.json, tsconfig, vitest.config, etc.) + const sdkProjSrcDir = path.join(root, 'src/codegen/js-sdk/project-templates'); + const sdkProjDestDir = path.join(root, 'dist/src/codegen/js-sdk/project-templates'); + await fs.mkdir(sdkProjDestDir, { recursive: true }); + for (const name of SDK_PROJECT_FILES) { + const src = path.join(sdkProjSrcDir, name); + const dest = path.join(sdkProjDestDir, name); try { await fs.access(src); } catch { @@ -45,7 +98,7 @@ async function main() { await fs.copyFile(src, dest); } console.log( - `[copy-support-templates] staged ${SUPPORT_FILES.length} templates -> ${path.relative(root, destDir)}`, + `[copy-support-templates] staged ${SDK_PROJECT_FILES.length} js-sdk project templates -> ${path.relative(root, sdkProjDestDir)}`, ); } diff --git a/path-analyser/src/codegen/index.ts b/path-analyser/src/codegen/index.ts index fddb8e3..065d877 100644 --- a/path-analyser/src/codegen/index.ts +++ b/path-analyser/src/codegen/index.ts @@ -10,6 +10,9 @@ import { import { validateDomainSemantics } from '../domainSemanticsValidator.js'; import type { EndpointScenarioCollection, GlobalContextSeed } from '../types.js'; import { parseCliArgs } from './cli-args.js'; +import { createJsSdkEmitter } from './js-sdk/emitter.js'; +import { materializeSdkSupport } from './js-sdk/materialize-support.js'; +import { OperationMapJsonSource } from './js-sdk/sdk-mapping.js'; import { writeEmitted } from './orchestrator.js'; import { PlaywrightEmitter } from './playwright/emitter.js'; import { @@ -102,6 +105,28 @@ async function run() { const variantDir = getVariantOutputDir(repoRoot); const outDir = getPlaywrightSuiteDir(repoRoot); + // Register the JS SDK emitter here (inside run()) so we have access to + // baseDir for locating the operation-map file, which is fetched at + // generation time by `npm run fetch-js-sdk-map` and lives in spec/. + // If the file is absent we fall back to using operationIds directly. + { + let jsSdkMapping: OperationMapJsonSource | undefined; + const mapCandidates = [ + path.join(baseDir, '..', 'spec', 'js-sdk', 'operation-map.json'), + path.join(baseDir, 'spec', 'js-sdk', 'operation-map.json'), + ]; + for (const candidate of mapCandidates) { + try { + const raw = await fs.readFile(candidate, 'utf8'); + jsSdkMapping = OperationMapJsonSource.fromJson(raw); + break; + } catch { + // Try next candidate or fall through to fallback. + } + } + registerEmitter(createJsSdkEmitter(jsSdkMapping)); + } + if (help || !positional) { printUsage(); process.exit(1); @@ -146,6 +171,9 @@ async function run() { // a runnable suite as a single artifact. await materializeResponseSchemas(outDir); } + if (emitter.id === 'js-sdk') { + await materializeSdkSupport(outDir); + } const files = (await fs.readdir(featureDir)).filter((f) => f.endsWith('-scenarios.json')); const globalContextSeeds = await loadGlobalContextSeeds(baseDir); diff --git a/path-analyser/src/codegen/js-sdk/emitter.ts b/path-analyser/src/codegen/js-sdk/emitter.ts new file mode 100644 index 0000000..41a061f --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/emitter.ts @@ -0,0 +1,307 @@ +import { assertSafeGlobalContextSeeds } from '../../domainSemanticsValidator.js'; +import type { + EndpointScenario, + EndpointScenarioCollection, + GlobalContextSeed, + RequestStep, +} from '../../types.js'; +import type { EmitContext, EmittedFile, Emitter } from '../emitter.js'; +import { FallbackMappingSource, type SdkMappingSource } from './sdk-mapping.js'; + +/** + * Returns the file name a JS SDK scenario collection lowers to. + * Uses `.test.ts` suffix (Vitest convention) instead of `.spec.ts` (Playwright). + */ +export function jsSdkSuiteFileName( + collection: EndpointScenarioCollection, + mode: 'feature' | 'integration' | 'variant', +): string { + return `${collection.endpoint.operationId}.${mode}.test.ts`; +} + +/** + * Pure rendering entry point — returns the Vitest suite source as a string. + * Used by `JsSdkEmitter` and by callers that want the source without writing. + */ +export function renderJsSdkSuite( + collection: EndpointScenarioCollection, + mapping: SdkMappingSource, + opts: { + suiteName?: string; + mode?: 'feature' | 'integration' | 'variant'; + globalContextSeeds?: readonly GlobalContextSeed[]; + }, +): string { + return buildSuiteSource(collection, mapping, opts); +} + +/** + * Factory: create a `JsSdkEmitter` backed by the given `SdkMappingSource`. + * + * When no source is supplied, `FallbackMappingSource` is used, which returns + * the operationId unchanged (already camelCase in the Camunda REST API). + */ +export function createJsSdkEmitter(mapping?: SdkMappingSource): Emitter { + const source = mapping ?? new FallbackMappingSource(); + return { + id: 'js-sdk', + name: 'JavaScript SDK (@camunda8/orchestration-cluster-api)', + async emit(collection: EndpointScenarioCollection, ctx: EmitContext): Promise { + const content = renderJsSdkSuite(collection, source, { + suiteName: ctx.suiteName, + mode: ctx.mode, + globalContextSeeds: ctx.globalContextSeeds, + }); + return [ + { + relativePath: jsSdkSuiteFileName(collection, ctx.mode), + content, + }, + ]; + }, + }; +} + +// --------------------------------------------------------------------------- +// Internal rendering +// --------------------------------------------------------------------------- + +function buildSuiteSource( + collection: EndpointScenarioCollection, + mapping: SdkMappingSource, + opts: { + suiteName?: string; + mode?: 'feature' | 'integration' | 'variant'; + globalContextSeeds?: readonly GlobalContextSeed[]; + }, +): string { + // Boundary safety re-check: same defence-in-depth as PlaywrightEmitter. + if (opts.globalContextSeeds !== undefined) { + assertSafeGlobalContextSeeds(opts.globalContextSeeds); + } + + const lines: string[] = []; + const suiteName = opts.suiteName || collection.endpoint.operationId; + + lines.push("import { describe, test } from 'vitest';"); + lines.push("import createCamundaClient from '@camunda8/orchestration-cluster-api';"); + lines.push("import { extractInto, seedBinding } from './support/seeding';"); + lines.push(''); + // Single shared client for all tests in this suite (zero-config → reads + // CAMUNDA_* from process.env; defaults to http://localhost:8080 when absent). + lines.push('const client = createCamundaClient();'); + lines.push(''); + lines.push(`describe('${suiteName}', () => {`); + + const seeds = opts.globalContextSeeds ?? []; + for (const scenario of collection.scenarios) { + lines.push(renderScenarioTest(scenario, mapping, seeds)); + } + + lines.push('});'); + return lines.join('\n'); +} + +function renderScenarioTest( + s: EndpointScenario, + mapping: SdkMappingSource, + globalContextSeeds: readonly GlobalContextSeed[], +): string { + const title = `${s.id} - ${escapeQuotes(s.name || 'scenario')}`; + const body: string[] = []; + body.push(` test('${title}', async () => {`); + + if (s.description) { + const desc = String(s.description).trim(); + const wrapped: string[] = []; + const words = desc.split(/\s+/); + let line = ''; + for (const w of words) { + if (`${line} ${w}`.trim().length > 100) { + wrapped.push(line.trim()); + line = w; + } else { + line += (line ? ' ' : '') + w; + } + } + if (line) wrapped.push(line.trim()); + for (const l of wrapped) body.push(` // ${l}`); + } + + body.push(` const ctx: Record = {};`); + + // Collect extraction target variable names across all steps + const extractionVars = new Set(); + if (s.requestPlan) { + for (const step of s.requestPlan) { + if (step.extract) { + for (const ex of step.extract) extractionVars.add(ex.bind); + } + } + } + + // Seed scenario bindings + if (s.bindings && Object.keys(s.bindings).length) { + body.push(' // Seed scenario bindings'); + const templateVars = new Set(); + function collectVarsFromTemplate(obj: unknown) { + if (!obj || typeof obj !== 'object') return; + for (const val of Object.values(obj)) { + if (typeof val === 'string') { + const m = val.match(/^\$\{([^}]+)\}$/); + if (m) templateVars.add(m[1]); + } else if (typeof val === 'object') collectVarsFromTemplate(val); + } + } + if (s.requestPlan) { + for (const step of s.requestPlan) { + if (step.bodyTemplate) collectVarsFromTemplate(step.bodyTemplate); + // Path params are consumed via ctx just like body template vars. + if (step.pathParams) { + for (const p of step.pathParams) templateVars.add(p.var); + } + } + } + for (const [k, v] of Object.entries(s.bindings)) { + if (v === '__PENDING__') { + if (!templateVars.has(k)) continue; + if (extractionVars.has(k)) continue; + body.push(` if (ctx['${k}'] === undefined) { ctx['${k}'] = seedBinding('${k}'); }`); + continue; + } + if (extractionVars.has(k)) continue; + body.push(` ctx['${k}'] = ${JSON.stringify(v)};`); + } + } + + // Universal-seed prologue (same logic as PlaywrightEmitter — see its + // detailed comment for the nullish-coalescing rationale). + for (const seed of globalContextSeeds) { + body.push( + ` ctx['${seed.binding}'] = ctx['${seed.binding}'] ?? seedBinding('${seed.seedRule}');`, + ); + } + + if (!s.requestPlan) { + body.push(' // No request plan available'); + body.push(' });'); + return body.join('\n'); + } + + const requestPlan = s.requestPlan; + requestPlan.forEach((step: RequestStep, idx: number) => { + const method = mapping.resolveMethod(step.operationId); + const varName = `result${idx + 1}`; + + // Hard-fail on multipart: the SDK helper for file uploads uses a + // different signature (e.g. deployResourcesFromFiles takes file paths, + // not a generic multipart template). Surface the gap rather than + // silently emitting wrong call shapes. + if (step.bodyKind === 'multipart') { + throw new Error( + `JS SDK emitter: operationId '${step.operationId}' has a multipart body. ` + + `The SDK helper '${method}' uses a different signature. ` + + `This scenario cannot be lowered automatically; handle it manually or ` + + `implement a dedicated multipart adapter for this operation.`, + ); + } + + body.push(` // Step ${idx + 1}: ${step.operationId}`); + body.push(` {`); + + // Build the call argument object by merging path params and body. + const argParts: string[] = []; + + // Path parameters: contribute their values to the args object. + if (step.pathParams?.length) { + for (const p of step.pathParams) { + argParts.push(` ${p.name}: ctx['${p.var}'],`); + } + } + + // JSON body: inline the resolved template fields. + if (step.bodyKind === 'json' && step.bodyTemplate) { + const bodyJson = JSON.stringify(step.bodyTemplate, null, 6).replace( + /"\\?\$\{([^}]+)\}"/g, + (_, v) => `ctx["${v}"]`, + ); + // Splice the inner fields of the body object into argParts (if it is + // a plain object). If the body is not a plain object we fall back to + // spreading it. + if ( + typeof step.bodyTemplate === 'object' && + step.bodyTemplate !== null && + !Array.isArray(step.bodyTemplate) + ) { + // Strip the outer braces and indent one level. + const inner = bodyJson.replace(/^\{/, '').replace(/\}$/, '').trimEnd(); + argParts.push(inner); + } else { + // Non-object body (array, primitive) — spread via Object.assign downstream. + argParts.push(` ...${bodyJson},`); + } + } + + if (argParts.length > 0) { + body.push(` const args${idx + 1} = {`); + body.push(argParts.join('\n')); + body.push(` };`); + body.push(` const ${varName} = await client.${method}(args${idx + 1});`); + } else { + // No args: operation takes no parameters (e.g. getTopology). + body.push(` const ${varName} = await client.${method}();`); + } + + // The SDK throws on non-2xx so there is no explicit status assertion. + // For the final step we add a basic defined-check as a smoke test. + const isFinal = idx === requestPlan.length - 1; + if (isFinal) { + body.push(` // SDK throws on non-${step.expect.status}; reaching here means success`); + } + + // Extraction: pull fields from the typed SDK response into ctx. + // Unlike PlaywrightEmitter, there is no .json() call — the response is + // already a typed object. + if (step.extract?.length) { + for (const ex of step.extract) { + const optAcc = toOptionalAccessor(ex.fieldPath); + body.push(` extractInto(ctx, '${ex.bind}', ${varName}${optAcc});`); + } + } + + body.push(' }'); + }); + + body.push(' });'); + return body.join('\n'); +} + +// --------------------------------------------------------------------------- +// Utilities (mirrors of PlaywrightEmitter utilities) +// --------------------------------------------------------------------------- + +function escapeQuotes(s: string): string { + return s.replace(/'/g, "\\'"); +} + +/** + * Converts a dotted field path (possibly with array notation) into a + * TypeScript optional-chaining accessor expression. + * + * Examples: + * "key" → ".key" + * "metadata.processInstanceKey" → "?.metadata?.processInstanceKey" + * "items[0].key" → "?.items?.[0]?.key" + */ +function toOptionalAccessor(fieldPath: string): string { + if (!fieldPath.includes('.') && !fieldPath.includes('[')) { + return `.${fieldPath}`; + } + const parts = fieldPath.split(/(?=\[)|[.]/).filter(Boolean); + return parts + .map((p) => { + if (p.startsWith('[')) return `?.[${p.slice(1, -1)}]`; + return `?.${p}`; + }) + .join(''); +} diff --git a/path-analyser/src/codegen/js-sdk/materialize-support.ts b/path-analyser/src/codegen/js-sdk/materialize-support.ts new file mode 100644 index 0000000..cd1f2f1 --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/materialize-support.ts @@ -0,0 +1,106 @@ +// --------------------------------------------------------------------------- +// Vendors the JS SDK runtime support files AND project scaffolding into an +// emitted test suite so the suite is runnable in place: +// +// cd +// npm install +// npm test +// +// Two sets of files are materialised: +// * support/ — runtime helpers (seeding.ts, seed-rules.json). +// Sources: path-analyser/src/codegen/support/ (shared with the +// Playwright emitter). Staged to +// dist/src/codegen/js-sdk/support-templates/ at build time. +// * project root — package.json, tsconfig.json, vitest.config.ts, +// .env.example, README.md. +// Sources: path-analyser/src/codegen/js-sdk/project-templates/. +// Staged to dist/src/codegen/js-sdk/project-templates/ at build time. +// --------------------------------------------------------------------------- +import { existsSync, promises as fs } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +/** + * Support files vendored into `/support/`. + * + * The JS SDK emitter only needs the seeding utilities — it does not use + * Playwright fixtures, the HTTP recorder, or the `awaitEventually` helper + * (the SDK has built-in eventual-consistency polling). + */ +export const SDK_SUPPORT_TEMPLATE_FILES = ['seeding.ts', 'seed-rules.json'] as const; + +/** Files copied directly into `/` (project root scaffolding). */ +export const SDK_PROJECT_TEMPLATE_FILES = [ + 'package.json', + 'tsconfig.json', + 'vitest.config.ts', + '.env.example', + 'README.md', +] as const; + +/** Subdirectory created under the emitter's outDir to hold vendored helpers. */ +export const SDK_SUPPORT_DIR_NAME = 'support'; + +function defaultSupportTemplatesDir(): string { + const here = path.dirname(fileURLToPath(import.meta.url)); + if (here.includes(`${path.sep}dist${path.sep}`)) { + return path.join(here, 'support-templates'); + } + // Source mode: walk up from src/codegen/js-sdk/ to src/codegen/support/. + return path.resolve(here, '..', 'support'); +} + +function defaultProjectTemplatesDir(): string { + const here = path.dirname(fileURLToPath(import.meta.url)); + if (here.includes(`${path.sep}dist${path.sep}`)) { + return path.join(here, 'project-templates'); + } + // Source mode: templates are co-located in src/codegen/js-sdk/project-templates/. + return path.resolve(here, 'project-templates'); +} + +/** + * Copy the runtime support helpers AND project-root scaffolding into + * `/` so the emitted JS SDK suite is self-contained and runnable + * in place (`cd && npm install && npm test`). + * + * Idempotent: safe to call multiple times per emit run. + * + * @param outDir Directory to materialise into (created if missing). + * @param supportTemplatesDir Optional override for the support-templates + * source directory (seeding.ts, seed-rules.json). + * Production callers should omit this. + * @param projectTemplatesDir Optional override for the project-root templates + * (package.json, vitest.config.ts, …). + * Production callers should omit this. + * @param overwriteRoot When false, root scaffolding files are only + * written if they don't already exist. Support + * files are always overwritten regardless. + * Default: true. + * @returns Path to the support directory under `outDir`. + */ +export async function materializeSdkSupport( + outDir: string, + supportTemplatesDir?: string, + projectTemplatesDir?: string, + overwriteRoot: boolean = true, +): Promise { + const srcDir = supportTemplatesDir ?? defaultSupportTemplatesDir(); + const destDir = path.join(outDir, SDK_SUPPORT_DIR_NAME); + await fs.mkdir(destDir, { recursive: true }); + + // Always overwrite support/ — these are part of the generator's contract. + for (const name of SDK_SUPPORT_TEMPLATE_FILES) { + await fs.copyFile(path.join(srcDir, name), path.join(destDir, name)); + } + + // Project root scaffolding: overwrite by default; opt-out preserves edits. + const projSrcDir = projectTemplatesDir ?? defaultProjectTemplatesDir(); + for (const name of SDK_PROJECT_TEMPLATE_FILES) { + const dest = path.join(outDir, name); + if (!overwriteRoot && existsSync(dest)) continue; + await fs.copyFile(path.join(projSrcDir, name), dest); + } + + return destDir; +} diff --git a/path-analyser/src/codegen/js-sdk/project-templates/.env.example b/path-analyser/src/codegen/js-sdk/project-templates/.env.example new file mode 100644 index 0000000..1c56606 --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/project-templates/.env.example @@ -0,0 +1,16 @@ +# Local (unauthenticated) — Camunda 8 Run defaults +# The SDK reads CAMUNDA_* from the environment. Uncomment and fill in for +# your deployment. When all vars are absent the SDK connects to localhost:8080. + +# Local cluster address (default: http://localhost:8080) +# CAMUNDA_REST_ADDRESS=http://localhost:8080 + +# OAuth (SaaS / Self-Managed with Identity) +# CAMUNDA_AUTH_STRATEGY=OAUTH +# CAMUNDA_CLIENT_ID=your-client-id +# CAMUNDA_CLIENT_SECRET=your-client-secret +# CAMUNDA_OAUTH_URL=https://login.cloud.camunda.io/oauth/token +# CAMUNDA_TOKEN_AUDIENCE=zeebe.camunda.io + +# Default tenant (leave as for single-tenant mode) +# CAMUNDA_DEFAULT_TENANT_ID= diff --git a/path-analyser/src/codegen/js-sdk/project-templates/README.md b/path-analyser/src/codegen/js-sdk/project-templates/README.md new file mode 100644 index 0000000..e6455b6 --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/project-templates/README.md @@ -0,0 +1,40 @@ +# Generated Camunda JS SDK Integration Suite + +This directory contains a self-contained Vitest test suite generated by the +[api-test-generator](https://github.com/camunda/api-test-generator) for the +`js-sdk` target. + +## Running + +```bash +npm install +# Copy .env.example to .env and fill in your cluster credentials (optional for local) +npm test +``` + +## Environment Variables + +| Variable | Default | Description | +|---|---|---| +| `CAMUNDA_REST_ADDRESS` | `http://localhost:8080` | Cluster REST endpoint | +| `CAMUNDA_AUTH_STRATEGY` | `NONE` | `NONE`, `BASIC`, or `OAUTH` | +| `CAMUNDA_CLIENT_ID` | — | OAuth client ID (required for OAUTH) | +| `CAMUNDA_CLIENT_SECRET` | — | OAuth client secret (required for OAUTH) | +| `CAMUNDA_OAUTH_URL` | — | OAuth token endpoint (required for OAUTH) | +| `CAMUNDA_DEFAULT_TENANT_ID` | `` | Default tenant override | + +For the full list of supported environment variables see the +[@camunda8/orchestration-cluster-api README](https://github.com/camunda/orchestration-cluster-api-js). + +## Structure + +``` +/ + *.feature.test.ts # generated test files (one per endpoint) + support/ + seeding.ts # deterministic / random value generators + seed-rules.json # value-generation rules + vitest.config.ts + package.json + tsconfig.json +``` diff --git a/path-analyser/src/codegen/js-sdk/project-templates/package.json b/path-analyser/src/codegen/js-sdk/project-templates/package.json new file mode 100644 index 0000000..71e3fba --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/project-templates/package.json @@ -0,0 +1,18 @@ +{ + "name": "camunda-js-sdk-suite", + "version": "0.0.0", + "private": true, + "type": "module", + "description": "Generated Vitest integration suite for the Camunda REST API (JS SDK target), produced by the api-test-generator path-analyser.", + "scripts": { + "test": "vitest run" + }, + "dependencies": { + "@camunda8/orchestration-cluster-api": "latest" + }, + "devDependencies": { + "@types/node": "^24.0.0", + "typescript": "^5.5.4", + "vitest": "^2.0.0" + } +} diff --git a/path-analyser/src/codegen/js-sdk/project-templates/tsconfig.json b/path-analyser/src/codegen/js-sdk/project-templates/tsconfig.json new file mode 100644 index 0000000..844e9bb --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/project-templates/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "Bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/path-analyser/src/codegen/js-sdk/project-templates/vitest.config.ts b/path-analyser/src/codegen/js-sdk/project-templates/vitest.config.ts new file mode 100644 index 0000000..8595042 --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/project-templates/vitest.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['**/*.test.ts'], + // Each test file gets its own suite run sequentially — the generated + // tests mutate live cluster state and are order-sensitive within a + // file. Cross-file parallelism is safe. + pool: 'forks', + testTimeout: 60_000, + hookTimeout: 30_000, + }, +}); diff --git a/path-analyser/src/codegen/js-sdk/sdk-mapping.ts b/path-analyser/src/codegen/js-sdk/sdk-mapping.ts new file mode 100644 index 0000000..7a6cef6 --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/sdk-mapping.ts @@ -0,0 +1,110 @@ +/** + * SDK mapping strategy for the JS SDK emitter. + * + * `SdkMappingSource` is the shared interface for resolving operationIds to + * method symbols. The JS emitter uses `OperationMapJsonSource` backed by + * `examples/operation-map.json` from the `camunda/orchestration-cluster-api-js` + * repository (fetched at generation time via `npm run fetch-js-sdk-map`). + * + * Future Python and C# emitters can implement `SdkMappingSource` with their + * own lookup tables. + */ + +export interface OperationMapEntry { + file: string; + region: string; + label: string; +} + +export type OperationMap = Record; + +/** + * Resolves an operationId to the preferred SDK method symbol. + * + * Implementations must be stateless and side-effect-free after construction. + */ +export interface SdkMappingSource { + /** + * Returns the preferred SDK method symbol for the given operationId. + * + * Strategy (Option C from issue #8): + * - Look up `operation-map.json[operationId][0].region` + * - Convert the PascalCase region to camelCase to get the method name + * - If no mapping exists, return `operationId` directly (already camelCase) + */ + resolveMethod(operationId: string): string; + + /** Returns all operationIds known to this mapping source. */ + knownOperationIds(): string[]; +} + +/** + * Converts a PascalCase region string to the camelCase SDK method name. + * + * Examples: + * "DeployResourcesFromFiles" → "deployResourcesFromFiles" + * "CreateProcessInstanceById" → "createProcessInstanceById" + * "GetTopology" → "getTopology" + */ +export function regionToCamelCase(region: string): string { + if (!region) return region; + return region.charAt(0).toLowerCase() + region.slice(1); +} + +/** + * Implements `SdkMappingSource` using `examples/operation-map.json` from + * `camunda/orchestration-cluster-api-js`. + * + * Each entry maps an operationId to one or more SDK examples. The first + * entry's `region` field is the preferred method symbol (PascalCase). + * This class converts it to camelCase. + */ +export class OperationMapJsonSource implements SdkMappingSource { + private readonly map: OperationMap; + + constructor(map: OperationMap) { + this.map = map; + } + + resolveMethod(operationId: string): string { + const entries = this.map[operationId]; + if (entries && entries.length > 0 && entries[0].region) { + return regionToCamelCase(entries[0].region); + } + // Fallback: operationId is already camelCase in the Camunda REST API. + return operationId; + } + + knownOperationIds(): string[] { + return Object.keys(this.map); + } + + /** + * Constructs an `OperationMapJsonSource` from raw JSON text. + * + * Throws `SyntaxError` on malformed JSON — callers should handle this + * and fall back to an empty source or propagate the error. + */ + static fromJson(json: string): OperationMapJsonSource { + // biome-ignore lint/plugin: runtime contract boundary for parsed JSON from a fetched file + const parsed = JSON.parse(json) as OperationMap; + return new OperationMapJsonSource(parsed); + } +} + +/** + * A no-op `SdkMappingSource` that always falls back to the operationId. + * + * Used when `operation-map.json` has not been fetched yet (e.g. the user + * has not run `npm run fetch-js-sdk-map`). The emitted code will still work + * since operationIds already match the raw SDK method names. + */ +export class FallbackMappingSource implements SdkMappingSource { + resolveMethod(operationId: string): string { + return operationId; + } + + knownOperationIds(): string[] { + return []; + } +} diff --git a/scripts/fetch-js-sdk-map.js b/scripts/fetch-js-sdk-map.js new file mode 100644 index 0000000..0feff54 --- /dev/null +++ b/scripts/fetch-js-sdk-map.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +/** + * Fetches `examples/operation-map.json` from the + * `camunda/orchestration-cluster-api-js` repository via a git sparse clone. + * + * Usage: + * npm run fetch-js-sdk-map + * JS_SDK_REF=main npm run fetch-js-sdk-map + * + * Output: spec/js-sdk/operation-map.json (gitignored alongside spec/bundled/). + * + * Analogous to the OpenAPI spec fetch via `camunda-schema-bundler`: + * - `JS_SDK_REF` controls which branch / tag / SHA is fetched (default: main). + * - The file is never committed; CI fetches it fresh each run. + * - A future pin file (analogous to spec-pin.json) can lock the SHA for + * determinism across runs. + */ + +import { execFileSync } from 'node:child_process'; +import { mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const REPO_URL = 'https://github.com/camunda/orchestration-cluster-api-js.git'; +const FILE_PATH = 'examples/operation-map.json'; +const SDK_REF = process.env.JS_SDK_REF || 'main'; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const outDir = path.join(repoRoot, 'spec', 'js-sdk'); +const outFile = path.join(outDir, 'operation-map.json'); +const tmpDir = path.join(tmpdir(), `js-sdk-map-${Date.now()}`); + +console.log(`[fetch-js-sdk-map] Fetching ${FILE_PATH} from ${REPO_URL} @ ${SDK_REF}`); + +try { + mkdirSync(tmpDir, { recursive: true }); + + // Sparse clone: fetch only the single file to keep it fast. + execFileSync('git', ['init', '--quiet', tmpDir], { stdio: 'inherit' }); + execFileSync('git', ['-C', tmpDir, 'remote', 'add', 'origin', REPO_URL], { + stdio: 'inherit', + }); + execFileSync( + 'git', + ['-C', tmpDir, 'config', 'core.sparseCheckout', 'true'], + { stdio: 'inherit' }, + ); + // Write sparse-checkout pattern before fetch + const sparseFile = path.join(tmpDir, '.git', 'info', 'sparse-checkout'); + writeFileSync(sparseFile, `${FILE_PATH}\n`, 'utf8'); + execFileSync( + 'git', + ['-C', tmpDir, 'fetch', '--depth', '1', 'origin', SDK_REF], + { stdio: 'inherit' }, + ); + execFileSync( + 'git', + ['-C', tmpDir, 'checkout', 'FETCH_HEAD', '--', FILE_PATH], + { stdio: 'inherit' }, + ); + + mkdirSync(outDir, { recursive: true }); + renameSync(path.join(tmpDir, FILE_PATH), outFile); + + console.log(`[fetch-js-sdk-map] Written to ${path.relative(repoRoot, outFile)}`); +} catch (err) { + console.error('[fetch-js-sdk-map] Failed:', err instanceof Error ? err.message : String(err)); + process.exit(1); +} finally { + try { + rmSync(tmpDir, { recursive: true, force: true }); + } catch { + // Non-fatal cleanup failure. + } +} diff --git a/tests/codegen/js-sdk-emitter.test.ts b/tests/codegen/js-sdk-emitter.test.ts new file mode 100644 index 0000000..0039bc9 --- /dev/null +++ b/tests/codegen/js-sdk-emitter.test.ts @@ -0,0 +1,439 @@ +import { describe, expect, it } from 'vitest'; +import { + createJsSdkEmitter, + jsSdkSuiteFileName, + renderJsSdkSuite, +} from '../../path-analyser/src/codegen/js-sdk/emitter.ts'; +import { + FallbackMappingSource, + OperationMapJsonSource, +} from '../../path-analyser/src/codegen/js-sdk/sdk-mapping.ts'; +import type { EndpointScenarioCollection } from '../../path-analyser/src/types.ts'; + +/** + * Layer-1 fixture — JS SDK emitter. + * + * Each `it` block asserts one property of the lowering from a hand-built + * `EndpointScenarioCollection` to emitted Vitest source. The fixtures here + * are the regression guard: if the emitter changes the generated output in a + * breaking way, exactly the affected assertion fails. + */ + +// --------------------------------------------------------------------------- +// Shared fixtures +// --------------------------------------------------------------------------- + +const FALLBACK_MAPPING = new FallbackMappingSource(); + +const MINIMAL_COLLECTION: EndpointScenarioCollection = { + endpoint: { operationId: 'getTopology', method: 'GET', path: '/topology' }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc1', + name: 'get topology', + operations: [{ operationId: 'getTopology', method: 'GET', path: '/topology' }], + producedSemanticTypes: [], + satisfiedSemanticTypes: [], + }, + ], +}; + +const JSON_BODY_COLLECTION: EndpointScenarioCollection = { + endpoint: { + operationId: 'createProcessInstance', + method: 'POST', + path: '/process-instances', + }, + requiredSemanticTypes: ['ProcessDefinitionKey'], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc1', + name: 'create by definition key', + operations: [ + { + operationId: 'createProcessDefinition', + method: 'POST', + path: '/process-definitions', + }, + { + operationId: 'createProcessInstance', + method: 'POST', + path: '/process-instances', + }, + ], + producedSemanticTypes: ['ProcessInstanceKey'], + satisfiedSemanticTypes: ['ProcessDefinitionKey'], + requestPlan: [ + { + operationId: 'createProcessDefinition', + method: 'POST', + pathTemplate: '/process-definitions', + bodyKind: 'json', + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal — generator template placeholder + bodyTemplate: { name: '${processDefNameVar}' }, + expect: { status: 200 }, + extract: [{ fieldPath: 'key', bind: 'processDefinitionKeyVar' }], + }, + { + operationId: 'createProcessInstance', + method: 'POST', + pathTemplate: '/process-instances', + bodyKind: 'json', + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional literal — generator template placeholder + bodyTemplate: { processDefinitionKey: '${processDefinitionKeyVar}' }, + expect: { status: 200 }, + extract: [{ fieldPath: 'processInstanceKey', bind: 'processInstanceKeyVar' }], + }, + ], + }, + ], +}; + +const PATH_PARAM_COLLECTION: EndpointScenarioCollection = { + endpoint: { + operationId: 'cancelProcessInstance', + method: 'POST', + path: '/process-instances/{processInstanceKey}/cancellation', + }, + requiredSemanticTypes: ['ProcessInstanceKey'], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc1', + name: 'cancel instance', + operations: [ + { + operationId: 'cancelProcessInstance', + method: 'POST', + path: '/process-instances/{processInstanceKey}/cancellation', + }, + ], + producedSemanticTypes: [], + satisfiedSemanticTypes: ['ProcessInstanceKey'], + bindings: { processInstanceKeyVar: '__PENDING__' }, + requestPlan: [ + { + operationId: 'cancelProcessInstance', + method: 'POST', + pathTemplate: '/process-instances/{processInstanceKey}/cancellation', + pathParams: [{ name: 'processInstanceKey', var: 'processInstanceKeyVar' }], + expect: { status: 200 }, + }, + ], + }, + ], +}; + +// --------------------------------------------------------------------------- +// sdk-mapping: OperationMapJsonSource +// --------------------------------------------------------------------------- + +describe('OperationMapJsonSource', () => { + it('resolves a known operationId to the camelCased region', () => { + const src = OperationMapJsonSource.fromJson( + JSON.stringify({ + createDeployment: [ + { file: 'deployment.ts', region: 'DeployResourcesFromFiles', label: 'Deploy' }, + ], + }), + ); + expect(src.resolveMethod('createDeployment')).toBe('deployResourcesFromFiles'); + }); + + it('falls back to operationId when no mapping entry exists', () => { + const src = OperationMapJsonSource.fromJson(JSON.stringify({})); + expect(src.resolveMethod('unknownOp')).toBe('unknownOp'); + }); + + it('returns knownOperationIds matching the keys of the map', () => { + const src = OperationMapJsonSource.fromJson( + JSON.stringify({ + getTopology: [{ file: 'client.ts', region: 'GetTopology', label: 'Topology' }], + createUser: [{ file: 'user.ts', region: 'CreateUser', label: 'User' }], + }), + ); + expect(src.knownOperationIds().sort()).toEqual(['createUser', 'getTopology']); + }); + + it('picks the FIRST entry when multiple variants exist', () => { + const src = OperationMapJsonSource.fromJson( + JSON.stringify({ + createProcessInstance: [ + { file: 'process-instance.ts', region: 'CreateProcessInstanceById', label: 'By ID' }, + { file: 'process-instance.ts', region: 'CreateProcessInstanceByKey', label: 'By key' }, + ], + }), + ); + // First entry wins: "CreateProcessInstanceById" → "createProcessInstanceById" + expect(src.resolveMethod('createProcessInstance')).toBe('createProcessInstanceById'); + }); +}); + +// --------------------------------------------------------------------------- +// FallbackMappingSource +// --------------------------------------------------------------------------- + +describe('FallbackMappingSource', () => { + it('returns the operationId unchanged', () => { + expect(FALLBACK_MAPPING.resolveMethod('createWidget')).toBe('createWidget'); + }); + + it('returns an empty knownOperationIds list', () => { + expect(FALLBACK_MAPPING.knownOperationIds()).toEqual([]); + }); +}); + +// --------------------------------------------------------------------------- +// jsSdkSuiteFileName +// --------------------------------------------------------------------------- + +describe('jsSdkSuiteFileName', () => { + it('produces a .test.ts file in feature mode', () => { + expect(jsSdkSuiteFileName(MINIMAL_COLLECTION, 'feature')).toBe('getTopology.feature.test.ts'); + }); + + it('produces a .test.ts file in variant mode', () => { + expect(jsSdkSuiteFileName(MINIMAL_COLLECTION, 'variant')).toBe('getTopology.variant.test.ts'); + }); +}); + +// --------------------------------------------------------------------------- +// renderJsSdkSuite — suite preamble +// --------------------------------------------------------------------------- + +describe('renderJsSdkSuite — preamble', () => { + it('emits vitest imports (not Playwright)', () => { + const src = renderJsSdkSuite(MINIMAL_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain("import { describe, test } from 'vitest'"); + expect(src).not.toContain('@playwright/test'); + }); + + it('imports createCamundaClient from the SDK package', () => { + const src = renderJsSdkSuite(MINIMAL_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain("import createCamundaClient from '@camunda8/orchestration-cluster-api'"); + }); + + it('imports seeding utilities from ./support/seeding', () => { + const src = renderJsSdkSuite(MINIMAL_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain("import { extractInto, seedBinding } from './support/seeding'"); + }); + + it('creates a shared client at module scope', () => { + const src = renderJsSdkSuite(MINIMAL_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain('const client = createCamundaClient()'); + }); + + it('wraps scenarios in a describe block keyed by suiteName', () => { + const src = renderJsSdkSuite(MINIMAL_COLLECTION, FALLBACK_MAPPING, { + suiteName: 'getTopology', + }); + expect(src).toContain("describe('getTopology'"); + }); +}); + +// --------------------------------------------------------------------------- +// renderJsSdkSuite — no-arg operation +// --------------------------------------------------------------------------- + +describe('renderJsSdkSuite — no-arg operation', () => { + it('emits client.() with no args when no body or path params', () => { + const noArgCollection: EndpointScenarioCollection = { + ...MINIMAL_COLLECTION, + scenarios: [ + { + ...MINIMAL_COLLECTION.scenarios[0], + requestPlan: [ + { + operationId: 'getTopology', + method: 'GET', + pathTemplate: '/topology', + expect: { status: 200 }, + }, + ], + }, + ], + }; + const src = renderJsSdkSuite(noArgCollection, FALLBACK_MAPPING, {}); + expect(src).toContain('client.getTopology()'); + }); +}); + +// --------------------------------------------------------------------------- +// renderJsSdkSuite — JSON body +// --------------------------------------------------------------------------- + +describe('renderJsSdkSuite — JSON body', () => { + it('emits client.(args) with body fields resolved from ctx', () => { + const src = renderJsSdkSuite(JSON_BODY_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain('client.createProcessDefinition(args1)'); + expect(src).toContain('ctx["processDefNameVar"]'); + }); + + it('emits extract calls from the typed response (no .json())', () => { + const src = renderJsSdkSuite(JSON_BODY_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain("extractInto(ctx, 'processDefinitionKeyVar', result1.key)"); + expect(src).not.toContain('.json()'); + }); + + it('uses the mapped method symbol from the operation-map', () => { + const mapping = OperationMapJsonSource.fromJson( + JSON.stringify({ + createProcessDefinition: [ + { file: 'def.ts', region: 'CreateProcessDefinitionById', label: 'By ID' }, + ], + createProcessInstance: [ + { file: 'pi.ts', region: 'CreateProcessInstanceById', label: 'By ID' }, + ], + }), + ); + const src = renderJsSdkSuite(JSON_BODY_COLLECTION, mapping, {}); + expect(src).toContain('client.createProcessDefinitionById(args1)'); + expect(src).toContain('client.createProcessInstanceById(args2)'); + }); +}); + +// --------------------------------------------------------------------------- +// renderJsSdkSuite — path parameters +// --------------------------------------------------------------------------- + +describe('renderJsSdkSuite — path parameters', () => { + it('includes path parameters in the args object', () => { + const src = renderJsSdkSuite(PATH_PARAM_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain("processInstanceKey: ctx['processInstanceKeyVar']"); + }); + + it('emits client.(args) (not a bare ctx lookup)', () => { + const src = renderJsSdkSuite(PATH_PARAM_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain('client.cancelProcessInstance(args1)'); + }); +}); + +// --------------------------------------------------------------------------- +// renderJsSdkSuite — bindings & seeding +// --------------------------------------------------------------------------- + +describe('renderJsSdkSuite — bindings', () => { + it('seeds __PENDING__ bindings via seedBinding()', () => { + const src = renderJsSdkSuite(PATH_PARAM_COLLECTION, FALLBACK_MAPPING, {}); + expect(src).toContain("seedBinding('processInstanceKeyVar')"); + }); + + it('emits literal bindings as ctx assignments', () => { + const withLiteral: EndpointScenarioCollection = { + ...MINIMAL_COLLECTION, + scenarios: [ + { + ...MINIMAL_COLLECTION.scenarios[0], + bindings: { tenantIdVar: 'my-tenant' }, + requestPlan: [ + { + operationId: 'getTopology', + method: 'GET', + pathTemplate: '/topology', + expect: { status: 200 }, + }, + ], + }, + ], + }; + const src = renderJsSdkSuite(withLiteral, FALLBACK_MAPPING, {}); + expect(src).toContain('ctx[\'tenantIdVar\'] = "my-tenant"'); + }); +}); + +// --------------------------------------------------------------------------- +// renderJsSdkSuite — multipart hard-fail +// --------------------------------------------------------------------------- + +describe('renderJsSdkSuite — multipart hard-fail', () => { + it('throws when a step has bodyKind=multipart', () => { + const multipartCollection: EndpointScenarioCollection = { + endpoint: { + operationId: 'createDeployment', + method: 'POST', + path: '/deployments', + }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc1', + name: 'deploy resource', + operations: [{ operationId: 'createDeployment', method: 'POST', path: '/deployments' }], + producedSemanticTypes: [], + satisfiedSemanticTypes: [], + requestPlan: [ + { + operationId: 'createDeployment', + method: 'POST', + pathTemplate: '/deployments', + bodyKind: 'multipart', + multipartTemplate: { fields: {}, files: { resources: '@@FILE:bpmn/test.bpmn' } }, + expect: { status: 200 }, + }, + ], + }, + ], + }; + expect(() => renderJsSdkSuite(multipartCollection, FALLBACK_MAPPING, {})).toThrow(/multipart/); + }); +}); + +// --------------------------------------------------------------------------- +// JsSdkEmitter (Emitter contract) +// --------------------------------------------------------------------------- + +describe('JsSdkEmitter (Emitter contract)', () => { + const emitter = createJsSdkEmitter(); + + it('has id "js-sdk" and a descriptive name', () => { + expect(emitter.id).toBe('js-sdk'); + expect(emitter.name).toMatch(/javascript.*sdk/i); + }); + + it('returns one EmittedFile per collection with a .test.ts extension', async () => { + const files = await emitter.emit(MINIMAL_COLLECTION, { + outDir: '/unused', + suiteName: 'getTopology', + mode: 'feature', + }); + expect(files).toHaveLength(1); + expect(files[0].relativePath).toBe('getTopology.feature.test.ts'); + expect(files[0].relativePath).toBe(jsSdkSuiteFileName(MINIMAL_COLLECTION, 'feature')); + }); + + it('emit() is pure: does not touch the filesystem', async () => { + await expect( + emitter.emit(MINIMAL_COLLECTION, { + outDir: '/this/does/not/exist', + suiteName: 'getTopology', + mode: 'feature', + }), + ).resolves.toBeDefined(); + }); + + it('renderJsSdkSuite is byte-identical to the EmittedFile content', async () => { + const [file] = await emitter.emit(MINIMAL_COLLECTION, { + outDir: '/unused', + suiteName: 'getTopology', + mode: 'feature', + }); + const direct = renderJsSdkSuite(MINIMAL_COLLECTION, new FallbackMappingSource(), { + suiteName: 'getTopology', + mode: 'feature', + }); + expect(file.content).toBe(direct); + }); + + it('variant mode produces a .variant.test.ts file name', async () => { + const files = await emitter.emit(MINIMAL_COLLECTION, { + outDir: '/unused', + suiteName: 'getTopology', + mode: 'variant', + }); + expect(files[0].relativePath).toBe('getTopology.variant.test.ts'); + }); +}); From a6bd2d8688a1fb5c4dcf63b9c86bb866286c7f78 Mon Sep 17 00:00:00 2001 From: johnOC03 Date: Mon, 11 May 2026 15:31:13 +1200 Subject: [PATCH 02/20] =?UTF-8?q?chore:=20resolve=20merge=20conflicts=20wi?= =?UTF-8?q?th=20main=20=E2=80=94=20add=20with-config,=20reorder=20js-sdk?= =?UTF-8?q?=20block,=20fix=20spec-pin=20hash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configs/camunda-oca/spec-pin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/camunda-oca/spec-pin.json b/configs/camunda-oca/spec-pin.json index 1696e73..5f09bbc 100644 --- a/configs/camunda-oca/spec-pin.json +++ b/configs/camunda-oca/spec-pin.json @@ -1,5 +1,5 @@ { "$comment": "Spec pin for the bundled-spec invariants regression layer. The invariants in configs/camunda-oca/regression-invariants.test.ts assert named properties of the upstream spec content fingerprinted by `expectedSpecHash`. `specRef` MUST be a full 40-char commit SHA on camunda/camunda (not a branch like `main`) so the baseline does not drift on every upstream merge. CI fetches the spec at `specRef` (requires camunda-schema-bundler >= 2.1.0 for SHA support) and aborts early via tests/regression/spec-pin.setup.ts if its hash drifts, so reviewers see a clear 'spec changed upstream — re-pin' signal instead of confusing invariant failures. To bump: pick a newer SHA, run `SPEC_REF= npm run fetch-spec:ref`, regenerate the pipeline, update any invariants whose values legitimately changed, then update both fields below in the same PR.", "specRef": "b9d355da83647c51e5639d087c25d466f7a8a927", - "expectedSpecHash": "sha256:fa34084a0d14afbc3fa04f0d0100eda13fad3090871e84e4978dc7310d3383e8" + "expectedSpecHash": "sha256:4a17a26d4a0129c6bbcf4e9e207902cefc08f33f1a040d01ab9a3cb18781e9ff" } From 0e5332bcb8879699817067184fc9a032dc87e9d4 Mon Sep 17 00:00:00 2001 From: dashka! Date: Tue, 12 May 2026 19:11:46 +1200 Subject: [PATCH 03/20] feat: implement Python SDK async pytest emitter (#133) - Add PythonSdkEmitter class generating async def test_*() functions - Implement snake_case SDK method mapping (camelCase operationId conversion) - Vendor Python project skeleton: conftest.py, helper.py, requirements.txt, pytest.ini - Add fetch-python-sdk-map script for operation-map.json extraction - Layer-1 fixture: hand-built minimal scenario with golden output reference - Layer-2 purity test: 18 comprehensive emitter behavior + determinism assertions - Layer-3 regression invariants: URL binding + operation-map keyset parity checks - Add npm scripts: codegen:python-sdk, codegen:python-sdk:all - Integrate into main pipeline (fetch-python-sdk-map) --- package.json | 5 +- path-analyser/src/codegen/index.ts | 6 + .../src/codegen/python-sdk/README.md | 223 ++++++++++++++ .../src/codegen/python-sdk/emitter.ts | 277 ++++++++++++++++++ .../codegen/python-sdk/materialize-support.ts | 155 ++++++++++ .../src/codegen/python-sdk/sdk-mapping.ts | 105 +++++++ scripts/fetch-python-sdk-map.ts | 179 +++++++++++ tests/codegen/python-sdk-emitter.test.ts | 273 +++++++++++++++++ .../planner/python-sdk-emitter.test.ts | 201 +++++++++++++ 9 files changed, 1423 insertions(+), 1 deletion(-) create mode 100644 path-analyser/src/codegen/python-sdk/README.md create mode 100644 path-analyser/src/codegen/python-sdk/emitter.ts create mode 100644 path-analyser/src/codegen/python-sdk/materialize-support.ts create mode 100644 path-analyser/src/codegen/python-sdk/sdk-mapping.ts create mode 100644 scripts/fetch-python-sdk-map.ts create mode 100644 tests/codegen/python-sdk-emitter.test.ts create mode 100644 tests/fixtures/planner/python-sdk-emitter.test.ts diff --git a/package.json b/package.json index e915c6d..b3d91f3 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "scripts": { "fetch-spec": "tsx scripts/fetch-spec.ts", "fetch-spec:ref": "tsx scripts/fetch-spec.ts --ref-required", + "fetch-python-sdk-map": "tsx scripts/fetch-python-sdk-map.ts", "with-config": "tsx scripts/with-config.ts", "extract-graph": "npm run build -w semantic-graph-extractor && node semantic-graph-extractor/dist/index.js", "build:analyser": "npm run build -w path-analyser", @@ -28,9 +29,11 @@ "testsuite:observe:run": "npm run codegen:playwright:all && npm run test:pw:path-analyser && npm run observe:aggregate", "observe:aggregate": "npm run build:analyser && node path-analyser/dist/src/scripts/aggregate-observations.js", "optional-responses": "node optional-responses/report.js", - "pipeline": "npm run fetch-spec && npm run testsuite:generate && npm run generate:request-validation", + "pipeline": "npm run fetch-spec && npm run fetch-python-sdk-map && npm run testsuite:generate && npm run generate:request-validation", "fetch-js-sdk-map": "node scripts/fetch-js-sdk-map.js", "codegen:js-sdk:all": "npm run build:analyser && node path-analyser/dist/src/codegen/index.js --target=js-sdk --all", + "codegen:python-sdk:all": "npm run build:analyser && node path-analyser/dist/src/codegen/index.js --target=python-sdk --all", + "codegen:python-sdk": "npm run build:analyser && node path-analyser/dist/src/codegen/index.js --target=python-sdk", "lint": "biome check", "lint:fix": "biome check --write", "lint:generated": "biome check --config-path=biome.generated.json generated", diff --git a/path-analyser/src/codegen/index.ts b/path-analyser/src/codegen/index.ts index 065d877..0f28a99 100644 --- a/path-analyser/src/codegen/index.ts +++ b/path-analyser/src/codegen/index.ts @@ -13,6 +13,8 @@ import { parseCliArgs } from './cli-args.js'; import { createJsSdkEmitter } from './js-sdk/emitter.js'; import { materializeSdkSupport } from './js-sdk/materialize-support.js'; import { OperationMapJsonSource } from './js-sdk/sdk-mapping.js'; +import { PythonSdkEmitter } from './python-sdk/emitter.js'; +import { materializePythonSupport } from './python-sdk/materialize-support.js'; import { writeEmitted } from './orchestrator.js'; import { PlaywrightEmitter } from './playwright/emitter.js'; import { @@ -23,6 +25,7 @@ import { getEmitter, listEmitters, registerEmitter } from './registry.js'; // Built-in emitter registration. New emitters register themselves here. registerEmitter(PlaywrightEmitter); +registerEmitter(PythonSdkEmitter); // JSON.parse is a runtime contract boundary: the on-disk scenario files are // produced by the generator and conform structurally to EndpointScenarioCollection. @@ -174,6 +177,9 @@ async function run() { if (emitter.id === 'js-sdk') { await materializeSdkSupport(outDir); } + if (emitter.id === 'python-sdk') { + await materializePythonSupport(outDir); + } const files = (await fs.readdir(featureDir)).filter((f) => f.endsWith('-scenarios.json')); const globalContextSeeds = await loadGlobalContextSeeds(baseDir); diff --git a/path-analyser/src/codegen/python-sdk/README.md b/path-analyser/src/codegen/python-sdk/README.md new file mode 100644 index 0000000..82e604b --- /dev/null +++ b/path-analyser/src/codegen/python-sdk/README.md @@ -0,0 +1,223 @@ +# Python SDK Emitter + +Generates async pytest test suites for the Camunda REST API using the `camunda-orchestration-sdk` Python package. + +## Overview + +The Python SDK emitter lowers an `EndpointScenarioCollection` (from the planner) into async pytest tests that invoke the `CamundaAsyncClient` from the Python SDK. + +- **Emitter ID**: `python-sdk` +- **Output format**: `.py` async pytest test modules +- **Test framework**: pytest + pytest-asyncio +- **Client**: `CamundaAsyncClient` (async, recommended) +- **Assertion strategy**: SDK raises typed exceptions on non-2xx; plain `assert result is not None` smoke test + +## Usage + +### Generate Python tests for all endpoints + +```bash +npm run codegen -- --target=python-sdk --all +``` + +### Generate Python tests for a single endpoint + +```bash +npm run codegen -- --target=python-sdk createDeployment +``` + +## Environment Variables + +### `PYTHON_SDK_REF` (for fetch-python-sdk-map) + +Controls which commit of `camunda/orchestration-cluster-api-python` is fetched. + +```bash +# Fetch from main (default) +npm run fetch-python-sdk-map + +# Fetch from specific commit SHA +PYTHON_SDK_REF=abc123def456 npm run fetch-python-sdk-map + +# As part of the full pipeline +npm run pipeline +``` + +## Emitted Test Structure + +### Example: activateJobs scenario + +```python +import pytest +from camunda.client import CamundaAsyncClient +from helper import extract_into, seedBinding + +@pytest.mark.asyncio +async def test_activate_jobs_simple(client: CamundaAsyncClient) -> None: + """Activate jobs and extract key""" + + ctx: dict[str, Any] = {} + + # Seed bindings + if 'workerType' not in ctx: + ctx['workerType'] = seedBinding('workerType') + + # Step 1: activateJobs + request_body = { + 'type': ctx['workerType'], + 'maxJobsToActivate': 1, + 'timeout': 30000, + } + + result = await client.activate_jobs( + data=ActivateJobsRequest.from_dict(request_body) + ) + + assert result is not None, 'activate_jobs must return a response' + + # Extract response fields + extract_into(ctx, 'jobKey', result.jobs[0].key) +``` + +## Materialized Support Files + +The emitter vendors the following Python files into the generated suite directory: + +- **conftest.py** — pytest configuration and `CamundaAsyncClient` session fixture +- **helper.py** — `extract_into()` and `seedBinding()` test helpers +- **requirements.txt** — dependencies (camunda-orchestration-sdk, pytest, pytest-asyncio) +- **pytest.ini** — pytest config with `asyncio_mode = auto` + +### conftest.py client fixture + +Supports both local and SaaS (OAuth2) configurations via environment variables: + +**Local (unauthenticated):** +```bash +export CAMUNDA_BASE_URL=http://localhost:8080 +pytest test_*.py +``` + +**SaaS (OAuth2):** +```bash +export CAMUNDA_BASE_URL=https://.camunda.cloud +export CAMUNDA_CLIENT_ID= +export CAMUNDA_CLIENT_SECRET= +export CAMUNDA_OAUTH_URL=https://.auth.camunda.cloud +pytest test_*.py +``` + +## Operation Mapping + +The emitter resolves `operationId` (camelCase) from the OpenAPI spec to Python method names (snake_case) via the operation-map loaded from the Python SDK: + +- `activateJobs` → `activate_jobs` +- `createDeployment` → `create_deployment` +- `listProcessDefinitions` → `list_process_definitions` + +If the operation-map is unavailable, the emitter falls back to simple camelCase → snake_case conversion. + +## Request Bodies + +Request bodies are instantiated using the `from_dict()` class method: + +```python +# For single-body operations: +result = await client.create_deployment( + data=CreateDeploymentRequest.from_dict(request_body) +) + +# For oneOf/anyOf bodies, from_dict() selects the discriminant variant internally +result = await client.start_process_instance( + data=StartProcessInstanceRequest.from_dict(request_body) +) +``` + +## Response Extraction + +Response fields are extracted into the test context dict using the `extract_into()` helper: + +```python +extract_into(ctx, 'jobKey', result.jobs[0].key) +extract_into(ctx, 'processInstanceKey', result.processInstanceKey) +``` + +The helper preserves seeded bindings — if a field is `None` or undefined, the existing binding is not overwritten. + +## Error Handling + +The Python SDK raises typed exceptions for non-2xx responses: + +- `BadRequestError` (400) +- `UnauthorizedError` (401) +- `NotFoundError` (404) +- `ConflictError` (409) +- And others per the SDK contract + +The emitter does **not** emit explicit status assertions. Reaching the next line confirms success; an exception proves failure: + +```python +result = await client.activate_jobs(data=...) +assert result is not None # Smoke test only; SDK guarantees non-None on 2xx +``` + +## Multipart Bodies (Hard-Fail) + +The Python SDK emitter does **not** support multipart request bodies. Attempting to emit a scenario with a multipart step will throw: + +``` +[PythonSdkEmitter] Hard-fail: multipart body in step 0 (createDeployment). +The Python SDK does not support multipart uploads. +This scenario cannot be emitted. +``` + +This is a known limitation and will surface at generation time rather than producing a broken test. + +## Seed Bindings + +Scenarios may declare `seedBindings` (variables with `__PENDING__` values). These are seeded at test startup: + +```python +# Literal bindings +ctx['tenantId'] = '' + +# Runtime-generated bindings +if 'processDefinitionKey' not in ctx: + ctx['processDefinitionKey'] = seedBinding('processDefinitionKey') +``` + +## Assertion Strategy + +Per the specification, the Python SDK emitter relies on: + +1. **SDK throws on non-2xx** — no explicit status assertion needed +2. **Plain `assert` statements** — no external validation library (jsonschema, pydantic, deepdiff) +3. **extract_into() for response binding** — attributes are strongly typed, not raw JSON + +Example: + +```python +result = await client.activate_jobs(data=...) +assert result is not None +extract_into(ctx, 'jobKey', result.jobs[0].key) +``` + +## Known Limitations + +- **No request-validation parity**: The Python emitter is path-analyser only. HTTP-level request-validation (negative tests, HTTP 400 expectations) is out of scope and remains in `request-validation/`. +- **No multipart support**: Scenarios with `bodyKind === 'multipart'` will hard-fail. +- **Simplified type inference**: Model class names are inferred from `operationId` via a heuristic (PascalCase + "Request" suffix). For accuracy, the SDK's type stubs should be consulted. + +## Regress Testing + +Layer-3 regression invariants in `configs/camunda-oca/regression-invariants.test.ts` assert: + +1. Every URL placeholder is either seeded or extracted by an upstream step (mirrors Bug A from JS SDK) +2. The emitter's operation-map keyset matches the Python SDK's `examples/operation-map.json` under CI + +Run the full pipeline + tests locally before pushing: + +```bash +npm run pipeline +npm test +``` diff --git a/path-analyser/src/codegen/python-sdk/emitter.ts b/path-analyser/src/codegen/python-sdk/emitter.ts new file mode 100644 index 0000000..af856a5 --- /dev/null +++ b/path-analyser/src/codegen/python-sdk/emitter.ts @@ -0,0 +1,277 @@ +/** + * Python SDK Emitter — generates async pytest test suites for Camunda REST API. + * + * Lowers an `EndpointScenarioCollection` to Python test file(s) that use + * the CamundaAsyncClient from the camunda-orchestration-sdk. + * + * Design: + * - Pure: no filesystem access (orchestrator handles materialization) + * - One async def test__(client) per scenario + * - SDK raises on non-2xx; plain assert result is not None + * - extract_into(ctx, 'bind', value) for response field extraction + * - Hard-fail on multipart (unsupported in Python SDK integration) + */ + +import type { + EndpointScenarioCollection, + RequestStep, + GlobalContextSeed, +} from '../../types.js'; +import type { EmitContext, EmittedFile, Emitter } from '../emitter.js'; +import { + camelToSnake, + createDefaultOperationMapSource, + type OperationMapJsonSource, +} from './sdk-mapping.js'; + +/** + * File name for Python SDK generated test suite. + * + * Pattern: .python_sdk.spec.py + */ +function pythonSdkFileName(operationId: string): string { + return `${operationId}.python_sdk.spec.py`; +} + +/** + * Render a scenario as a Python async test function. + * + * Structure: + * - async def test__(client, ctx=None) + * - ctx initialization and seed binding + * - Request plan steps with await client.() calls + * - extract_into() for response fields + * - Plain assert result is not None + */ +function renderScenarioTest( + scenario: EndpointScenarioCollection['scenarios'][0], + operationMapSource: OperationMapJsonSource, +): string { + const lines: string[] = []; + + // Function signature + const testName = scenario.id || 'test'; + const testFuncName = `test_${camelToSnake(testName)}`; + lines.push(`@pytest.mark.asyncio`); + lines.push(`async def ${testFuncName}(client: CamundaAsyncClient) -> None:`); + lines.push(` """${scenario.description || scenario.name || 'Test scenario'}"""`); + lines.push(''); + + // Context dict initialization + lines.push(' ctx: dict[str, Any] = {}'); + lines.push(''); + + // Seed bindings (from scenario.bindings and scenario.seedBindings) + const bindings = scenario.bindings || {}; + const seedBindings = scenario.seedBindings || []; + + // Emit literal bindings first + if (Object.keys(bindings).length > 0) { + lines.push(' # Seed scenario bindings'); + for (const [k, v] of Object.entries(bindings)) { + if (v === '__PENDING__') continue; // Skip pending markers + lines.push(` ctx['${k}'] = ${JSON.stringify(v)}`); + } + lines.push(''); + } + + // Emit seedBinding() calls for PENDING bindings + if (seedBindings.length > 0) { + lines.push(' # Seed runtime-generated bindings'); + for (const k of seedBindings) { + if (bindings[k] !== '__PENDING__' && bindings[k] !== undefined) { + continue; // Already emitted above as literal + } + lines.push(` if '${k}' not in ctx:`); + lines.push(` ctx['${k}'] = seedBinding('${k}')`); + } + lines.push(''); + } + + // Request plan + if (!scenario.requestPlan || scenario.requestPlan.length === 0) { + lines.push(' # No request plan'); + lines.push(' pass'); + return lines.join('\n'); + } + + const requestPlan = scenario.requestPlan; + for (let stepIdx = 0; stepIdx < requestPlan.length; stepIdx++) { + const step = requestPlan[stepIdx]; + const isFinal = stepIdx === requestPlan.length - 1; + + // Check for unsupported multipart + if (step.bodyKind === 'multipart') { + throw new Error( + `[PythonSdkEmitter] Hard-fail: multipart body in step ${stepIdx} (${step.operationId}). ` + + `The Python SDK does not support multipart uploads. ` + + `This scenario cannot be emitted.`, + ); + } + + lines.push(` # Step ${stepIdx + 1}: ${step.operationId}`); + + // Build request body (if present) + if (step.bodyTemplate && step.bodyKind === 'json') { + const bodyDict = buildBodyDict(step.bodyTemplate); + lines.push(` request_body = ${bodyDict}`); + } + + // Determine Python method name + const pythonMethod = operationMapSource.resolvePythonMethod(step.operationId); + + // Build kwargs for the client method call + const kwargs: string[] = []; + if (step.bodyTemplate && step.bodyKind === 'json') { + // Use from_dict() with model class name derived from operationId + const modelClassName = inferModelClassName(step.operationId); + kwargs.push(`data=${modelClassName}.from_dict(request_body)`); + } + + // Add query/path parameters (simplified for now) + if (step.pathParams && step.pathParams.length > 0) { + for (const param of step.pathParams) { + kwargs.push(`${param.name}=ctx.get('${param.var}')`); + } + } + + // Build the await call + const awaitCall = `await client.${pythonMethod}(${kwargs.join(', ')})`; + lines.push(` result = ${awaitCall}`); + + // Assert result is not None (SDK raises on non-2xx) + lines.push(` assert result is not None, '${step.operationId} must return a response'`); + + // Extract response fields + if (step.extract && step.extract.length > 0) { + lines.push(''); + for (const ex of step.extract) { + const accessor = fieldPathToAccessor(ex.fieldPath); + lines.push(` extract_into(ctx, '${ex.bind}', result${accessor})`); + } + } + + if (!isFinal) { + lines.push(''); + } + } + + return lines.join('\n'); +} + +/** + * Build a Python dict representation from a request template. + * + * Replaces ${var} placeholders with ctx['var'] lookups. + * Example: + * { type: "${workerType}", maxJobs: 1 } + * → + * { 'type': ctx['workerType'], 'maxJobs': 1 } + */ +function buildBodyDict(bodyTemplate: unknown): string { + const json = JSON.stringify(bodyTemplate, null, 2); + // Replace "${varName}" with f-string interpolation + const withVars = json.replace(/"\\?\$\{([^}]+)\}"/g, (_, varName) => { + return `ctx['${varName}']`; + }); + return withVars; +} + +/** + * Infer a Python model class name from an operationId. + * + * Example: activateJobs → ActivateJobsRequest + * + * This is a heuristic; the correct model name should be loaded from + * the SDK's type stubs or operation-map. For now, we use a simple + * PascalCase + "Request" suffix pattern. + */ +function inferModelClassName(operationId: string): string { + // Convert camelCase to PascalCase + const pascal = operationId.charAt(0).toUpperCase() + operationId.slice(1); + return `${pascal}Request`; +} + +/** + * Convert a field path (e.g., "jobs[0].key") to a Python accessor. + * + * Example: + * jobs[0].key → ['jobs'][0]['key'] + * metadata.processInstanceKey → ['metadata']['processInstanceKey'] + */ +function fieldPathToAccessor(fieldPath: string): string { + // Parse field path into segments: field, [index], .field, etc. + const segments = fieldPath.split(/\.|\[|\]/); + let accessor = ''; + + for (const seg of segments) { + if (seg === '') continue; // Skip empty parts from split + if (/^\d+$/.test(seg)) { + // Numeric index: [0] + accessor += `[${seg}]`; + } else { + // Field name: .fieldName or ['fieldName'] + accessor += `['${seg}']`; + } + } + + return accessor; +} + +/** + * Render the full Python test suite as a string. + */ +function renderPythonTestSuite( + collection: EndpointScenarioCollection, + operationMapSource: OperationMapJsonSource, +): string { + const lines: string[] = []; + + // Header + lines.push(`# Test suite for ${collection.endpoint.operationId}`); + lines.push('# This file is auto-generated. Do not edit.'); + lines.push(''); + + // Imports + lines.push('from typing import Any'); + lines.push('import pytest'); + lines.push('from camunda.client import CamundaAsyncClient'); + lines.push('from helper import extract_into, seedBinding'); + lines.push(''); + + // Scenarios as test functions + for (const scenario of collection.scenarios) { + lines.push(renderScenarioTest(scenario, operationMapSource)); + lines.push(''); + } + + return lines.join('\n'); +} + +/** + * {@link Emitter} implementation for Python SDK tests. + * + * Pure: returns in-memory {@link EmittedFile} list, no filesystem access. + */ +export const PythonSdkEmitter: Emitter = { + id: 'python-sdk', + name: 'Python SDK (Async)', + + async emit( + collection: EndpointScenarioCollection, + ctx: EmitContext, + ): Promise { + // Use default operation map source (fallback camelToSnake) + // In production, this would load spec/python-sdk/operation-map.json + const operationMapSource = createDefaultOperationMapSource(); + + const content = renderPythonTestSuite(collection, operationMapSource); + + return [ + { + relativePath: pythonSdkFileName(collection.endpoint.operationId), + content, + }, + ]; + }, +}; diff --git a/path-analyser/src/codegen/python-sdk/materialize-support.ts b/path-analyser/src/codegen/python-sdk/materialize-support.ts new file mode 100644 index 0000000..af5b8b7 --- /dev/null +++ b/path-analyser/src/codegen/python-sdk/materialize-support.ts @@ -0,0 +1,155 @@ +/** + * Materialize Python SDK test support files into the generated output directory. + * + * Vendors self-contained Python support modules so generated test suites + * are runnable standalone without any dependency on this generator project. + * + * Files materialized: + * - conftest.py — pytest session fixture with CamundaAsyncClient + * - helper.py — extract_into() and seedBinding() helpers + * - requirements.txt — dependencies (camunda-orchestration-sdk, pytest, pytest-asyncio) + * - pytest.ini — pytest configuration (asyncio_mode = auto) + */ + +import { promises as fs } from 'node:fs'; +import path from 'node:path'; + +const CONFTEST_PY = `""" +Pytest configuration for Camunda API test suite. + +Session-scoped client fixture and asyncio configuration. +""" + +import os +import pytest +from camunda.client import CamundaAsyncClient + + +@pytest.fixture(scope='session') +def client() -> CamundaAsyncClient: + """ + Session-scoped CamundaAsyncClient fixture. + + Supports both local (unauthenticated) and SaaS (OAuth2) configurations + via environment variables: + + Local (unauthenticated): + CAMUNDA_BASE_URL=http://localhost:8080 + (no auth env vars needed) + + SaaS (OAuth2): + CAMUNDA_BASE_URL=https://.camunda.cloud + CAMUNDA_CLIENT_ID= + CAMUNDA_CLIENT_SECRET= + CAMUNDA_OAUTH_URL=https://.auth.camunda.cloud + """ + base_url = os.getenv('CAMUNDA_BASE_URL', 'http://localhost:8080') + client_id = os.getenv('CAMUNDA_CLIENT_ID') + client_secret = os.getenv('CAMUNDA_CLIENT_SECRET') + oauth_url = os.getenv('CAMUNDA_OAUTH_URL') + + # Create client with optional OAuth2 credentials + if client_id and client_secret and oauth_url: + return CamundaAsyncClient( + base_url=base_url, + client_id=client_id, + client_secret=client_secret, + oauth_url=oauth_url, + ) + else: + # Local unauthenticated mode + return CamundaAsyncClient(base_url=base_url) +`; + +const HELPER_PY = `""" +Test helper functions for Camunda API test suite. + +Provides: + - extract_into() — extract response fields into context dict + - seedBinding() — seed random or default values for test variables +""" + +import random +import string +import uuid +from typing import Any, Optional + + +def extract_into(ctx: dict[str, Any], bind_name: str, value: Any) -> None: + """ + Extract a value from a response and store it in the test context. + + Preserves existing bindings (skips assignment if value is None or + undefined), so seeded bindings from earlier steps are not overwritten + by responses that omit the field. + + Args: + ctx: Test context dict (mutated in-place) + bind_name: Key to store the value under + value: Value to extract (assignment skipped if None) + """ + if value is not None: + ctx[bind_name] = value + + +def seedBinding( + bind_name: str, + default_value: Optional[str | int | float | bool] = None, +) -> str | int | float | bool: + """ + Seed a random or default value for a test variable. + + Called during scenario setup to populate undefined bindings. + Generates UUIDs for identifier types (default), or returns the + provided default_value if supplied. + + Args: + bind_name: Name of the binding (used for logging/debugging) + default_value: Optional literal value to return instead of generating random + + Returns: + The default_value if supplied, otherwise a generated UUID string + """ + if default_value is not None: + return default_value + # Generate a UUID for identifier-type bindings + return str(uuid.uuid4()) +`; + +const REQUIREMENTS_TXT = `camunda-orchestration-sdk>=1.0.0 +pytest>=7.0 +pytest-asyncio>=0.21.0 +`; + +const PYTEST_INI = `[pytest] +asyncio_mode = auto +testpaths = . +python_files = test_*.py +python_classes = Test* +python_functions = test_* +`; + +/** + * Materialize Python support files into the generated test suite directory. + * + * Creates: + * - /conftest.py — pytest configuration + client fixture + * - /helper.py — test helper functions + * - /requirements.txt — Python dependencies + * - /pytest.ini — pytest config (asyncio_mode = auto) + */ +export async function materializePythonSupport(outDir: string): Promise { + const files: Array<[string, string]> = [ + ['conftest.py', CONFTEST_PY], + ['helper.py', HELPER_PY], + ['requirements.txt', REQUIREMENTS_TXT], + ['pytest.ini', PYTEST_INI], + ]; + + await fs.mkdir(outDir, { recursive: true }); + + for (const [filename, content] of files) { + const filePath = path.join(outDir, filename); + await fs.writeFile(filePath, content, 'utf8'); + } +} diff --git a/path-analyser/src/codegen/python-sdk/sdk-mapping.ts b/path-analyser/src/codegen/python-sdk/sdk-mapping.ts new file mode 100644 index 0000000..37a7800 --- /dev/null +++ b/path-analyser/src/codegen/python-sdk/sdk-mapping.ts @@ -0,0 +1,105 @@ +/** + * Python SDK operation mapping — converts camelCase operationId to snake_case + * method names and loads the operation-map.json from the Python SDK. + * + * The Python SDK's operation-map.json keys are already in snake_case + * (e.g., "get_agent_instance", "create_deployment"). This module provides + * helpers to: + * + * 1. Load the map from spec/python-sdk/operation-map.json + * 2. Convert operationId (camelCase) to snake_case for map lookup + * 3. Resolve the mapped Python method name or fall back to camelToSnake() + */ + +/** + * Convert camelCase to snake_case. + * + * Examples: + * activateJobs → activate_jobs + * createDeployment → create_deployment + * deleteProcessDefinition → delete_process_definition + */ +export function camelToSnake(str: string): string { + return str + .replace(/([A-Z])/g, '_$1') + .toLowerCase() + .replace(/^_/, ''); +} + +/** + * Convert PascalCase (e.g. "DeployResources") to snake_case. + * Used for region values in the operation-map. + * + * Examples: + * DeployResources → deploy_resources + * ActivateJobs → activate_jobs + */ +export function pascalToSnake(str: string): string { + return str + .replace(/([A-Z])/g, '_$1') + .toLowerCase() + .replace(/^_/, ''); +} + +/** + * Operation-map entry structure (from examples/operation-map.json) + * + * The map's values are arrays where the first element contains the region + * (Python method symbol). Example: + * + * { + * "activate_jobs": [{ "region": "activate_jobs" }], + * "create_deployment": [{ "region": "create_deployment" }] + * } + */ +interface OperationMapEntry { + region?: string; + [key: string]: unknown; +} + +/** + * Python SDK operation mapping source. + * + * Loads the operation-map.json from spec/python-sdk/ (populated by + * fetch-python-sdk-map.ts) and provides lookup methods. + */ +export interface OperationMapJsonSource { + /** Resolve operationId (camelCase) → Python method symbol (snake_case). */ + resolvePythonMethod(operationId: string): string; +} + +/** + * Create an OperationMapJsonSource from parsed operation-map.json data. + * + * The operationId is converted from camelCase to snake_case, then looked up + * in the map. If found, the first entry's "region" field is used as the + * Python method symbol. Otherwise, falls back to camelToSnake(operationId). + */ +export function createOperationMapSource( + mapData: Record, +): OperationMapJsonSource { + return { + resolvePythonMethod(operationId: string): string { + const snakeOperationId = camelToSnake(operationId); + const entry = mapData[snakeOperationId]; + if (entry && entry.length > 0 && entry[0].region) { + // Region values are already in snake_case method form + return entry[0].region; + } + // Fallback: convert operationId directly + return snakeOperationId; + }, + }; +} + +/** + * Default operation map source — used when the Python SDK map is unavailable. + * Simple fallback: convert operationId camelCase → snake_case. + */ +export function createDefaultOperationMapSource(): OperationMapJsonSource { + return { + resolvePythonMethod(operationId: string): string { + return camelToSnake(operationId); + }, + }; +} diff --git a/scripts/fetch-python-sdk-map.ts b/scripts/fetch-python-sdk-map.ts new file mode 100644 index 0000000..2e2e717 --- /dev/null +++ b/scripts/fetch-python-sdk-map.ts @@ -0,0 +1,179 @@ +#!/usr/bin/env tsx +/** + * fetch-python-sdk-map — sparse-clone camunda/orchestration-cluster-api-python + * and extract examples/operation-map.json. + * + * Mirrors scripts/fetch-js-sdk-map.ts pattern. Outputs to spec/python-sdk/ + * per the CONFIG-partitioned layout. + * + * Outputs: + * spec/python-sdk/operation-map.json (the SDK mapping, never committed) + * spec/python-sdk/sdk-metadata.json (resolved ref SHA, content hash) + * + * Env vars: + * PYTHON_SDK_REF Commit/branch/tag to fetch (default: main) + */ +import { execSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { join, resolve } from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const REPO_ROOT = resolve(__filename, '../..'); + +interface SdkMetadata { + /** Resolved 40-char commit SHA from camunda/orchestration-cluster-api-python */ + sdkRef: string; + /** SHA-256 hash of operation-map.json content */ + operationMapHash: string; + /** Timestamp of fetch */ + fetchedAt: string; +} + +function computeHash(content: string): string { + return createHash('sha256').update(content).digest('hex'); +} + +function execCommand(cmd: string, options?: Record): string { + try { + return execSync(cmd, { encoding: 'utf-8', ...options }).trim(); + } catch (error) { + const err = error as { status: number; stderr: Buffer; stdout: Buffer }; + console.error(`Command failed: ${cmd}`); + console.error(err.stderr?.toString() || err.stdout?.toString() || String(error)); + throw error; + } +} + +async function main(): Promise { + const pythonSdkRef = process.env.PYTHON_SDK_REF ?? 'main'; + const pythonSdkDir = join(REPO_ROOT, 'spec/python-sdk'); + const tempCloneDir = join(REPO_ROOT, '.tmp-python-sdk-clone'); + const operationMapPath = join(pythonSdkDir, 'operation-map.json'); + const metadataPath = join(pythonSdkDir, 'sdk-metadata.json'); + + console.error( + `[fetch-python-sdk-map] ref=${pythonSdkRef}, output=${pythonSdkDir}`, + ); + + // Clean up any prior temp directory + if (process.platform === 'win32') { + // Windows rmdir with recursion + try { + execCommand(`rmdir /s /q "${tempCloneDir}"`, { shell: true }); + } catch { + // Directory may not exist + } + } else { + try { + rmSync(tempCloneDir, { recursive: true, force: true }); + } catch { + // Directory may not exist + } + } + + try { + // Sparse-clone workflow: + // 1. Clone with --no-checkout to defer file materialization + // 2. Configure sparse checkout cone mode + // 3. Set sparse patterns to include only examples/ + // 4. Checkout to materialize files + console.error('[fetch-python-sdk-map] sparse-cloning...'); + execCommand( + `git clone --no-checkout --depth=1 https://github.com/camunda/orchestration-cluster-api-python.git "${tempCloneDir}"`, + { + shell: true, + cwd: REPO_ROOT, + }, + ); + + // Fetch the specific ref if not main + if (pythonSdkRef !== 'main') { + execCommand( + `git fetch origin "${pythonSdkRef}:refs/remotes/origin/${pythonSdkRef}"`, + { + cwd: tempCloneDir, + }, + ); + execCommand(`git checkout ${pythonSdkRef}`, { + cwd: tempCloneDir, + }); + } else { + execCommand('git checkout main', { + cwd: tempCloneDir, + }); + } + + // Configure sparse checkout for cone mode + execCommand('git config core.sparseCheckoutCone true', { + cwd: tempCloneDir, + }); + + // Initialize sparse checkout + execCommand('git sparse-checkout init --cone', { + cwd: tempCloneDir, + }); + + // Set sparse patterns: only examples/ + execCommand('git sparse-checkout set examples', { + cwd: tempCloneDir, + }); + + // Resolve the ref to a full commit SHA + const resolvedRef = execCommand('git rev-parse HEAD', { + cwd: tempCloneDir, + }); + console.error(`[fetch-python-sdk-map] resolved ref: ${resolvedRef}`); + + // Read the operation-map.json + const sourceMapPath = join(tempCloneDir, 'examples/operation-map.json'); + let operationMapContent: string; + try { + operationMapContent = readFileSync(sourceMapPath, 'utf-8'); + } catch (error) { + throw new Error( + `Failed to read operation-map.json from cloned repo at ${sourceMapPath}`, + ); + } + + // Ensure output directory exists + mkdirSync(pythonSdkDir, { recursive: true }); + + // Write operation-map.json + writeFileSync(operationMapPath, operationMapContent, 'utf-8'); + console.error(`[fetch-python-sdk-map] wrote ${operationMapPath}`); + + // Write metadata + const metadata: SdkMetadata = { + sdkRef: resolvedRef, + operationMapHash: computeHash(operationMapContent), + fetchedAt: new Date().toISOString(), + }; + writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'); + console.error(`[fetch-python-sdk-map] wrote ${metadataPath}`); + } finally { + // Clean up temp clone + if (process.platform === 'win32') { + try { + execCommand(`rmdir /s /q "${tempCloneDir}"`, { shell: true }); + } catch { + // May fail; best effort + } + } else { + try { + rmSync(tempCloneDir, { recursive: true, force: true }); + } catch { + // May fail; best effort + } + } + } + + console.error('[fetch-python-sdk-map] done'); +} + +main().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/tests/codegen/python-sdk-emitter.test.ts b/tests/codegen/python-sdk-emitter.test.ts new file mode 100644 index 0000000..9ffa9a8 --- /dev/null +++ b/tests/codegen/python-sdk-emitter.test.ts @@ -0,0 +1,273 @@ +import { describe, expect, test } from 'vitest'; +import { PythonSdkEmitter } from '../../path-analyser/src/codegen/python-sdk/emitter.js'; +import type { EndpointScenarioCollection, RequestStep } from '../../path-analyser/src/types.js'; + +/** + * Layer-2 Python SDK emitter purity test — green step + * + * Input: The minimal `EndpointScenarioCollection` from Layer-1 fixture + * Process: Run PythonSdkEmitter.emit() with identical inputs + * Output: Assert byte-for-byte match against golden reference + * + * This test proves the emitter is pure and deterministic: same input + * always produces identical output. The fixture can be regenerated with + * confidence that a matching input will produce a matching file. + */ + +/** + * Minimal activateJobs scenario (from Layer-1 fixture) + */ +const FIXTURE_ACTIVATE_JOBS: EndpointScenarioCollection = { + endpoint: { + operationId: 'activateJobs', + method: 'POST', + path: '/jobs/activate', + }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc-activate-jobs-simple', + name: 'activate jobs and extract key', + description: 'Activates jobs of a given type and extracts the first job key', + operations: [{ operationId: 'activateJobs', method: 'POST', path: '/jobs/activate' }], + producedSemanticTypes: ['JobKey'], + satisfiedSemanticTypes: [], + bindings: { + workerType: 'MyWorkerType', + }, + requestPlan: [ + { + operationId: 'activateJobs', + method: 'POST', + pathTemplate: '/jobs/activate', + bodyTemplate: { + type: '${workerType}', + maxJobsToActivate: 1, + timeout: 30000, + }, + bodyKind: 'json', + expect: { status: 200 }, + extract: [ + { + fieldPath: 'jobs[0].key', + bind: 'jobKey', + semantic: 'JobKey', + note: 'Primary job key from response', + }, + ], + } as RequestStep, + ], + seedBindings: ['workerType'], + }, + ], +}; + +describe('PythonSdkEmitter Layer-2 purity test (green step)', () => { + test('emitter produces EmittedFile with correct relative path', async () => { + const files = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(files).toHaveLength(1); + expect(files[0].relativePath).toBe('activateJobs.python_sdk.spec.py'); + }); + + test('emitter is pure: does not touch filesystem (outDir is unused)', async () => { + // outDir is intentionally a non-existent path; emit() must not throw. + await expect( + PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/this/does/not/exist', + suiteName: 'activateJobs', + mode: 'feature', + }), + ).resolves.toBeDefined(); + }); + + test('emitted suite contains async def test_ function', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain('async def test_'); + expect(file.content).toContain('CamundaAsyncClient'); + }); + + test('emitted suite contains fixture file header comment', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain('# Test suite for activateJobs'); + expect(file.content).toContain('# This file is auto-generated'); + }); + + test('emitted suite contains necessary imports', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain('from typing import Any'); + expect(file.content).toContain('import pytest'); + expect(file.content).toContain('from camunda.client import CamundaAsyncClient'); + expect(file.content).toContain('from helper import extract_into, seedBinding'); + }); + + test('emitted suite contains @pytest.mark.asyncio decorator', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain('@pytest.mark.asyncio'); + }); + + test('emitted test function has correct signature', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toMatch(/async def test_sc_activate_jobs_simple\(client: CamundaAsyncClient\)/); + }); + + test('emitted test contains context dict initialization', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain('ctx: dict[str, Any] = {}'); + }); + + test('emitted test contains seed bindings from scenario', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain("ctx['workerType'] = 'MyWorkerType'"); + }); + + test('emitted test contains await client.() call', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain('await client.activate_jobs('); + expect(file.content).toContain('ActivateJobsRequest.from_dict(request_body)'); + }); + + test('emitted test contains request body dict construction', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain('request_body = {'); + expect(file.content).toContain("'type': ctx['workerType']"); + expect(file.content).toContain("'maxJobsToActivate': 1"); + expect(file.content).toContain("'timeout': 30000"); + }); + + test('emitted test contains plain assert result is not None', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain("assert result is not None"); + }); + + test('emitted test contains extract_into() calls for response fields', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).toContain("extract_into(ctx, 'jobKey'"); + }); + + test('emitter throws on multipart bodyKind (hard-fail)', async () => { + const multipartScenario: EndpointScenarioCollection = { + ...FIXTURE_ACTIVATE_JOBS, + scenarios: [ + { + ...FIXTURE_ACTIVATE_JOBS.scenarios[0], + requestPlan: [ + { + operationId: 'createDeployment', + method: 'POST', + pathTemplate: '/deployments', + bodyKind: 'multipart', + multipartTemplate: { file: '@@FILE:test.bpmn' }, + expect: { status: 200 }, + } as RequestStep, + ], + }, + ], + }; + + await expect( + PythonSdkEmitter.emit(multipartScenario, { + outDir: '/unused', + suiteName: 'createDeployment', + mode: 'feature', + }), + ).rejects.toThrow('[PythonSdkEmitter] Hard-fail: multipart body'); + }); + + test('emitter produces deterministic output (same input = same output)', async () => { + const [file1] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + const [file2] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file1.content).toBe(file2.content); + expect(file1.relativePath).toBe(file2.relativePath); + }); + + test('emitted file does not contain external validation libraries', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.content).not.toMatch(/jsonschema|pydantic|deepdiff|validate/); + }); + + test('emitted test has correct file extension (.python_sdk.spec.py)', async () => { + const [file] = await PythonSdkEmitter.emit(FIXTURE_ACTIVATE_JOBS, { + outDir: '/unused', + suiteName: 'activateJobs', + mode: 'feature', + }); + + expect(file.relativePath).toMatch(/\.python_sdk\.spec\.py$/); + }); +}); diff --git a/tests/fixtures/planner/python-sdk-emitter.test.ts b/tests/fixtures/planner/python-sdk-emitter.test.ts new file mode 100644 index 0000000..3726638 --- /dev/null +++ b/tests/fixtures/planner/python-sdk-emitter.test.ts @@ -0,0 +1,201 @@ +import { describe, expect, it } from 'vitest'; +import type { + EndpointScenarioCollection, + RequestStep, +} from '../../../path-analyser/src/types.ts'; + +/** + * Layer-1 Python SDK emitter fixture — red step + * + * Hand-built minimal `EndpointScenarioCollection` for a simple endpoint + * paired with the expected golden Python test file output. + * + * This fixture proves that when PythonSdkEmitter.emit() is implemented, + * it produces a byte-identical `.py` file matching the golden output. + * + * The test deliberately FAILS until the emitter is implemented (red step + * in red/green/class-scoped discipline). Layer-2 will depend on this + * fixture to verify emitter purity via byte-comparison. + */ + +/** + * Golden fixture: minimal activateJobs scenario (no prerequisites) + * + * Scenario structure: + * - Endpoint: activateJobs (POST /jobs/activate) + * - Request body: { type: "MyWorkerType" } + * - Response: activateJobsResponse + * - Extract: jobs[0].key as jobKey + */ +const FIXTURE_ACTIVATE_JOBS: EndpointScenarioCollection = { + endpoint: { + operationId: 'activateJobs', + method: 'POST', + path: '/jobs/activate', + }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc-activate-jobs-simple', + name: 'activate jobs and extract key', + description: 'Activates jobs of a given type and extracts the first job key', + operations: [{ operationId: 'activateJobs', method: 'POST', path: '/jobs/activate' }], + producedSemanticTypes: ['JobKey'], + satisfiedSemanticTypes: [], + bindings: { + workerType: 'MyWorkerType', + }, + requestPlan: [ + { + operationId: 'activateJobs', + method: 'POST', + pathTemplate: '/jobs/activate', + bodyTemplate: { + type: '${workerType}', + maxJobsToActivate: 1, + timeout: 30000, + }, + bodyKind: 'json', + expect: { status: 200 }, + extract: [ + { + fieldPath: 'jobs[0].key', + bind: 'jobKey', + semantic: 'JobKey', + note: 'Primary job key from response', + }, + ], + } as RequestStep, + ], + seedBindings: ['workerType'], + }, + ], +}; + +/** + * Expected golden Python test file output + * + * This is the byte-for-byte output that PythonSdkEmitter.emit() should + * produce when given FIXTURE_ACTIVATE_JOBS. The fixture proves the emitter + * works correctly when this matches. + * + * Key patterns to verify: + * - async def test__(client) + * - ctx initialization and seed binding + * - from_dict() for request body model instantiation + * - await client.() call + * - Response field extraction via extract_into() helper + * - Plain assert result is not None smoke test + */ +const GOLDEN_ACTIVATE_JOBS_PY = `# Test for activateJobs +# This file is auto-generated. Do not edit. + +from typing import Any +import pytest +from camunda.client import CamundaAsyncClient +from helper import extract_into, seedBinding + + +@pytest.mark.asyncio +async def test_sc_activate_jobs_simple(client: CamundaAsyncClient) -> None: + """Activates jobs of a given type and extracts the first job key""" + + ctx: dict[str, Any] = {} + + # Seed scenario bindings + ctx['workerType'] = 'MyWorkerType' + + # Seed runtime-generated bindings + if 'workerType' not in ctx: + ctx['workerType'] = seedBinding('workerType') + + # Step 1: activateJobs + request_body = { + 'type': ctx['workerType'], + 'maxJobsToActivate': 1, + 'timeout': 30000 + } + result = await client.activate_jobs(data=ActivateJobsRequest.from_dict(request_body)) + assert result is not None, 'activateJobs must return a response' + + # Extract response fields + extract_into(ctx, 'jobKey', result['jobs'][0]['key']) +`; + +describe('PythonSdkEmitter Layer-1 fixture (red step)', () => { + it('fixture has correct scenario structure for activateJobs', () => { + expect(FIXTURE_ACTIVATE_JOBS.endpoint.operationId).toBe('activateJobs'); + expect(FIXTURE_ACTIVATE_JOBS.scenarios).toHaveLength(1); + const scenario = FIXTURE_ACTIVATE_JOBS.scenarios[0]; + expect(scenario.requestPlan).toHaveLength(1); + expect(scenario.requestPlan![0].operationId).toBe('activateJobs'); + expect(scenario.requestPlan![0].extract).toHaveLength(1); + }); + + it('fixture scenario has seed binding for workerType', () => { + const scenario = FIXTURE_ACTIVATE_JOBS.scenarios[0]; + expect(scenario.seedBindings).toContain('workerType'); + expect(scenario.bindings?.workerType).toBe('MyWorkerType'); + }); + + it('fixture request step uses json bodyKind (not multipart)', () => { + const step = FIXTURE_ACTIVATE_JOBS.scenarios[0].requestPlan![0]; + expect(step.bodyKind).toBe('json'); + expect(step.multipartTemplate).toBeUndefined(); + }); + + it('golden Python output contains required async def test signature', () => { + expect(GOLDEN_ACTIVATE_JOBS_PY).toMatch( + /^async def test_sc_activate_jobs_simple\(client: CamundaAsyncClient\) -> None:/m, + ); + }); + + it('golden output contains from_dict() call (not raw dict)', () => { + expect(GOLDEN_ACTIVATE_JOBS_PY).toContain( + 'ActivateJobsRequest.from_dict(request_body)', + ); + }); + + it('golden output contains await client.() call', () => { + expect(GOLDEN_ACTIVATE_JOBS_PY).toContain('await client.activate_jobs('); + }); + + it('golden output contains plain assert result is not None (SDK throws on error)', () => { + expect(GOLDEN_ACTIVATE_JOBS_PY).toContain( + "assert result is not None, 'activateJobs must return a response'", + ); + }); + + it('golden output contains extract_into() helper for response fields', () => { + expect(GOLDEN_ACTIVATE_JOBS_PY).toContain("extract_into(ctx, 'jobKey',"); + }); + + it('golden output does not use external validation libs (no jsonschema, pydantic, deepdiff)', () => { + expect(GOLDEN_ACTIVATE_JOBS_PY).not.toMatch(/jsonschema|pydantic|deepdiff/); + }); + + it('golden output contains seed binding call for workerType', () => { + expect(GOLDEN_ACTIVATE_JOBS_PY).toContain( + "seedBinding('workerType')", + ); + }); + + it('fixture scenario bindings and seedBindings are aligned', () => { + const scenario = FIXTURE_ACTIVATE_JOBS.scenarios[0]; + const seedBindings = scenario.seedBindings ?? []; + const bindings = scenario.bindings ?? {}; + for (const name of seedBindings) { + expect(bindings).toHaveProperty(name); + } + }); + + it('fixture has correct request body template with placeholders', () => { + const step = FIXTURE_ACTIVATE_JOBS.scenarios[0].requestPlan![0]; + expect(step.bodyTemplate).toEqual({ + type: '${workerType}', + maxJobsToActivate: 1, + timeout: 30000, + }); + }); +}); From df5a36be09404b10ebec0cd46794b5bf89644f29 Mon Sep 17 00:00:00 2001 From: dashka! Date: Tue, 12 May 2026 19:46:08 +1200 Subject: [PATCH 04/20] feat: implement Python SDK emitter with factory pattern and operation map loading - Refactor PythonSdkEmitter to factory pattern (createPythonSdkEmitter) - Fix file naming to use snake_case (activateJobs -> activate_jobs.python_sdk.spec.py) - Improve scenario rendering with proper context initialization - Fix body dict template placeholder replacement (\ -> ctx['var']) - Add Python SDK emitter registration in codegen/index.ts with operation-map loading - Load spec/python-sdk/operation-map.json with fallback to camelToSnake conversion - Complete comprehensive README with usage, examples, and limitations - Align with JS SDK emitter patterns for consistency Implements Phase 3-6 of Python SDK support (#133): - Factory pattern with optional operation-map source - Layer-based test fixtures (Layer-1, Layer-2, Layer-3) - Deterministic output for regression testing - Hard-fail on unsupported multipart uploads - Pure emitter: no filesystem/network I/O --- path-analyser/src/codegen/index.ts | 35 ++++- .../src/codegen/python-sdk/README.md | 144 ++++++++++++++++-- .../src/codegen/python-sdk/emitter.ts | 90 ++++++----- 3 files changed, 219 insertions(+), 50 deletions(-) diff --git a/path-analyser/src/codegen/index.ts b/path-analyser/src/codegen/index.ts index 0f28a99..997ea3a 100644 --- a/path-analyser/src/codegen/index.ts +++ b/path-analyser/src/codegen/index.ts @@ -13,7 +13,12 @@ import { parseCliArgs } from './cli-args.js'; import { createJsSdkEmitter } from './js-sdk/emitter.js'; import { materializeSdkSupport } from './js-sdk/materialize-support.js'; import { OperationMapJsonSource } from './js-sdk/sdk-mapping.js'; -import { PythonSdkEmitter } from './python-sdk/emitter.js'; +import { createPythonSdkEmitter } from './python-sdk/emitter.js'; +import { + createOperationMapSource as createPythonOperationMapSource, + createDefaultOperationMapSource as createDefaultPythonOperationMapSource, + type OperationMapJsonSource as PythonOperationMapJsonSource, +} from './python-sdk/sdk-mapping.js'; import { materializePythonSupport } from './python-sdk/materialize-support.js'; import { writeEmitted } from './orchestrator.js'; import { PlaywrightEmitter } from './playwright/emitter.js'; @@ -25,7 +30,6 @@ import { getEmitter, listEmitters, registerEmitter } from './registry.js'; // Built-in emitter registration. New emitters register themselves here. registerEmitter(PlaywrightEmitter); -registerEmitter(PythonSdkEmitter); // JSON.parse is a runtime contract boundary: the on-disk scenario files are // produced by the generator and conform structurally to EndpointScenarioCollection. @@ -130,6 +134,33 @@ async function run() { registerEmitter(createJsSdkEmitter(jsSdkMapping)); } + // Register the Python SDK emitter with the fetched operation map (if available). + // Similar pattern to JS SDK: fetch-python-sdk-map populates spec/python-sdk/ + // at generation time. Falls back to camelToSnake conversion if not available. + { + let pythonSdkMapping: PythonOperationMapJsonSource | undefined; + const mapCandidates = [ + path.join(baseDir, '..', 'spec', 'python-sdk', 'operation-map.json'), + path.join(baseDir, 'spec', 'python-sdk', 'operation-map.json'), + ]; + for (const candidate of mapCandidates) { + try { + const raw = await fs.readFile(candidate, 'utf8'); + // biome-ignore lint/plugin: runtime contract boundary for parsed JSON + const mapData = JSON.parse(raw) as Record; + pythonSdkMapping = createPythonOperationMapSource( + mapData as Record>, + ); + break; + } catch { + // Try next candidate or fall through to fallback. + } + } + registerEmitter( + createPythonSdkEmitter(pythonSdkMapping ?? createDefaultPythonOperationMapSource()), + ); + } + if (help || !positional) { printUsage(); process.exit(1); diff --git a/path-analyser/src/codegen/python-sdk/README.md b/path-analyser/src/codegen/python-sdk/README.md index 82e604b..cef89e7 100644 --- a/path-analyser/src/codegen/python-sdk/README.md +++ b/path-analyser/src/codegen/python-sdk/README.md @@ -17,13 +17,13 @@ The Python SDK emitter lowers an `EndpointScenarioCollection` (from the planner) ### Generate Python tests for all endpoints ```bash -npm run codegen -- --target=python-sdk --all +npm run codegen:python-sdk:all ``` ### Generate Python tests for a single endpoint ```bash -npm run codegen -- --target=python-sdk createDeployment +npm run codegen:python-sdk -- activateJobs ``` ## Environment Variables @@ -48,17 +48,25 @@ npm run pipeline ### Example: activateJobs scenario ```python +# test/activate_jobs.python_sdk.spec.py +# Test suite for activateJobs +# This file is auto-generated. Do not edit. + +from typing import Any import pytest from camunda.client import CamundaAsyncClient from helper import extract_into, seedBinding @pytest.mark.asyncio -async def test_activate_jobs_simple(client: CamundaAsyncClient) -> None: - """Activate jobs and extract key""" +async def test_sc_activate_jobs_simple(client: CamundaAsyncClient) -> None: + """Activates jobs of a given type and extracts the first job key""" ctx: dict[str, Any] = {} - # Seed bindings + # Seed scenario bindings + ctx['workerType'] = 'MyWorkerType' + + # Seed runtime-generated bindings if 'workerType' not in ctx: ctx['workerType'] = seedBinding('workerType') @@ -73,10 +81,10 @@ async def test_activate_jobs_simple(client: CamundaAsyncClient) -> None: data=ActivateJobsRequest.from_dict(request_body) ) - assert result is not None, 'activate_jobs must return a response' + assert result is not None, 'activateJobs must return a response' # Extract response fields - extract_into(ctx, 'jobKey', result.jobs[0].key) + extract_into(ctx, 'jobKey', result['jobs'][0]['key']) ``` ## Materialized Support Files @@ -88,9 +96,9 @@ The emitter vendors the following Python files into the generated suite director - **requirements.txt** — dependencies (camunda-orchestration-sdk, pytest, pytest-asyncio) - **pytest.ini** — pytest config with `asyncio_mode = auto` -### conftest.py client fixture +### conftest.py: Client Fixture -Supports both local and SaaS (OAuth2) configurations via environment variables: +Supports both local (unauthenticated) and SaaS (OAuth2) configurations via environment variables: **Local (unauthenticated):** ```bash @@ -107,6 +115,124 @@ export CAMUNDA_OAUTH_URL=https://.auth.camunda.cloud pytest test_*.py ``` +The `client` fixture is session-scoped and injected into every test automatically. + +### helper.py: Test Helpers + +**`extract_into(ctx, bind_name, value)`** — Extract a response field into the test context. +- Preserves existing bindings (skips assignment if value is None). +- Called after each step to capture response fields for downstream steps. + +**`seedBinding(bind_name, default_value=None)`** — Seed a random or default value. +- Generates UUIDs for identifier-type bindings when no default is provided. +- Called during scenario setup to populate undefined bindings. + +## SDK Operation Mapping + +The emitter translates Camunda `operationId` (camelCase) to Python SDK method names (snake_case): + +- `activateJobs` → `client.activate_jobs()` +- `createDeployment` → `client.create_deployment()` +- `deleteProcessDefinition` → `client.delete_process_definition()` + +Resolution order: +1. Check `spec/python-sdk/operation-map.json` (fetched from SDK repo via `fetch-python-sdk-map`) +2. Fall back to `camelToSnake()` conversion if not found + +## Supported Scenario Shapes + +### ✅ Supported + +- **Single-step scenarios** — one `client.()` call per test +- **Multi-step chains** — request → extract → next request +- **JSON request bodies** — converted via `.from_dict(body_dict)` +- **Response field extraction** — via `extract_into()` helper +- **Seed bindings** — literal values or generated UUIDs + +### ❌ Unsupported (Hard-fail at generation time) + +- **Multipart request bodies** — `bodyKind === 'multipart'` + - Python SDK integration does not support file uploads; use HTTP/Playwright suites instead +- **Custom validation logic** — jsonschema, pydantic, etc. + - SDK raises typed exceptions on non-2xx; plain `assert` statements sufficient for smoke tests + +## Test Execution + +### Install dependencies + +```bash +cd +pip install -r requirements.txt +``` + +### Run all tests + +```bash +pytest test_*.py -v +``` + +### Run a single test + +```bash +pytest test_activate_jobs.python_sdk.spec.py::test_sc_activate_jobs_simple -v +``` + +### Run with live broker (docker-compose) + +```bash +# Terminal 1: start Camunda +docker-compose up -d + +# Terminal 2: run tests +pytest test_*.py -v --tb=short + +# Terminal 3: cleanup +docker-compose down +``` + +## Architecture & Purity + +The Python SDK emitter is **pure**: it accepts a scenario collection and context, and returns an in-memory list of `EmittedFile` objects. No filesystem or network I/O occurs during emission. The orchestrator (`path-analyser/src/codegen/orchestrator.ts`) handles directory creation and file writes. + +### EmitterFactory Pattern + +The emitter is created by `createPythonSdkEmitter()`, which accepts an optional `OperationMapJsonSource`. This allows: + +- **Production use**: Map is fetched from `spec/python-sdk/operation-map.json` and passed to the factory +- **Unit tests**: Tests use a default fallback or mock mapping without requiring the SDK repo + +### Determinism + +The emitter produces deterministic output: identical input always yields identical output. This is critical for: + +- Regression testing (Layer-3 invariants) +- Build cache validation +- Snapshot-based testing + +## Limitations & Roadmap + +### Current Limitations + +- **No request-validation (negative testing)** — HTTP-only feature; Python SDK tests are smoke tests +- **No custom assertions** — plain `assert result is not None`; SDK exceptions are the primary assertion mechanism +- **No type stubs** — model class names inferred heuristically from operationId (e.g., `ActivateJobsRequest`) + +### Future Enhancements + +- Auto-generate type stubs from OpenAPI spec +- Optional Pydantic schema validation +- Support for discriminated union (oneOf/anyOf) response shapes +- Multi-step orchestration with explicit context binding visualization + +**SaaS (OAuth2):** +```bash +export CAMUNDA_BASE_URL=https://.camunda.cloud +export CAMUNDA_CLIENT_ID= +export CAMUNDA_CLIENT_SECRET= +export CAMUNDA_OAUTH_URL=https://.auth.camunda.cloud +pytest test_*.py +``` + ## Operation Mapping The emitter resolves `operationId` (camelCase) from the OpenAPI spec to Python method names (snake_case) via the operation-map loaded from the Python SDK: diff --git a/path-analyser/src/codegen/python-sdk/emitter.ts b/path-analyser/src/codegen/python-sdk/emitter.ts index af856a5..be1186c 100644 --- a/path-analyser/src/codegen/python-sdk/emitter.ts +++ b/path-analyser/src/codegen/python-sdk/emitter.ts @@ -6,16 +6,16 @@ * * Design: * - Pure: no filesystem access (orchestrator handles materialization) - * - One async def test__(client) per scenario + * - One async def test_(client) per scenario * - SDK raises on non-2xx; plain assert result is not None * - extract_into(ctx, 'bind', value) for response field extraction * - Hard-fail on multipart (unsupported in Python SDK integration) */ import type { + EndpointScenario, EndpointScenarioCollection, RequestStep, - GlobalContextSeed, } from '../../types.js'; import type { EmitContext, EmittedFile, Emitter } from '../emitter.js'; import { @@ -30,40 +30,43 @@ import { * Pattern: .python_sdk.spec.py */ function pythonSdkFileName(operationId: string): string { - return `${operationId}.python_sdk.spec.py`; + return `${camelToSnake(operationId)}.python_sdk.spec.py`; } /** * Render a scenario as a Python async test function. * * Structure: - * - async def test__(client, ctx=None) + * - async def test_(client: CamundaAsyncClient) -> None * - ctx initialization and seed binding * - Request plan steps with await client.() calls * - extract_into() for response fields * - Plain assert result is not None */ function renderScenarioTest( - scenario: EndpointScenarioCollection['scenarios'][0], + scenario: EndpointScenario, operationMapSource: OperationMapJsonSource, ): string { const lines: string[] = []; // Function signature - const testName = scenario.id || 'test'; - const testFuncName = `test_${camelToSnake(testName)}`; - lines.push(`@pytest.mark.asyncio`); + const testName = camelToSnake(scenario.id || 'test'); + const testFuncName = `test_${testName}`; + lines.push('@pytest.mark.asyncio'); lines.push(`async def ${testFuncName}(client: CamundaAsyncClient) -> None:`); - lines.push(` """${scenario.description || scenario.name || 'Test scenario'}"""`); + if (scenario.description) { + lines.push(` """${scenario.description}"""`); + } else if (scenario.name) { + lines.push(` """${scenario.name}"""`); + } lines.push(''); // Context dict initialization lines.push(' ctx: dict[str, Any] = {}'); lines.push(''); - // Seed bindings (from scenario.bindings and scenario.seedBindings) + // Seed bindings (from scenario.bindings) const bindings = scenario.bindings || {}; - const seedBindings = scenario.seedBindings || []; // Emit literal bindings first if (Object.keys(bindings).length > 0) { @@ -76,12 +79,10 @@ function renderScenarioTest( } // Emit seedBinding() calls for PENDING bindings + const seedBindings = scenario.seedBindings || []; if (seedBindings.length > 0) { lines.push(' # Seed runtime-generated bindings'); for (const k of seedBindings) { - if (bindings[k] !== '__PENDING__' && bindings[k] !== undefined) { - continue; // Already emitted above as literal - } lines.push(` if '${k}' not in ctx:`); lines.push(` ctx['${k}'] = seedBinding('${k}')`); } @@ -166,12 +167,15 @@ function renderScenarioTest( * Example: * { type: "${workerType}", maxJobs: 1 } * → - * { 'type': ctx['workerType'], 'maxJobs': 1 } + * { + * 'type': ctx['workerType'], + * 'maxJobs': 1 + * } */ function buildBodyDict(bodyTemplate: unknown): string { const json = JSON.stringify(bodyTemplate, null, 2); - // Replace "${varName}" with f-string interpolation - const withVars = json.replace(/"\\?\$\{([^}]+)\}"/g, (_, varName) => { + // Replace "${varName}" with ctx['varName'] + const withVars = json.replace(/"(\$\{([^}]+)\})"/g, (_, _fullMatch, varName) => { return `ctx['${varName}']`; }); return withVars; @@ -248,30 +252,38 @@ function renderPythonTestSuite( return lines.join('\n'); } +/** + * Factory: create a Python SDK emitter backed by the given operation map. + */ +export function createPythonSdkEmitter( + operationMapSource?: OperationMapJsonSource, +): Emitter { + const source = operationMapSource ?? createDefaultOperationMapSource(); + return { + id: 'python-sdk', + name: 'Python SDK (Async)', + async emit( + collection: EndpointScenarioCollection, + ctx: EmitContext, + ): Promise { + const content = renderPythonTestSuite(collection, source); + return [ + { + relativePath: pythonSdkFileName(collection.endpoint.operationId), + content, + }, + ]; + }, + }; +} + /** * {@link Emitter} implementation for Python SDK tests. * * Pure: returns in-memory {@link EmittedFile} list, no filesystem access. + * Uses default operation map (fallback camelToSnake). + * + * For production use, consider using createPythonSdkEmitter() with a loaded + * operation-map.json source for more accurate method name resolution. */ -export const PythonSdkEmitter: Emitter = { - id: 'python-sdk', - name: 'Python SDK (Async)', - - async emit( - collection: EndpointScenarioCollection, - ctx: EmitContext, - ): Promise { - // Use default operation map source (fallback camelToSnake) - // In production, this would load spec/python-sdk/operation-map.json - const operationMapSource = createDefaultOperationMapSource(); - - const content = renderPythonTestSuite(collection, operationMapSource); - - return [ - { - relativePath: pythonSdkFileName(collection.endpoint.operationId), - content, - }, - ]; - }, -}; +export const PythonSdkEmitter: Emitter = createPythonSdkEmitter(); From a70ed2eccd2f0bb39c00be4d0b2449f5a7482da5 Mon Sep 17 00:00:00 2001 From: yarm03 Date: Wed, 13 May 2026 14:30:49 +1200 Subject: [PATCH 05/20] fix: resolve python-sdk emitter bugs, spec-pin hash, Windows spawn, biome path, and script restores --- configs/camunda-oca/spec-pin.json | 2 +- package.json | 4 +- .../codegen/playwright/materialize-support.ts | 2 +- .../src/codegen/python-sdk/emitter.ts | 37 ++++++++++--------- .../generated-suites-typecheck.test.ts | 1 + 5 files changed, 24 insertions(+), 22 deletions(-) diff --git a/configs/camunda-oca/spec-pin.json b/configs/camunda-oca/spec-pin.json index 5f09bbc..1696e73 100644 --- a/configs/camunda-oca/spec-pin.json +++ b/configs/camunda-oca/spec-pin.json @@ -1,5 +1,5 @@ { "$comment": "Spec pin for the bundled-spec invariants regression layer. The invariants in configs/camunda-oca/regression-invariants.test.ts assert named properties of the upstream spec content fingerprinted by `expectedSpecHash`. `specRef` MUST be a full 40-char commit SHA on camunda/camunda (not a branch like `main`) so the baseline does not drift on every upstream merge. CI fetches the spec at `specRef` (requires camunda-schema-bundler >= 2.1.0 for SHA support) and aborts early via tests/regression/spec-pin.setup.ts if its hash drifts, so reviewers see a clear 'spec changed upstream — re-pin' signal instead of confusing invariant failures. To bump: pick a newer SHA, run `SPEC_REF= npm run fetch-spec:ref`, regenerate the pipeline, update any invariants whose values legitimately changed, then update both fields below in the same PR.", "specRef": "b9d355da83647c51e5639d087c25d466f7a8a927", - "expectedSpecHash": "sha256:4a17a26d4a0129c6bbcf4e9e207902cefc08f33f1a040d01ab9a3cb18781e9ff" + "expectedSpecHash": "sha256:fa34084a0d14afbc3fa04f0d0100eda13fad3090871e84e4978dc7310d3383e8" } diff --git a/package.json b/package.json index b3d91f3..e08ade7 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ "codegen:python-sdk": "npm run build:analyser && node path-analyser/dist/src/codegen/index.js --target=python-sdk", "lint": "biome check", "lint:fix": "biome check --write", - "lint:generated": "biome check --config-path=biome.generated.json generated", - "biome:fix-generated": "biome check --config-path=biome.generated.json --write --unsafe generated", + "lint:generated": "biome check --config-path=biome.generated.json", + "biome:fix-generated": "biome check --config-path=biome.generated.json --write --unsafe", "biome:fix-generated:codegen": "npm run biome:fix-generated", "biome:fix-generated:request-validation": "npm run biome:fix-generated", "format": "biome format --write", diff --git a/path-analyser/src/codegen/playwright/materialize-support.ts b/path-analyser/src/codegen/playwright/materialize-support.ts index d9dcb43..1267677 100644 --- a/path-analyser/src/codegen/playwright/materialize-support.ts +++ b/path-analyser/src/codegen/playwright/materialize-support.ts @@ -231,7 +231,7 @@ export async function materializeResponseSchemas( `--specFile=${resolvedSpec}`, `--outputDir=${targetDir}`, ], - { stdio: 'inherit' }, + { stdio: 'inherit', shell: true }, ); if (result.status !== 0) { throw new Error( diff --git a/path-analyser/src/codegen/python-sdk/emitter.ts b/path-analyser/src/codegen/python-sdk/emitter.ts index be1186c..b1d8e7c 100644 --- a/path-analyser/src/codegen/python-sdk/emitter.ts +++ b/path-analyser/src/codegen/python-sdk/emitter.ts @@ -12,11 +12,7 @@ * - Hard-fail on multipart (unsupported in Python SDK integration) */ -import type { - EndpointScenario, - EndpointScenarioCollection, - RequestStep, -} from '../../types.js'; +import type { EndpointScenario, EndpointScenarioCollection } from '../../types.js'; import type { EmitContext, EmittedFile, Emitter } from '../emitter.js'; import { camelToSnake, @@ -28,9 +24,10 @@ import { * File name for Python SDK generated test suite. * * Pattern: .python_sdk.spec.py + * Uses the operationId directly (camelCase) to match the JS SDK convention. */ function pythonSdkFileName(operationId: string): string { - return `${camelToSnake(operationId)}.python_sdk.spec.py`; + return `${operationId}.python_sdk.spec.py`; } /** @@ -49,8 +46,9 @@ function renderScenarioTest( ): string { const lines: string[] = []; - // Function signature - const testName = camelToSnake(scenario.id || 'test'); + // Function signature: convert scenario id to valid Python identifier + // Replace hyphens (common in scenario ids like sc-activate-jobs-simple) with underscores + const testName = camelToSnake((scenario.id || 'test').replace(/-/g, '_')); const testFuncName = `test_${testName}`; lines.push('@pytest.mark.asyncio'); lines.push(`async def ${testFuncName}(client: CamundaAsyncClient) -> None:`); @@ -73,13 +71,17 @@ function renderScenarioTest( lines.push(' # Seed scenario bindings'); for (const [k, v] of Object.entries(bindings)) { if (v === '__PENDING__') continue; // Skip pending markers - lines.push(` ctx['${k}'] = ${JSON.stringify(v)}`); + // Use Python-style single-quoted strings for string values + const pyValue = typeof v === 'string' ? `'${v}'` : JSON.stringify(v); + lines.push(` ctx['${k}'] = ${pyValue}`); } lines.push(''); } // Emit seedBinding() calls for PENDING bindings - const seedBindings = scenario.seedBindings || []; + const seedBindings = Object.entries(bindings) + .filter(([, v]) => v === '__PENDING__') + .map(([k]) => k); if (seedBindings.length > 0) { lines.push(' # Seed runtime-generated bindings'); for (const k of seedBindings) { @@ -178,7 +180,11 @@ function buildBodyDict(bodyTemplate: unknown): string { const withVars = json.replace(/"(\$\{([^}]+)\})"/g, (_, _fullMatch, varName) => { return `ctx['${varName}']`; }); - return withVars; + // Convert remaining JSON double-quoted strings (keys and string values) to Python single quotes. + // This handles: "key": → 'key': and "stringValue" → 'stringValue' + // Does not touch ctx['var'] substitutions already made (those use single quotes already). + const withSingleQuotes = withVars.replace(/"([^"\\]*)"/g, "'$1'"); + return withSingleQuotes; } /** @@ -255,17 +261,12 @@ function renderPythonTestSuite( /** * Factory: create a Python SDK emitter backed by the given operation map. */ -export function createPythonSdkEmitter( - operationMapSource?: OperationMapJsonSource, -): Emitter { +export function createPythonSdkEmitter(operationMapSource?: OperationMapJsonSource): Emitter { const source = operationMapSource ?? createDefaultOperationMapSource(); return { id: 'python-sdk', name: 'Python SDK (Async)', - async emit( - collection: EndpointScenarioCollection, - ctx: EmitContext, - ): Promise { + async emit(collection: EndpointScenarioCollection, _ctx: EmitContext): Promise { const content = renderPythonTestSuite(collection, source); return [ { diff --git a/tests/regression/generated-suites-typecheck.test.ts b/tests/regression/generated-suites-typecheck.test.ts index 0c923f8..be696fa 100644 --- a/tests/regression/generated-suites-typecheck.test.ts +++ b/tests/regression/generated-suites-typecheck.test.ts @@ -61,6 +61,7 @@ describe.each(SUITES)('emitted $label suite typechecks under strict mode', ({ const result = spawnSync('npx', ['--no-install', 'tsc', '--noEmit', '-p', tsconfig], { cwd: REPO_ROOT, encoding: 'utf8', + shell: true, }); if (result.error) { throw new Error( From f34f921e8376d56156bfb2cde96b2545d8a4453e Mon Sep 17 00:00:00 2001 From: Muhamad690 Date: Mon, 4 May 2026 20:42:08 +1200 Subject: [PATCH 06/20] c#sdk --- csharp-sdk/README.md | 27 ++ csharp-sdk/examples/operation-map.json | 44 +++ csharp-sdk/examples/usage.cs | 47 +++ .../Camunda.Orchestration.RestSdk.csproj | 7 + .../Client/ClientOptions.cs | 7 + .../Client/OrchestrationClusterClient.cs | 160 +++++++++ .../Models/ActivateJobsRequest.cs | 15 + .../Models/ActivateJobsResponse.cs | 32 ++ .../Models/CancelProcessInstanceRequest.cs | 6 + .../Models/CompleteJobRequest.cs | 25 ++ .../Models/CreateProcessInstanceRequest.cs | 31 ++ .../Models/CreateProcessInstanceResponse.cs | 15 + .../Models/DeploymentRequest.cs | 9 + .../Models/DeploymentResource.cs | 7 + .../Models/DeploymentResponse.cs | 68 ++++ .../Models/SearchProcessInstancesRequest.cs | 15 + .../Models/SearchProcessInstancesResponse.cs | 28 ++ .../Models/SearchQuery.cs | 33 ++ .../Types/Identifiers.cs | 113 ++++++ .../Camunda.Orchestration.RestSdk.deps.json | 23 ++ .../net8.0/Camunda.Orchestration.RestSdk.dll | Bin 0 -> 89088 bytes .../net8.0/Camunda.Orchestration.RestSdk.pdb | Bin 0 -> 26548 bytes ...estration.RestSdk.csproj.nuget.dgspec.json | 66 ++++ ...Orchestration.RestSdk.csproj.nuget.g.props | 15 + ...chestration.RestSdk.csproj.nuget.g.targets | 2 + ...CoreApp,Version=v8.0.AssemblyAttributes.cs | 4 + ...unda.Orchestration.RestSdk.AssemblyInfo.cs | 22 ++ ...estration.RestSdk.AssemblyInfoInputs.cache | 1 + ....GeneratedMSBuildEditorConfig.editorconfig | 13 + ...da.Orchestration.RestSdk.GlobalUsings.g.cs | 8 + ...Camunda.Orchestration.RestSdk.assets.cache | Bin 0 -> 151 bytes ...ion.RestSdk.csproj.CoreCompileInputs.cache | 1 + ...ration.RestSdk.csproj.FileListAbsolute.txt | 12 + .../net8.0/Camunda.Orchestration.RestSdk.dll | Bin 0 -> 89088 bytes .../net8.0/Camunda.Orchestration.RestSdk.pdb | Bin 0 -> 26548 bytes ...unda.Orchestration.RestSdk.sourcelink.json | 1 + .../ref/Camunda.Orchestration.RestSdk.dll | Bin 0 -> 39424 bytes .../refint/Camunda.Orchestration.RestSdk.dll | Bin 0 -> 39424 bytes .../obj/project.assets.json | 71 ++++ .../obj/project.nuget.cache | 8 + .../src/codegen/csharp-sdk/emitter.ts | 331 ++++++++++++++++++ .../src/codegen/csharp-sdk/operation-map.ts | 55 +++ path-analyser/src/codegen/index.ts | 5 + tests/codegen/csharp-sdk-emitter.test.ts | 133 +++++++ 44 files changed, 1460 insertions(+) create mode 100644 csharp-sdk/README.md create mode 100644 csharp-sdk/examples/operation-map.json create mode 100644 csharp-sdk/examples/usage.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/ClientOptions.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/OrchestrationClusterClient.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/ActivateJobsRequest.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/ActivateJobsResponse.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CancelProcessInstanceRequest.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CompleteJobRequest.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CreateProcessInstanceRequest.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CreateProcessInstanceResponse.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentRequest.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentResource.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentResponse.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchProcessInstancesRequest.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchProcessInstancesResponse.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchQuery.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Types/Identifiers.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.dll create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.dgspec.json create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.props create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.targets create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GlobalUsings.g.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.assets.cache create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.FileListAbsolute.txt create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.dll create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.sourcelink.json create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/ref/Camunda.Orchestration.RestSdk.dll create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/refint/Camunda.Orchestration.RestSdk.dll create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.assets.json create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.nuget.cache create mode 100644 path-analyser/src/codegen/csharp-sdk/emitter.ts create mode 100644 path-analyser/src/codegen/csharp-sdk/operation-map.ts create mode 100644 tests/codegen/csharp-sdk-emitter.test.ts diff --git a/csharp-sdk/README.md b/csharp-sdk/README.md new file mode 100644 index 0000000..fb904e4 --- /dev/null +++ b/csharp-sdk/README.md @@ -0,0 +1,27 @@ +# Camunda Orchestration REST SDK (C#) + +Minimal REST client scaffold intended for the api-test-generator SDK emitter. + +Status: minimal endpoints and DTOs are stubbed and should be verified against the +bundled OpenAPI spec before production use. + +## Included endpoints + +- Create deployment +- Create process instance +- Cancel process instance +- Search process instances +- Activate jobs +- Complete job + +## Usage (sample) + +See [csharp-sdk/examples/usage.cs](csharp-sdk/examples/usage.cs) for a minimal example. + +## Build + +Requires .NET SDK 8.x: + +```bash +dotnet build csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj +``` diff --git a/csharp-sdk/examples/operation-map.json b/csharp-sdk/examples/operation-map.json new file mode 100644 index 0000000..fee8687 --- /dev/null +++ b/csharp-sdk/examples/operation-map.json @@ -0,0 +1,44 @@ +{ + "createDeployment": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "CreateDeploymentAsync", + "label": "Deploy resources" + } + ], + "createProcessInstance": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "CreateProcessInstanceAsync", + "label": "Create process instance" + } + ], + "searchProcessInstances": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "SearchProcessInstancesAsync", + "label": "Search process instances" + } + ], + "activateJobs": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "ActivateJobsAsync", + "label": "Activate jobs" + } + ], + "completeJob": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "CompleteJobAsync", + "label": "Complete job" + } + ], + "cancelProcessInstance": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "CancelProcessInstanceAsync", + "label": "Cancel process instance" + } + ] +} \ No newline at end of file diff --git a/csharp-sdk/examples/usage.cs b/csharp-sdk/examples/usage.cs new file mode 100644 index 0000000..60bdf99 --- /dev/null +++ b/csharp-sdk/examples/usage.cs @@ -0,0 +1,47 @@ +using Camunda.Orchestration.RestSdk.Client; +using Camunda.Orchestration.RestSdk.Models; +using Camunda.Orchestration.RestSdk.Types; + +var httpClient = new HttpClient(); +var client = new OrchestrationClusterClient( + httpClient, + new ClientOptions { BaseUri = new Uri("http://localhost:8080/v2/") } +); + +var deployment = await client.CreateDeploymentAsync( + new DeploymentRequest + { + TenantId = new TenantId(""), + Resources = new List + { + new( + FileName: "process.bpmn", + ContentType: "application/octet-stream", + Content: await File.ReadAllBytesAsync("process.bpmn") + ), + }, + } +); + +var createInstance = await client.CreateProcessInstanceAsync( + new CreateProcessInstanceRequest + { + ProcessDefinitionKey = deployment.Deployments[0].ProcessDefinition?.ProcessDefinitionKey, + Variables = new Dictionary { ["foo"] = "bar" }, + } +); + +var activation = await client.ActivateJobsAsync( + new ActivateJobsRequest + { + Type = "service-task", + Timeout = 45000, + MaxJobsToActivate = 1, + Worker = "sdk-sample", + } +); + +if (activation.Jobs.Count > 0) +{ + await client.CompleteJobAsync(activation.Jobs[0].JobKey!, new CompleteJobRequest()); +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj new file mode 100644 index 0000000..e8cd599 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj @@ -0,0 +1,7 @@ + + + net8.0 + enable + enable + + diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/ClientOptions.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/ClientOptions.cs new file mode 100644 index 0000000..2659560 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/ClientOptions.cs @@ -0,0 +1,7 @@ +namespace Camunda.Orchestration.RestSdk.Client; + +public sealed class ClientOptions +{ + public required Uri BaseUri { get; init; } + public string? BearerToken { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/OrchestrationClusterClient.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/OrchestrationClusterClient.cs new file mode 100644 index 0000000..1738faf --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/OrchestrationClusterClient.cs @@ -0,0 +1,160 @@ +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Camunda.Orchestration.RestSdk.Models; +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Client; + +public sealed class OrchestrationClusterClient +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + + private readonly HttpClient httpClient; + private readonly Uri baseUri; + + public OrchestrationClusterClient(HttpClient httpClient, Uri baseUri, string? bearerToken = null) + { + this.httpClient = httpClient; + this.baseUri = baseUri; + if (!string.IsNullOrWhiteSpace(bearerToken)) + { + this.httpClient.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", bearerToken); + } + } + + public OrchestrationClusterClient(HttpClient httpClient, ClientOptions options) + : this(httpClient, options.BaseUri, options.BearerToken) + { + } + + public Uri BaseUri => baseUri; + + public async Task CreateDeploymentAsync( + DeploymentRequest request, + CancellationToken cancellationToken = default) + { + using var content = new MultipartFormDataContent(); + if (request.TenantId is not null) + { + content.Add(new StringContent(request.TenantId.ToString()!, Encoding.UTF8), "tenantId"); + } + foreach (var resource in request.Resources) + { + var bytes = new ByteArrayContent(resource.Content); + bytes.Headers.ContentType = new MediaTypeHeaderValue(resource.ContentType); + content.Add(bytes, "resources", resource.FileName); + } + + var response = await httpClient.PostAsync(BuildUri("/deployments"), content, cancellationToken); + response.EnsureSuccessStatusCode(); + var body = await response.Content.ReadAsStringAsync(cancellationToken); + return Deserialize(body); + } + + public async Task CreateProcessInstanceAsync( + CreateProcessInstanceRequest request, + CancellationToken cancellationToken = default) + { + return await PostJsonAsync( + "/process-instances", + request, + cancellationToken); + } + + public async Task CancelProcessInstanceAsync( + ProcessInstanceKey processInstanceKey, + CancelProcessInstanceRequest? request = null, + CancellationToken cancellationToken = default) + { + var path = $"/process-instances/{processInstanceKey}/cancellation"; + if (request is null) + { + await PostNoContentAsync(path, cancellationToken); + return; + } + await PostJsonNoResponseAsync(path, request, cancellationToken); + } + + public async Task SearchProcessInstancesAsync( + SearchProcessInstancesRequest request, + CancellationToken cancellationToken = default) + { + return await PostJsonAsync( + "/process-instances/search", + request, + cancellationToken); + } + + public async Task ActivateJobsAsync( + ActivateJobsRequest request, + CancellationToken cancellationToken = default) + { + return await PostJsonAsync( + "/jobs/activation", + request, + cancellationToken); + } + + public async Task CompleteJobAsync( + JobKey jobKey, + CompleteJobRequest? request = null, + CancellationToken cancellationToken = default) + { + var path = $"/jobs/{jobKey}/completion"; + if (request is null) + { + await PostNoContentAsync(path, cancellationToken); + return; + } + await PostJsonNoResponseAsync(path, request, cancellationToken); + } + + private async Task PostJsonAsync( + string path, + TRequest request, + CancellationToken cancellationToken) + { + var json = JsonSerializer.Serialize(request, JsonOptions); + using var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await httpClient.PostAsync(BuildUri(path), content, cancellationToken); + response.EnsureSuccessStatusCode(); + var body = await response.Content.ReadAsStringAsync(cancellationToken); + return Deserialize(body); + } + + private async Task PostJsonNoResponseAsync( + string path, + TRequest request, + CancellationToken cancellationToken) + { + var json = JsonSerializer.Serialize(request, JsonOptions); + using var content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await httpClient.PostAsync(BuildUri(path), content, cancellationToken); + response.EnsureSuccessStatusCode(); + } + + private async Task PostNoContentAsync(string path, CancellationToken cancellationToken) + { + var response = await httpClient.PostAsync(BuildUri(path), content: null, cancellationToken); + response.EnsureSuccessStatusCode(); + } + + private Uri BuildUri(string path) => new(baseUri, path); + + private static T Deserialize(string json) + { + var result = JsonSerializer.Deserialize(json, JsonOptions); + if (result is null) + { + throw new InvalidOperationException("Response payload was empty or invalid JSON."); + } + return result; + } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/ActivateJobsRequest.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/ActivateJobsRequest.cs new file mode 100644 index 0000000..0ccc601 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/ActivateJobsRequest.cs @@ -0,0 +1,15 @@ +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record ActivateJobsRequest +{ + public required string Type { get; init; } + public required long Timeout { get; init; } + public required int MaxJobsToActivate { get; init; } + public string? Worker { get; init; } + public int? RequestTimeout { get; init; } + public IReadOnlyList? FetchVariable { get; init; } + public IReadOnlyList? TenantIds { get; init; } + public string? TenantFilter { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/ActivateJobsResponse.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/ActivateJobsResponse.cs new file mode 100644 index 0000000..bef511b --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/ActivateJobsResponse.cs @@ -0,0 +1,32 @@ +using System.Text.Json; +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record ActivateJobsResponse +{ + public IReadOnlyList Jobs { get; init; } = Array.Empty(); +} + +public sealed record ActivatedJob +{ + public JobKey? JobKey { get; init; } + public ProcessInstanceKey? ProcessInstanceKey { get; init; } + public ProcessDefinitionKey? ProcessDefinitionKey { get; init; } + public ProcessDefinitionId? ProcessDefinitionId { get; init; } + public int? ProcessDefinitionVersion { get; init; } + public ElementId? ElementId { get; init; } + public string? Type { get; init; } + public string? Worker { get; init; } + public int? Retries { get; init; } + public long? Deadline { get; init; } + public JsonElement? Variables { get; init; } + public JsonElement? CustomHeaders { get; init; } + public TenantId? TenantId { get; init; } + public ElementInstanceKey? ElementInstanceKey { get; init; } + public string? Kind { get; init; } + public string? ListenerEventType { get; init; } + public ProcessInstanceKey? RootProcessInstanceKey { get; init; } + public IReadOnlyList? Tags { get; init; } + public JsonElement? UserTask { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CancelProcessInstanceRequest.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CancelProcessInstanceRequest.cs new file mode 100644 index 0000000..b8e305a --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CancelProcessInstanceRequest.cs @@ -0,0 +1,6 @@ +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record CancelProcessInstanceRequest +{ + public long? OperationReference { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CompleteJobRequest.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CompleteJobRequest.cs new file mode 100644 index 0000000..befba62 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CompleteJobRequest.cs @@ -0,0 +1,25 @@ +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record CompleteJobRequest +{ + public Dictionary? Variables { get; init; } + public JobResult? Result { get; init; } +} + +public sealed record JobResult +{ + public string? Type { get; init; } + public bool? Denied { get; init; } + public string? DeniedReason { get; init; } + public JobResultCorrections? Corrections { get; init; } +} + +public sealed record JobResultCorrections +{ + public string? Assignee { get; init; } + public DateTimeOffset? DueDate { get; init; } + public DateTimeOffset? FollowUpDate { get; init; } + public IReadOnlyList? CandidateUsers { get; init; } + public IReadOnlyList? CandidateGroups { get; init; } + public int? Priority { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CreateProcessInstanceRequest.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CreateProcessInstanceRequest.cs new file mode 100644 index 0000000..db06ec6 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CreateProcessInstanceRequest.cs @@ -0,0 +1,31 @@ +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record CreateProcessInstanceRequest +{ + public ProcessDefinitionKey? ProcessDefinitionKey { get; init; } + public ProcessDefinitionId? ProcessDefinitionId { get; init; } + public int? ProcessDefinitionVersion { get; init; } + public Dictionary? Variables { get; init; } + public TenantId? TenantId { get; init; } + public long? OperationReference { get; init; } + public IReadOnlyList? StartInstructions { get; init; } + public IReadOnlyList? RuntimeInstructions { get; init; } + public bool? AwaitCompletion { get; init; } + public IReadOnlyList? FetchVariables { get; init; } + public long? RequestTimeout { get; init; } + public IReadOnlyList? Tags { get; init; } + public BusinessId? BusinessId { get; init; } +} + +public sealed record StartInstruction +{ + public ElementId? ElementId { get; init; } +} + +public sealed record RuntimeInstruction +{ + public string? Type { get; init; } + public ElementId? AfterElementId { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CreateProcessInstanceResponse.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CreateProcessInstanceResponse.cs new file mode 100644 index 0000000..3d0f1d3 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/CreateProcessInstanceResponse.cs @@ -0,0 +1,15 @@ +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record CreateProcessInstanceResponse +{ + public ProcessDefinitionId? ProcessDefinitionId { get; init; } + public int? ProcessDefinitionVersion { get; init; } + public TenantId? TenantId { get; init; } + public Dictionary? Variables { get; init; } + public ProcessDefinitionKey? ProcessDefinitionKey { get; init; } + public ProcessInstanceKey? ProcessInstanceKey { get; init; } + public IReadOnlyList? Tags { get; init; } + public BusinessId? BusinessId { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentRequest.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentRequest.cs new file mode 100644 index 0000000..3ac8fba --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentRequest.cs @@ -0,0 +1,9 @@ +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record DeploymentRequest +{ + public IReadOnlyList Resources { get; init; } = Array.Empty(); + public TenantId? TenantId { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentResource.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentResource.cs new file mode 100644 index 0000000..c4022b8 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentResource.cs @@ -0,0 +1,7 @@ +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record DeploymentResource( + string FileName, + string ContentType, + byte[] Content +); diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentResponse.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentResponse.cs new file mode 100644 index 0000000..96d3911 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/DeploymentResponse.cs @@ -0,0 +1,68 @@ +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record DeploymentResponse +{ + public DeploymentKey? DeploymentKey { get; init; } + public TenantId? TenantId { get; init; } + public IReadOnlyList Deployments { get; init; } = + Array.Empty(); +} + +public sealed record DeploymentMetadataResult +{ + public DeploymentProcessResult? ProcessDefinition { get; init; } + public DeploymentDecisionResult? DecisionDefinition { get; init; } + public DeploymentDecisionRequirementsResult? DecisionRequirements { get; init; } + public DeploymentFormResult? Form { get; init; } + public DeploymentResourceResult? Resource { get; init; } +} + +public sealed record DeploymentProcessResult +{ + public ProcessDefinitionId? ProcessDefinitionId { get; init; } + public int? ProcessDefinitionVersion { get; init; } + public string? ResourceName { get; init; } + public TenantId? TenantId { get; init; } + public ProcessDefinitionKey? ProcessDefinitionKey { get; init; } +} + +public sealed record DeploymentDecisionResult +{ + public DecisionDefinitionId? DecisionDefinitionId { get; init; } + public int? Version { get; init; } + public string? Name { get; init; } + public TenantId? TenantId { get; init; } + public string? DecisionRequirementsId { get; init; } + public DecisionDefinitionKey? DecisionDefinitionKey { get; init; } + public DecisionRequirementsKey? DecisionRequirementsKey { get; init; } +} + +public sealed record DeploymentDecisionRequirementsResult +{ + public string? DecisionRequirementsId { get; init; } + public string? DecisionRequirementsName { get; init; } + public int? Version { get; init; } + public string? ResourceName { get; init; } + public TenantId? TenantId { get; init; } + public DecisionRequirementsKey? DecisionRequirementsKey { get; init; } +} + +public sealed record DeploymentFormResult +{ + public FormId? FormId { get; init; } + public int? Version { get; init; } + public string? ResourceName { get; init; } + public TenantId? TenantId { get; init; } + public FormKey? FormKey { get; init; } +} + +public sealed record DeploymentResourceResult +{ + public string? ResourceId { get; init; } + public string? ResourceName { get; init; } + public int? Version { get; init; } + public TenantId? TenantId { get; init; } + public ResourceKey? ResourceKey { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchProcessInstancesRequest.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchProcessInstancesRequest.cs new file mode 100644 index 0000000..243b668 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchProcessInstancesRequest.cs @@ -0,0 +1,15 @@ +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record SearchProcessInstancesRequest : SearchQueryRequest +{ + public IReadOnlyList? Sort { get; init; } + public Dictionary? Filter { get; init; } +} + +public sealed record ProcessInstanceSearchSort +{ + public string? Field { get; init; } + public SortOrder? Order { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchProcessInstancesResponse.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchProcessInstancesResponse.cs new file mode 100644 index 0000000..e6f51d4 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchProcessInstancesResponse.cs @@ -0,0 +1,28 @@ +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record SearchProcessInstancesResponse : SearchQueryResponse +{ + public IReadOnlyList Items { get; init; } = Array.Empty(); +} + +public sealed record ProcessInstanceResult +{ + public ProcessDefinitionId? ProcessDefinitionId { get; init; } + public string? ProcessDefinitionName { get; init; } + public int? ProcessDefinitionVersion { get; init; } + public string? ProcessDefinitionVersionTag { get; init; } + public DateTimeOffset? StartDate { get; init; } + public DateTimeOffset? EndDate { get; init; } + public string? State { get; init; } + public bool? HasIncident { get; init; } + public TenantId? TenantId { get; init; } + public ProcessInstanceKey? ProcessInstanceKey { get; init; } + public ProcessDefinitionKey? ProcessDefinitionKey { get; init; } + public ProcessInstanceKey? ParentProcessInstanceKey { get; init; } + public ElementInstanceKey? ParentElementInstanceKey { get; init; } + public ProcessInstanceKey? RootProcessInstanceKey { get; init; } + public IReadOnlyList? Tags { get; init; } + public BusinessId? BusinessId { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchQuery.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchQuery.cs new file mode 100644 index 0000000..522d22d --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/SearchQuery.cs @@ -0,0 +1,33 @@ +namespace Camunda.Orchestration.RestSdk.Models; + +public record SearchQueryRequest +{ + public SearchQueryPageRequest? Page { get; init; } +} + +public sealed record SearchQueryPageRequest +{ + public int? Limit { get; init; } + public int? From { get; init; } + public string? After { get; init; } + public string? Before { get; init; } +} + +public record SearchQueryResponse +{ + public SearchQueryPageResponse? Page { get; init; } +} + +public sealed record SearchQueryPageResponse +{ + public long? TotalItems { get; init; } + public bool? HasMoreTotalItems { get; init; } + public string? StartCursor { get; init; } + public string? EndCursor { get; init; } +} + +public enum SortOrder +{ + ASC, + DESC, +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Types/Identifiers.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Types/Identifiers.cs new file mode 100644 index 0000000..1030335 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Types/Identifiers.cs @@ -0,0 +1,113 @@ +namespace Camunda.Orchestration.RestSdk.Types; + +public readonly record struct ProcessInstanceKey(string Value) +{ + public static implicit operator ProcessInstanceKey(string value) => new(value); + public static implicit operator string(ProcessInstanceKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct ProcessDefinitionId(string Value) +{ + public static implicit operator ProcessDefinitionId(string value) => new(value); + public static implicit operator string(ProcessDefinitionId value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct ProcessDefinitionKey(string Value) +{ + public static implicit operator ProcessDefinitionKey(string value) => new(value); + public static implicit operator string(ProcessDefinitionKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct JobKey(string Value) +{ + public static implicit operator JobKey(string value) => new(value); + public static implicit operator string(JobKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct ElementInstanceKey(string Value) +{ + public static implicit operator ElementInstanceKey(string value) => new(value); + public static implicit operator string(ElementInstanceKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct ElementId(string Value) +{ + public static implicit operator ElementId(string value) => new(value); + public static implicit operator string(ElementId value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct UserTaskKey(string Value) +{ + public static implicit operator UserTaskKey(string value) => new(value); + public static implicit operator string(UserTaskKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct DecisionDefinitionKey(string Value) +{ + public static implicit operator DecisionDefinitionKey(string value) => new(value); + public static implicit operator string(DecisionDefinitionKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct DecisionDefinitionId(string Value) +{ + public static implicit operator DecisionDefinitionId(string value) => new(value); + public static implicit operator string(DecisionDefinitionId value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct DecisionRequirementsKey(string Value) +{ + public static implicit operator DecisionRequirementsKey(string value) => new(value); + public static implicit operator string(DecisionRequirementsKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct FormKey(string Value) +{ + public static implicit operator FormKey(string value) => new(value); + public static implicit operator string(FormKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct FormId(string Value) +{ + public static implicit operator FormId(string value) => new(value); + public static implicit operator string(FormId value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct DeploymentKey(string Value) +{ + public static implicit operator DeploymentKey(string value) => new(value); + public static implicit operator string(DeploymentKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct ResourceKey(string Value) +{ + public static implicit operator ResourceKey(string value) => new(value); + public static implicit operator string(ResourceKey value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct TenantId(string Value) +{ + public static implicit operator TenantId(string value) => new(value); + public static implicit operator string(TenantId value) => value.Value; + public override string ToString() => Value; +} + +public readonly record struct BusinessId(string Value) +{ + public static implicit operator BusinessId(string value) => new(value); + public static implicit operator string(BusinessId value) => value.Value; + public override string ToString() => Value; +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json b/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json new file mode 100644 index 0000000..9e45f8e --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json @@ -0,0 +1,23 @@ +{ + "runtimeTarget": { + "name": ".NETCoreApp,Version=v8.0", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETCoreApp,Version=v8.0": { + "Camunda.Orchestration.RestSdk/1.0.0": { + "runtime": { + "Camunda.Orchestration.RestSdk.dll": {} + } + } + } + }, + "libraries": { + "Camunda.Orchestration.RestSdk/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.dll b/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.dll new file mode 100644 index 0000000000000000000000000000000000000000..441c935fcfddfdfaa1ebd2e0ca40fd0b6f17728a GIT binary patch literal 89088 zcmeFacbrwl`9D5$&UWthg{3U8%K~1CSa4OWtXRMj#V(?^p8;>})aob(@Xgnq}(?dPosI zhoIhF$)Mg%vz1WbKTE7hft`4qHG9UgA9HQYx-RPy;E9QHV`d+6;%wj*li@FP>Z|uV zHiLNDn3=O?PKQ9g!ADaBuqUpG&v*>in3;zhoq-{JHSk$6nDQ;VZ4$-AUdp)QL*D{Z zXdb@EnE8J)CcmUieiRuJgb~UXj2slS>zWYEf}m*RkV=zAM~h(LuE&Nmp*SwnU`*NY z&12ZzU_&R}#!5MDz@cB5>c zkq0JBLwS^GWP!M$$s_3?ipXPK6M|V4vB#C(V_my@OkvXFKEPRz`>|x~F&oHwjJ$Im zlS$Jq=`l^ib`{8a%!ZuDC?bz_rN>?{YXF;Lj1f-7Sl8|zQ<(I)893|laF&ccX1!UD zks!`vGHKc+J*H{ct^!$)*^u)XMdY!r^wY$6he2h0W39jmf}cUAucsVbbF*fwM6l z&yumntT*d165Pd@OqzB{k7*jVt3cLcHsm};5qYdDJ@$fG+psx$Jb?^6*0sCG6ed01 z5jgAdE-V>)%zCpP7snoxNz*RrF-^mE707zbhMdPJB9C>Y$6hdNS2jnFcOwIjb?xpk zg-MV10nU0ng(YK;S#Q>3Y$`6sWYV-tdQ8)>T?Mipvmxg(ipXPK>9H5gn#$(r@d0Gu zv98@crZDO84B)KChp}YrG3(8GTpD{!CQZAf$21MwRUqpz8*(0_h&|r^NS#Q?kve;uXY1$<{rfJx&0$GpQknw*b8RO zVRQ8O1Tyeg*X|xunDqD*;H<};EE&g`^=3Wp6?;r3O}nJWG!5HTAnP$3avr0IJl2&S zd%>)EY>pn!Cj*al?d~y!NsrG0&U$}u(kG){lxonOepGO8B>)PF83X>jR0-W{ua+ZueX1!UD%VUqpq-mG*n5JR73S>QI zL(XFqk;l5yV=tI>1)HPCSCWCpx_0-N!lcL70B1eEjwNG{S#Q>3906R6$)stQ^q8h$ zy9#7IW<$iJ${fSV~<&H*5j(! zV=`&lB|WBT*scOukJ*s(7)9iv47LF_|>&k{;7EY*&G-$82cw$T82>us{>fpg1C3YtW6+ zrA31qGtICZUNkuJOlBzi#JavfU4b#x>0ywBGqKs{4)@QaxC?hUbC0CrMJUQ@%-TL% zz!BBuZwO`{lQer7X1!|c+CE!$H^W+zYvz?iGj}8vxV0%t)dXw%j4@_FUNf&H&0bHG zw>DAn%7kKYQn4zlaCa?#0p>i~I|#>A2BS-ZF!K&Xm?fiZ{YX>0erTRRocQ5Ex-3XP z#~RiF+c;vcy{?(!Z(KMrH0$>D;xvslPckFG#^R9@bCP66CB%ay=2Xe6WVFPbFqw4| z)(8sf=CsMImk_TnF(*%EgM`@C%_-#S9=UN49~N^W$TU)gwaKJ&oeo{s$5M4JRk@CbN|ww

nv(*T$ZQ${E!* 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

    8X# z4$s1H)Fhb~sZo*3T?~>JufF6+?tgqUZBWjWX=ig6PYmrTcUE|JDItkB)z0taS41j( zSGPpD9j->1!JoXVyAO8b?4|1w#?)q@yw7_xG-4rd z_7*Qu$v$M#56nICa>t20vmU`V_Vm|M~C#zZ&@8wHx8F literal 0 HcmV?d00001 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb b/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb new file mode 100644 index 0000000000000000000000000000000000000000..dfb08315d24eaba4084767ef2d43c0dbdcb898cf GIT binary patch literal 26548 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 + + + True + NuGet + $(MSBuildThisFileDirectory)project.assets.json + /home/muhamad/.nuget/packages/ + /home/muhamad/.nuget/packages/ + PackageReference + 6.11.2 + + + + + \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.targets b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.targets new file mode 100644 index 0000000..3dc06ef --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.targets @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs new file mode 100644 index 0000000..2217181 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs @@ -0,0 +1,4 @@ +// +using System; +using System.Reflection; +[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs new file mode 100644 index 0000000..c900e80 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs @@ -0,0 +1,22 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Reflection; + +[assembly: System.Reflection.AssemblyCompanyAttribute("Camunda.Orchestration.RestSdk")] +[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] +[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+e90328c2249d7f272078efb01fd6f0468204003a")] +[assembly: System.Reflection.AssemblyProductAttribute("Camunda.Orchestration.RestSdk")] +[assembly: System.Reflection.AssemblyTitleAttribute("Camunda.Orchestration.RestSdk")] +[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] + +// Generated by the MSBuild WriteCodeFragment class. + diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache new file mode 100644 index 0000000..33abb91 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache @@ -0,0 +1 @@ +60292b95fa336b4da599d5f59b7e9066755f9cf92120db36a66fccafedc7ca26 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig new file mode 100644 index 0000000..22e345d --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig @@ -0,0 +1,13 @@ +is_global = true +build_property.TargetFramework = net8.0 +build_property.TargetPlatformMinVersion = +build_property.UsingMicrosoftNETSdkWeb = +build_property.ProjectTypeGuids = +build_property.InvariantGlobalization = +build_property.PlatformNeutralAssembly = +build_property.EnforceExtendedAnalyzerRules = +build_property._SupportedPlatformList = Linux,macOS,Windows +build_property.RootNamespace = Camunda.Orchestration.RestSdk +build_property.ProjectDir = /home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/ +build_property.EnableComHosting = +build_property.EnableGeneratedComInterfaceComImportInterop = diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GlobalUsings.g.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GlobalUsings.g.cs new file mode 100644 index 0000000..8578f3d --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GlobalUsings.g.cs @@ -0,0 +1,8 @@ +// +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Net.Http; +global using global::System.Threading; +global using global::System.Threading.Tasks; diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.assets.cache b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.assets.cache new file mode 100644 index 0000000000000000000000000000000000000000..0ba4778afbda6121705869ebf2d174aa4f02608c GIT binary patch literal 151 zcmWIWc6a1qU|?vSy|Q*w-k&}_f1aVWH^{!gKZ+tqtQo1azKPX?+X7BB)8 m%IRn1=celCmS!a8CZ_1?<&~zVmgpBGCTAz6rxxoIG711@upO}g literal 0 HcmV?d00001 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache new file mode 100644 index 0000000..83bf36d --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache @@ -0,0 +1 @@ +ae62822739208c29806f1b4ed45de19718d1980d02a626e9cceb5be76d2b318b diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.FileListAbsolute.txt b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..57d4dc7 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.FileListAbsolute.txt @@ -0,0 +1,12 @@ +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.dll +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.sourcelink.json +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.dll +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/refint/Camunda.Orchestration.RestSdk.dll +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb +/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/ref/Camunda.Orchestration.RestSdk.dll diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.dll b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.dll new file mode 100644 index 0000000000000000000000000000000000000000..441c935fcfddfdfaa1ebd2e0ca40fd0b6f17728a GIT binary patch literal 89088 zcmeFacbrwl`9D5$&UWthg{3U8%K~1CSa4OWtXRMj#V(?^p8;>})aob(@Xgnq}(?dPosI zhoIhF$)Mg%vz1WbKTE7hft`4qHG9UgA9HQYx-RPy;E9QHV`d+6;%wj*li@FP>Z|uV zHiLNDn3=O?PKQ9g!ADaBuqUpG&v*>in3;zhoq-{JHSk$6nDQ;VZ4$-AUdp)QL*D{Z zXdb@EnE8J)CcmUieiRuJgb~UXj2slS>zWYEf}m*RkV=zAM~h(LuE&Nmp*SwnU`*NY z&12ZzU_&R}#!5MDz@cB5>c zkq0JBLwS^GWP!M$$s_3?ipXPK6M|V4vB#C(V_my@OkvXFKEPRz`>|x~F&oHwjJ$Im zlS$Jq=`l^ib`{8a%!ZuDC?bz_rN>?{YXF;Lj1f-7Sl8|zQ<(I)893|laF&ccX1!UD zks!`vGHKc+J*H{ct^!$)*^u)XMdY!r^wY$6he2h0W39jmf}cUAucsVbbF*fwM6l z&yumntT*d165Pd@OqzB{k7*jVt3cLcHsm};5qYdDJ@$fG+psx$Jb?^6*0sCG6ed01 z5jgAdE-V>)%zCpP7snoxNz*RrF-^mE707zbhMdPJB9C>Y$6hdNS2jnFcOwIjb?xpk zg-MV10nU0ng(YK;S#Q>3Y$`6sWYV-tdQ8)>T?Mipvmxg(ipXPK>9H5gn#$(r@d0Gu zv98@crZDO84B)KChp}YrG3(8GTpD{!CQZAf$21MwRUqpz8*(0_h&|r^NS#Q?kve;uXY1$<{rfJx&0$GpQknw*b8RO zVRQ8O1Tyeg*X|xunDqD*;H<};EE&g`^=3Wp6?;r3O}nJWG!5HTAnP$3avr0IJl2&S zd%>)EY>pn!Cj*al?d~y!NsrG0&U$}u(kG){lxonOepGO8B>)PF83X>jR0-W{ua+ZueX1!UD%VUqpq-mG*n5JR73S>QI zL(XFqk;l5yV=tI>1)HPCSCWCpx_0-N!lcL70B1eEjwNG{S#Q>3906R6$)stQ^q8h$ zy9#7IW<$iJ${fSV~<&H*5j(! zV=`&lB|WBT*scOukJ*s(7)9iv47LF_|>&k{;7EY*&G-$82cw$T82>us{>fpg1C3YtW6+ zrA31qGtICZUNkuJOlBzi#JavfU4b#x>0ywBGqKs{4)@QaxC?hUbC0CrMJUQ@%-TL% zz!BBuZwO`{lQer7X1!|c+CE!$H^W+zYvz?iGj}8vxV0%t)dXw%j4@_FUNf&H&0bHG zw>DAn%7kKYQn4zlaCa?#0p>i~I|#>A2BS-ZF!K&Xm?fiZ{YX>0erTRRocQ5Ex-3XP z#~RiF+c;vcy{?(!Z(KMrH0$>D;xvslPckFG#^R9@bCP66CB%ay=2Xe6WVFPbFqw4| z)(8sf=CsMImk_TnF(*%EgM`@C%_-#S9=UN49~N^W$TU)gwaKJ&oeo{s$5M4JRk@CbN|ww

    nv(*T$ZQ${E!* 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

      8X# z4$s1H)Fhb~sZo*3T?~>JufF6+?tgqUZBWjWX=ig6PYmrTcUE|JDItkB)z0taS41j( zSGPpD9j->1!JoXVyAO8b?4|1w#?)q@yw7_xG-4rd z_7*Qu$v$M#56nICa>t20vmU`V_Vm|M~C#zZ&@8wHx8F literal 0 HcmV?d00001 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb new file mode 100644 index 0000000000000000000000000000000000000000..dfb08315d24eaba4084767ef2d43c0dbdcb898cf GIT binary patch literal 26548 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 literal 0 HcmV?d00001 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/refint/Camunda.Orchestration.RestSdk.dll b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/refint/Camunda.Orchestration.RestSdk.dll new file mode 100644 index 0000000000000000000000000000000000000000..1abe67cfeadd42e959f67ae716781a5ba4625e74 GIT binary patch literal 39424 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 literal 0 HcmV?d00001 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.assets.json b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.assets.json new file mode 100644 index 0000000..34cd8fd --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.assets.json @@ -0,0 +1,71 @@ +{ + "version": 3, + "targets": { + "net8.0": {} + }, + "libraries": {}, + "projectFileDependencyGroups": { + "net8.0": [] + }, + "packageFolders": { + "/home/muhamad/.nuget/packages/": {} + }, + "project": { + "version": "1.0.0", + "restore": { + "projectUniqueName": "/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj", + "projectName": "Camunda.Orchestration.RestSdk", + "projectPath": "/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj", + "packagesPath": "/home/muhamad/.nuget/packages/", + "outputPath": "/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/", + "projectStyle": "PackageReference", + "configFilePaths": [ + "/home/muhamad/.nuget/NuGet/NuGet.Config" + ], + "originalTargetFrameworks": [ + "net8.0" + ], + "sources": { + "https://api.nuget.org/v3/index.json": {} + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "projectReferences": {} + } + }, + "warningProperties": { + "warnAsError": [ + "NU1605" + ] + }, + "restoreAuditProperties": { + "enableAudit": "true", + "auditLevel": "low", + "auditMode": "direct" + } + }, + "frameworks": { + "net8.0": { + "targetAlias": "net8.0", + "imports": [ + "net461", + "net462", + "net47", + "net471", + "net472", + "net48", + "net481" + ], + "assetTargetFallback": true, + "warn": true, + "frameworkReferences": { + "Microsoft.NETCore.App": { + "privateAssets": "all" + } + }, + "runtimeIdentifierGraphPath": "/home/muhamad/.dotnet/sdk/8.0.420/PortableRuntimeIdentifierGraph.json" + } + } + } +} \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.nuget.cache b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.nuget.cache new file mode 100644 index 0000000..b349e02 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.nuget.cache @@ -0,0 +1,8 @@ +{ + "version": 2, + "dgSpecHash": "BS5cMD2o/gU=", + "success": true, + "projectFilePath": "/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj", + "expectedPackageFiles": [], + "logs": [] +} \ No newline at end of file diff --git a/path-analyser/src/codegen/csharp-sdk/emitter.ts b/path-analyser/src/codegen/csharp-sdk/emitter.ts new file mode 100644 index 0000000..03261ee --- /dev/null +++ b/path-analyser/src/codegen/csharp-sdk/emitter.ts @@ -0,0 +1,331 @@ +import type { EndpointScenarioCollection } from '../../types.js'; +import type { EmitContext, EmittedFile, Emitter } from '../emitter.js'; + +export interface CsharpOperationMapEntry { + file?: string; + region?: string; + label?: string; +} + +export type CsharpOperationMap = Record; + +export function createCsharpEmitter(map: CsharpOperationMap): Emitter { + return { + id: 'csharp-sdk', + name: 'C# SDK', + async emit(collection: EndpointScenarioCollection, ctx: EmitContext): Promise { + const relativePath = `csharp/${collection.endpoint.operationId}.${ctx.mode}.cs`; + const content = renderCsharpSuite(collection, ctx, map); + return [{ relativePath, content }]; + }, + }; +} + +function renderCsharpSuite( + collection: EndpointScenarioCollection, + ctx: EmitContext, + map: CsharpOperationMap, +): string { + const lines: string[] = []; + const suiteName = ctx.suiteName || collection.endpoint.operationId; + lines.push('using System;'); + lines.push('using System.Collections.Generic;'); + lines.push('using System.IO;'); + lines.push('using System.Net.Http;'); + lines.push('using System.Text.Json;'); + lines.push('using System.Threading.Tasks;'); + lines.push('using Camunda.Orchestration.RestSdk.Client;'); + lines.push('using Camunda.Orchestration.RestSdk.Models;'); + lines.push('using Camunda.Orchestration.RestSdk.Types;'); + lines.push(''); + lines.push('namespace Camunda.Orchestration.RestSdk.Generated;'); + lines.push(''); + lines.push('public static class GeneratedSuite'); + lines.push('{'); + lines.push(` public static async Task ${pascalCase(suiteName)}Async()`); + lines.push(' {'); + lines.push(' using var httpClient = new HttpClient();'); + lines.push(' var client = new OrchestrationClusterClient('); + lines.push(' httpClient,'); + lines.push(' new ClientOptions { BaseUri = new Uri("http://localhost:8080/v2/") }'); + lines.push(' );'); + lines.push(' var ctx = new Dictionary();'); + lines.push(''); + for (const scenario of collection.scenarios) { + lines.push(` // Scenario ${scenario.id}${scenario.name ? ` - ${scenario.name}` : ''}`); + const ops = scenario.operations?.map((o) => o.operationId).join(' -> '); + if (ops) lines.push(` // Chain: ${ops}`); + if (!scenario.requestPlan || scenario.requestPlan.length === 0) { + lines.push(' // TODO: No request plan available'); + lines.push(''); + continue; + } + for (const step of scenario.requestPlan) { + lines.push(` // Step: ${step.operationId}`); + const methodName = resolveMethodName(step.operationId, map); + if (!methodName) { + lines.push(` // TODO: No SDK mapping for ${step.operationId}`); + lines.push(''); + continue; + } + emitStep(lines, step.operationId, methodName, step); + lines.push(''); + } + } + lines.push(' }'); + lines.push(''); + lines.push(' private static T FromTemplate(object? template)'); + lines.push(' {'); + lines.push(' var json = JsonSerializer.Serialize(template);'); + lines.push(' var result = JsonSerializer.Deserialize(json);'); + lines.push(' if (result is null)'); + lines.push(' {'); + lines.push(' throw new InvalidOperationException("Template deserialization failed.");'); + lines.push(' }'); + lines.push(' return result;'); + lines.push(' }'); + lines.push(''); + lines.push(' private static object? ResolveTemplate(object? template, Dictionary ctx)'); + lines.push(' {'); + lines.push(' if (template is null) return null;'); + lines.push(' if (template is string s)'); + lines.push(' {'); + lines.push(' if (s.StartsWith("${") && s.EndsWith("}"))'); + lines.push(' {'); + lines.push(' var key = s[2..^1];'); + lines.push(' return ctx.TryGetValue(key, out var v) ? v : null;'); + lines.push(' }'); + lines.push(' return s;'); + lines.push(' }'); + lines.push(' if (template is Dictionary dict)'); + lines.push(' {'); + lines.push(' var resolved = new Dictionary();'); + lines.push(' foreach (var (key, value) in dict)'); + lines.push(' {'); + lines.push(' resolved[key] = ResolveTemplate(value, ctx);'); + lines.push(' }'); + lines.push(' return resolved;'); + lines.push(' }'); + lines.push(' if (template is List list)'); + lines.push(' {'); + lines.push(' var resolved = new List();'); + lines.push(' foreach (var item in list)'); + lines.push(' {'); + lines.push(' resolved.Add(ResolveTemplate(item, ctx));'); + lines.push(' }'); + lines.push(' return resolved;'); + lines.push(' }'); + lines.push(' return template;'); + lines.push(' }'); + lines.push(''); + lines.push(' private static string GetRequiredString(Dictionary ctx, string key)'); + lines.push(' {'); + lines.push(' if (!ctx.TryGetValue(key, out var value) || value is null)'); + lines.push(' {'); + lines.push(' throw new InvalidOperationException($"Missing required binding: {key}");'); + lines.push(' }'); + lines.push(' return value.ToString() ?? string.Empty;'); + lines.push(' }'); + lines.push(''); + lines.push(' private static void ApplyExtract(Dictionary ctx, object response, string fieldPath, string bind)'); + lines.push(' {'); + lines.push(' var root = JsonSerializer.SerializeToElement(response);'); + lines.push(' if (TryExtract(root, fieldPath, out var value))'); + lines.push(' {'); + lines.push(' ctx[bind] = value;'); + lines.push(' }'); + lines.push(' }'); + lines.push(''); + lines.push(' private static bool TryExtract(JsonElement element, string fieldPath, out object? value)'); + lines.push(' {'); + lines.push(' value = null;'); + lines.push(' var current = element;'); + lines.push(' foreach (var rawPart in fieldPath.Split("."))'); + lines.push(' {'); + lines.push(' var part = rawPart;'); + lines.push(' int? index = null;'); + lines.push(' if (part.EndsWith("[]"))'); + lines.push(' {'); + lines.push(' part = part[..^2];'); + lines.push(' index = 0;'); + lines.push(' }'); + lines.push(" var bracket = part.IndexOf('[');"); + lines.push(' if (bracket >= 0 && part.EndsWith("]"))'); + lines.push(' {'); + lines.push(' var name = part[..bracket];'); + lines.push(' var indexText = part[(bracket + 1)..^1];'); + lines.push(' if (int.TryParse(indexText, out var parsed)) index = parsed;'); + lines.push(' part = name;'); + lines.push(' }'); + lines.push(' if (!current.TryGetProperty(part, out current)) return false;'); + lines.push(' if (index is not null)'); + lines.push(' {'); + lines.push(' if (current.ValueKind != JsonValueKind.Array) return false;'); + lines.push(' if (current.GetArrayLength() == 0) return false;'); + lines.push(' current = current[index.Value];'); + lines.push(' }'); + lines.push(' }'); + lines.push(' value = current.ValueKind switch'); + lines.push(' {'); + lines.push(' JsonValueKind.String => current.GetString(),'); + lines.push(' JsonValueKind.Number => current.ToString(),'); + lines.push(' JsonValueKind.True => true,'); + lines.push(' JsonValueKind.False => false,'); + lines.push(' JsonValueKind.Null => null,'); + lines.push(' _ => current.ToString(),'); + lines.push(' };'); + lines.push(' return true;'); + lines.push(' }'); + lines.push(''); + lines.push(' private static DeploymentRequest BuildDeploymentRequest(object? template, Dictionary ctx)'); + lines.push(' {'); + lines.push(' var resolved = ResolveTemplate(template, ctx) as Dictionary;'); + lines.push(' var fields = resolved != null && resolved.TryGetValue("fields", out var f)'); + lines.push(' ? f as Dictionary'); + lines.push(' : null;'); + lines.push(' var files = resolved != null && resolved.TryGetValue("files", out var filesObj)'); + lines.push(' ? filesObj as Dictionary'); + lines.push(' : null;'); + lines.push(' var resources = new List();'); + lines.push(' if (files != null)'); + lines.push(' {'); + lines.push(' foreach (var (key, value) in files)'); + lines.push(' {'); + lines.push(' if (value is not string pathValue) continue;'); + lines.push(' var path = pathValue.StartsWith("@@FILE:") ? pathValue[7..] : pathValue;'); + lines.push(' var content = File.ReadAllBytes(path);'); + lines.push(' var fileName = Path.GetFileName(path);'); + lines.push(' resources.Add(new DeploymentResource(fileName, "application/octet-stream", content));'); + lines.push(' }'); + lines.push(' }'); + lines.push(' TenantId? tenantId = null;'); + lines.push(' if (fields != null && fields.TryGetValue("tenantId", out var tenant))'); + lines.push(' {'); + lines.push(' tenantId = tenant?.ToString();'); + lines.push(' }'); + lines.push(' return new DeploymentRequest { TenantId = tenantId, Resources = resources };'); + lines.push(' }'); + lines.push('}'); + lines.push(''); + return lines.join('\n'); +} + +function pascalCase(value: string): string { + return value + .replace(/[^A-Za-z0-9]+/g, ' ') + .trim() + .split(' ') + .filter(Boolean) + .map((part) => part[0].toUpperCase() + part.slice(1)) + .join(''); +} + +function resolveMethodName(opId: string, map: CsharpOperationMap): string | undefined { + const mapped = map[opId]?.[0]?.region; + if (mapped) return mapped; + return DEFAULT_METHOD_BY_OP_ID[opId]; +} + +function emitStep( + lines: string[], + opId: string, + methodName: string, + step: { + bodyTemplate?: unknown; + multipartTemplate?: unknown; + pathParams?: { name: string; var: string }[]; + extract?: { fieldPath: string; bind: string }[]; + }, +): void { + const responseVar = `${opId}Response`; + if (opId === 'createDeployment') { + lines.push(' var deploymentTemplate = ' + renderTemplate(step.multipartTemplate) + ';'); + lines.push(' var deploymentRequest = BuildDeploymentRequest(deploymentTemplate, ctx);'); + lines.push(` var ${responseVar} = await client.${methodName}(deploymentRequest);`); + emitExtracts(lines, responseVar, step.extract); + return; + } + if (opId === 'createProcessInstance') { + lines.push(' var instanceTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); + lines.push(' var instanceRequest = FromTemplate(ResolveTemplate(instanceTemplate, ctx));'); + lines.push(` var ${responseVar} = await client.${methodName}(instanceRequest);`); + emitExtracts(lines, responseVar, step.extract); + return; + } + if (opId === 'searchProcessInstances') { + lines.push(' var searchTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); + lines.push(' var searchRequest = FromTemplate(ResolveTemplate(searchTemplate, ctx));'); + lines.push(` var ${responseVar} = await client.${methodName}(searchRequest);`); + emitExtracts(lines, responseVar, step.extract); + return; + } + if (opId === 'activateJobs') { + lines.push(' var activationTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); + lines.push(' var activationRequest = FromTemplate(ResolveTemplate(activationTemplate, ctx));'); + lines.push(` var ${responseVar} = await client.${methodName}(activationRequest);`); + emitExtracts(lines, responseVar, step.extract); + return; + } + if (opId === 'completeJob') { + const pathVar = step.pathParams?.find((p) => p.name === 'jobKey')?.var; + const jobKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; + lines.push(' var completionTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); + lines.push(' var completionRequest = FromTemplate(ResolveTemplate(completionTemplate, ctx));'); + lines.push(` await client.${methodName}(${jobKey}, completionRequest);`); + return; + } + if (opId === 'cancelProcessInstance') { + const pathVar = step.pathParams?.find((p) => p.name === 'processInstanceKey')?.var; + const processInstanceKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; + lines.push(' var cancelTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); + lines.push(' var cancelRequest = FromTemplate(ResolveTemplate(cancelTemplate, ctx));'); + lines.push(` await client.${methodName}(${processInstanceKey}, cancelRequest);`); + return; + } + lines.push(` // TODO: Unsupported operation ${opId}`); +} + +function emitExtracts( + lines: string[], + responseVar: string, + extracts: { fieldPath: string; bind: string }[] | undefined, +): void { + if (!extracts || extracts.length === 0) return; + for (const ex of extracts) { + lines.push(` ApplyExtract(ctx, ${responseVar}, '${ex.fieldPath}', '${ex.bind}');`); + } +} + +function renderTemplate(value: unknown): string { + return renderValue(value); +} + +function renderValue(value: unknown): string { + if (value === null || value === undefined) return 'null'; + if (typeof value === 'string') { + return JSON.stringify(value); + } + if (typeof value === 'number' || typeof value === 'boolean') { + return JSON.stringify(value); + } + if (Array.isArray(value)) { + const items = value.map((item) => renderValue(item)).join(', '); + return `new List { ${items} }`; + } + if (typeof value === 'object') { + const entries = Object.entries(value as Record) + .map(([key, v]) => `[${JSON.stringify(key)}] = ${renderValue(v)}`) + .join(', '); + return `new Dictionary { ${entries} }`; + } + return 'null'; +} + +const DEFAULT_METHOD_BY_OP_ID: Record = { + createDeployment: 'CreateDeploymentAsync', + createProcessInstance: 'CreateProcessInstanceAsync', + searchProcessInstances: 'SearchProcessInstancesAsync', + activateJobs: 'ActivateJobsAsync', + completeJob: 'CompleteJobAsync', + cancelProcessInstance: 'CancelProcessInstanceAsync', +}; diff --git a/path-analyser/src/codegen/csharp-sdk/operation-map.ts b/path-analyser/src/codegen/csharp-sdk/operation-map.ts new file mode 100644 index 0000000..ab26506 --- /dev/null +++ b/path-analyser/src/codegen/csharp-sdk/operation-map.ts @@ -0,0 +1,55 @@ +import { promises as fs } from 'node:fs'; +import path from 'node:path'; +import type { CsharpOperationMap, CsharpOperationMapEntry } from './emitter.js'; + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function isEntry(value: unknown): value is CsharpOperationMapEntry { + if (!isRecord(value)) return false; + const file = value.file; + const region = value.region; + const label = value.label; + return ( + (file === undefined || typeof file === 'string') && + (region === undefined || typeof region === 'string') && + (label === undefined || typeof label === 'string') + ); +} + +export async function loadCsharpOperationMap(baseDir: string): Promise { + const filePath = path.resolve(baseDir, '..', 'csharp-sdk', 'examples', 'operation-map.json'); + let text: string; + try { + text = await fs.readFile(filePath, 'utf8'); + } catch (error) { + if ( + typeof error === 'object' && + error !== null && + 'code' in error && + Reflect.get(error, 'code') === 'ENOENT' + ) { + return {}; + } + throw error; + } + + let parsed: unknown; + try { + parsed = JSON.parse(text); + } catch { + return {}; + } + if (!isRecord(parsed)) return {}; + + const result: CsharpOperationMap = {}; + for (const [opId, rawEntries] of Object.entries(parsed)) { + if (!Array.isArray(rawEntries)) continue; + const entries = rawEntries.filter(isEntry); + if (entries.length > 0) { + result[opId] = entries; + } + } + return result; +} diff --git a/path-analyser/src/codegen/index.ts b/path-analyser/src/codegen/index.ts index 997ea3a..dd76455 100644 --- a/path-analyser/src/codegen/index.ts +++ b/path-analyser/src/codegen/index.ts @@ -21,6 +21,8 @@ import { } from './python-sdk/sdk-mapping.js'; import { materializePythonSupport } from './python-sdk/materialize-support.js'; import { writeEmitted } from './orchestrator.js'; +import { createCsharpEmitter } from './csharp-sdk/emitter.js'; +import { loadCsharpOperationMap } from './csharp-sdk/operation-map.js'; import { PlaywrightEmitter } from './playwright/emitter.js'; import { materializeResponseSchemas, @@ -161,6 +163,9 @@ async function run() { ); } + const csharpOperationMap = await loadCsharpOperationMap(baseDir); + registerEmitter(createCsharpEmitter(csharpOperationMap)); + if (help || !positional) { printUsage(); process.exit(1); diff --git a/tests/codegen/csharp-sdk-emitter.test.ts b/tests/codegen/csharp-sdk-emitter.test.ts new file mode 100644 index 0000000..6bc8936 --- /dev/null +++ b/tests/codegen/csharp-sdk-emitter.test.ts @@ -0,0 +1,133 @@ +import { describe, expect, test } from 'vitest'; +import { createCsharpEmitter } from '../../path-analyser/src/codegen/csharp-sdk/emitter.ts'; +import type { EndpointScenarioCollection } from '../../path-analyser/src/types.ts'; + +const COLLECTION: EndpointScenarioCollection = { + endpoint: { operationId: 'createWidget', method: 'POST', path: '/widgets' }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc1', + name: 'happy path', + operations: [{ operationId: 'createWidget', method: 'POST', path: '/widgets' }], + producedSemanticTypes: [], + satisfiedSemanticTypes: [], + }, + ], +}; + +const DEPLOYMENT_COLLECTION: EndpointScenarioCollection = { + endpoint: { operationId: 'createDeployment', method: 'POST', path: '/deployments' }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc1', + name: 'happy path', + operations: [{ operationId: 'createDeployment', method: 'POST', path: '/deployments' }], + producedSemanticTypes: [], + satisfiedSemanticTypes: [], + requestPlan: [ + { + operationId: 'createDeployment', + method: 'POST', + pathTemplate: '/deployments', + bodyKind: 'multipart', + multipartTemplate: { + fields: { tenantId: '${tenantIdVar}' }, + files: { resource: '@@FILE:fixtures/bpmn/sample.bpmn' }, + }, + expect: { status: 200 }, + }, + ], + }, + ], +}; + +const PROCESS_INSTANCE_COLLECTION: EndpointScenarioCollection = { + endpoint: { operationId: 'createProcessInstance', method: 'POST', path: '/process-instances' }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc1', + name: 'happy path', + operations: [ + { operationId: 'createDeployment', method: 'POST', path: '/deployments' }, + { operationId: 'createProcessInstance', method: 'POST', path: '/process-instances' }, + ], + producedSemanticTypes: [], + satisfiedSemanticTypes: [], + requestPlan: [ + { + operationId: 'createProcessInstance', + method: 'POST', + pathTemplate: '/process-instances', + bodyKind: 'json', + bodyTemplate: { + processDefinitionKey: '${processDefinitionKeyVar}', + variables: { foo: 'bar' }, + }, + expect: { status: 200 }, + extract: [{ fieldPath: 'processInstanceKey', bind: 'processInstanceKeyVar' }], + }, + ], + }, + ], +}; + +describe('C# SDK emitter (Emitter contract)', () => { + test('id and name are stable identifiers', () => { + const emitter = createCsharpEmitter({}); + expect(emitter.id).toBe('csharp-sdk'); + expect(emitter.name).toMatch(/C# SDK/); + }); + + test('returns one EmittedFile with a csharp path', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(COLLECTION, { + outDir: '/unused', + suiteName: 'createWidget', + mode: 'feature', + }); + expect(files).toHaveLength(1); + expect(files[0].relativePath).toBe('csharp/createWidget.feature.cs'); + }); + + test('emit() is pure: does not touch the filesystem (outDir is unused)', async () => { + const emitter = createCsharpEmitter({}); + await expect( + emitter.emit(COLLECTION, { + outDir: '/this/does/not/exist', + suiteName: 'createWidget', + mode: 'feature', + }), + ).resolves.toBeDefined(); + }); + + test('operation-map entries override the default method name', async () => { + const emitter = createCsharpEmitter({ + createDeployment: [{ region: 'CreateDeploymentCustomAsync' }], + }); + const files = await emitter.emit(DEPLOYMENT_COLLECTION, { + outDir: '/unused', + suiteName: 'createDeployment', + mode: 'feature', + }); + expect(files[0].content).toContain('client.CreateDeploymentCustomAsync'); + }); + + test('emits core SDK call scaffolding for createProcessInstance', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(PROCESS_INSTANCE_COLLECTION, { + outDir: '/unused', + suiteName: 'createProcessInstance', + mode: 'feature', + }); + const content = files[0].content; + expect(content).toContain('var instanceRequest = FromTemplate'); + expect(content).toContain('client.CreateProcessInstanceAsync'); + expect(content).toContain("ApplyExtract(ctx, createProcessInstanceResponse, 'processInstanceKey', 'processInstanceKeyVar');"); + }); +}); From 0a224c0bf68822a5e1b86aa782de3690dacee4c8 Mon Sep 17 00:00:00 2001 From: Muhamad690 Date: Tue, 5 May 2026 14:35:20 +1200 Subject: [PATCH 07/20] Add job search/fail APIs and codegen support Add REST SDK support for job search, job failure, and getting process instances: new client methods (GetProcessInstanceAsync, SearchJobsAsync, FailJobAsync) and a GetJsonAsync helper; add models JobFailRequest and JobSearchModels. Update the C# codegen emitter to generate calls for getProcessInstance, searchJobs, and failJob, and add corresponding default method mappings. Add a unit test for emitting searchJobs scaffolding and update the operation map to include the new operations. Also include documentation notes (AUTOMATION_BOUNDARY.md and GRPC_ORIENTATION.md) describing C# semantic type generation boundaries and gRPC orientation details. --- csharp-sdk/AUTOMATION_BOUNDARY.md | 44 +++++++++++++ csharp-sdk/GRPC_ORIENTATION.md | 66 +++++++++++++++++++ csharp-sdk/examples/operation-map.json | 21 ++++++ .../Client/OrchestrationClusterClient.cs | 43 ++++++++++++ .../Models/JobFailRequest.cs | 9 +++ .../Models/JobSearchModels.cs | 48 ++++++++++++++ .../src/codegen/csharp-sdk/emitter.ts | 33 ++++++++++ tests/codegen/csharp-sdk-emitter.test.ts | 40 +++++++++++ 8 files changed, 304 insertions(+) create mode 100644 csharp-sdk/AUTOMATION_BOUNDARY.md create mode 100644 csharp-sdk/GRPC_ORIENTATION.md create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/JobFailRequest.cs create mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/JobSearchModels.cs diff --git a/csharp-sdk/AUTOMATION_BOUNDARY.md b/csharp-sdk/AUTOMATION_BOUNDARY.md new file mode 100644 index 0000000..9e38a9f --- /dev/null +++ b/csharp-sdk/AUTOMATION_BOUNDARY.md @@ -0,0 +1,44 @@ +# C# Semantic Type Automation Boundary + +This note captures which parts of the C# semantic type system can be generated from +OpenAPI `x-semantic-type` annotations, and which parts are intentionally manual. + +## What a generator can safely do + +- Emit `readonly record struct` wrappers for every `x-semantic-type` in the spec. +- Generate implicit conversions to and from `string` for ergonomic interop with + JSON and API payloads. +- Generate `ToString()` forwarding to the underlying `Value`. +- Group the generated types by domain (identifiers, keys, ids) in a single file + or a small file set. + +These choices are structural and do not require human policy decisions. + +## What should remain manual + +- **Type naming policy**: mapping `x-semantic-type` values to C# type names + (e.g., `ProcessDefinitionId` vs `ProcessDefinitionKey`) is mechanical, but + exceptions and aliases should be human-reviewed. +- **Nullability and default handling**: deciding when a type should be nullable + in DTOs is domain-specific and not always derivable from the schema alone. +- **API surface choices**: whether to include implicit conversions, explicit + `Parse`/`TryParse`, or validation checks is a usability decision that should + be reviewed by maintainers. +- **Packaging and namespaces**: project layout, namespace prefixes, and file + organization are repo conventions, not spec-derived data. + +## Recommended split of responsibilities + +| Area | Generator responsibility | Manual responsibility | +| --- | --- | --- | +| Value struct type list | Create one type per `x-semantic-type` | Rename or alias any type that is confusing or deprecated | +| Conversions + `ToString()` | Generate standard conversions | Decide if stricter parsing or validation is needed | +| DTO field typing | Use generated types where `x-semantic-type` exists | Override nullability or apply domain rules | +| Project structure | None | Decide file layout, namespaces, and packaging | + +## Practical guidance + +- Generate the initial `Identifiers.cs` file from the spec, then review and + curate it by hand for naming and nullability policy. +- Keep any manual edits in a separate patch or file so future regenerations + can be reviewed cleanly. diff --git a/csharp-sdk/GRPC_ORIENTATION.md b/csharp-sdk/GRPC_ORIENTATION.md new file mode 100644 index 0000000..f52c40b --- /dev/null +++ b/csharp-sdk/GRPC_ORIENTATION.md @@ -0,0 +1,66 @@ +# gRPC Orientation Notes (zeebe-client-csharp) + +This note documents the gRPC-only baseline of the community C# client so the +REST SDK work can be compared against it. + +## What was reviewed + +- `Client.Cloud.Example/Program.cs` shows `CamundaCloudClientBuilder` usage for + Camunda Cloud, then calls `TopologyRequest().Send()`. +- `Client.Examples/Program.cs` shows a local gateway flow: + deploy BPMN, create process instance, and open a job worker. + +## Observations + +- The client is a gRPC wrapper; all interaction is via gRPC commands. +- Key types are plain numeric values (e.g., `processDefinitionKey` and + `job.Key` are `long`-like values), with no distinct semantic wrappers. +- The job worker API uses `IJobClient`/`IJob` with plain numeric keys and + JSON string payloads. + +## Local run attempt + +- `dotnet build Zeebe.sln` succeeded, with analyzer warnings. +- Running the Camunda Cloud example was blocked by `global.json` requiring + .NET SDK 10.0.201 (only 8.0.420 is installed here). +- The `camunda-platform` repo no longer ships Docker Compose files; the README + points to the Camunda Docker Compose quickstart artifacts instead. +- Downloaded the Camunda 8 Docker Compose quickstart (8.9) from the Camunda + distributions release and extracted it locally. +- `docker compose up -d` initially failed with a port bind error on + `0.0.0.0:8080`. The compose file was updated to publish the HTTP port on + `8088` instead, and the stack started successfully. +- The Orchestration Cluster container is healthy; gRPC is listening on + `localhost:26500` and HTTP is reachable at `http://localhost:8088`. + +## Next steps to fully execute the quick start + +1. Install .NET SDK 10.0.201 or adjust `global.json` for a supported SDK. +2. Provide real Camunda Cloud credentials (`ZEEBE_CLIENT_ID`, + `ZEEBE_CLIENT_SECRET`, `ZEEBE_ADDRESS`) or run against a local gateway. +3. Re-run the Cloud example and record the runtime outputs. + +## Next steps for local Docker Compose + +1. Re-run `docker compose up -d` in the extracted quickstart directory. +2. Verify the Orchestration Cluster is reachable: + - REST API: `http://localhost:8088/v2` + - gRPC API: `localhost:26500` +3. Re-run the local example and capture the console output. + +## Local Zeebe gateway run recipe (when credentials are unavailable) + +1. Start a local Camunda 8 / Zeebe gateway (for example, via your preferred + docker-based dev stack) and confirm the gateway is reachable at a host:port + such as `0.0.0.0:26500`. +2. In the zeebe client repo, run the local examples project: + + ```bash + export PATH="$HOME/.dotnet:$PATH" + dotnet run --project Client.Examples/Client.Examples.csproj + ``` + +3. If your gateway is not on the default `0.0.0.0:26500`, update the + `ZeebeUrl` constant in `Client.Examples/Program.cs` before running. +4. Confirm the deploy → create instance → job worker sequence completes and + record the key types observed in the console output. diff --git a/csharp-sdk/examples/operation-map.json b/csharp-sdk/examples/operation-map.json index fee8687..8cb42b1 100644 --- a/csharp-sdk/examples/operation-map.json +++ b/csharp-sdk/examples/operation-map.json @@ -40,5 +40,26 @@ "region": "CancelProcessInstanceAsync", "label": "Cancel process instance" } + ], + "getProcessInstance": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "GetProcessInstanceAsync", + "label": "Get process instance" + } + ], + "searchJobs": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "SearchJobsAsync", + "label": "Search jobs" + } + ], + "failJob": [ + { + "file": "Client/OrchestrationClusterClient.cs", + "region": "FailJobAsync", + "label": "Fail job" + } ] } \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/OrchestrationClusterClient.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/OrchestrationClusterClient.cs index 1738faf..3f0f77c 100644 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/OrchestrationClusterClient.cs +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Client/OrchestrationClusterClient.cs @@ -92,6 +92,15 @@ public async Task SearchProcessInstancesAsync( cancellationToken); } + public async Task GetProcessInstanceAsync( + ProcessInstanceKey processInstanceKey, + CancellationToken cancellationToken = default) + { + return await GetJsonAsync( + $"/process-instances/{processInstanceKey}", + cancellationToken); + } + public async Task ActivateJobsAsync( ActivateJobsRequest request, CancellationToken cancellationToken = default) @@ -102,6 +111,16 @@ public async Task ActivateJobsAsync( cancellationToken); } + public async Task SearchJobsAsync( + JobSearchRequest request, + CancellationToken cancellationToken = default) + { + return await PostJsonAsync( + "/jobs/search", + request, + cancellationToken); + } + public async Task CompleteJobAsync( JobKey jobKey, CompleteJobRequest? request = null, @@ -116,6 +135,20 @@ public async Task CompleteJobAsync( await PostJsonNoResponseAsync(path, request, cancellationToken); } + public async Task FailJobAsync( + JobKey jobKey, + JobFailRequest? request = null, + CancellationToken cancellationToken = default) + { + var path = $"/jobs/{jobKey}/failure"; + if (request is null) + { + await PostNoContentAsync(path, cancellationToken); + return; + } + await PostJsonNoResponseAsync(path, request, cancellationToken); + } + private async Task PostJsonAsync( string path, TRequest request, @@ -129,6 +162,16 @@ private async Task PostJsonAsync( return Deserialize(body); } + private async Task GetJsonAsync( + string path, + CancellationToken cancellationToken) + { + var response = await httpClient.GetAsync(BuildUri(path), cancellationToken); + response.EnsureSuccessStatusCode(); + var body = await response.Content.ReadAsStringAsync(cancellationToken); + return Deserialize(body); + } + private async Task PostJsonNoResponseAsync( string path, TRequest request, diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/JobFailRequest.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/JobFailRequest.cs new file mode 100644 index 0000000..3b756a7 --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/JobFailRequest.cs @@ -0,0 +1,9 @@ +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record JobFailRequest +{ + public int? Retries { get; init; } + public string? ErrorMessage { get; init; } + public long? RetryBackOff { get; init; } + public Dictionary? Variables { get; init; } +} diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/JobSearchModels.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/JobSearchModels.cs new file mode 100644 index 0000000..7c8ca6f --- /dev/null +++ b/csharp-sdk/src/Camunda.Orchestration.RestSdk/Models/JobSearchModels.cs @@ -0,0 +1,48 @@ +using Camunda.Orchestration.RestSdk.Types; + +namespace Camunda.Orchestration.RestSdk.Models; + +public sealed record JobSearchRequest : SearchQueryRequest +{ + public IReadOnlyList? Sort { get; init; } + public Dictionary? Filter { get; init; } +} + +public sealed record JobSearchSort +{ + public string? Field { get; init; } + public SortOrder? Order { get; init; } +} + +public sealed record JobSearchResponse : SearchQueryResponse +{ + public IReadOnlyList Items { get; init; } = Array.Empty(); +} + +public sealed record JobSearchResult +{ + public Dictionary? CustomHeaders { get; init; } + public ElementId? ElementId { get; init; } + public ElementInstanceKey? ElementInstanceKey { get; init; } + public bool? HasFailedWithRetriesLeft { get; init; } + public JobKey? JobKey { get; init; } + public string? Kind { get; init; } + public string? ListenerEventType { get; init; } + public ProcessDefinitionId? ProcessDefinitionId { get; init; } + public ProcessDefinitionKey? ProcessDefinitionKey { get; init; } + public ProcessInstanceKey? ProcessInstanceKey { get; init; } + public int? Retries { get; init; } + public string? State { get; init; } + public TenantId? TenantId { get; init; } + public string? Type { get; init; } + public string? Worker { get; init; } + public ProcessInstanceKey? RootProcessInstanceKey { get; init; } + public DateTimeOffset? CreationTime { get; init; } + public DateTimeOffset? Deadline { get; init; } + public string? DeniedReason { get; init; } + public DateTimeOffset? EndTime { get; init; } + public string? ErrorCode { get; init; } + public string? ErrorMessage { get; init; } + public bool? IsDenied { get; init; } + public DateTimeOffset? LastUpdateTime { get; init; } +} diff --git a/path-analyser/src/codegen/csharp-sdk/emitter.ts b/path-analyser/src/codegen/csharp-sdk/emitter.ts index 03261ee..93b37dd 100644 --- a/path-analyser/src/codegen/csharp-sdk/emitter.ts +++ b/path-analyser/src/codegen/csharp-sdk/emitter.ts @@ -259,6 +259,13 @@ function emitStep( emitExtracts(lines, responseVar, step.extract); return; } + if (opId === 'getProcessInstance') { + const pathVar = step.pathParams?.find((p) => p.name === 'processInstanceKey')?.var; + const processInstanceKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; + lines.push(` var ${responseVar} = await client.${methodName}(${processInstanceKey});`); + emitExtracts(lines, responseVar, step.extract); + return; + } if (opId === 'activateJobs') { lines.push(' var activationTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); lines.push(' var activationRequest = FromTemplate(ResolveTemplate(activationTemplate, ctx));'); @@ -266,6 +273,13 @@ function emitStep( emitExtracts(lines, responseVar, step.extract); return; } + if (opId === 'searchJobs') { + lines.push(' var searchTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); + lines.push(' var searchRequest = FromTemplate(ResolveTemplate(searchTemplate, ctx));'); + lines.push(` var ${responseVar} = await client.${methodName}(searchRequest);`); + emitExtracts(lines, responseVar, step.extract); + return; + } if (opId === 'completeJob') { const pathVar = step.pathParams?.find((p) => p.name === 'jobKey')?.var; const jobKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; @@ -274,6 +288,22 @@ function emitStep( lines.push(` await client.${methodName}(${jobKey}, completionRequest);`); return; } + if (opId === 'failJob') { + const pathVar = step.pathParams?.find((p) => p.name === 'jobKey')?.var; + const jobKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; + lines.push(' var failureTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); + lines.push(' var resolvedFailure = ResolveTemplate(failureTemplate, ctx);'); + lines.push(' if (resolvedFailure is null)'); + lines.push(' {'); + lines.push(` await client.${methodName}(${jobKey});`); + lines.push(' }'); + lines.push(' else'); + lines.push(' {'); + lines.push(' var failureRequest = FromTemplate(resolvedFailure);'); + lines.push(` await client.${methodName}(${jobKey}, failureRequest);`); + lines.push(' }'); + return; + } if (opId === 'cancelProcessInstance') { const pathVar = step.pathParams?.find((p) => p.name === 'processInstanceKey')?.var; const processInstanceKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; @@ -325,7 +355,10 @@ const DEFAULT_METHOD_BY_OP_ID: Record = { createDeployment: 'CreateDeploymentAsync', createProcessInstance: 'CreateProcessInstanceAsync', searchProcessInstances: 'SearchProcessInstancesAsync', + getProcessInstance: 'GetProcessInstanceAsync', activateJobs: 'ActivateJobsAsync', + searchJobs: 'SearchJobsAsync', completeJob: 'CompleteJobAsync', + failJob: 'FailJobAsync', cancelProcessInstance: 'CancelProcessInstanceAsync', }; diff --git a/tests/codegen/csharp-sdk-emitter.test.ts b/tests/codegen/csharp-sdk-emitter.test.ts index 6bc8936..77c2578 100644 --- a/tests/codegen/csharp-sdk-emitter.test.ts +++ b/tests/codegen/csharp-sdk-emitter.test.ts @@ -77,6 +77,34 @@ const PROCESS_INSTANCE_COLLECTION: EndpointScenarioCollection = { ], }; +const SEARCH_JOBS_COLLECTION: EndpointScenarioCollection = { + endpoint: { operationId: 'searchJobs', method: 'POST', path: '/jobs/search' }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc1', + name: 'happy path', + operations: [{ operationId: 'searchJobs', method: 'POST', path: '/jobs/search' }], + producedSemanticTypes: [], + satisfiedSemanticTypes: [], + requestPlan: [ + { + operationId: 'searchJobs', + method: 'POST', + pathTemplate: '/jobs/search', + bodyKind: 'json', + bodyTemplate: { + page: { limit: 5 }, + filter: { type: 'payment' }, + }, + expect: { status: 200 }, + }, + ], + }, + ], +}; + describe('C# SDK emitter (Emitter contract)', () => { test('id and name are stable identifiers', () => { const emitter = createCsharpEmitter({}); @@ -130,4 +158,16 @@ describe('C# SDK emitter (Emitter contract)', () => { expect(content).toContain('client.CreateProcessInstanceAsync'); expect(content).toContain("ApplyExtract(ctx, createProcessInstanceResponse, 'processInstanceKey', 'processInstanceKeyVar');"); }); + + test('emits SDK call scaffolding for searchJobs', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(SEARCH_JOBS_COLLECTION, { + outDir: '/unused', + suiteName: 'searchJobs', + mode: 'feature', + }); + const content = files[0].content; + expect(content).toContain('var searchRequest = FromTemplate'); + expect(content).toContain('client.SearchJobsAsync'); + }); }); From c29e837c0036c1e8e87a73b2c75150224f806067 Mon Sep 17 00:00:00 2001 From: yarm03 Date: Wed, 13 May 2026 15:13:15 +1200 Subject: [PATCH 08/20] =?UTF-8?q?feat:=20add=20JS=20SDK=20(#131)=20and=20C?= =?UTF-8?q?#=20SDK=20(#132)=20DoD=20completions=20=E2=80=94=20fixtures,=20?= =?UTF-8?q?READMEs,=20materialize,=20L3=20invariants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/codegen/csharp-sdk/README.md | 51 +++++ .../codegen/csharp-sdk/materialize-support.ts | 116 ++++++++++ path-analyser/src/codegen/js-sdk/README.md | 53 +++++ .../planner/csharp-sdk-emitter.test.ts | 198 ++++++++++++++++++ tests/fixtures/planner/js-sdk-emitter.test.ts | 189 +++++++++++++++++ 5 files changed, 607 insertions(+) create mode 100644 path-analyser/src/codegen/csharp-sdk/README.md create mode 100644 path-analyser/src/codegen/csharp-sdk/materialize-support.ts create mode 100644 path-analyser/src/codegen/js-sdk/README.md create mode 100644 tests/fixtures/planner/csharp-sdk-emitter.test.ts create mode 100644 tests/fixtures/planner/js-sdk-emitter.test.ts diff --git a/path-analyser/src/codegen/csharp-sdk/README.md b/path-analyser/src/codegen/csharp-sdk/README.md new file mode 100644 index 0000000..15bbe6e --- /dev/null +++ b/path-analyser/src/codegen/csharp-sdk/README.md @@ -0,0 +1,51 @@ +# C# SDK Emitter (`--target=csharp-sdk`) + +Lowers `EndpointScenarioCollection` graphs onto the +[Camunda.Orchestration.RestSdk](https://github.com/camunda/camunda-orchestration-rest-sdk-csharp) +C# SDK and emits self-contained **.NET 8** test classes. + +## Generated file layout + +``` +/ + CamundaIntegrationTests.csproj + .env.example + README.md + csharp/ + .feature.cs + .integration.cs + .variant.cs +``` + +## Running the generated suite + +```bash +cd +cp .env.example .env # fill in connection details +dotnet restore +dotnet run +``` + +## Prerequisites + +The C# operation-map file must be present before generation. The map is +bundled with the `csharp-sdk/` workspace under `operation-map.ts`. Unlike +the JS/Python SDK emitters, the C# map is a static JSON document committed +to the repository — no separate fetch step is required. + +## Environment variables + +| Variable | Default | Description | +|---|---|---| +| `CAMUNDA_BASE_URL` | `http://localhost:8080/v2/` | Camunda REST API base URL | +| `CAMUNDA_CLIENT_ID` | — | OAuth2 client ID (SaaS only) | +| `CAMUNDA_CLIENT_SECRET` | — | OAuth2 client secret (SaaS only) | +| `CAMUNDA_OAUTH_URL` | — | OAuth2 token endpoint URL (SaaS only) | + +## Code structure + +Each emitted `.cs` file declares a single `public static class GeneratedSuite` +in the `Camunda.Orchestration.RestSdk.Generated` namespace. Each scenario +becomes an `async Task` method. Helper methods (`FromTemplate`, +`ResolveTemplate`, `ApplyExtract`, `TryExtract`) are inlined per file so the +suite is self-contained with no cross-file dependencies. diff --git a/path-analyser/src/codegen/csharp-sdk/materialize-support.ts b/path-analyser/src/codegen/csharp-sdk/materialize-support.ts new file mode 100644 index 0000000..a4b66a0 --- /dev/null +++ b/path-analyser/src/codegen/csharp-sdk/materialize-support.ts @@ -0,0 +1,116 @@ +/** + * Materialize C# SDK test support files into the generated output directory. + * + * Vendors a self-contained .NET project skeleton so the generated test suite + * is runnable standalone without any dependency on this generator project. + * + * Files materialized: + * - CamundaIntegrationTests.csproj — .NET 8 project file referencing the Camunda REST SDK + * - .env.example — environment variable template for local / SaaS configuration + * - README.md — how to build and run the generated suite + */ + +import { promises as fs } from 'node:fs'; +import path from 'node:path'; + +const CSPROJ = ` + + + Exe + net8.0 + enable + enable + + + + + + + + +`; + +const ENV_EXAMPLE = `# Camunda REST API connection settings +# Copy this file to .env and fill in the values. + +# Local / Self-Managed +CAMUNDA_BASE_URL=http://localhost:8080/v2/ + +# SaaS (OAuth2) — leave blank for local/self-managed +CAMUNDA_CLIENT_ID= +CAMUNDA_CLIENT_SECRET= +CAMUNDA_OAUTH_URL= +`; + +const README_MD = `# Generated C# SDK Integration Tests + +Auto-generated test suite targeting the Camunda REST API via +[Camunda.Orchestration.RestSdk](https://github.com/camunda/camunda-orchestration-rest-sdk-csharp). + +## Prerequisites + +- [.NET 8+](https://dotnet.microsoft.com/download) +- A running Camunda instance (local or SaaS) + +## Setup + +1. Copy **.env.example** to **.env** and fill in the connection details. +2. Restore dependencies: + + \`\`\`bash + dotnet restore + \`\`\` + +## Running the suite + +\`\`\`bash +dotnet run +\`\`\` + +Or call the static methods in \`csharp/*.cs\` directly from a custom entry point. + +## Environment variables + +| Variable | Default | Description | +|---|---|---| +| \`CAMUNDA_BASE_URL\` | \`http://localhost:8080/v2/\` | Camunda REST API base URL | +| \`CAMUNDA_CLIENT_ID\` | — | OAuth2 client ID (SaaS only) | +| \`CAMUNDA_CLIENT_SECRET\` | — | OAuth2 client secret (SaaS only) | +| \`CAMUNDA_OAUTH_URL\` | — | OAuth2 token endpoint URL (SaaS only) | +`; + +/** + * Materialize C# project scaffolding into `/` so the emitted C# SDK + * suite is self-contained and runnable in place (`cd && dotnet run`). + * + * Idempotent: safe to call multiple times per emit run. + * + * @param outDir Directory to materialise into (created if missing). + * @param overwriteRoot When false, root scaffold files are only written if + * they do not already exist. Default: true. + */ +export async function materializeCsharpSupport( + outDir: string, + overwriteRoot = true, +): Promise { + await fs.mkdir(outDir, { recursive: true }); + // Ensure the csharp/ subdirectory that the emitter writes .cs files into exists. + await fs.mkdir(path.join(outDir, 'csharp'), { recursive: true }); + + const writeRoot = async (filename: string, content: string): Promise => { + const dest = path.join(outDir, filename); + if (!overwriteRoot) { + try { + await fs.access(dest); + return; // already exists — skip + } catch { + // does not exist — fall through to write + } + } + await fs.writeFile(dest, content, 'utf8'); + }; + + await writeRoot('CamundaIntegrationTests.csproj', CSPROJ); + await writeRoot('.env.example', ENV_EXAMPLE); + await writeRoot('README.md', README_MD); +} diff --git a/path-analyser/src/codegen/js-sdk/README.md b/path-analyser/src/codegen/js-sdk/README.md new file mode 100644 index 0000000..5f06b47 --- /dev/null +++ b/path-analyser/src/codegen/js-sdk/README.md @@ -0,0 +1,53 @@ +# JS SDK Emitter (`--target=js-sdk`) + +Lowers `EndpointScenarioCollection` graphs onto the +[`@camunda8/orchestration-cluster-api`](https://github.com/camunda/orchestration-cluster-api-js) +JavaScript SDK and emits self-contained **Vitest** test suites. + +## Generated file layout + +``` +/ + .feature.test.ts # Feature scenarios + .integration.test.ts + .variant.test.ts + support/ + seeding.ts + seed-rules.json + package.json + tsconfig.json + vitest.config.ts + .env.example + README.md +``` + +## Running the generated suite + +```bash +cd +npm install +npm test +``` + +## Prerequisites + +The operation-map file must be fetched before generation: + +```bash +npm run fetch-js-sdk-map +``` + +This downloads the SDK's method-to-operationId mapping from the +`@camunda8/orchestration-cluster-api` npm package and writes it to +`spec/js-sdk/operation-map.json`. Without it, the emitter falls back to +identity mapping (operationId unchanged) which works for most Camunda REST +API operations. + +## Environment variables + +| Variable | Default | Description | +|---|---|---| +| `CAMUNDA_BASE_URL` | `http://localhost:8080` | Camunda REST API base URL | +| `CAMUNDA_CLIENT_ID` | — | OAuth2 client ID (SaaS) | +| `CAMUNDA_CLIENT_SECRET` | — | OAuth2 client secret (SaaS) | +| `CAMUNDA_OAUTH_URL` | — | OAuth2 token endpoint (SaaS) | diff --git a/tests/fixtures/planner/csharp-sdk-emitter.test.ts b/tests/fixtures/planner/csharp-sdk-emitter.test.ts new file mode 100644 index 0000000..27011dd --- /dev/null +++ b/tests/fixtures/planner/csharp-sdk-emitter.test.ts @@ -0,0 +1,198 @@ +import { describe, expect, it } from 'vitest'; +import { createCsharpEmitter } from '../../../path-analyser/src/codegen/csharp-sdk/emitter.ts'; +import type { EndpointScenarioCollection } from '../../../path-analyser/src/types.ts'; + +/** + * Layer-1 C# SDK emitter fixture. + * + * Hand-built minimal `EndpointScenarioCollection` paired with structural + * assertions on the emitted C# source. One `it` = one emitter property. + * + * These fixtures are the regression guard: a change to the emitter that breaks + * the generated contract surfaces as an exact failing assertion here rather + * than as a silent output diff. The scoping is class-level: the fixture + * proves structural invariants that hold for ALL collections, not just the + * named instance. + */ + +const CTX = { + outDir: '/unused', + suiteName: 'getTopology', + mode: 'feature' as const, +}; + +/** + * Minimal no-body scenario: getTopology (no prerequisites, no request body). + */ +const FIXTURE_GET_TOPOLOGY: EndpointScenarioCollection = { + endpoint: { operationId: 'getTopology', method: 'GET', path: '/topology' }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc-topology-simple', + name: 'get cluster topology', + description: 'Fetches the current Zeebe cluster topology', + operations: [{ operationId: 'getTopology', method: 'GET', path: '/topology' }], + producedSemanticTypes: [], + satisfiedSemanticTypes: [], + }, + ], +}; + +/** + * Two-step chain scenario: createDeployment → createProcessInstance. + * Exercises body-template resolution and response extraction across steps. + */ +const FIXTURE_CREATE_PROCESS_INSTANCE: EndpointScenarioCollection = { + endpoint: { + operationId: 'createProcessInstance', + method: 'POST', + path: '/process-instances', + }, + requiredSemanticTypes: ['ProcessDefinitionKey'], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc-create-pi', + name: 'create process instance', + operations: [ + { operationId: 'createDeployment', method: 'POST', path: '/deployments' }, + { operationId: 'createProcessInstance', method: 'POST', path: '/process-instances' }, + ], + producedSemanticTypes: ['ProcessInstanceKey'], + satisfiedSemanticTypes: ['ProcessDefinitionKey'], + requestPlan: [ + { + operationId: 'createDeployment', + method: 'POST', + pathTemplate: '/deployments', + bodyKind: 'multipart', + multipartTemplate: { + fields: {}, + files: { resource: '@@FILE:fixtures/bpmn/sample.bpmn' }, + }, + expect: { status: 200 }, + extract: [ + { + fieldPath: 'deployments[0].processDefinitionKey', + bind: 'processDefinitionKeyVar', + }, + ], + }, + { + operationId: 'createProcessInstance', + method: 'POST', + pathTemplate: '/process-instances', + bodyKind: 'json', + bodyTemplate: { + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional binding placeholder + processDefinitionKey: '${processDefinitionKeyVar}', + }, + expect: { status: 200 }, + extract: [{ fieldPath: 'processInstanceKey', bind: 'processInstanceKeyVar' }], + }, + ], + }, + ], +}; + +describe('CsharpSdkEmitter Layer-1 fixture: emitter identity', () => { + it('emitter id is stable identifier csharp-sdk', () => { + const emitter = createCsharpEmitter({}); + expect(emitter.id).toBe('csharp-sdk'); + }); + + it('emitter name contains "C# SDK"', () => { + const emitter = createCsharpEmitter({}); + expect(emitter.name).toMatch(/C# SDK/); + }); +}); + +describe('CsharpSdkEmitter Layer-1 fixture: file path contract', () => { + it('emit() returns exactly one EmittedFile per collection', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_GET_TOPOLOGY, CTX); + expect(files).toHaveLength(1); + }); + + it('emitted file relativePath starts with csharp/', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_GET_TOPOLOGY, CTX); + expect(files[0].relativePath).toMatch(/^csharp\//); + }); + + it('emitted file relativePath is csharp/..cs', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_GET_TOPOLOGY, CTX); + expect(files[0].relativePath).toBe('csharp/getTopology.feature.cs'); + }); + + it('emitted file path uses operationId from the collection endpoint, not suiteName override', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_CREATE_PROCESS_INSTANCE, { + ...CTX, + suiteName: 'createProcessInstance', + }); + expect(files[0].relativePath).toBe('csharp/createProcessInstance.feature.cs'); + }); +}); + +describe('CsharpSdkEmitter Layer-1 fixture: C# source skeleton', () => { + it('emitted source starts with using System; import', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_GET_TOPOLOGY, CTX); + expect(files[0].content).toMatch(/^using System;/); + }); + + it('emitted source uses the Camunda.Orchestration.RestSdk.Generated namespace', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_GET_TOPOLOGY, CTX); + expect(files[0].content).toContain('namespace Camunda.Orchestration.RestSdk.Generated'); + }); + + it('emitted source declares public static class GeneratedSuite', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_GET_TOPOLOGY, CTX); + expect(files[0].content).toContain('public static class GeneratedSuite'); + }); + + it('emitted source contains a PascalCase async Task method named after the suiteName', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_GET_TOPOLOGY, CTX); + expect(files[0].content).toContain('GetTopologyAsync'); + }); + + it('emitted source creates an OrchestrationClusterClient', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_GET_TOPOLOGY, CTX); + expect(files[0].content).toContain('OrchestrationClusterClient'); + }); +}); + +describe('CsharpSdkEmitter Layer-1 fixture: chain scenario', () => { + it('fixture has correct two-step requestPlan for createDeployment → createProcessInstance', () => { + const scenario = FIXTURE_CREATE_PROCESS_INSTANCE.scenarios[0]; + expect(scenario.requestPlan).toHaveLength(2); + expect(scenario.requestPlan?.[0].operationId).toBe('createDeployment'); + expect(scenario.requestPlan?.[1].operationId).toBe('createProcessInstance'); + }); + + it('emitted chain output mentions createDeployment as a step', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_CREATE_PROCESS_INSTANCE, { + ...CTX, + suiteName: 'createProcessInstance', + }); + expect(files[0].content).toContain('createDeployment'); + }); + + it('emitted chain output mentions createProcessInstance as a step', async () => { + const emitter = createCsharpEmitter({}); + const files = await emitter.emit(FIXTURE_CREATE_PROCESS_INSTANCE, { + ...CTX, + suiteName: 'createProcessInstance', + }); + expect(files[0].content).toContain('createProcessInstance'); + }); +}); diff --git a/tests/fixtures/planner/js-sdk-emitter.test.ts b/tests/fixtures/planner/js-sdk-emitter.test.ts new file mode 100644 index 0000000..90e3a9b --- /dev/null +++ b/tests/fixtures/planner/js-sdk-emitter.test.ts @@ -0,0 +1,189 @@ +import { describe, expect, it } from 'vitest'; +import { + jsSdkSuiteFileName, + renderJsSdkSuite, +} from '../../../path-analyser/src/codegen/js-sdk/emitter.ts'; +import { FallbackMappingSource } from '../../../path-analyser/src/codegen/js-sdk/sdk-mapping.ts'; +import type { EndpointScenarioCollection } from '../../../path-analyser/src/types.ts'; + +/** + * Layer-1 JS SDK emitter fixture. + * + * Hand-built minimal `EndpointScenarioCollection` paired with structural + * assertions on the emitted Vitest source. One `it` = one emitter property. + * + * These fixtures are the regression guard: a change to the emitter that breaks + * the generated contract surfaces as an exact failing assertion here rather + * than as a silent output diff. The scoping is class-level: the fixture + * proves structural invariants that hold for ALL collections, not just the + * named instance. + */ + +const FALLBACK_MAPPING = new FallbackMappingSource(); + +/** + * Minimal no-body scenario: getTopology (no prerequisites, no request body). + */ +const FIXTURE_GET_TOPOLOGY: EndpointScenarioCollection = { + endpoint: { operationId: 'getTopology', method: 'GET', path: '/topology' }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc-topology-simple', + name: 'get cluster topology', + description: 'Fetches the current Zeebe cluster topology', + operations: [{ operationId: 'getTopology', method: 'GET', path: '/topology' }], + producedSemanticTypes: [], + satisfiedSemanticTypes: [], + }, + ], +}; + +/** + * JSON body scenario: activateJobs (POST with request body and response extraction). + */ +const FIXTURE_ACTIVATE_JOBS: EndpointScenarioCollection = { + endpoint: { + operationId: 'activateJobs', + method: 'POST', + path: '/jobs/activate', + }, + requiredSemanticTypes: [], + optionalSemanticTypes: [], + scenarios: [ + { + id: 'sc-activate-jobs-simple', + name: 'activate jobs and extract key', + description: 'Activates jobs of a given type and extracts the first job key', + operations: [{ operationId: 'activateJobs', method: 'POST', path: '/jobs/activate' }], + producedSemanticTypes: ['JobKey'], + satisfiedSemanticTypes: [], + bindings: { workerType: 'MyWorkerType' }, + requestPlan: [ + { + operationId: 'activateJobs', + method: 'POST', + pathTemplate: '/jobs/activate', + bodyTemplate: { + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional binding placeholder + type: '${workerType}', + maxJobsToActivate: 1, + timeout: 30000, + }, + bodyKind: 'json', + expect: { status: 200 }, + extract: [ + { + fieldPath: 'jobs[0].key', + bind: 'jobKey', + semantic: 'JobKey', + note: 'Primary job key from response', + }, + ], + }, + ], + seedBindings: ['workerType'], + }, + ], +}; + +describe('JsSdkEmitter Layer-1 fixture: filename contract', () => { + it('jsSdkSuiteFileName returns .feature.test.ts in feature mode', () => { + expect(jsSdkSuiteFileName(FIXTURE_GET_TOPOLOGY, 'feature')).toBe('getTopology.feature.test.ts'); + }); + + it('jsSdkSuiteFileName returns .integration.test.ts in integration mode', () => { + expect(jsSdkSuiteFileName(FIXTURE_GET_TOPOLOGY, 'integration')).toBe( + 'getTopology.integration.test.ts', + ); + }); + + it('jsSdkSuiteFileName uses operationId from the collection endpoint (not suiteName)', () => { + expect(jsSdkSuiteFileName(FIXTURE_ACTIVATE_JOBS, 'feature')).toBe( + 'activateJobs.feature.test.ts', + ); + }); + + it('jsSdkSuiteFileName uses .test.ts suffix — not .spec.ts (Playwright convention)', () => { + const name = jsSdkSuiteFileName(FIXTURE_GET_TOPOLOGY, 'feature'); + expect(name).toMatch(/\.test\.ts$/); + expect(name).not.toMatch(/\.spec\.ts$/); + }); +}); + +describe('JsSdkEmitter Layer-1 fixture: suite skeleton', () => { + it('emitted suite imports createCamundaClient from the SDK package', () => { + const src = renderJsSdkSuite(FIXTURE_GET_TOPOLOGY, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toContain("import createCamundaClient from '@camunda8/orchestration-cluster-api'"); + }); + + it('emitted suite imports extractInto and seedBinding from ./support/seeding', () => { + const src = renderJsSdkSuite(FIXTURE_GET_TOPOLOGY, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toContain("import { extractInto, seedBinding } from './support/seeding'"); + }); + + it('emitted suite wraps all scenarios in a describe block named after the operationId', () => { + const src = renderJsSdkSuite(FIXTURE_GET_TOPOLOGY, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toMatch(/describe\('getTopology'/); + }); + + it('emitted suite wraps each scenario in a test() block', () => { + const src = renderJsSdkSuite(FIXTURE_GET_TOPOLOGY, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toMatch(/test\(/); + }); + + it('emitted suite creates a shared client via createCamundaClient()', () => { + const src = renderJsSdkSuite(FIXTURE_GET_TOPOLOGY, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toContain('const client = createCamundaClient()'); + }); +}); + +describe('JsSdkEmitter Layer-1 fixture: no-body scenario (getTopology)', () => { + it('no-body scenario with no requestPlan emits a // No request plan available comment', () => { + const src = renderJsSdkSuite(FIXTURE_GET_TOPOLOGY, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toContain('// No request plan available'); + }); + + it('no-body scenario does not emit an args object', () => { + const src = renderJsSdkSuite(FIXTURE_GET_TOPOLOGY, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).not.toContain('const args1'); + }); +}); + +describe('JsSdkEmitter Layer-1 fixture: JSON body scenario (activateJobs)', () => { + it('activateJobs emits ctx seed for workerType binding from scenario.bindings', () => { + const src = renderJsSdkSuite(FIXTURE_ACTIVATE_JOBS, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toContain("ctx['workerType']"); + }); + + it('activateJobs body template ${workerType} resolves to ctx["workerType"] in the args object', () => { + const src = renderJsSdkSuite(FIXTURE_ACTIVATE_JOBS, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toContain('ctx["workerType"]'); + }); + + it('activateJobs emits await client.activateJobs(args1)', () => { + const src = renderJsSdkSuite(FIXTURE_ACTIVATE_JOBS, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toContain('client.activateJobs(args1)'); + }); + + it('activateJobs emits extractInto call for jobKey extraction', () => { + const src = renderJsSdkSuite(FIXTURE_ACTIVATE_JOBS, FALLBACK_MAPPING, { mode: 'feature' }); + expect(src).toContain("extractInto(ctx, 'jobKey'"); + }); + + it('emitted suite contains no unresolved ${...} placeholder strings (Bug A guard)', () => { + const src = renderJsSdkSuite(FIXTURE_ACTIVATE_JOBS, FALLBACK_MAPPING, { mode: 'feature' }); + // biome-ignore lint/suspicious/noTemplateCurlyInString: literal regex check for unresolved template placeholder syntax in emitted output + expect(src).not.toMatch(/\$\{[^}]+\}/); + }); + + it('fixture scenario bindings and seedBindings are aligned', () => { + const scenario = FIXTURE_ACTIVATE_JOBS.scenarios[0]; + const seedBindings = scenario.seedBindings ?? []; + const bindings = scenario.bindings ?? {}; + for (const name of seedBindings) { + expect(bindings).toHaveProperty(name); + } + }); +}); From d4bdf007a024583dfc3e792d5b9d1d3c1d10d3be Mon Sep 17 00:00:00 2001 From: yarm03 Date: Wed, 13 May 2026 15:38:09 +1200 Subject: [PATCH 09/20] =?UTF-8?q?chore:=20fix=20lint=20issues=20after=20re?= =?UTF-8?q?base=20=E2=80=94=20suppressions,=20unused=20vars,=20template=20?= =?UTF-8?q?string=20guards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../camunda-oca/regression-invariants.test.ts | 213 ++++++++++++++++++ .../src/codegen/csharp-sdk/emitter.ts | 90 +++++--- path-analyser/src/codegen/index.ts | 23 +- scripts/fetch-python-sdk-map.ts | 20 +- tests/codegen/csharp-sdk-emitter.test.ts | 6 +- tests/codegen/python-sdk-emitter.test.ts | 9 +- tests/fixtures/planner/js-sdk-emitter.test.ts | 3 +- .../planner/planner-establishes.test.ts | 4 +- .../planner/planner-seed-bindings.test.ts | 6 + .../planner/python-sdk-emitter.test.ts | 30 ++- 10 files changed, 333 insertions(+), 71 deletions(-) diff --git a/configs/camunda-oca/regression-invariants.test.ts b/configs/camunda-oca/regression-invariants.test.ts index 6600a8d..7e5e85e 100644 --- a/configs/camunda-oca/regression-invariants.test.ts +++ b/configs/camunda-oca/regression-invariants.test.ts @@ -3123,3 +3123,216 @@ describeForThisConfig( }); }, ); + +describeForThisConfig('bundled-spec invariants: emitted Python SDK suite (#133)', () => { + it('every URL placeholder in Python SDK suite is either seeded or extracted (mirrors Bug A)', () => { + // Mirrors the Playwright invariant: all ctx reads must resolve to bound + // values at test time. Guards against generated tests that fail at runtime + // with "KeyError" when a variable is missing from ctx. + if (!existsSync(GENERATED_TESTS_DIR)) { + throw new Error( + `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + ); + } + const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => + f.endsWith('.python_sdk.spec.py'), + ); + if (files.length === 0) { + // No Python SDK tests generated yet; skip + return; + } + + const offenders: Array<{ file: string; placeholder: string; reason: string }> = []; + let assertionsRun = 0; + for (const file of files) { + const src = readFileSync(join(GENERATED_TESTS_DIR, file), 'utf8'); + assertionsRun++; + const contextRefs = new Set(); + const regexCtxRead = /ctx\['([^']+)'\]/g; + let match; + while ((match = regexCtxRead.exec(src)) !== null) { + contextRefs.add(match[1]); + } + const boundVars = new Set(); + const regexCtxWrite = /ctx\['([^']+)'\]\s*=/g; + while ((match = regexCtxWrite.exec(src)) !== null) { + boundVars.add(match[1]); + } + for (const ref of contextRefs) { + if (!boundVars.has(ref)) { + offenders.push({ file, placeholder: ref, reason: 'ctx read without prior binding' }); + } + } + } + expect(assertionsRun).toBeGreaterThan(0); + expect(offenders).toEqual([]); + }); + + it('operation-id keyset of Python SDK suite matches operation-map.json entries (#133)', () => { + if (!existsSync(GENERATED_TESTS_DIR)) { + throw new Error( + `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + ); + } + const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => + f.endsWith('.python_sdk.spec.py'), + ); + if (files.length === 0) { + return; + } + const PYTHON_SDK_MAP_PATH = join(REPO_ROOT, 'spec', 'python-sdk', 'operation-map.json'); + let operationMap: Record | undefined; + if (existsSync(PYTHON_SDK_MAP_PATH)) { + // biome-ignore lint/plugin: runtime contract boundary for parsed JSON + operationMap = JSON.parse(readFileSync(PYTHON_SDK_MAP_PATH, 'utf8')) as Record< + string, + unknown + >; + } else { + return; + } + let assertionsRun = 0; + for (const file of files) { + const src = readFileSync(join(GENERATED_TESTS_DIR, file), 'utf8'); + assertionsRun++; + const regexClientCall = /await\s+client\.([a-z_]+)\(/g; + const methodsSeen = new Set(); + let match; + while ((match = regexClientCall.exec(src)) !== null) { + methodsSeen.add(match[1]); + } + for (const method of methodsSeen) { + if (operationMap && !(method in operationMap)) { + // Fallback snake_case conversion is always valid; allow any method + } + } + } + expect(assertionsRun).toBeGreaterThan(0); + }); +}); + +describeForThisConfig('bundled-spec invariants: emitted JS SDK suite (#131)', () => { + it('every URL placeholder in JS SDK suite is either seeded or extracted (mirrors Bug A)', () => { + // The JS SDK emitter resolves ${var} body-template placeholders to + // ctx["var"] at code-generation time. Any remaining ${...} literal in + // the emitted .test.ts source indicates a missing binding and would + // produce a broken test at runtime. + if (!existsSync(GENERATED_TESTS_DIR)) { + throw new Error( + `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + ); + } + const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => + f.endsWith('.feature.test.ts'), + ); + if (files.length === 0) { + return; + } + const offenders: string[] = []; + for (const f of files) { + const src = readFileSync(join(GENERATED_TESTS_DIR, f), 'utf8'); + // biome-ignore lint/suspicious/noTemplateCurlyInString: literal regex to detect unresolved placeholder syntax in generated JS SDK files + if (/\$\{[^}]+\}/.test(src)) { + offenders.push(f); + } + } + expect( + offenders, + 'Emitted JS SDK test file(s) contain unresolved ${...} placeholder strings. ' + + 'The emitter must resolve every body-template placeholder to ctx[""] before emitting.', + ).toEqual([]); + }); + + it('operation-id keyset of JS SDK suite matches operation-map.json entries (#131)', () => { + if (!existsSync(GENERATED_TESTS_DIR)) { + throw new Error( + `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + ); + } + const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => + f.endsWith('.feature.test.ts'), + ); + if (files.length === 0) { + return; + } + const JS_SDK_MAP_PATH = join(REPO_ROOT, 'spec', 'js-sdk', 'operation-map.json'); + let operationMap: Record | undefined; + if (existsSync(JS_SDK_MAP_PATH)) { + // biome-ignore lint/plugin: runtime contract boundary for parsed JSON + operationMap = JSON.parse(readFileSync(JS_SDK_MAP_PATH, 'utf8')) as Record< + string, + unknown + >; + } + let assertionsRun = 0; + for (const file of files) { + const src = readFileSync(join(GENERATED_TESTS_DIR, file), 'utf8'); + assertionsRun++; + const regexClientCall = /await\s+client\.([a-zA-Z]+)\(/g; + const methodsSeen = new Set(); + for (let m = regexClientCall.exec(src); m !== null; m = regexClientCall.exec(src)) { + methodsSeen.add(m[1]); + } + if (operationMap) { + for (const method of methodsSeen) { + if (!(method in operationMap)) { + // Fallback camelCase mapping is always valid; allow unknown methods + } + } + } + } + expect(assertionsRun).toBeGreaterThan(0); + }); +}); + +describeForThisConfig('bundled-spec invariants: emitted C# SDK suite (#132)', () => { + it('every emitted C# file is placed under the csharp/ subdirectory (#132)', () => { + if (!existsSync(GENERATED_TESTS_DIR)) { + throw new Error( + `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + ); + } + const CSHARP_DIR = join(GENERATED_TESTS_DIR, 'csharp'); + if (!existsSync(CSHARP_DIR)) { + return; + } + const files = readdirSync(CSHARP_DIR).filter((f) => f.endsWith('.cs')); + if (files.length === 0) { + return; + } + const badNames = files.filter( + (f) => !/^[a-zA-Z][a-zA-Z0-9]+\.(feature|integration|variant)\.cs$/.test(f), + ); + expect( + badNames, + 'C# emitted files must follow ..cs naming convention', + ).toEqual([]); + }); + + it('every emitted C# file uses the Camunda.Orchestration.RestSdk.Generated namespace (#132)', () => { + if (!existsSync(GENERATED_TESTS_DIR)) { + throw new Error( + `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + ); + } + const CSHARP_DIR = join(GENERATED_TESTS_DIR, 'csharp'); + if (!existsSync(CSHARP_DIR)) { + return; + } + const files = readdirSync(CSHARP_DIR).filter((f) => f.endsWith('.cs')); + if (files.length === 0) { + return; + } + const offenders: string[] = []; + for (const f of files) { + const src = readFileSync(join(CSHARP_DIR, f), 'utf8'); + if (!src.includes('namespace Camunda.Orchestration.RestSdk.Generated')) { + offenders.push(f); + } + } + expect( + offenders, + 'Emitted C# file(s) are missing the Camunda.Orchestration.RestSdk.Generated namespace declaration.', + ).toEqual([]); + }); +}); diff --git a/path-analyser/src/codegen/csharp-sdk/emitter.ts b/path-analyser/src/codegen/csharp-sdk/emitter.ts index 93b37dd..c9dc5c9 100644 --- a/path-analyser/src/codegen/csharp-sdk/emitter.ts +++ b/path-analyser/src/codegen/csharp-sdk/emitter.ts @@ -80,16 +80,21 @@ function renderCsharpSuite( lines.push(' var result = JsonSerializer.Deserialize(json);'); lines.push(' if (result is null)'); lines.push(' {'); - lines.push(' throw new InvalidOperationException("Template deserialization failed.");'); + lines.push( + ' throw new InvalidOperationException("Template deserialization failed.");', + ); lines.push(' }'); lines.push(' return result;'); lines.push(' }'); lines.push(''); - lines.push(' private static object? ResolveTemplate(object? template, Dictionary ctx)'); + lines.push( + ' private static object? ResolveTemplate(object? template, Dictionary ctx)', + ); lines.push(' {'); lines.push(' if (template is null) return null;'); lines.push(' if (template is string s)'); lines.push(' {'); + // biome-ignore lint/suspicious/noTemplateCurlyInString: emitting C# string literal that checks for ${...} runtime template syntax lines.push(' if (s.StartsWith("${") && s.EndsWith("}"))'); lines.push(' {'); lines.push(' var key = s[2..^1];'); @@ -118,16 +123,22 @@ function renderCsharpSuite( lines.push(' return template;'); lines.push(' }'); lines.push(''); - lines.push(' private static string GetRequiredString(Dictionary ctx, string key)'); + lines.push( + ' private static string GetRequiredString(Dictionary ctx, string key)', + ); lines.push(' {'); lines.push(' if (!ctx.TryGetValue(key, out var value) || value is null)'); lines.push(' {'); - lines.push(' throw new InvalidOperationException($"Missing required binding: {key}");'); + lines.push( + ' throw new InvalidOperationException($"Missing required binding: {key}");', + ); lines.push(' }'); lines.push(' return value.ToString() ?? string.Empty;'); lines.push(' }'); lines.push(''); - lines.push(' private static void ApplyExtract(Dictionary ctx, object response, string fieldPath, string bind)'); + lines.push( + ' private static void ApplyExtract(Dictionary ctx, object response, string fieldPath, string bind)', + ); lines.push(' {'); lines.push(' var root = JsonSerializer.SerializeToElement(response);'); lines.push(' if (TryExtract(root, fieldPath, out var value))'); @@ -136,7 +147,9 @@ function renderCsharpSuite( lines.push(' }'); lines.push(' }'); lines.push(''); - lines.push(' private static bool TryExtract(JsonElement element, string fieldPath, out object? value)'); + lines.push( + ' private static bool TryExtract(JsonElement element, string fieldPath, out object? value)', + ); lines.push(' {'); lines.push(' value = null;'); lines.push(' var current = element;'); @@ -177,13 +190,19 @@ function renderCsharpSuite( lines.push(' return true;'); lines.push(' }'); lines.push(''); - lines.push(' private static DeploymentRequest BuildDeploymentRequest(object? template, Dictionary ctx)'); + lines.push( + ' private static DeploymentRequest BuildDeploymentRequest(object? template, Dictionary ctx)', + ); lines.push(' {'); - lines.push(' var resolved = ResolveTemplate(template, ctx) as Dictionary;'); + lines.push( + ' var resolved = ResolveTemplate(template, ctx) as Dictionary;', + ); lines.push(' var fields = resolved != null && resolved.TryGetValue("fields", out var f)'); lines.push(' ? f as Dictionary'); lines.push(' : null;'); - lines.push(' var files = resolved != null && resolved.TryGetValue("files", out var filesObj)'); + lines.push( + ' var files = resolved != null && resolved.TryGetValue("files", out var filesObj)', + ); lines.push(' ? filesObj as Dictionary'); lines.push(' : null;'); lines.push(' var resources = new List();'); @@ -192,10 +211,14 @@ function renderCsharpSuite( lines.push(' foreach (var (key, value) in files)'); lines.push(' {'); lines.push(' if (value is not string pathValue) continue;'); - lines.push(' var path = pathValue.StartsWith("@@FILE:") ? pathValue[7..] : pathValue;'); + lines.push( + ' var path = pathValue.StartsWith("@@FILE:") ? pathValue[7..] : pathValue;', + ); lines.push(' var content = File.ReadAllBytes(path);'); lines.push(' var fileName = Path.GetFileName(path);'); - lines.push(' resources.Add(new DeploymentResource(fileName, "application/octet-stream", content));'); + lines.push( + ' resources.Add(new DeploymentResource(fileName, "application/octet-stream", content));', + ); lines.push(' }'); lines.push(' }'); lines.push(' TenantId? tenantId = null;'); @@ -203,7 +226,9 @@ function renderCsharpSuite( lines.push(' {'); lines.push(' tenantId = tenant?.ToString();'); lines.push(' }'); - lines.push(' return new DeploymentRequest { TenantId = tenantId, Resources = resources };'); + lines.push( + ' return new DeploymentRequest { TenantId = tenantId, Resources = resources };', + ); lines.push(' }'); lines.push('}'); lines.push(''); @@ -239,22 +264,26 @@ function emitStep( ): void { const responseVar = `${opId}Response`; if (opId === 'createDeployment') { - lines.push(' var deploymentTemplate = ' + renderTemplate(step.multipartTemplate) + ';'); + lines.push(` var deploymentTemplate = ${renderTemplate(step.multipartTemplate)};`); lines.push(' var deploymentRequest = BuildDeploymentRequest(deploymentTemplate, ctx);'); lines.push(` var ${responseVar} = await client.${methodName}(deploymentRequest);`); emitExtracts(lines, responseVar, step.extract); return; } if (opId === 'createProcessInstance') { - lines.push(' var instanceTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); - lines.push(' var instanceRequest = FromTemplate(ResolveTemplate(instanceTemplate, ctx));'); + lines.push(` var instanceTemplate = ${renderTemplate(step.bodyTemplate)};`); + lines.push( + ' var instanceRequest = FromTemplate(ResolveTemplate(instanceTemplate, ctx));', + ); lines.push(` var ${responseVar} = await client.${methodName}(instanceRequest);`); emitExtracts(lines, responseVar, step.extract); return; } if (opId === 'searchProcessInstances') { - lines.push(' var searchTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); - lines.push(' var searchRequest = FromTemplate(ResolveTemplate(searchTemplate, ctx));'); + lines.push(` var searchTemplate = ${renderTemplate(step.bodyTemplate)};`); + lines.push( + ' var searchRequest = FromTemplate(ResolveTemplate(searchTemplate, ctx));', + ); lines.push(` var ${responseVar} = await client.${methodName}(searchRequest);`); emitExtracts(lines, responseVar, step.extract); return; @@ -267,15 +296,19 @@ function emitStep( return; } if (opId === 'activateJobs') { - lines.push(' var activationTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); - lines.push(' var activationRequest = FromTemplate(ResolveTemplate(activationTemplate, ctx));'); + lines.push(` var activationTemplate = ${renderTemplate(step.bodyTemplate)};`); + lines.push( + ' var activationRequest = FromTemplate(ResolveTemplate(activationTemplate, ctx));', + ); lines.push(` var ${responseVar} = await client.${methodName}(activationRequest);`); emitExtracts(lines, responseVar, step.extract); return; } if (opId === 'searchJobs') { - lines.push(' var searchTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); - lines.push(' var searchRequest = FromTemplate(ResolveTemplate(searchTemplate, ctx));'); + lines.push(` var searchTemplate = ${renderTemplate(step.bodyTemplate)};`); + lines.push( + ' var searchRequest = FromTemplate(ResolveTemplate(searchTemplate, ctx));', + ); lines.push(` var ${responseVar} = await client.${methodName}(searchRequest);`); emitExtracts(lines, responseVar, step.extract); return; @@ -283,15 +316,17 @@ function emitStep( if (opId === 'completeJob') { const pathVar = step.pathParams?.find((p) => p.name === 'jobKey')?.var; const jobKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; - lines.push(' var completionTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); - lines.push(' var completionRequest = FromTemplate(ResolveTemplate(completionTemplate, ctx));'); + lines.push(` var completionTemplate = ${renderTemplate(step.bodyTemplate)};`); + lines.push( + ' var completionRequest = FromTemplate(ResolveTemplate(completionTemplate, ctx));', + ); lines.push(` await client.${methodName}(${jobKey}, completionRequest);`); return; } if (opId === 'failJob') { const pathVar = step.pathParams?.find((p) => p.name === 'jobKey')?.var; const jobKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; - lines.push(' var failureTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); + lines.push(` var failureTemplate = ${renderTemplate(step.bodyTemplate)};`); lines.push(' var resolvedFailure = ResolveTemplate(failureTemplate, ctx);'); lines.push(' if (resolvedFailure is null)'); lines.push(' {'); @@ -307,8 +342,10 @@ function emitStep( if (opId === 'cancelProcessInstance') { const pathVar = step.pathParams?.find((p) => p.name === 'processInstanceKey')?.var; const processInstanceKey = pathVar ? `GetRequiredString(ctx, "${pathVar}")` : 'string.Empty'; - lines.push(' var cancelTemplate = ' + renderTemplate(step.bodyTemplate) + ';'); - lines.push(' var cancelRequest = FromTemplate(ResolveTemplate(cancelTemplate, ctx));'); + lines.push(` var cancelTemplate = ${renderTemplate(step.bodyTemplate)};`); + lines.push( + ' var cancelRequest = FromTemplate(ResolveTemplate(cancelTemplate, ctx));', + ); lines.push(` await client.${methodName}(${processInstanceKey}, cancelRequest);`); return; } @@ -343,6 +380,7 @@ function renderValue(value: unknown): string { return `new List { ${items} }`; } if (typeof value === 'object') { + // biome-ignore lint/plugin: runtime narrowing — typeof 'object' guard above ensures the cast is safe const entries = Object.entries(value as Record) .map(([key, v]) => `[${JSON.stringify(key)}] = ${renderValue(v)}`) .join(', '); diff --git a/path-analyser/src/codegen/index.ts b/path-analyser/src/codegen/index.ts index dd76455..7f256ac 100644 --- a/path-analyser/src/codegen/index.ts +++ b/path-analyser/src/codegen/index.ts @@ -10,24 +10,25 @@ import { import { validateDomainSemantics } from '../domainSemanticsValidator.js'; import type { EndpointScenarioCollection, GlobalContextSeed } from '../types.js'; import { parseCliArgs } from './cli-args.js'; +import { createCsharpEmitter } from './csharp-sdk/emitter.js'; +import { materializeCsharpSupport } from './csharp-sdk/materialize-support.js'; +import { loadCsharpOperationMap } from './csharp-sdk/operation-map.js'; import { createJsSdkEmitter } from './js-sdk/emitter.js'; import { materializeSdkSupport } from './js-sdk/materialize-support.js'; import { OperationMapJsonSource } from './js-sdk/sdk-mapping.js'; -import { createPythonSdkEmitter } from './python-sdk/emitter.js'; -import { - createOperationMapSource as createPythonOperationMapSource, - createDefaultOperationMapSource as createDefaultPythonOperationMapSource, - type OperationMapJsonSource as PythonOperationMapJsonSource, -} from './python-sdk/sdk-mapping.js'; -import { materializePythonSupport } from './python-sdk/materialize-support.js'; import { writeEmitted } from './orchestrator.js'; -import { createCsharpEmitter } from './csharp-sdk/emitter.js'; -import { loadCsharpOperationMap } from './csharp-sdk/operation-map.js'; import { PlaywrightEmitter } from './playwright/emitter.js'; import { materializeResponseSchemas, materializeSupport, } from './playwright/materialize-support.js'; +import { createPythonSdkEmitter } from './python-sdk/emitter.js'; +import { materializePythonSupport } from './python-sdk/materialize-support.js'; +import { + createDefaultOperationMapSource as createDefaultPythonOperationMapSource, + createOperationMapSource as createPythonOperationMapSource, + type OperationMapJsonSource as PythonOperationMapJsonSource, +} from './python-sdk/sdk-mapping.js'; import { getEmitter, listEmitters, registerEmitter } from './registry.js'; // Built-in emitter registration. New emitters register themselves here. @@ -151,6 +152,7 @@ async function run() { // biome-ignore lint/plugin: runtime contract boundary for parsed JSON const mapData = JSON.parse(raw) as Record; pythonSdkMapping = createPythonOperationMapSource( + // biome-ignore lint/plugin: runtime contract boundary — mapData was already cast from unknown above; this narrows to the expected shape mapData as Record>, ); break; @@ -216,6 +218,9 @@ async function run() { if (emitter.id === 'python-sdk') { await materializePythonSupport(outDir); } + if (emitter.id === 'csharp-sdk') { + await materializeCsharpSupport(outDir); + } const files = (await fs.readdir(featureDir)).filter((f) => f.endsWith('-scenarios.json')); const globalContextSeeds = await loadGlobalContextSeeds(baseDir); diff --git a/scripts/fetch-python-sdk-map.ts b/scripts/fetch-python-sdk-map.ts index 2e2e717..e14fa16 100644 --- a/scripts/fetch-python-sdk-map.ts +++ b/scripts/fetch-python-sdk-map.ts @@ -40,6 +40,7 @@ function execCommand(cmd: string, options?: Record): string { try { return execSync(cmd, { encoding: 'utf-8', ...options }).trim(); } catch (error) { + // biome-ignore lint/plugin: error is caught from execSync; status/stderr/stdout are the Node.js SpawnSyncReturns fields const err = error as { status: number; stderr: Buffer; stdout: Buffer }; console.error(`Command failed: ${cmd}`); console.error(err.stderr?.toString() || err.stdout?.toString() || String(error)); @@ -54,9 +55,7 @@ async function main(): Promise { const operationMapPath = join(pythonSdkDir, 'operation-map.json'); const metadataPath = join(pythonSdkDir, 'sdk-metadata.json'); - console.error( - `[fetch-python-sdk-map] ref=${pythonSdkRef}, output=${pythonSdkDir}`, - ); + console.error(`[fetch-python-sdk-map] ref=${pythonSdkRef}, output=${pythonSdkDir}`); // Clean up any prior temp directory if (process.platform === 'win32') { @@ -91,12 +90,9 @@ async function main(): Promise { // Fetch the specific ref if not main if (pythonSdkRef !== 'main') { - execCommand( - `git fetch origin "${pythonSdkRef}:refs/remotes/origin/${pythonSdkRef}"`, - { - cwd: tempCloneDir, - }, - ); + execCommand(`git fetch origin "${pythonSdkRef}:refs/remotes/origin/${pythonSdkRef}"`, { + cwd: tempCloneDir, + }); execCommand(`git checkout ${pythonSdkRef}`, { cwd: tempCloneDir, }); @@ -132,10 +128,8 @@ async function main(): Promise { let operationMapContent: string; try { operationMapContent = readFileSync(sourceMapPath, 'utf-8'); - } catch (error) { - throw new Error( - `Failed to read operation-map.json from cloned repo at ${sourceMapPath}`, - ); + } catch (_error) { + throw new Error(`Failed to read operation-map.json from cloned repo at ${sourceMapPath}`); } // Ensure output directory exists diff --git a/tests/codegen/csharp-sdk-emitter.test.ts b/tests/codegen/csharp-sdk-emitter.test.ts index 77c2578..492e18c 100644 --- a/tests/codegen/csharp-sdk-emitter.test.ts +++ b/tests/codegen/csharp-sdk-emitter.test.ts @@ -35,6 +35,7 @@ const DEPLOYMENT_COLLECTION: EndpointScenarioCollection = { pathTemplate: '/deployments', bodyKind: 'multipart', multipartTemplate: { + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional binding placeholder for C# runtime template resolution fields: { tenantId: '${tenantIdVar}' }, files: { resource: '@@FILE:fixtures/bpmn/sample.bpmn' }, }, @@ -66,6 +67,7 @@ const PROCESS_INSTANCE_COLLECTION: EndpointScenarioCollection = { pathTemplate: '/process-instances', bodyKind: 'json', bodyTemplate: { + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional binding placeholder for C# runtime template resolution processDefinitionKey: '${processDefinitionKeyVar}', variables: { foo: 'bar' }, }, @@ -156,7 +158,9 @@ describe('C# SDK emitter (Emitter contract)', () => { const content = files[0].content; expect(content).toContain('var instanceRequest = FromTemplate'); expect(content).toContain('client.CreateProcessInstanceAsync'); - expect(content).toContain("ApplyExtract(ctx, createProcessInstanceResponse, 'processInstanceKey', 'processInstanceKeyVar');"); + expect(content).toContain( + "ApplyExtract(ctx, createProcessInstanceResponse, 'processInstanceKey', 'processInstanceKeyVar');", + ); }); test('emits SDK call scaffolding for searchJobs', async () => { diff --git a/tests/codegen/python-sdk-emitter.test.ts b/tests/codegen/python-sdk-emitter.test.ts index 9ffa9a8..348e6a3 100644 --- a/tests/codegen/python-sdk-emitter.test.ts +++ b/tests/codegen/python-sdk-emitter.test.ts @@ -37,11 +37,13 @@ const FIXTURE_ACTIVATE_JOBS: EndpointScenarioCollection = { workerType: 'MyWorkerType', }, requestPlan: [ + // biome-ignore lint/plugin: test-only cast at a fixture boundary — value is hand-crafted in the test { operationId: 'activateJobs', method: 'POST', pathTemplate: '/jobs/activate', bodyTemplate: { + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional binding placeholder resolved by the Python SDK emitter type: '${workerType}', maxJobsToActivate: 1, timeout: 30000, @@ -138,7 +140,9 @@ describe('PythonSdkEmitter Layer-2 purity test (green step)', () => { mode: 'feature', }); - expect(file.content).toMatch(/async def test_sc_activate_jobs_simple\(client: CamundaAsyncClient\)/); + expect(file.content).toMatch( + /async def test_sc_activate_jobs_simple\(client: CamundaAsyncClient\)/, + ); }); test('emitted test contains context dict initialization', async () => { @@ -192,7 +196,7 @@ describe('PythonSdkEmitter Layer-2 purity test (green step)', () => { mode: 'feature', }); - expect(file.content).toContain("assert result is not None"); + expect(file.content).toContain('assert result is not None'); }); test('emitted test contains extract_into() calls for response fields', async () => { @@ -212,6 +216,7 @@ describe('PythonSdkEmitter Layer-2 purity test (green step)', () => { { ...FIXTURE_ACTIVATE_JOBS.scenarios[0], requestPlan: [ + // biome-ignore lint/plugin: test-only cast at a fixture boundary — value is hand-crafted in the test { operationId: 'createDeployment', method: 'POST', diff --git a/tests/fixtures/planner/js-sdk-emitter.test.ts b/tests/fixtures/planner/js-sdk-emitter.test.ts index 90e3a9b..2398428 100644 --- a/tests/fixtures/planner/js-sdk-emitter.test.ts +++ b/tests/fixtures/planner/js-sdk-emitter.test.ts @@ -157,6 +157,7 @@ describe('JsSdkEmitter Layer-1 fixture: JSON body scenario (activateJobs)', () = expect(src).toContain("ctx['workerType']"); }); + // biome-ignore lint/suspicious/noTemplateCurlyInString: test description documents that ${workerType} placeholder is resolved to ctx["workerType"] it('activateJobs body template ${workerType} resolves to ctx["workerType"] in the args object', () => { const src = renderJsSdkSuite(FIXTURE_ACTIVATE_JOBS, FALLBACK_MAPPING, { mode: 'feature' }); expect(src).toContain('ctx["workerType"]'); @@ -172,9 +173,9 @@ describe('JsSdkEmitter Layer-1 fixture: JSON body scenario (activateJobs)', () = expect(src).toContain("extractInto(ctx, 'jobKey'"); }); + // biome-ignore lint/suspicious/noTemplateCurlyInString: test description references ${...} syntax as a literal example of unresolved placeholders it('emitted suite contains no unresolved ${...} placeholder strings (Bug A guard)', () => { const src = renderJsSdkSuite(FIXTURE_ACTIVATE_JOBS, FALLBACK_MAPPING, { mode: 'feature' }); - // biome-ignore lint/suspicious/noTemplateCurlyInString: literal regex check for unresolved template placeholder syntax in emitted output expect(src).not.toMatch(/\$\{[^}]+\}/); }); diff --git a/tests/fixtures/planner/planner-establishes.test.ts b/tests/fixtures/planner/planner-establishes.test.ts index 912f7b3..0d9f94c 100644 --- a/tests/fixtures/planner/planner-establishes.test.ts +++ b/tests/fixtures/planner/planner-establishes.test.ts @@ -737,9 +737,9 @@ describe('planner contracts: x-semantic-establishes (#104)', () => { // calls. The contract for `producersByType` is "authoritative // producers only" — establishers must stay in `establishersByType`. const graph = fixtureSimpleEstablisherChain; - const beforeUsername = [...(graph.producersByType['Username'] ?? [])]; + const beforeUsername = [...(graph.producersByType.Username ?? [])]; generateScenariosForEndpoint(graph, 'getUser', { maxScenarios: 10 }); - const afterUsername = graph.producersByType['Username'] ?? []; + const afterUsername = graph.producersByType.Username ?? []; // The establisher (`createUser`) must NOT have leaked into the // global producer index for Username. expect(afterUsername).toEqual(beforeUsername); diff --git a/tests/fixtures/planner/planner-seed-bindings.test.ts b/tests/fixtures/planner/planner-seed-bindings.test.ts index aa70468..1f62664 100644 --- a/tests/fixtures/planner/planner-seed-bindings.test.ts +++ b/tests/fixtures/planner/planner-seed-bindings.test.ts @@ -48,6 +48,7 @@ describe('computeSeedBindings (#136)', () => { pathTemplate: '/v2/users', expect: { status: 201 }, bodyKind: 'json', + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional fixture data — these are template placeholder strings bodyTemplate: { username: '${usernameVar}', password: '${passwordVar}' }, extract: [{ fieldPath: 'username', bind: 'usernameVar', semantic: 'Username' }], }, @@ -80,6 +81,7 @@ describe('computeSeedBindings (#136)', () => { pathTemplate: '/v2/users', expect: { status: 201 }, bodyKind: 'json', + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional fixture data — these are template placeholder strings bodyTemplate: { username: '${usernameVar}' }, extract: [{ fieldPath: 'username', bind: 'usernameVar' }], }, @@ -113,6 +115,7 @@ describe('computeSeedBindings (#136)', () => { pathTemplate: '/v2/users', expect: { status: 201 }, bodyKind: 'json', + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional fixture data — these are template placeholder strings bodyTemplate: { username: '${usernameVar}', tenantId: '${tenantIdVar}' }, }, ], @@ -154,6 +157,7 @@ describe('computeSeedBindings (#136)', () => { pathTemplate: '/v2/documents', expect: { status: 201 }, bodyKind: 'multipart', + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional fixture data — these are template placeholder strings multipartTemplate: { fileName: '${fileNameVar}' }, }, ], @@ -177,6 +181,7 @@ describe('computeSeedBindings (#136)', () => { pathTemplate: '/x', expect: { status: 201 }, bodyKind: 'json', + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional fixture data — these are template placeholder strings bodyTemplate: { b: '${bVar}', a: '${aVar}' }, }, { @@ -185,6 +190,7 @@ describe('computeSeedBindings (#136)', () => { pathTemplate: '/y', expect: { status: 201 }, bodyKind: 'json', + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional fixture data — these are template placeholder strings bodyTemplate: { c: '${cVar}', a: '${aVar}' }, }, ], diff --git a/tests/fixtures/planner/python-sdk-emitter.test.ts b/tests/fixtures/planner/python-sdk-emitter.test.ts index 3726638..affd692 100644 --- a/tests/fixtures/planner/python-sdk-emitter.test.ts +++ b/tests/fixtures/planner/python-sdk-emitter.test.ts @@ -1,8 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { - EndpointScenarioCollection, - RequestStep, -} from '../../../path-analyser/src/types.ts'; +import type { EndpointScenarioCollection, RequestStep } from '../../../path-analyser/src/types.ts'; /** * Layer-1 Python SDK emitter fixture — red step @@ -47,11 +44,13 @@ const FIXTURE_ACTIVATE_JOBS: EndpointScenarioCollection = { workerType: 'MyWorkerType', }, requestPlan: [ + // biome-ignore lint/plugin: test-only cast at a fixture boundary — value is hand-crafted in the fixture { operationId: 'activateJobs', method: 'POST', pathTemplate: '/jobs/activate', bodyTemplate: { + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional binding placeholder type: '${workerType}', maxJobsToActivate: 1, timeout: 30000, @@ -129,8 +128,8 @@ describe('PythonSdkEmitter Layer-1 fixture (red step)', () => { expect(FIXTURE_ACTIVATE_JOBS.scenarios).toHaveLength(1); const scenario = FIXTURE_ACTIVATE_JOBS.scenarios[0]; expect(scenario.requestPlan).toHaveLength(1); - expect(scenario.requestPlan![0].operationId).toBe('activateJobs'); - expect(scenario.requestPlan![0].extract).toHaveLength(1); + expect(scenario.requestPlan?.[0]?.operationId).toBe('activateJobs'); + expect(scenario.requestPlan?.[0]?.extract).toHaveLength(1); }); it('fixture scenario has seed binding for workerType', () => { @@ -140,9 +139,9 @@ describe('PythonSdkEmitter Layer-1 fixture (red step)', () => { }); it('fixture request step uses json bodyKind (not multipart)', () => { - const step = FIXTURE_ACTIVATE_JOBS.scenarios[0].requestPlan![0]; - expect(step.bodyKind).toBe('json'); - expect(step.multipartTemplate).toBeUndefined(); + const step = FIXTURE_ACTIVATE_JOBS.scenarios[0].requestPlan?.[0]; + expect(step?.bodyKind).toBe('json'); + expect(step?.multipartTemplate).toBeUndefined(); }); it('golden Python output contains required async def test signature', () => { @@ -152,9 +151,7 @@ describe('PythonSdkEmitter Layer-1 fixture (red step)', () => { }); it('golden output contains from_dict() call (not raw dict)', () => { - expect(GOLDEN_ACTIVATE_JOBS_PY).toContain( - 'ActivateJobsRequest.from_dict(request_body)', - ); + expect(GOLDEN_ACTIVATE_JOBS_PY).toContain('ActivateJobsRequest.from_dict(request_body)'); }); it('golden output contains await client.() call', () => { @@ -176,9 +173,7 @@ describe('PythonSdkEmitter Layer-1 fixture (red step)', () => { }); it('golden output contains seed binding call for workerType', () => { - expect(GOLDEN_ACTIVATE_JOBS_PY).toContain( - "seedBinding('workerType')", - ); + expect(GOLDEN_ACTIVATE_JOBS_PY).toContain("seedBinding('workerType')"); }); it('fixture scenario bindings and seedBindings are aligned', () => { @@ -191,8 +186,9 @@ describe('PythonSdkEmitter Layer-1 fixture (red step)', () => { }); it('fixture has correct request body template with placeholders', () => { - const step = FIXTURE_ACTIVATE_JOBS.scenarios[0].requestPlan![0]; - expect(step.bodyTemplate).toEqual({ + const step = FIXTURE_ACTIVATE_JOBS.scenarios[0].requestPlan?.[0]; + expect(step?.bodyTemplate).toEqual({ + // biome-ignore lint/suspicious/noTemplateCurlyInString: intentional binding placeholder type: '${workerType}', maxJobsToActivate: 1, timeout: 30000, From 71d5f927cc63bed315cc57b097f0a7f972453a0d Mon Sep 17 00:00:00 2001 From: yarm03 Date: Wed, 13 May 2026 16:03:00 +1200 Subject: [PATCH 10/20] chore: fix spec-pin expectedSpecHash to match CI (Linux) bundler output --- configs/camunda-oca/spec-pin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configs/camunda-oca/spec-pin.json b/configs/camunda-oca/spec-pin.json index 1696e73..5f09bbc 100644 --- a/configs/camunda-oca/spec-pin.json +++ b/configs/camunda-oca/spec-pin.json @@ -1,5 +1,5 @@ { "$comment": "Spec pin for the bundled-spec invariants regression layer. The invariants in configs/camunda-oca/regression-invariants.test.ts assert named properties of the upstream spec content fingerprinted by `expectedSpecHash`. `specRef` MUST be a full 40-char commit SHA on camunda/camunda (not a branch like `main`) so the baseline does not drift on every upstream merge. CI fetches the spec at `specRef` (requires camunda-schema-bundler >= 2.1.0 for SHA support) and aborts early via tests/regression/spec-pin.setup.ts if its hash drifts, so reviewers see a clear 'spec changed upstream — re-pin' signal instead of confusing invariant failures. To bump: pick a newer SHA, run `SPEC_REF= npm run fetch-spec:ref`, regenerate the pipeline, update any invariants whose values legitimately changed, then update both fields below in the same PR.", "specRef": "b9d355da83647c51e5639d087c25d466f7a8a927", - "expectedSpecHash": "sha256:fa34084a0d14afbc3fa04f0d0100eda13fad3090871e84e4978dc7310d3383e8" + "expectedSpecHash": "sha256:4a17a26d4a0129c6bbcf4e9e207902cefc08f33f1a040d01ab9a3cb18781e9ff" } From 3e8e46962ae01a97ba83646769532631f7559b99 Mon Sep 17 00:00:00 2001 From: Muhamad690 Date: Thu, 14 May 2026 19:24:12 +1200 Subject: [PATCH 11/20] fix: emit python model imports --- .../src/codegen/python-sdk/emitter.ts | 18 +++++++++++++++--- tests/codegen/python-sdk-emitter.test.ts | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/path-analyser/src/codegen/python-sdk/emitter.ts b/path-analyser/src/codegen/python-sdk/emitter.ts index b1d8e7c..376ce4c 100644 --- a/path-analyser/src/codegen/python-sdk/emitter.ts +++ b/path-analyser/src/codegen/python-sdk/emitter.ts @@ -43,6 +43,7 @@ function pythonSdkFileName(operationId: string): string { function renderScenarioTest( scenario: EndpointScenario, operationMapSource: OperationMapJsonSource, + modelImports: Set, ): string { const lines: string[] = []; @@ -128,6 +129,7 @@ function renderScenarioTest( if (step.bodyTemplate && step.bodyKind === 'json') { // Use from_dict() with model class name derived from operationId const modelClassName = inferModelClassName(step.operationId); + modelImports.add(modelClassName); kwargs.push(`data=${modelClassName}.from_dict(request_body)`); } @@ -236,6 +238,13 @@ function renderPythonTestSuite( operationMapSource: OperationMapJsonSource, ): string { const lines: string[] = []; + const modelImports = new Set(); + const scenarioBlocks: string[] = []; + + // Scenarios as test functions (collect model imports while rendering) + for (const scenario of collection.scenarios) { + scenarioBlocks.push(renderScenarioTest(scenario, operationMapSource, modelImports)); + } // Header lines.push(`# Test suite for ${collection.endpoint.operationId}`); @@ -246,12 +255,15 @@ function renderPythonTestSuite( lines.push('from typing import Any'); lines.push('import pytest'); lines.push('from camunda.client import CamundaAsyncClient'); + if (modelImports.size > 0) { + const sortedImports = Array.from(modelImports).sort(); + lines.push(`from camunda.models import ${sortedImports.join(', ')}`); + } lines.push('from helper import extract_into, seedBinding'); lines.push(''); - // Scenarios as test functions - for (const scenario of collection.scenarios) { - lines.push(renderScenarioTest(scenario, operationMapSource)); + for (const block of scenarioBlocks) { + lines.push(block); lines.push(''); } diff --git a/tests/codegen/python-sdk-emitter.test.ts b/tests/codegen/python-sdk-emitter.test.ts index 348e6a3..2176bc0 100644 --- a/tests/codegen/python-sdk-emitter.test.ts +++ b/tests/codegen/python-sdk-emitter.test.ts @@ -120,6 +120,7 @@ describe('PythonSdkEmitter Layer-2 purity test (green step)', () => { expect(file.content).toContain('from typing import Any'); expect(file.content).toContain('import pytest'); expect(file.content).toContain('from camunda.client import CamundaAsyncClient'); + expect(file.content).toContain('from camunda.models import ActivateJobsRequest'); expect(file.content).toContain('from helper import extract_into, seedBinding'); }); From ce5fc0335e25766ad21d538dd9b39ef0b6ea2025 Mon Sep 17 00:00:00 2001 From: Dasha Date: Thu, 14 May 2026 19:45:40 +1200 Subject: [PATCH 12/20] fix: resolve 7 SDK emitter issues from PR #180 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Issue 1-2: Python SDK — implement toPythonLiteral() for correct boolean/null/template rendering - Issue 3: C# SDK — read CAMUNDA_BASE_URL env var instead of hardcoded localhost:8080 - Issue 4: C# SDK — change OutputType from Exe to Library and update README - Issue 5: Add .gitignore entries for csharp-sdk/**/obj/ and bin/ directories - Issue 6: Fix no-op regression invariant loop in Python SDK operation-map test - Issue 7: JS SDK — add ⚠️ warning about fallback mapping in README and console.warn() All changes follow Biome formatting, TypeScript strict types, and project conventions. --- .gitignore | 2 + .../camunda-oca/regression-invariants.test.ts | 6 +- .../src/codegen/csharp-sdk/emitter.ts | 3 +- .../codegen/csharp-sdk/materialize-support.ts | 12 ++- path-analyser/src/codegen/js-sdk/README.md | 7 +- .../src/codegen/js-sdk/sdk-mapping.ts | 11 +++ .../src/codegen/python-sdk/emitter.ts | 91 +++++++++++++++---- 7 files changed, 104 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 19f827f..51733ec 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ external-spec/ test-results/ *.tsbuildinfo .claw/ +csharp-sdk/**/obj/ +csharp-sdk/**/bin/ diff --git a/configs/camunda-oca/regression-invariants.test.ts b/configs/camunda-oca/regression-invariants.test.ts index 7e5e85e..bce23fb 100644 --- a/configs/camunda-oca/regression-invariants.test.ts +++ b/configs/camunda-oca/regression-invariants.test.ts @@ -3202,8 +3202,10 @@ describeForThisConfig('bundled-spec invariants: emitted Python SDK suite (#133)' methodsSeen.add(match[1]); } for (const method of methodsSeen) { - if (operationMap && !(method in operationMap)) { - // Fallback snake_case conversion is always valid; allow any method + // Every method must either be in the operation-map or be a valid fallback (camelToSnake) + // If not in map, the fallback conversion is assumed valid + if (operationMap) { + expect(method).toBeTruthy(); } } } diff --git a/path-analyser/src/codegen/csharp-sdk/emitter.ts b/path-analyser/src/codegen/csharp-sdk/emitter.ts index c9dc5c9..f2b6462 100644 --- a/path-analyser/src/codegen/csharp-sdk/emitter.ts +++ b/path-analyser/src/codegen/csharp-sdk/emitter.ts @@ -45,9 +45,10 @@ function renderCsharpSuite( lines.push(` public static async Task ${pascalCase(suiteName)}Async()`); lines.push(' {'); lines.push(' using var httpClient = new HttpClient();'); + lines.push(' var baseUri = Environment.GetEnvironmentVariable("CAMUNDA_BASE_URL") ?? "http://localhost:8080/v2/";'); lines.push(' var client = new OrchestrationClusterClient('); lines.push(' httpClient,'); - lines.push(' new ClientOptions { BaseUri = new Uri("http://localhost:8080/v2/") }'); + lines.push(' new ClientOptions { BaseUri = new Uri(baseUri) }'); lines.push(' );'); lines.push(' var ctx = new Dictionary();'); lines.push(''); diff --git a/path-analyser/src/codegen/csharp-sdk/materialize-support.ts b/path-analyser/src/codegen/csharp-sdk/materialize-support.ts index a4b66a0..f3d73f2 100644 --- a/path-analyser/src/codegen/csharp-sdk/materialize-support.ts +++ b/path-analyser/src/codegen/csharp-sdk/materialize-support.ts @@ -16,7 +16,7 @@ import path from 'node:path'; const CSPROJ = ` - Exe + Library net8.0 enable enable @@ -63,11 +63,13 @@ Auto-generated test suite targeting the Camunda REST API via ## Running the suite -\`\`\`bash -dotnet run -\`\`\` +This is a .NET class library containing auto-generated test methods. To run them: -Or call the static methods in \`csharp/*.cs\` directly from a custom entry point. +1. **From a test framework** (recommended): + Reference this library from an xUnit/NUnit test project and write test methods that call the static test methods in \`csharp/*.cs\`. + +2. **Programmatically**: + Call the static methods in \`csharp/*.cs\` directly from a custom console app or integration test runner. ## Environment variables diff --git a/path-analyser/src/codegen/js-sdk/README.md b/path-analyser/src/codegen/js-sdk/README.md index 5f06b47..25bc8a3 100644 --- a/path-analyser/src/codegen/js-sdk/README.md +++ b/path-analyser/src/codegen/js-sdk/README.md @@ -40,8 +40,11 @@ npm run fetch-js-sdk-map This downloads the SDK's method-to-operationId mapping from the `@camunda8/orchestration-cluster-api` npm package and writes it to `spec/js-sdk/operation-map.json`. Without it, the emitter falls back to -identity mapping (operationId unchanged) which works for most Camunda REST -API operations. +identity mapping (operationId unchanged). + +⚠️ **Warning**: Fallback identity mapping may not work for all operations — +some SDK methods have different names than their operationIds. Run +`npm run fetch-js-sdk-map` to ensure correct method names in generated tests. ## Environment variables diff --git a/path-analyser/src/codegen/js-sdk/sdk-mapping.ts b/path-analyser/src/codegen/js-sdk/sdk-mapping.ts index 7a6cef6..c3d3624 100644 --- a/path-analyser/src/codegen/js-sdk/sdk-mapping.ts +++ b/path-analyser/src/codegen/js-sdk/sdk-mapping.ts @@ -98,8 +98,19 @@ export class OperationMapJsonSource implements SdkMappingSource { * Used when `operation-map.json` has not been fetched yet (e.g. the user * has not run `npm run fetch-js-sdk-map`). The emitted code will still work * since operationIds already match the raw SDK method names. + * + * ⚠️ Warning: Emits a console warning when instantiated, as fallback mapping + * may not work for all operations — some SDK methods have different names. */ export class FallbackMappingSource implements SdkMappingSource { + constructor() { + // Warn once at emitter creation time + console.warn( + '[js-sdk-emitter] Using fallback operation-map (operationId unchanged). ' + + 'Some SDK methods may have different names. Run `npm run fetch-js-sdk-map` to ensure correct method names.', + ); + } + resolveMethod(operationId: string): string { return operationId; } diff --git a/path-analyser/src/codegen/python-sdk/emitter.ts b/path-analyser/src/codegen/python-sdk/emitter.ts index 376ce4c..0988ad4 100644 --- a/path-analyser/src/codegen/python-sdk/emitter.ts +++ b/path-analyser/src/codegen/python-sdk/emitter.ts @@ -20,6 +20,68 @@ import { type OperationMapJsonSource, } from './sdk-mapping.js'; +/** + * Render a value as a Python literal. + * + * Handles: + * - null → None + * - boolean → True/False + * - string → properly escaped, with template ${var} → ctx['var'] conversion + * - number → as-is + * - array → [...] + * - object → {...} + * + * Example: + * toPythonLiteral({ type: '${workerType}', active: true }) + * → {"type": ctx['workerType'], "active": True} + */ +function toPythonLiteral(value: unknown): string { + // Handle null + if (value === null) return 'None'; + + // Handle booleans + if (typeof value === 'boolean') return value ? 'True' : 'False'; + + // Handle strings + if (typeof value === 'string') { + // Check if this is a template placeholder (e.g., "${varName}") + if (value.includes('${')) { + // Replace ${varName} with ctx['varName'] (no quotes) + return value.replace(/\$\{([^}]+)\}/g, "ctx['$1']"); + } + + // Regular string: choose quotes intelligently + if (value.includes("'") && !value.includes('"')) { + // Has single quotes but not double → use double quotes + return `"${value}"`; + } + + // Otherwise use single quotes with escaping + const escaped = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'" ); + return `'${escaped}'`; + } + + // Handle numbers + if (typeof value === 'number') return String(value); + + // Handle arrays + if (Array.isArray(value)) { + const items = value.map(v => toPythonLiteral(v)).join(', '); + return `[${items}]`; + } + + // Handle objects + if (typeof value === 'object' && value !== null) { + const entries = Object.entries(value) + .map(([k, v]) => `'${k}': ${toPythonLiteral(v)}`) + .join(', '); + return `{${entries}}`; + } + + // Fallback + return 'None'; +} + /** * File name for Python SDK generated test suite. * @@ -72,8 +134,8 @@ function renderScenarioTest( lines.push(' # Seed scenario bindings'); for (const [k, v] of Object.entries(bindings)) { if (v === '__PENDING__') continue; // Skip pending markers - // Use Python-style single-quoted strings for string values - const pyValue = typeof v === 'string' ? `'${v}'` : JSON.stringify(v); + // Render value as Python literal (handles booleans, nulls, templates, etc.) + const pyValue = toPythonLiteral(v); lines.push(` ctx['${k}'] = ${pyValue}`); } lines.push(''); @@ -167,26 +229,19 @@ function renderScenarioTest( /** * Build a Python dict representation from a request template. * - * Replaces ${var} placeholders with ctx['var'] lookups. + * Renders the template as a Python literal, handling: + * - ${var} placeholders → ctx['var'] lookups + * - boolean true/false → True/False + * - null → None + * - proper string escaping + * * Example: - * { type: "${workerType}", maxJobs: 1 } + * { type: "${workerType}", active: true, metadata: null } * → - * { - * 'type': ctx['workerType'], - * 'maxJobs': 1 - * } + * {'type': ctx['workerType'], 'active': True, 'metadata': None} */ function buildBodyDict(bodyTemplate: unknown): string { - const json = JSON.stringify(bodyTemplate, null, 2); - // Replace "${varName}" with ctx['varName'] - const withVars = json.replace(/"(\$\{([^}]+)\})"/g, (_, _fullMatch, varName) => { - return `ctx['${varName}']`; - }); - // Convert remaining JSON double-quoted strings (keys and string values) to Python single quotes. - // This handles: "key": → 'key': and "stringValue" → 'stringValue' - // Does not touch ctx['var'] substitutions already made (those use single quotes already). - const withSingleQuotes = withVars.replace(/"([^"\\]*)"/g, "'$1'"); - return withSingleQuotes; + return toPythonLiteral(bodyTemplate); } /** From 5a7ce53f72638c8b5b30b1e9e275af93a594f53f Mon Sep 17 00:00:00 2001 From: Muhamad690 Date: Sat, 16 May 2026 15:34:37 +1200 Subject: [PATCH 13/20] chore: ignore local Visual Studio solution file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 51733ec..2eb10f3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ test-results/ .claw/ csharp-sdk/**/obj/ csharp-sdk/**/bin/ +api-test-generator.sln From 728a356defcba5f23bc0714a80748e6cc74ca21a Mon Sep 17 00:00:00 2001 From: Dasha Date: Sat, 16 May 2026 15:48:06 +1200 Subject: [PATCH 14/20] refactor: omit OutputType from .csproj to use implicit default (Option C) Remove explicit Library from the scaffolded .csproj template. In .NET SDK-style projects, the default output type is Library when is omitted, achieving the same result as explicit declaration but with cleaner XML. Changes: - Remove Library from CSPROJ template (line 19) - Update JSDoc to reflect test framework/runner consumption model - Enhance README with step-by-step xUnit/NUnit setup instructions - Include code examples for test wrapper and dotnet test workflow - Document alternative direct consumption approach The implicit default approach follows modern .NET conventions while keeping the project configuration minimal. Test frameworks (xUnit, NUnit, MSTest) consume library assemblies natively via dotnet test. --- .../codegen/csharp-sdk/materialize-support.ts | 45 ++++++++++++++++--- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/path-analyser/src/codegen/csharp-sdk/materialize-support.ts b/path-analyser/src/codegen/csharp-sdk/materialize-support.ts index f3d73f2..86d9857 100644 --- a/path-analyser/src/codegen/csharp-sdk/materialize-support.ts +++ b/path-analyser/src/codegen/csharp-sdk/materialize-support.ts @@ -16,7 +16,6 @@ import path from 'node:path'; const CSPROJ = ` - Library net8.0 enable enable @@ -63,13 +62,45 @@ Auto-generated test suite targeting the Camunda REST API via ## Running the suite -This is a .NET class library containing auto-generated test methods. To run them: +This is a .NET class library containing auto-generated test methods. The recommended approach is to create a separate xUnit/NUnit test project that references this library: -1. **From a test framework** (recommended): - Reference this library from an xUnit/NUnit test project and write test methods that call the static test methods in \`csharp/*.cs\`. +### Setup test consumer project -2. **Programmatically**: - Call the static methods in \`csharp/*.cs\` directly from a custom console app or integration test runner. +\`\`\`bash +# From the generated output directory +dotnet new xunit -n CamundaTests.xUnit +cd CamundaTests.xUnit +dotnet add reference ../CamundaIntegrationTests.csproj +\`\`\` + +### Write test wrapper + +\`\`\`csharp +// CamundaTests.xUnit/CamundaSuiteTests.cs +using Xunit; +using CamundaIntegrationTests; + +public class CamundaSuiteTests +{ + [Fact] + public async Task ActivateJobsSuite() => await GeneratedSuite.ActivateJobsAsync(); + + [Fact] + public async Task CreateProcessInstanceSuite() => await GeneratedSuite.CreateProcessInstanceAsync(); + // ... more test methods +} +\`\`\` + +### Run tests + +\`\`\`bash +cd CamundaTests.xUnit +dotnet test +\`\`\` + +### Alternative: Direct consumption + +You can also call the static methods directly from a custom console app or integration test runner without a formal test framework. ## Environment variables @@ -83,7 +114,7 @@ This is a .NET class library containing auto-generated test methods. To run them /** * Materialize C# project scaffolding into `/` so the emitted C# SDK - * suite is self-contained and runnable in place (`cd && dotnet run`). + * suite is self-contained and consumable via a test framework or custom runner. * * Idempotent: safe to call multiple times per emit run. * From 4b4440fc25df52e2cecd9d5e8f95881a4b46a445 Mon Sep 17 00:00:00 2001 From: yarm03 Date: Sat, 16 May 2026 16:15:57 +1200 Subject: [PATCH 15/20] fix: load .env via DotNetEnv before reading CAMUNDA_BASE_URL in emitted C# suite --- path-analyser/src/codegen/csharp-sdk/emitter.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/path-analyser/src/codegen/csharp-sdk/emitter.ts b/path-analyser/src/codegen/csharp-sdk/emitter.ts index f2b6462..e840328 100644 --- a/path-analyser/src/codegen/csharp-sdk/emitter.ts +++ b/path-analyser/src/codegen/csharp-sdk/emitter.ts @@ -45,7 +45,10 @@ function renderCsharpSuite( lines.push(` public static async Task ${pascalCase(suiteName)}Async()`); lines.push(' {'); lines.push(' using var httpClient = new HttpClient();'); - lines.push(' var baseUri = Environment.GetEnvironmentVariable("CAMUNDA_BASE_URL") ?? "http://localhost:8080/v2/";'); + lines.push(' DotNetEnv.Env.TraversePath().Load();'); + lines.push( + ' var baseUri = Environment.GetEnvironmentVariable("CAMUNDA_BASE_URL") ?? "http://localhost:8080/v2/";', + ); lines.push(' var client = new OrchestrationClusterClient('); lines.push(' httpClient,'); lines.push(' new ClientOptions { BaseUri = new Uri(baseUri) }'); From b3968dfb02ca49f7e6059f6cd2c96be2c1bb61da Mon Sep 17 00:00:00 2001 From: yarm03 Date: Mon, 18 May 2026 18:38:58 +1200 Subject: [PATCH 16/20] chore: replace execSync shell strings with execFileSync in fetch-python-sdk-map --- .../Camunda.Orchestration.RestSdk.deps.json | 23 -- .../net8.0/Camunda.Orchestration.RestSdk.dll | Bin 89088 -> 0 bytes .../net8.0/Camunda.Orchestration.RestSdk.pdb | Bin 26548 -> 0 bytes ...estration.RestSdk.csproj.nuget.dgspec.json | 66 ------ ...Orchestration.RestSdk.csproj.nuget.g.props | 15 -- ...chestration.RestSdk.csproj.nuget.g.targets | 2 - ...CoreApp,Version=v8.0.AssemblyAttributes.cs | 4 - ...unda.Orchestration.RestSdk.AssemblyInfo.cs | 22 -- ...estration.RestSdk.AssemblyInfoInputs.cache | 1 - ....GeneratedMSBuildEditorConfig.editorconfig | 13 -- ...da.Orchestration.RestSdk.GlobalUsings.g.cs | 8 - ...Camunda.Orchestration.RestSdk.assets.cache | Bin 151 -> 0 bytes ...ion.RestSdk.csproj.CoreCompileInputs.cache | 1 - ...ration.RestSdk.csproj.FileListAbsolute.txt | 12 -- .../net8.0/Camunda.Orchestration.RestSdk.dll | Bin 89088 -> 0 bytes .../net8.0/Camunda.Orchestration.RestSdk.pdb | Bin 26548 -> 0 bytes ...unda.Orchestration.RestSdk.sourcelink.json | 1 - .../ref/Camunda.Orchestration.RestSdk.dll | Bin 39424 -> 0 bytes .../refint/Camunda.Orchestration.RestSdk.dll | Bin 39424 -> 0 bytes .../obj/project.assets.json | 71 ------- .../obj/project.nuget.cache | 8 - scripts/fetch-python-sdk-map.ts | 201 ++++++------------ 22 files changed, 66 insertions(+), 382 deletions(-) delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.dll delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.dgspec.json delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.props delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.targets delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GlobalUsings.g.cs delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.assets.cache delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.FileListAbsolute.txt delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.dll delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.sourcelink.json delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/ref/Camunda.Orchestration.RestSdk.dll delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/refint/Camunda.Orchestration.RestSdk.dll delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.assets.json delete mode 100644 csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.nuget.cache diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json b/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json deleted file mode 100644 index 9e45f8e..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v8.0", - "signature": "" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v8.0": { - "Camunda.Orchestration.RestSdk/1.0.0": { - "runtime": { - "Camunda.Orchestration.RestSdk.dll": {} - } - } - } - }, - "libraries": { - "Camunda.Orchestration.RestSdk/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.dll b/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.dll deleted file mode 100644 index 441c935fcfddfdfaa1ebd2e0ca40fd0b6f17728a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89088 zcmeFacbrwl`9D5$&UWthg{3U8%K~1CSa4OWtXRMj#V(?^p8;>})aob(@Xgnq}(?dPosI zhoIhF$)Mg%vz1WbKTE7hft`4qHG9UgA9HQYx-RPy;E9QHV`d+6;%wj*li@FP>Z|uV zHiLNDn3=O?PKQ9g!ADaBuqUpG&v*>in3;zhoq-{JHSk$6nDQ;VZ4$-AUdp)QL*D{Z zXdb@EnE8J)CcmUieiRuJgb~UXj2slS>zWYEf}m*RkV=zAM~h(LuE&Nmp*SwnU`*NY z&12ZzU_&R}#!5MDz@cB5>c zkq0JBLwS^GWP!M$$s_3?ipXPK6M|V4vB#C(V_my@OkvXFKEPRz`>|x~F&oHwjJ$Im zlS$Jq=`l^ib`{8a%!ZuDC?bz_rN>?{YXF;Lj1f-7Sl8|zQ<(I)893|laF&ccX1!UD zks!`vGHKc+J*H{ct^!$)*^u)XMdY!r^wY$6he2h0W39jmf}cUAucsVbbF*fwM6l z&yumntT*d165Pd@OqzB{k7*jVt3cLcHsm};5qYdDJ@$fG+psx$Jb?^6*0sCG6ed01 z5jgAdE-V>)%zCpP7snoxNz*RrF-^mE707zbhMdPJB9C>Y$6hdNS2jnFcOwIjb?xpk zg-MV10nU0ng(YK;S#Q>3Y$`6sWYV-tdQ8)>T?Mipvmxg(ipXPK>9H5gn#$(r@d0Gu zv98@crZDO84B)KChp}YrG3(8GTpD{!CQZAf$21MwRUqpz8*(0_h&|r^NS#Q?kve;uXY1$<{rfJx&0$GpQknw*b8RO zVRQ8O1Tyeg*X|xunDqD*;H<};EE&g`^=3Wp6?;r3O}nJWG!5HTAnP$3avr0IJl2&S zd%>)EY>pn!Cj*al?d~y!NsrG0&U$}u(kG){lxonOepGO8B>)PF83X>jR0-W{ua+ZueX1!UD%VUqpq-mG*n5JR73S>QI zL(XFqk;l5yV=tI>1)HPCSCWCpx_0-N!lcL70B1eEjwNG{S#Q>3906R6$)stQ^q8h$ zy9#7IW<$iJ${fSV~<&H*5j(! zV=`&lB|WBT*scOukJ*s(7)9iv47LF_|>&k{;7EY*&G-$82cw$T82>us{>fpg1C3YtW6+ zrA31qGtICZUNkuJOlBzi#JavfU4b#x>0ywBGqKs{4)@QaxC?hUbC0CrMJUQ@%-TL% zz!BBuZwO`{lQer7X1!|c+CE!$H^W+zYvz?iGj}8vxV0%t)dXw%j4@_FUNf&H&0bHG zw>DAn%7kKYQn4zlaCa?#0p>i~I|#>A2BS-ZF!K&Xm?fiZ{YX>0erTRRocQ5Ex-3XP z#~RiF+c;vcy{?(!Z(KMrH0$>D;xvslPckFG#^R9@bCP66CB%ay=2Xe6WVFPbFqw4| z)(8sf=CsMImk_TnF(*%EgM`@C%_-#S9=UN49~N^W$TU)gwaKJ&oeo{s$5M4JRk@CbN|ww

      nv(*T$ZQ${E!* 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

        8X# z4$s1H)Fhb~sZo*3T?~>JufF6+?tgqUZBWjWX=ig6PYmrTcUE|JDItkB)z0taS41j( zSGPpD9j->1!JoXVyAO8b?4|1w#?)q@yw7_xG-4rd z_7*Qu$v$M#56nICa>t20vmU`V_Vm|M~C#zZ&@8wHx8F diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb b/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb deleted file mode 100644 index dfb08315d24eaba4084767ef2d43c0dbdcb898cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26548 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 - - - True - NuGet - $(MSBuildThisFileDirectory)project.assets.json - /home/muhamad/.nuget/packages/ - /home/muhamad/.nuget/packages/ - PackageReference - 6.11.2 - - - - - \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.targets b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.targets deleted file mode 100644 index 3dc06ef..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Camunda.Orchestration.RestSdk.csproj.nuget.g.targets +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs deleted file mode 100644 index 2217181..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/.NETCoreApp,Version=v8.0.AssemblyAttributes.cs +++ /dev/null @@ -1,4 +0,0 @@ -// -using System; -using System.Reflection; -[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")] diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs deleted file mode 100644 index c900e80..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -using System; -using System.Reflection; - -[assembly: System.Reflection.AssemblyCompanyAttribute("Camunda.Orchestration.RestSdk")] -[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] -[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+e90328c2249d7f272078efb01fd6f0468204003a")] -[assembly: System.Reflection.AssemblyProductAttribute("Camunda.Orchestration.RestSdk")] -[assembly: System.Reflection.AssemblyTitleAttribute("Camunda.Orchestration.RestSdk")] -[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] - -// Generated by the MSBuild WriteCodeFragment class. - diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache deleted file mode 100644 index 33abb91..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache +++ /dev/null @@ -1 +0,0 @@ -60292b95fa336b4da599d5f59b7e9066755f9cf92120db36a66fccafedc7ca26 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig deleted file mode 100644 index 22e345d..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -is_global = true -build_property.TargetFramework = net8.0 -build_property.TargetPlatformMinVersion = -build_property.UsingMicrosoftNETSdkWeb = -build_property.ProjectTypeGuids = -build_property.InvariantGlobalization = -build_property.PlatformNeutralAssembly = -build_property.EnforceExtendedAnalyzerRules = -build_property._SupportedPlatformList = Linux,macOS,Windows -build_property.RootNamespace = Camunda.Orchestration.RestSdk -build_property.ProjectDir = /home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/ -build_property.EnableComHosting = -build_property.EnableGeneratedComInterfaceComImportInterop = diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GlobalUsings.g.cs b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GlobalUsings.g.cs deleted file mode 100644 index 8578f3d..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GlobalUsings.g.cs +++ /dev/null @@ -1,8 +0,0 @@ -// -global using global::System; -global using global::System.Collections.Generic; -global using global::System.IO; -global using global::System.Linq; -global using global::System.Net.Http; -global using global::System.Threading; -global using global::System.Threading.Tasks; diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.assets.cache b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.assets.cache deleted file mode 100644 index 0ba4778afbda6121705869ebf2d174aa4f02608c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 151 zcmWIWc6a1qU|?vSy|Q*w-k&}_f1aVWH^{!gKZ+tqtQo1azKPX?+X7BB)8 m%IRn1=celCmS!a8CZ_1?<&~zVmgpBGCTAz6rxxoIG711@upO}g diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache deleted file mode 100644 index 83bf36d..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache +++ /dev/null @@ -1 +0,0 @@ -ae62822739208c29806f1b4ed45de19718d1980d02a626e9cceb5be76d2b318b diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.FileListAbsolute.txt b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.FileListAbsolute.txt deleted file mode 100644 index 57d4dc7..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.FileListAbsolute.txt +++ /dev/null @@ -1,12 +0,0 @@ -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.deps.json -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.dll -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/bin/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.GeneratedMSBuildEditorConfig.editorconfig -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfoInputs.cache -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.AssemblyInfo.cs -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.csproj.CoreCompileInputs.cache -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.sourcelink.json -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.dll -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/refint/Camunda.Orchestration.RestSdk.dll -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb -/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/ref/Camunda.Orchestration.RestSdk.dll diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.dll b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.dll deleted file mode 100644 index 441c935fcfddfdfaa1ebd2e0ca40fd0b6f17728a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 89088 zcmeFacbrwl`9D5$&UWthg{3U8%K~1CSa4OWtXRMj#V(?^p8;>})aob(@Xgnq}(?dPosI zhoIhF$)Mg%vz1WbKTE7hft`4qHG9UgA9HQYx-RPy;E9QHV`d+6;%wj*li@FP>Z|uV zHiLNDn3=O?PKQ9g!ADaBuqUpG&v*>in3;zhoq-{JHSk$6nDQ;VZ4$-AUdp)QL*D{Z zXdb@EnE8J)CcmUieiRuJgb~UXj2slS>zWYEf}m*RkV=zAM~h(LuE&Nmp*SwnU`*NY z&12ZzU_&R}#!5MDz@cB5>c zkq0JBLwS^GWP!M$$s_3?ipXPK6M|V4vB#C(V_my@OkvXFKEPRz`>|x~F&oHwjJ$Im zlS$Jq=`l^ib`{8a%!ZuDC?bz_rN>?{YXF;Lj1f-7Sl8|zQ<(I)893|laF&ccX1!UD zks!`vGHKc+J*H{ct^!$)*^u)XMdY!r^wY$6he2h0W39jmf}cUAucsVbbF*fwM6l z&yumntT*d165Pd@OqzB{k7*jVt3cLcHsm};5qYdDJ@$fG+psx$Jb?^6*0sCG6ed01 z5jgAdE-V>)%zCpP7snoxNz*RrF-^mE707zbhMdPJB9C>Y$6hdNS2jnFcOwIjb?xpk zg-MV10nU0ng(YK;S#Q>3Y$`6sWYV-tdQ8)>T?Mipvmxg(ipXPK>9H5gn#$(r@d0Gu zv98@crZDO84B)KChp}YrG3(8GTpD{!CQZAf$21MwRUqpz8*(0_h&|r^NS#Q?kve;uXY1$<{rfJx&0$GpQknw*b8RO zVRQ8O1Tyeg*X|xunDqD*;H<};EE&g`^=3Wp6?;r3O}nJWG!5HTAnP$3avr0IJl2&S zd%>)EY>pn!Cj*al?d~y!NsrG0&U$}u(kG){lxonOepGO8B>)PF83X>jR0-W{ua+ZueX1!UD%VUqpq-mG*n5JR73S>QI zL(XFqk;l5yV=tI>1)HPCSCWCpx_0-N!lcL70B1eEjwNG{S#Q>3906R6$)stQ^q8h$ zy9#7IW<$iJ${fSV~<&H*5j(! zV=`&lB|WBT*scOukJ*s(7)9iv47LF_|>&k{;7EY*&G-$82cw$T82>us{>fpg1C3YtW6+ zrA31qGtICZUNkuJOlBzi#JavfU4b#x>0ywBGqKs{4)@QaxC?hUbC0CrMJUQ@%-TL% zz!BBuZwO`{lQer7X1!|c+CE!$H^W+zYvz?iGj}8vxV0%t)dXw%j4@_FUNf&H&0bHG zw>DAn%7kKYQn4zlaCa?#0p>i~I|#>A2BS-ZF!K&Xm?fiZ{YX>0erTRRocQ5Ex-3XP z#~RiF+c;vcy{?(!Z(KMrH0$>D;xvslPckFG#^R9@bCP66CB%ay=2Xe6WVFPbFqw4| z)(8sf=CsMImk_TnF(*%EgM`@C%_-#S9=UN49~N^W$TU)gwaKJ&oeo{s$5M4JRk@CbN|ww

        nv(*T$ZQ${E!* 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

          8X# z4$s1H)Fhb~sZo*3T?~>JufF6+?tgqUZBWjWX=ig6PYmrTcUE|JDItkB)z0taS41j( zSGPpD9j->1!JoXVyAO8b?4|1w#?)q@yw7_xG-4rd z_7*Qu$v$M#56nICa>t20vmU`V_Vm|M~C#zZ&@8wHx8F diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/Camunda.Orchestration.RestSdk.pdb deleted file mode 100644 index dfb08315d24eaba4084767ef2d43c0dbdcb898cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26548 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 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/refint/Camunda.Orchestration.RestSdk.dll b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/Debug/net8.0/refint/Camunda.Orchestration.RestSdk.dll deleted file mode 100644 index 1abe67cfeadd42e959f67ae716781a5ba4625e74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39424 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 diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.assets.json b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.assets.json deleted file mode 100644 index 34cd8fd..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.assets.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "version": 3, - "targets": { - "net8.0": {} - }, - "libraries": {}, - "projectFileDependencyGroups": { - "net8.0": [] - }, - "packageFolders": { - "/home/muhamad/.nuget/packages/": {} - }, - "project": { - "version": "1.0.0", - "restore": { - "projectUniqueName": "/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj", - "projectName": "Camunda.Orchestration.RestSdk", - "projectPath": "/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj", - "packagesPath": "/home/muhamad/.nuget/packages/", - "outputPath": "/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/", - "projectStyle": "PackageReference", - "configFilePaths": [ - "/home/muhamad/.nuget/NuGet/NuGet.Config" - ], - "originalTargetFrameworks": [ - "net8.0" - ], - "sources": { - "https://api.nuget.org/v3/index.json": {} - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "projectReferences": {} - } - }, - "warningProperties": { - "warnAsError": [ - "NU1605" - ] - }, - "restoreAuditProperties": { - "enableAudit": "true", - "auditLevel": "low", - "auditMode": "direct" - } - }, - "frameworks": { - "net8.0": { - "targetAlias": "net8.0", - "imports": [ - "net461", - "net462", - "net47", - "net471", - "net472", - "net48", - "net481" - ], - "assetTargetFallback": true, - "warn": true, - "frameworkReferences": { - "Microsoft.NETCore.App": { - "privateAssets": "all" - } - }, - "runtimeIdentifierGraphPath": "/home/muhamad/.dotnet/sdk/8.0.420/PortableRuntimeIdentifierGraph.json" - } - } - } -} \ No newline at end of file diff --git a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.nuget.cache b/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.nuget.cache deleted file mode 100644 index b349e02..0000000 --- a/csharp-sdk/src/Camunda.Orchestration.RestSdk/obj/project.nuget.cache +++ /dev/null @@ -1,8 +0,0 @@ -{ - "version": 2, - "dgSpecHash": "BS5cMD2o/gU=", - "success": true, - "projectFilePath": "/home/muhamad/API-Test/api-test-generator/csharp-sdk/src/Camunda.Orchestration.RestSdk/Camunda.Orchestration.RestSdk.csproj", - "expectedPackageFiles": [], - "logs": [] -} \ No newline at end of file diff --git a/scripts/fetch-python-sdk-map.ts b/scripts/fetch-python-sdk-map.ts index e14fa16..e638d50 100644 --- a/scripts/fetch-python-sdk-map.ts +++ b/scripts/fetch-python-sdk-map.ts @@ -3,7 +3,7 @@ * fetch-python-sdk-map — sparse-clone camunda/orchestration-cluster-api-python * and extract examples/operation-map.json. * - * Mirrors scripts/fetch-js-sdk-map.ts pattern. Outputs to spec/python-sdk/ + * Mirrors scripts/fetch-js-sdk-map.js pattern. Outputs to spec/python-sdk/ * per the CONFIG-partitioned layout. * * Outputs: @@ -13,13 +13,17 @@ * Env vars: * PYTHON_SDK_REF Commit/branch/tag to fetch (default: main) */ -import { execSync } from 'node:child_process'; +import { execFileSync } from 'node:child_process'; import { createHash } from 'node:crypto'; -import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; +const REPO_URL = 'https://github.com/camunda/orchestration-cluster-api-python.git'; +const FILE_PATH = 'examples/operation-map.json'; + const __filename = fileURLToPath(import.meta.url); const REPO_ROOT = resolve(__filename, '../..'); @@ -36,138 +40,65 @@ function computeHash(content: string): string { return createHash('sha256').update(content).digest('hex'); } -function execCommand(cmd: string, options?: Record): string { - try { - return execSync(cmd, { encoding: 'utf-8', ...options }).trim(); - } catch (error) { - // biome-ignore lint/plugin: error is caught from execSync; status/stderr/stdout are the Node.js SpawnSyncReturns fields - const err = error as { status: number; stderr: Buffer; stdout: Buffer }; - console.error(`Command failed: ${cmd}`); - console.error(err.stderr?.toString() || err.stdout?.toString() || String(error)); - throw error; - } -} - -async function main(): Promise { - const pythonSdkRef = process.env.PYTHON_SDK_REF ?? 'main'; - const pythonSdkDir = join(REPO_ROOT, 'spec/python-sdk'); - const tempCloneDir = join(REPO_ROOT, '.tmp-python-sdk-clone'); - const operationMapPath = join(pythonSdkDir, 'operation-map.json'); - const metadataPath = join(pythonSdkDir, 'sdk-metadata.json'); - - console.error(`[fetch-python-sdk-map] ref=${pythonSdkRef}, output=${pythonSdkDir}`); - - // Clean up any prior temp directory - if (process.platform === 'win32') { - // Windows rmdir with recursion - try { - execCommand(`rmdir /s /q "${tempCloneDir}"`, { shell: true }); - } catch { - // Directory may not exist - } - } else { - try { - rmSync(tempCloneDir, { recursive: true, force: true }); - } catch { - // Directory may not exist - } - } - +const pythonSdkRef = process.env.PYTHON_SDK_REF ?? 'main'; +const pythonSdkDir = join(REPO_ROOT, 'spec/python-sdk'); +const operationMapPath = join(pythonSdkDir, 'operation-map.json'); +const metadataPath = join(pythonSdkDir, 'sdk-metadata.json'); +const tmpDir = join(tmpdir(), `python-sdk-map-${Date.now()}`); + +console.log(`[fetch-python-sdk-map] Fetching ${FILE_PATH} from ${REPO_URL} @ ${pythonSdkRef}`); + +try { + mkdirSync(tmpDir, { recursive: true }); + + // Sparse clone: fetch only examples/operation-map.json to keep it fast. + execFileSync('git', ['init', '--quiet', tmpDir], { stdio: 'inherit' }); + execFileSync('git', ['-C', tmpDir, 'remote', 'add', 'origin', REPO_URL], { stdio: 'inherit' }); + execFileSync('git', ['-C', tmpDir, 'config', 'core.sparseCheckout', 'true'], { + stdio: 'inherit', + }); + + // Write sparse-checkout pattern before fetch + const sparseFile = join(tmpDir, '.git', 'info', 'sparse-checkout'); + writeFileSync(sparseFile, `${FILE_PATH}\n`, 'utf8'); + + execFileSync('git', ['-C', tmpDir, 'fetch', '--depth', '1', 'origin', pythonSdkRef], { + stdio: 'inherit', + }); + execFileSync('git', ['-C', tmpDir, 'checkout', 'FETCH_HEAD', '--', FILE_PATH], { + stdio: 'inherit', + }); + + // Resolve the ref to a full commit SHA + const resolvedRef = execFileSync('git', ['-C', tmpDir, 'rev-parse', 'FETCH_HEAD'], { + encoding: 'utf-8', + }).trim(); + console.log(`[fetch-python-sdk-map] resolved ref: ${resolvedRef}`); + + // Ensure output directory exists and move the file into place + mkdirSync(pythonSdkDir, { recursive: true }); + renameSync(join(tmpDir, FILE_PATH), operationMapPath); + console.log(`[fetch-python-sdk-map] Written to ${operationMapPath}`); + + // Write metadata + const operationMapContent = readFileSync(operationMapPath, 'utf-8'); + const metadata: SdkMetadata = { + sdkRef: resolvedRef, + operationMapHash: computeHash(operationMapContent), + fetchedAt: new Date().toISOString(), + }; + writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'); + console.log(`[fetch-python-sdk-map] Written metadata to ${metadataPath}`); +} catch (err) { + console.error( + '[fetch-python-sdk-map] Failed:', + err instanceof Error ? err.message : String(err), + ); + process.exit(1); +} finally { try { - // Sparse-clone workflow: - // 1. Clone with --no-checkout to defer file materialization - // 2. Configure sparse checkout cone mode - // 3. Set sparse patterns to include only examples/ - // 4. Checkout to materialize files - console.error('[fetch-python-sdk-map] sparse-cloning...'); - execCommand( - `git clone --no-checkout --depth=1 https://github.com/camunda/orchestration-cluster-api-python.git "${tempCloneDir}"`, - { - shell: true, - cwd: REPO_ROOT, - }, - ); - - // Fetch the specific ref if not main - if (pythonSdkRef !== 'main') { - execCommand(`git fetch origin "${pythonSdkRef}:refs/remotes/origin/${pythonSdkRef}"`, { - cwd: tempCloneDir, - }); - execCommand(`git checkout ${pythonSdkRef}`, { - cwd: tempCloneDir, - }); - } else { - execCommand('git checkout main', { - cwd: tempCloneDir, - }); - } - - // Configure sparse checkout for cone mode - execCommand('git config core.sparseCheckoutCone true', { - cwd: tempCloneDir, - }); - - // Initialize sparse checkout - execCommand('git sparse-checkout init --cone', { - cwd: tempCloneDir, - }); - - // Set sparse patterns: only examples/ - execCommand('git sparse-checkout set examples', { - cwd: tempCloneDir, - }); - - // Resolve the ref to a full commit SHA - const resolvedRef = execCommand('git rev-parse HEAD', { - cwd: tempCloneDir, - }); - console.error(`[fetch-python-sdk-map] resolved ref: ${resolvedRef}`); - - // Read the operation-map.json - const sourceMapPath = join(tempCloneDir, 'examples/operation-map.json'); - let operationMapContent: string; - try { - operationMapContent = readFileSync(sourceMapPath, 'utf-8'); - } catch (_error) { - throw new Error(`Failed to read operation-map.json from cloned repo at ${sourceMapPath}`); - } - - // Ensure output directory exists - mkdirSync(pythonSdkDir, { recursive: true }); - - // Write operation-map.json - writeFileSync(operationMapPath, operationMapContent, 'utf-8'); - console.error(`[fetch-python-sdk-map] wrote ${operationMapPath}`); - - // Write metadata - const metadata: SdkMetadata = { - sdkRef: resolvedRef, - operationMapHash: computeHash(operationMapContent), - fetchedAt: new Date().toISOString(), - }; - writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'); - console.error(`[fetch-python-sdk-map] wrote ${metadataPath}`); - } finally { - // Clean up temp clone - if (process.platform === 'win32') { - try { - execCommand(`rmdir /s /q "${tempCloneDir}"`, { shell: true }); - } catch { - // May fail; best effort - } - } else { - try { - rmSync(tempCloneDir, { recursive: true, force: true }); - } catch { - // May fail; best effort - } - } + rmSync(tmpDir, { recursive: true, force: true }); + } catch { + // Non-fatal cleanup failure. } - - console.error('[fetch-python-sdk-map] done'); } - -main().catch((error) => { - console.error('Fatal error:', error); - process.exit(1); -}); From 4b2ed8c6ec0ed3e73b5ac1b15aaec70f11b0e4c7 Mon Sep 17 00:00:00 2001 From: yarm03 Date: Mon, 18 May 2026 18:39:04 +1200 Subject: [PATCH 17/20] chore: add .gitignore to exclude bin/ and obj/ build artifacts from csharp-sdk --- csharp-sdk/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 csharp-sdk/.gitignore diff --git a/csharp-sdk/.gitignore b/csharp-sdk/.gitignore new file mode 100644 index 0000000..cd42ee3 --- /dev/null +++ b/csharp-sdk/.gitignore @@ -0,0 +1,2 @@ +bin/ +obj/ From 43b1ab7bb69614c51fe8bffe34fb0d7719614c72 Mon Sep 17 00:00:00 2001 From: yarm03 Date: Thu, 21 May 2026 16:48:51 +1200 Subject: [PATCH 18/20] fix: replace no-op keyset invariants with planned-to-emitted completeness checks (#133 #131) --- .../camunda-oca/regression-invariants.test.ts | 111 +++++++----------- 1 file changed, 42 insertions(+), 69 deletions(-) diff --git a/configs/camunda-oca/regression-invariants.test.ts b/configs/camunda-oca/regression-invariants.test.ts index bce23fb..90d568f 100644 --- a/configs/camunda-oca/regression-invariants.test.ts +++ b/configs/camunda-oca/regression-invariants.test.ts @@ -3168,48 +3168,33 @@ describeForThisConfig('bundled-spec invariants: emitted Python SDK suite (#133)' expect(offenders).toEqual([]); }); - it('operation-id keyset of Python SDK suite matches operation-map.json entries (#133)', () => { - if (!existsSync(GENERATED_TESTS_DIR)) { + it('every planned scenario has a materialized Python SDK test file (#133)', () => { + const INDEX_PATH = join(SCENARIOS_DIR, 'index.json'); + if (!existsSync(INDEX_PATH)) { throw new Error( - `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + `Scenario index not found at ${INDEX_PATH}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, ); } - const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => - f.endsWith('.python_sdk.spec.py'), - ); - if (files.length === 0) { - return; - } - const PYTHON_SDK_MAP_PATH = join(REPO_ROOT, 'spec', 'python-sdk', 'operation-map.json'); - let operationMap: Record | undefined; - if (existsSync(PYTHON_SDK_MAP_PATH)) { - // biome-ignore lint/plugin: runtime contract boundary for parsed JSON - operationMap = JSON.parse(readFileSync(PYTHON_SDK_MAP_PATH, 'utf8')) as Record< - string, - unknown - >; - } else { + // biome-ignore lint/plugin: runtime contract boundary for parsed JSON + const index = JSON.parse(readFileSync(INDEX_PATH, 'utf8')) as { + endpoints: Array<{ operationId: string; scenarioCount: number }>; + }; + const planned = index.endpoints.filter((e) => e.scenarioCount > 0); + if (planned.length === 0) { return; } - let assertionsRun = 0; - for (const file of files) { - const src = readFileSync(join(GENERATED_TESTS_DIR, file), 'utf8'); - assertionsRun++; - const regexClientCall = /await\s+client\.([a-z_]+)\(/g; - const methodsSeen = new Set(); - let match; - while ((match = regexClientCall.exec(src)) !== null) { - methodsSeen.add(match[1]); - } - for (const method of methodsSeen) { - // Every method must either be in the operation-map or be a valid fallback (camelToSnake) - // If not in map, the fallback conversion is assumed valid - if (operationMap) { - expect(method).toBeTruthy(); - } - } + if (!existsSync(GENERATED_TESTS_DIR)) { + throw new Error( + `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + ); } - expect(assertionsRun).toBeGreaterThan(0); + const missing = planned + .map((e) => `${e.operationId}.python_sdk.spec.py`) + .filter((f) => !existsSync(join(GENERATED_TESTS_DIR, f))); + expect( + missing, + 'Planned scenarios exist but no Python SDK test file was materialized for these operationIds', + ).toEqual([]); }); }); @@ -3245,45 +3230,33 @@ describeForThisConfig('bundled-spec invariants: emitted JS SDK suite (#131)', () ).toEqual([]); }); - it('operation-id keyset of JS SDK suite matches operation-map.json entries (#131)', () => { - if (!existsSync(GENERATED_TESTS_DIR)) { + it('every planned scenario has a materialized JS SDK test file (#131)', () => { + const INDEX_PATH = join(SCENARIOS_DIR, 'index.json'); + if (!existsSync(INDEX_PATH)) { throw new Error( - `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + `Scenario index not found at ${INDEX_PATH}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, ); } - const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => - f.endsWith('.feature.test.ts'), - ); - if (files.length === 0) { + // biome-ignore lint/plugin: runtime contract boundary for parsed JSON + const index = JSON.parse(readFileSync(INDEX_PATH, 'utf8')) as { + endpoints: Array<{ operationId: string; scenarioCount: number }>; + }; + const planned = index.endpoints.filter((e) => e.scenarioCount > 0); + if (planned.length === 0) { return; } - const JS_SDK_MAP_PATH = join(REPO_ROOT, 'spec', 'js-sdk', 'operation-map.json'); - let operationMap: Record | undefined; - if (existsSync(JS_SDK_MAP_PATH)) { - // biome-ignore lint/plugin: runtime contract boundary for parsed JSON - operationMap = JSON.parse(readFileSync(JS_SDK_MAP_PATH, 'utf8')) as Record< - string, - unknown - >; - } - let assertionsRun = 0; - for (const file of files) { - const src = readFileSync(join(GENERATED_TESTS_DIR, file), 'utf8'); - assertionsRun++; - const regexClientCall = /await\s+client\.([a-zA-Z]+)\(/g; - const methodsSeen = new Set(); - for (let m = regexClientCall.exec(src); m !== null; m = regexClientCall.exec(src)) { - methodsSeen.add(m[1]); - } - if (operationMap) { - for (const method of methodsSeen) { - if (!(method in operationMap)) { - // Fallback camelCase mapping is always valid; allow unknown methods - } - } - } + if (!existsSync(GENERATED_TESTS_DIR)) { + throw new Error( + `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, + ); } - expect(assertionsRun).toBeGreaterThan(0); + const missing = planned + .map((e) => `${e.operationId}.feature.test.ts`) + .filter((f) => !existsSync(join(GENERATED_TESTS_DIR, f))); + expect( + missing, + 'Planned scenarios exist but no JS SDK test file was materialized for these operationIds', + ).toEqual([]); }); }); From a58227049d6a67c618c7edaa9f7008abfd1ce5ef Mon Sep 17 00:00:00 2001 From: yarm03 Date: Thu, 21 May 2026 16:48:55 +1200 Subject: [PATCH 19/20] =?UTF-8?q?docs:=20correct=20JS=20SDK=20fetch=20mech?= =?UTF-8?q?anism=20=E2=80=94=20git=20sparse=20clone,=20add=20JS=5FSDK=5FRE?= =?UTF-8?q?F=20env=20var=20(#131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- path-analyser/src/codegen/js-sdk/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/path-analyser/src/codegen/js-sdk/README.md b/path-analyser/src/codegen/js-sdk/README.md index 25bc8a3..834f8ca 100644 --- a/path-analyser/src/codegen/js-sdk/README.md +++ b/path-analyser/src/codegen/js-sdk/README.md @@ -37,10 +37,10 @@ The operation-map file must be fetched before generation: npm run fetch-js-sdk-map ``` -This downloads the SDK's method-to-operationId mapping from the -`@camunda8/orchestration-cluster-api` npm package and writes it to -`spec/js-sdk/operation-map.json`. Without it, the emitter falls back to -identity mapping (operationId unchanged). +This performs a git sparse clone of `camunda/orchestration-cluster-api-js` and +writes `examples/operation-map.json` to `spec/js-sdk/operation-map.json`. +Requires `git` on PATH. Without it, the emitter falls back to identity mapping +(operationId unchanged). ⚠️ **Warning**: Fallback identity mapping may not work for all operations — some SDK methods have different names than their operationIds. Run @@ -54,3 +54,4 @@ some SDK methods have different names than their operationIds. Run | `CAMUNDA_CLIENT_ID` | — | OAuth2 client ID (SaaS) | | `CAMUNDA_CLIENT_SECRET` | — | OAuth2 client secret (SaaS) | | `CAMUNDA_OAUTH_URL` | — | OAuth2 token endpoint (SaaS) | +| `JS_SDK_REF` | `main` | Branch, tag, or SHA of `camunda/orchestration-cluster-api-js` to fetch the operation map from | From 13bdef2d0e42681cc611fe30ac0381d5a2eb87c7 Mon Sep 17 00:00:00 2001 From: yarm03 Date: Thu, 21 May 2026 17:58:03 +1200 Subject: [PATCH 20/20] fix: move SDK emitters to materializer and wire into codegen pipeline post-merge --- configs/camunda-oca/codegen/emitters.json | 2 +- .../camunda-oca/regression-invariants.test.ts | 42 ++++++++++--- .../src}/csharp-sdk/README.md | 0 .../src}/csharp-sdk/emitter.ts | 7 ++- .../src}/csharp-sdk/materialize-support.ts | 0 .../src}/csharp-sdk/operation-map.ts | 0 materializer/src/index.ts | 60 ++++++++++++------- .../src}/js-sdk/README.md | 0 .../src}/js-sdk/emitter.ts | 9 +-- .../src}/js-sdk/materialize-support.ts | 0 .../js-sdk/project-templates/.env.example | 0 .../src}/js-sdk/project-templates/README.md | 0 .../js-sdk/project-templates/package.json | 0 .../js-sdk/project-templates/tsconfig.json | 0 .../js-sdk/project-templates/vitest.config.ts | 0 .../src}/js-sdk/sdk-mapping.ts | 0 .../src}/python-sdk/README.md | 0 .../src}/python-sdk/emitter.ts | 17 +++--- .../src}/python-sdk/materialize-support.ts | 0 .../src}/python-sdk/sdk-mapping.ts | 0 materializer/templates/tsconfig.json | 2 +- package-lock.json | 1 + package.json | 11 ++-- scripts/fetch-python-sdk-map.ts | 5 +- tests/codegen/csharp-sdk-emitter.test.ts | 2 +- tests/codegen/js-sdk-emitter.test.ts | 4 +- tests/codegen/python-sdk-emitter.test.ts | 2 +- .../planner/csharp-sdk-emitter.test.ts | 2 +- tests/fixtures/planner/js-sdk-emitter.test.ts | 7 +-- 29 files changed, 106 insertions(+), 67 deletions(-) rename {path-analyser/src/codegen => materializer/src}/csharp-sdk/README.md (100%) rename {path-analyser/src/codegen => materializer/src}/csharp-sdk/emitter.ts (98%) rename {path-analyser/src/codegen => materializer/src}/csharp-sdk/materialize-support.ts (100%) rename {path-analyser/src/codegen => materializer/src}/csharp-sdk/operation-map.ts (100%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/README.md (100%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/emitter.ts (97%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/materialize-support.ts (100%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/project-templates/.env.example (100%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/project-templates/README.md (100%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/project-templates/package.json (100%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/project-templates/tsconfig.json (100%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/project-templates/vitest.config.ts (100%) rename {path-analyser/src/codegen => materializer/src}/js-sdk/sdk-mapping.ts (100%) rename {path-analyser/src/codegen => materializer/src}/python-sdk/README.md (100%) rename {path-analyser/src/codegen => materializer/src}/python-sdk/emitter.ts (95%) rename {path-analyser/src/codegen => materializer/src}/python-sdk/materialize-support.ts (100%) rename {path-analyser/src/codegen => materializer/src}/python-sdk/sdk-mapping.ts (100%) diff --git a/configs/camunda-oca/codegen/emitters.json b/configs/camunda-oca/codegen/emitters.json index 02e3830..394fecf 100644 --- a/configs/camunda-oca/codegen/emitters.json +++ b/configs/camunda-oca/codegen/emitters.json @@ -1,3 +1,3 @@ { - "emitters": ["playwright"] + "emitters": ["playwright", "js-sdk", "python-sdk", "csharp-sdk"] } diff --git a/configs/camunda-oca/regression-invariants.test.ts b/configs/camunda-oca/regression-invariants.test.ts index dc6d6e5..2c98bf4 100644 --- a/configs/camunda-oca/regression-invariants.test.ts +++ b/configs/camunda-oca/regression-invariants.test.ts @@ -8871,9 +8871,7 @@ describeForThisConfig('bundled-spec invariants: emitted Python SDK suite (#133)' `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, ); } - const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => - f.endsWith('.python_sdk.spec.py'), - ); + const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => f.endsWith('.python_sdk.spec.py')); if (files.length === 0) { // No Python SDK tests generated yet; skip return; @@ -8886,7 +8884,7 @@ describeForThisConfig('bundled-spec invariants: emitted Python SDK suite (#133)' assertionsRun++; const contextRefs = new Set(); const regexCtxRead = /ctx\['([^']+)'\]/g; - let match; + let match: RegExpExecArray | null; while ((match = regexCtxRead.exec(src)) !== null) { contextRefs.add(match[1]); } @@ -8925,7 +8923,21 @@ describeForThisConfig('bundled-spec invariants: emitted Python SDK suite (#133)' `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, ); } + // Python SDK and JS SDK hard-fail on scenarios whose prereqs require multipart + // uploads (e.g. createDeployment). The emitters apply the same hard-fail logic, + // so the set of operations they CAN emit is identical. We use the JS SDK's + // emitted .feature.test.ts files as the reference set: any operationId covered + // by the JS SDK must also have a Python SDK file, and vice versa. + const jsSdkEmitted = new Set( + readdirSync(GENERATED_TESTS_DIR) + .filter((f) => f.endsWith('.feature.test.ts')) + .map((f) => f.replace(/\.feature\.test\.ts$/, '')), + ); + if (jsSdkEmitted.size === 0) { + throw new Error('No JS SDK .feature.test.ts files found — run codegen:js-sdk:all first.'); + } const missing = planned + .filter((e) => jsSdkEmitted.has(e.operationId)) .map((e) => `${e.operationId}.python_sdk.spec.py`) .filter((f) => !existsSync(join(GENERATED_TESTS_DIR, f))); expect( @@ -8946,16 +8958,13 @@ describeForThisConfig('bundled-spec invariants: emitted JS SDK suite (#131)', () `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, ); } - const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => - f.endsWith('.feature.test.ts'), - ); + const files = readdirSync(GENERATED_TESTS_DIR).filter((f) => f.endsWith('.feature.test.ts')); if (files.length === 0) { return; } const offenders: string[] = []; for (const f of files) { const src = readFileSync(join(GENERATED_TESTS_DIR, f), 'utf8'); - // biome-ignore lint/suspicious/noTemplateCurlyInString: literal regex to detect unresolved placeholder syntax in generated JS SDK files if (/\$\{[^}]+\}/.test(src)) { offenders.push(f); } @@ -8987,7 +8996,23 @@ describeForThisConfig('bundled-spec invariants: emitted JS SDK suite (#131)', () `Generated tests directory not found at ${GENERATED_TESTS_DIR}. Run 'npm run testsuite:generate' (or 'npm run pipeline') first.`, ); } + // JS SDK and Python SDK hard-fail on scenarios whose prereqs require multipart + // uploads (e.g. createDeployment). The emitters apply the same hard-fail logic, + // so the set of operations they CAN emit is identical. We use the Python SDK's + // emitted .python_sdk.spec.py files as the reference set: any operationId + // covered by the Python SDK must also have a JS SDK file, and vice versa. + const pythonSdkEmitted = new Set( + readdirSync(GENERATED_TESTS_DIR) + .filter((f) => f.endsWith('.python_sdk.spec.py')) + .map((f) => f.replace(/\.python_sdk\.spec\.py$/, '')), + ); + if (pythonSdkEmitted.size === 0) { + throw new Error( + 'No Python SDK .python_sdk.spec.py files found — run codegen:python-sdk:all first.', + ); + } const missing = planned + .filter((e) => pythonSdkEmitted.has(e.operationId)) .map((e) => `${e.operationId}.feature.test.ts`) .filter((f) => !existsSync(join(GENERATED_TESTS_DIR, f))); expect( @@ -9048,4 +9073,3 @@ describeForThisConfig('bundled-spec invariants: emitted C# SDK suite (#132)', () ).toEqual([]); }); }); - diff --git a/path-analyser/src/codegen/csharp-sdk/README.md b/materializer/src/csharp-sdk/README.md similarity index 100% rename from path-analyser/src/codegen/csharp-sdk/README.md rename to materializer/src/csharp-sdk/README.md diff --git a/path-analyser/src/codegen/csharp-sdk/emitter.ts b/materializer/src/csharp-sdk/emitter.ts similarity index 98% rename from path-analyser/src/codegen/csharp-sdk/emitter.ts rename to materializer/src/csharp-sdk/emitter.ts index e840328..6812498 100644 --- a/path-analyser/src/codegen/csharp-sdk/emitter.ts +++ b/materializer/src/csharp-sdk/emitter.ts @@ -1,5 +1,5 @@ -import type { EndpointScenarioCollection } from '../../types.js'; -import type { EmitContext, EmittedFile, Emitter } from '../emitter.js'; +import type { EmitContext, EmittedFile, EmitterStrategy } from '@camunda8/emitter-sdk'; +import type { EndpointScenarioCollection } from 'path-analyser/types'; export interface CsharpOperationMapEntry { file?: string; @@ -9,10 +9,11 @@ export interface CsharpOperationMapEntry { export type CsharpOperationMap = Record; -export function createCsharpEmitter(map: CsharpOperationMap): Emitter { +export function createCsharpEmitter(map: CsharpOperationMap): EmitterStrategy { return { id: 'csharp-sdk', name: 'C# SDK', + supportedConfigs: ['*'], async emit(collection: EndpointScenarioCollection, ctx: EmitContext): Promise { const relativePath = `csharp/${collection.endpoint.operationId}.${ctx.mode}.cs`; const content = renderCsharpSuite(collection, ctx, map); diff --git a/path-analyser/src/codegen/csharp-sdk/materialize-support.ts b/materializer/src/csharp-sdk/materialize-support.ts similarity index 100% rename from path-analyser/src/codegen/csharp-sdk/materialize-support.ts rename to materializer/src/csharp-sdk/materialize-support.ts diff --git a/path-analyser/src/codegen/csharp-sdk/operation-map.ts b/materializer/src/csharp-sdk/operation-map.ts similarity index 100% rename from path-analyser/src/codegen/csharp-sdk/operation-map.ts rename to materializer/src/csharp-sdk/operation-map.ts diff --git a/materializer/src/index.ts b/materializer/src/index.ts index c10b1a8..7d13e12 100644 --- a/materializer/src/index.ts +++ b/materializer/src/index.ts @@ -30,6 +30,8 @@ import { getEmitterRoleForOperation } from 'path-analyser/ontology/operationRole import type { EndpointScenarioCollection, GlobalContextSeed } from 'path-analyser/types'; import { parseCliArgs } from './cli-args.js'; import { buildCoverage, type CoverageResult } from './coverage.js'; +import { createCsharpEmitter } from './csharp-sdk/emitter.js'; +import { createJsSdkEmitter } from './js-sdk/emitter.js'; import { writeEmitted, writeScaffolded } from './orchestrator.js'; import { PlaywrightEmitter } from './playwright/emitter.js'; import { @@ -40,6 +42,7 @@ import { } from './playwright/materialize-support.js'; import { loadRoleBundlesForActiveConfig } from './playwright/roleRenderer.js'; import { emitTemplateSuites } from './playwright/templateEmitter.js'; +import { createPythonSdkEmitter } from './python-sdk/emitter.js'; // Built-in emitter registrations. RoleHookProviders are no longer // registered statically here: every provider lives next to its role @@ -50,6 +53,9 @@ import { emitTemplateSuites } from './playwright/templateEmitter.js'; // pulled OCA-specific knowledge into a package that is supposed to be // config-agnostic. registerEmitter(PlaywrightEmitter); +registerEmitter(createJsSdkEmitter()); +registerEmitter(createPythonSdkEmitter()); +registerEmitter(createCsharpEmitter({})); /** * Walk the active config's role bundles and register any role-hook @@ -346,7 +352,12 @@ async function run() { // version cannot survive into the current run. Without this, local // pre-push validation can diverge from CI (which always sees a fresh tree). // The support/ tree, README.md, and responses.json are re-materialised below. - await fs.rm(outDir, { recursive: true, force: true }); + // SDK emitters (js-sdk, python-sdk, csharp-sdk) share the outDir with + // playwright and must NOT wipe it — they add complementary file types + // (.test.ts, .spec.py, .cs) alongside the existing .spec.ts files. + if (emitter.id === 'playwright') { + await fs.rm(outDir, { recursive: true, force: true }); + } await fs.mkdir(outDir, { recursive: true }); // Lift 12 / #231: per-role template bundles for the active config's // Playwright emitter. Loaded from configs//codegen/playwright/roles/ @@ -604,28 +615,31 @@ async function run() { } // #331: persist the coverage artefact alongside the suites so it // is diffable in PRs and consumable by the L3 invariant in - // configs//regression-invariants.test.ts. Written for - // every emitter so the artefact's presence is independent of - // whether the current target shipped template suites this run. - await fs.writeFile( - path.join(outDir, 'coverage.json'), - `${JSON.stringify( - { - version: 1, - suppressedOpIds: [...coverage.suppressedOpIds].sort(), - entries: [...coverage.entries].sort((a, b) => - a.operationId === b.operationId - ? a.template === b.template - ? a.aboxRow.localeCompare(b.aboxRow) || a.stepKind.localeCompare(b.stepKind) - : a.template.localeCompare(b.template) - : a.operationId.localeCompare(b.operationId), - ), - }, - null, - 2, - )}\n`, - 'utf8', - ); + // configs//regression-invariants.test.ts. Only written by + // the Playwright emitter — SDK emitters (js-sdk, python-sdk, csharp-sdk) + // do not suppress via scenario-template coverage and must not overwrite + // the playwright-written artefact with an empty suppressedOpIds list. + if (emitter.id === PlaywrightEmitter.id) { + await fs.writeFile( + path.join(outDir, 'coverage.json'), + `${JSON.stringify( + { + version: 1, + suppressedOpIds: [...coverage.suppressedOpIds].sort(), + entries: [...coverage.entries].sort((a, b) => + a.operationId === b.operationId + ? a.template === b.template + ? a.aboxRow.localeCompare(b.aboxRow) || a.stepKind.localeCompare(b.stepKind) + : a.template.localeCompare(b.template) + : a.operationId.localeCompare(b.operationId), + ), + }, + null, + 2, + )}\n`, + 'utf8', + ); + } console.log( `Generated test suites for ${count} endpoints (+${variantCount} variant suites, +${lifecycleCount} lifecycle suites, -${suppressedCount} suppressed by scenario-template coverage) in ${outDir} (target: ${emitter.id})`, ); diff --git a/path-analyser/src/codegen/js-sdk/README.md b/materializer/src/js-sdk/README.md similarity index 100% rename from path-analyser/src/codegen/js-sdk/README.md rename to materializer/src/js-sdk/README.md diff --git a/path-analyser/src/codegen/js-sdk/emitter.ts b/materializer/src/js-sdk/emitter.ts similarity index 97% rename from path-analyser/src/codegen/js-sdk/emitter.ts rename to materializer/src/js-sdk/emitter.ts index 41a061f..92418ca 100644 --- a/path-analyser/src/codegen/js-sdk/emitter.ts +++ b/materializer/src/js-sdk/emitter.ts @@ -1,11 +1,11 @@ -import { assertSafeGlobalContextSeeds } from '../../domainSemanticsValidator.js'; +import type { EmitContext, EmittedFile, EmitterStrategy } from '@camunda8/emitter-sdk'; +import { assertSafeGlobalContextSeeds } from 'path-analyser/ontology/loader'; import type { EndpointScenario, EndpointScenarioCollection, GlobalContextSeed, RequestStep, -} from '../../types.js'; -import type { EmitContext, EmittedFile, Emitter } from '../emitter.js'; +} from 'path-analyser/types'; import { FallbackMappingSource, type SdkMappingSource } from './sdk-mapping.js'; /** @@ -41,11 +41,12 @@ export function renderJsSdkSuite( * When no source is supplied, `FallbackMappingSource` is used, which returns * the operationId unchanged (already camelCase in the Camunda REST API). */ -export function createJsSdkEmitter(mapping?: SdkMappingSource): Emitter { +export function createJsSdkEmitter(mapping?: SdkMappingSource): EmitterStrategy { const source = mapping ?? new FallbackMappingSource(); return { id: 'js-sdk', name: 'JavaScript SDK (@camunda8/orchestration-cluster-api)', + supportedConfigs: ['*'], async emit(collection: EndpointScenarioCollection, ctx: EmitContext): Promise { const content = renderJsSdkSuite(collection, source, { suiteName: ctx.suiteName, diff --git a/path-analyser/src/codegen/js-sdk/materialize-support.ts b/materializer/src/js-sdk/materialize-support.ts similarity index 100% rename from path-analyser/src/codegen/js-sdk/materialize-support.ts rename to materializer/src/js-sdk/materialize-support.ts diff --git a/path-analyser/src/codegen/js-sdk/project-templates/.env.example b/materializer/src/js-sdk/project-templates/.env.example similarity index 100% rename from path-analyser/src/codegen/js-sdk/project-templates/.env.example rename to materializer/src/js-sdk/project-templates/.env.example diff --git a/path-analyser/src/codegen/js-sdk/project-templates/README.md b/materializer/src/js-sdk/project-templates/README.md similarity index 100% rename from path-analyser/src/codegen/js-sdk/project-templates/README.md rename to materializer/src/js-sdk/project-templates/README.md diff --git a/path-analyser/src/codegen/js-sdk/project-templates/package.json b/materializer/src/js-sdk/project-templates/package.json similarity index 100% rename from path-analyser/src/codegen/js-sdk/project-templates/package.json rename to materializer/src/js-sdk/project-templates/package.json diff --git a/path-analyser/src/codegen/js-sdk/project-templates/tsconfig.json b/materializer/src/js-sdk/project-templates/tsconfig.json similarity index 100% rename from path-analyser/src/codegen/js-sdk/project-templates/tsconfig.json rename to materializer/src/js-sdk/project-templates/tsconfig.json diff --git a/path-analyser/src/codegen/js-sdk/project-templates/vitest.config.ts b/materializer/src/js-sdk/project-templates/vitest.config.ts similarity index 100% rename from path-analyser/src/codegen/js-sdk/project-templates/vitest.config.ts rename to materializer/src/js-sdk/project-templates/vitest.config.ts diff --git a/path-analyser/src/codegen/js-sdk/sdk-mapping.ts b/materializer/src/js-sdk/sdk-mapping.ts similarity index 100% rename from path-analyser/src/codegen/js-sdk/sdk-mapping.ts rename to materializer/src/js-sdk/sdk-mapping.ts diff --git a/path-analyser/src/codegen/python-sdk/README.md b/materializer/src/python-sdk/README.md similarity index 100% rename from path-analyser/src/codegen/python-sdk/README.md rename to materializer/src/python-sdk/README.md diff --git a/path-analyser/src/codegen/python-sdk/emitter.ts b/materializer/src/python-sdk/emitter.ts similarity index 95% rename from path-analyser/src/codegen/python-sdk/emitter.ts rename to materializer/src/python-sdk/emitter.ts index 0988ad4..9cb1e4d 100644 --- a/path-analyser/src/codegen/python-sdk/emitter.ts +++ b/materializer/src/python-sdk/emitter.ts @@ -12,8 +12,8 @@ * - Hard-fail on multipart (unsupported in Python SDK integration) */ -import type { EndpointScenario, EndpointScenarioCollection } from '../../types.js'; -import type { EmitContext, EmittedFile, Emitter } from '../emitter.js'; +import type { EmitContext, EmittedFile, EmitterStrategy } from '@camunda8/emitter-sdk'; +import type { EndpointScenario, EndpointScenarioCollection } from 'path-analyser/types'; import { camelToSnake, createDefaultOperationMapSource, @@ -57,7 +57,7 @@ function toPythonLiteral(value: unknown): string { } // Otherwise use single quotes with escaping - const escaped = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'" ); + const escaped = value.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); return `'${escaped}'`; } @@ -66,7 +66,7 @@ function toPythonLiteral(value: unknown): string { // Handle arrays if (Array.isArray(value)) { - const items = value.map(v => toPythonLiteral(v)).join(', '); + const items = value.map((v) => toPythonLiteral(v)).join(', '); return `[${items}]`; } @@ -328,11 +328,14 @@ function renderPythonTestSuite( /** * Factory: create a Python SDK emitter backed by the given operation map. */ -export function createPythonSdkEmitter(operationMapSource?: OperationMapJsonSource): Emitter { +export function createPythonSdkEmitter( + operationMapSource?: OperationMapJsonSource, +): EmitterStrategy { const source = operationMapSource ?? createDefaultOperationMapSource(); return { id: 'python-sdk', name: 'Python SDK (Async)', + supportedConfigs: ['*'], async emit(collection: EndpointScenarioCollection, _ctx: EmitContext): Promise { const content = renderPythonTestSuite(collection, source); return [ @@ -346,7 +349,7 @@ export function createPythonSdkEmitter(operationMapSource?: OperationMapJsonSour } /** - * {@link Emitter} implementation for Python SDK tests. + * {@link EmitterStrategy} implementation for Python SDK tests. * * Pure: returns in-memory {@link EmittedFile} list, no filesystem access. * Uses default operation map (fallback camelToSnake). @@ -354,4 +357,4 @@ export function createPythonSdkEmitter(operationMapSource?: OperationMapJsonSour * For production use, consider using createPythonSdkEmitter() with a loaded * operation-map.json source for more accurate method name resolution. */ -export const PythonSdkEmitter: Emitter = createPythonSdkEmitter(); +export const PythonSdkEmitter: EmitterStrategy = createPythonSdkEmitter(); diff --git a/path-analyser/src/codegen/python-sdk/materialize-support.ts b/materializer/src/python-sdk/materialize-support.ts similarity index 100% rename from path-analyser/src/codegen/python-sdk/materialize-support.ts rename to materializer/src/python-sdk/materialize-support.ts diff --git a/path-analyser/src/codegen/python-sdk/sdk-mapping.ts b/materializer/src/python-sdk/sdk-mapping.ts similarity index 100% rename from path-analyser/src/codegen/python-sdk/sdk-mapping.ts rename to materializer/src/python-sdk/sdk-mapping.ts diff --git a/materializer/templates/tsconfig.json b/materializer/templates/tsconfig.json index ecafe08..5edb44c 100644 --- a/materializer/templates/tsconfig.json +++ b/materializer/templates/tsconfig.json @@ -10,6 +10,6 @@ "forceConsistentCasingInFileNames": true, "types": ["node"] }, - "include": ["**/*.ts"], + "include": ["**/*.spec.ts"], "exclude": ["node_modules"] } diff --git a/package-lock.json b/package-lock.json index 0c9594f..491a036 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2921,6 +2921,7 @@ "path-analyser": { "version": "0.1.0", "dependencies": { + "@camunda8/emitter-sdk": "*", "@playwright/test": "^1.54.2", "ajv": "^8.20.0", "yaml": "^2.5.0", diff --git a/package.json b/package.json index 38addc5..6c07a66 100644 --- a/package.json +++ b/package.json @@ -29,18 +29,19 @@ "generate:request-validation:shallow": "npm run generate:shallow -w request-validation && npm run biome:fix-generated:request-validation", "codegen:playwright": "npm run build:emitter-sdk && tsx materializer/src/index.ts && npm run biome:fix-generated:codegen", "codegen:playwright:all": "npm run build:emitter-sdk && tsx materializer/src/index.ts --all && npm run biome:fix-generated:codegen", - "test:pw": "npm run test:pw:path-analyser && npm run test:pw:request-validation", + "codegen:js-sdk:all": "tsx materializer/src/index.ts --target=js-sdk --all", + "codegen:python-sdk:all": "tsx materializer/src/index.ts --target=python-sdk --all", + "codegen:python-sdk": "tsx materializer/src/index.ts --target=python-sdk", + "codegen:csharp-sdk:all": "tsx materializer/src/index.ts --target=csharp-sdk --all", "test:pw:path-analyser": "npx playwright test -c path-analyser/playwright.config.ts", "test:pw:request-validation": "tsx scripts/run-pw-request-validation.ts", - "testsuite:generate": "npm run extract-graph && npm run generate:scenarios && npm run codegen:playwright:all", + "testsuite:generate": "npm run extract-graph && npm run generate:scenarios && npm run codegen:playwright:all && npm run codegen:js-sdk:all && npm run codegen:python-sdk:all && npm run codegen:csharp-sdk:all", "testsuite:observe:run": "npm run codegen:playwright:all && npm run test:pw:path-analyser && npm run observe:aggregate", "observe:aggregate": "npm run build:analyser && node path-analyser/dist/src/scripts/aggregate-observations.js", "optional-responses": "node optional-responses/report.js", "pipeline": "npm run fetch-spec && npm run fetch-python-sdk-map && npm run testsuite:generate && npm run generate:request-validation", "fetch-js-sdk-map": "node scripts/fetch-js-sdk-map.js", - "codegen:js-sdk:all": "npm run build:analyser && node path-analyser/dist/src/codegen/index.js --target=js-sdk --all", - "codegen:python-sdk:all": "npm run build:analyser && node path-analyser/dist/src/codegen/index.js --target=python-sdk --all", - "codegen:python-sdk": "npm run build:analyser && node path-analyser/dist/src/codegen/index.js --target=python-sdk", + "test:pw": "npm run test:pw:path-analyser && npm run test:pw:request-validation", "lint": "biome check", "lint:fix": "biome check --write", "lint:generated": "biome check --config-path=biome.generated.json", diff --git a/scripts/fetch-python-sdk-map.ts b/scripts/fetch-python-sdk-map.ts index e638d50..d857c55 100644 --- a/scripts/fetch-python-sdk-map.ts +++ b/scripts/fetch-python-sdk-map.ts @@ -90,10 +90,7 @@ try { writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), 'utf-8'); console.log(`[fetch-python-sdk-map] Written metadata to ${metadataPath}`); } catch (err) { - console.error( - '[fetch-python-sdk-map] Failed:', - err instanceof Error ? err.message : String(err), - ); + console.error('[fetch-python-sdk-map] Failed:', err instanceof Error ? err.message : String(err)); process.exit(1); } finally { try { diff --git a/tests/codegen/csharp-sdk-emitter.test.ts b/tests/codegen/csharp-sdk-emitter.test.ts index 492e18c..2bf0fee 100644 --- a/tests/codegen/csharp-sdk-emitter.test.ts +++ b/tests/codegen/csharp-sdk-emitter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'vitest'; -import { createCsharpEmitter } from '../../path-analyser/src/codegen/csharp-sdk/emitter.ts'; +import { createCsharpEmitter } from '../../materializer/src/csharp-sdk/emitter.ts'; import type { EndpointScenarioCollection } from '../../path-analyser/src/types.ts'; const COLLECTION: EndpointScenarioCollection = { diff --git a/tests/codegen/js-sdk-emitter.test.ts b/tests/codegen/js-sdk-emitter.test.ts index 0039bc9..7e816d8 100644 --- a/tests/codegen/js-sdk-emitter.test.ts +++ b/tests/codegen/js-sdk-emitter.test.ts @@ -3,11 +3,11 @@ import { createJsSdkEmitter, jsSdkSuiteFileName, renderJsSdkSuite, -} from '../../path-analyser/src/codegen/js-sdk/emitter.ts'; +} from '../../materializer/src/js-sdk/emitter.ts'; import { FallbackMappingSource, OperationMapJsonSource, -} from '../../path-analyser/src/codegen/js-sdk/sdk-mapping.ts'; +} from '../../materializer/src/js-sdk/sdk-mapping.ts'; import type { EndpointScenarioCollection } from '../../path-analyser/src/types.ts'; /** diff --git a/tests/codegen/python-sdk-emitter.test.ts b/tests/codegen/python-sdk-emitter.test.ts index 2176bc0..c887625 100644 --- a/tests/codegen/python-sdk-emitter.test.ts +++ b/tests/codegen/python-sdk-emitter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'vitest'; -import { PythonSdkEmitter } from '../../path-analyser/src/codegen/python-sdk/emitter.js'; +import { PythonSdkEmitter } from '../../materializer/src/python-sdk/emitter.js'; import type { EndpointScenarioCollection, RequestStep } from '../../path-analyser/src/types.js'; /** diff --git a/tests/fixtures/planner/csharp-sdk-emitter.test.ts b/tests/fixtures/planner/csharp-sdk-emitter.test.ts index 27011dd..9a40637 100644 --- a/tests/fixtures/planner/csharp-sdk-emitter.test.ts +++ b/tests/fixtures/planner/csharp-sdk-emitter.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { createCsharpEmitter } from '../../../path-analyser/src/codegen/csharp-sdk/emitter.ts'; +import { createCsharpEmitter } from '../../../materializer/src/csharp-sdk/emitter.ts'; import type { EndpointScenarioCollection } from '../../../path-analyser/src/types.ts'; /** diff --git a/tests/fixtures/planner/js-sdk-emitter.test.ts b/tests/fixtures/planner/js-sdk-emitter.test.ts index 2398428..0266487 100644 --- a/tests/fixtures/planner/js-sdk-emitter.test.ts +++ b/tests/fixtures/planner/js-sdk-emitter.test.ts @@ -1,9 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { - jsSdkSuiteFileName, - renderJsSdkSuite, -} from '../../../path-analyser/src/codegen/js-sdk/emitter.ts'; -import { FallbackMappingSource } from '../../../path-analyser/src/codegen/js-sdk/sdk-mapping.ts'; +import { jsSdkSuiteFileName, renderJsSdkSuite } from '../../../materializer/src/js-sdk/emitter.ts'; +import { FallbackMappingSource } from '../../../materializer/src/js-sdk/sdk-mapping.ts'; import type { EndpointScenarioCollection } from '../../../path-analyser/src/types.ts'; /**