From 440563977d9fcf94631267f5beb7f27150988a51 Mon Sep 17 00:00:00 2001 From: Lucas Holmquist Date: Thu, 30 Jul 2026 11:25:45 -0400 Subject: [PATCH 1/2] test(orchestrator): increase test coverage, dev harness, and contributor docs Assisted-by: Cursor AI --- .../.changeset/itchy-flowers-smash.md | 7 + .../CONTRIBUTING.md | 91 +++++++++ .../README.md | 2 + .../src/module.test.ts | 56 ++++++ .../orchestrator-backend/CONTRIBUTING.md | 136 +++++++++++++ .../plugins/orchestrator-backend/README.md | 2 + .../orchestrator-backend/src/plugin.test.ts | 188 ++++++++++++++++++ .../src/service/OrchestratorService.test.ts | 13 +- .../CONTRIBUTING.MD | 142 +++++++++++++ .../README.md | 2 + .../app-config.yaml | 12 ++ .../dev/index.ts | 26 +++ .../package.json | 3 + .../src/module.test.ts | 45 +++++ workspaces/orchestrator/yarn.lock | 15 +- 15 files changed, 732 insertions(+), 8 deletions(-) create mode 100644 workspaces/orchestrator/.changeset/itchy-flowers-smash.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend-module-loki/CONTRIBUTING.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend-module-loki/src/module.test.ts create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/CONTRIBUTING.md create mode 100644 workspaces/orchestrator/plugins/orchestrator-backend/src/plugin.test.ts create mode 100644 workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/CONTRIBUTING.MD create mode 100644 workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/app-config.yaml create mode 100644 workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/dev/index.ts create mode 100644 workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/src/module.test.ts diff --git a/workspaces/orchestrator/.changeset/itchy-flowers-smash.md b/workspaces/orchestrator/.changeset/itchy-flowers-smash.md new file mode 100644 index 00000000000..bc9f222308e --- /dev/null +++ b/workspaces/orchestrator/.changeset/itchy-flowers-smash.md @@ -0,0 +1,7 @@ +--- +'@red-hat-developer-hub/backstage-plugin-scaffolder-backend-module-orchestrator': patch +'@red-hat-developer-hub/backstage-plugin-orchestrator-backend-module-loki': patch +'@red-hat-developer-hub/backstage-plugin-orchestrator-backend': patch +--- + +Add module wiring tests, a local `dev/` harness, and contributor documentation. diff --git a/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/CONTRIBUTING.md b/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/CONTRIBUTING.md new file mode 100644 index 00000000000..e67a62dd6c0 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/CONTRIBUTING.md @@ -0,0 +1,91 @@ +# Contributing — Orchestrator Loki backend module + +Developer guide for +`@red-hat-developer-hub/backstage-plugin-orchestrator-backend-module-loki`. For +operator install and Loki configuration, see [README.md](./README.md). + +## Prerequisites + +- Node.js **22 or 24** (see workspace `engines` in + `workspaces/orchestrator/package.json`) +- Yarn (this workspace has its own `yarn.lock`; run commands from + `workspaces/orchestrator/`) +- The host + [`orchestrator-backend`](../orchestrator-backend) plugin (this module + registers against its workflow-logs extension point) + +## Development harness + +This package has **no** standalone `dev/` harness. Day-to-day work uses package +tests. To exercise the module in a running backend, load it next to +`orchestrator-backend` (see [README.md](./README.md) installation), for example +in a consumer app or the workspace +[`yarn dev`](../../docs/local-development.md) stack with Loki configured. + +Do not add a second full Backstage application under this package. + +### Config notes + +Required Loki keys live under `orchestrator.workflowLogProvider.loki` (`baseUrl`, +`token`, and optional hardening flags). Use local-only placeholder values for +development — do not commit secrets. See [README.md](./README.md) for the full +config table. + +## Validation commands + +From the workspace root (`workspaces/orchestrator`): + +```bash +yarn workspace @red-hat-developer-hub/backstage-plugin-orchestrator-backend-module-loki test +yarn workspace @red-hat-developer-hub/backstage-plugin-orchestrator-backend-module-loki lint +yarn tsc:full +``` + +## What automated tests cover + +CI exercises: + +- **Module wiring** — `startTestBackend` smoke that the module calls + `addWorkflowLogProvider` once with a provider whose id is `loki` +- **LokiProvider / helpers** — config validation (base URL, hosts, pipeline + filters), query URL construction, and HTTP error mapping + +CI does **not** replace reading +[Backstage release notes](https://github.com/backstage/backstage/releases) for +the `@backstage/*` packages this module depends on. After a dependency bump, +review those notes and decide whether additional validation is warranted. + +Live Loki log-content matrices and production log pipelines are out of scope for +bump-trust CI. + +## Full workspace app evaluation + +Bump default for this module is **package tests** (registration smoke + provider +units). The orchestrator workspace also includes `packages/app` and +`packages/backend`, started with [`yarn dev`](../../docs/local-development.md) +when you need SonataFlow + UI + a real Loki endpoint. + +End-to-end “view log” flows against a live Loki tenant belong in a consumer +Backstage / RHDH app or in +[rhdh-plugin-export-overlays](https://github.com/redhat-developer/rhdh-plugin-export-overlays). + +## Optional manual smoke checklist + +Use when you change extension-point registration or Loki client wiring, or when +reviewing a Backstage version bump: + +1. Confirm `yarn test` passes, including `src/module.test.ts`. +2. Optionally load this module with `orchestrator-backend` in a consumer or + workspace backend that has `orchestrator.workflowLogProvider.loki` configured, + then hit + `/api/orchestrator/v2/workflows/instances//logs` for a known + instance. +3. Playwright / production Loki coverage belongs in overlays or a consumer + deployment — not as a required signal for this module’s bump trust. + +## Related packages + +- [@red-hat-developer-hub/backstage-plugin-orchestrator-backend](../orchestrator-backend) + — host plugin that owns `workflowLogsExtensionEndpoint` +- [@red-hat-developer-hub/backstage-plugin-orchestrator-node](../orchestrator-node) + — extension-point and `WorkflowLogProvider` contract diff --git a/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/README.md b/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/README.md index 9b58bbd16b5..b598a190161 100644 --- a/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/README.md +++ b/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/README.md @@ -2,6 +2,8 @@ This is an extension module to the `backstage-plugin-orchestrator-backend` plugin. It provides access to the Loki log provider +For local development and contributor workflows, see [CONTRIBUTING.md](./CONTRIBUTING.md). + ## Prerequisites Before installing this module, ensure that the Orchestrator backend plugin is integrated into your Backstage instance. Follow the [Orchestrator README](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator) for setup instructions. diff --git a/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/src/module.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/src/module.test.ts new file mode 100644 index 00000000000..3e451b46f38 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend-module-loki/src/module.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices, startTestBackend } from '@backstage/backend-test-utils'; +import { + WorkflowLogProvider, + workflowLogsExtensionEndpoint, +} from '@red-hat-developer-hub/backstage-plugin-orchestrator-node'; + +import { orchestratorModuleLoki } from './module'; + +describe('orchestratorModuleLoki', () => { + it('registers the Loki workflow log provider on the extension point', async () => { + const extensionPoint = { + addWorkflowLogProvider: jest.fn(), + }; + + await startTestBackend({ + extensionPoints: [[workflowLogsExtensionEndpoint, extensionPoint]], + features: [ + orchestratorModuleLoki, + mockServices.rootConfig.factory({ + data: { + orchestrator: { + workflowLogProvider: { + loki: { + baseUrl: 'http://localhost:3100', + token: 'test-token', + allowInsecureHttp: true, + }, + }, + }, + }, + }), + ], + }); + + expect(extensionPoint.addWorkflowLogProvider).toHaveBeenCalledTimes(1); + const provider = extensionPoint.addWorkflowLogProvider.mock + .calls[0][0] as WorkflowLogProvider; + expect(provider.getProviderId()).toBe('loki'); + }); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/CONTRIBUTING.md b/workspaces/orchestrator/plugins/orchestrator-backend/CONTRIBUTING.md new file mode 100644 index 00000000000..e0201b5b625 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/CONTRIBUTING.md @@ -0,0 +1,136 @@ +# Contributing — Orchestrator backend plugin + +Developer guide for +`@red-hat-developer-hub/backstage-plugin-orchestrator-backend`. For operator +install and configuration, see [README.md](./README.md) and the workspace +[Orchestrator frontend README](../orchestrator/README.md). + +## Prerequisites + +- Node.js **22 or 24** (see workspace `engines` in + `workspaces/orchestrator/package.json`) +- Yarn (this workspace has its own `yarn.lock`; run commands from + `workspaces/orchestrator/`) + +## Development harness + +Start this plugin in isolation: + +```bash +yarn workspace @red-hat-developer-hub/backstage-plugin-orchestrator-backend start \ + --config app-config.yaml +``` + +(`--config` paths are resolved from the plugins directory.) + +This runs [`dev/index.ts`](dev/index.ts): a minimal backend with +`orchestratorPlugin` only. Use it to verify HTTP mount and local API wiring. + +The harness listens on the backend port from config (commonly **7007**). Only +one plugin `dev/` harness should run on that port at a time. + +### Config stubs and `autoStart` + +[`app-config.yaml`](./app-config.yaml) in this package is the **minimal** config +for local harness work (data-index URL stub). Prefer starting with +`--config app-config.yaml` as shown above. + +Optional overrides can go in an untracked `app-config.local.yaml` next to the +config file you pass. + +Integration tests and bump-trust CI must keep SonataFlow container launch off: + +```yaml +orchestrator: + sonataFlowService: + autoStart: false + dataIndexService: + url: http://localhost:8080 # stub; tests mock service collaborators +``` + +`autoStart: true` launches a SonataFlow container via `DevModeService`. That is +for full workspace local development +([`docs/local-development.md`](../../docs/local-development.md)), not for +package unit/integration tests. + +Do not commit secrets in package or test config. + +### Unauthenticated health check + +`GET /api/orchestrator/health` is registered with an unauthenticated auth +policy. You can smoke it without a bearer token: + +```bash +curl "http://localhost:7007/api/orchestrator/health" +``` + +Expect `{ "status": "ok" }`. + +## Validation commands + +From the workspace root (`workspaces/orchestrator`): + +```bash +yarn workspace @red-hat-developer-hub/backstage-plugin-orchestrator-backend test +yarn workspace @red-hat-developer-hub/backstage-plugin-orchestrator-backend lint:check +yarn tsc:full +``` + +## What automated tests cover + +CI exercises: + +- **Plugin wiring (`startTestBackend`)** — `/health` succeeds without auth; + health remains open when permissions deny; representative **execute** and + **logs** routes return **403** when permission is denied +- **Router authorization** — deeper ALLOW / DENY / CONDITIONAL matrices in + `src/service/router.test.ts` (not re-duplicated at plugin level) +- **OrchestratorService** — proportional outcome assertions (not mock-call-only) + for bump-sensitive paths such as abort and execute +- **Other unit suites** — SonataFlow client, data index, mappings, permissions + rules, etc. + +CI does **not** replace reading +[Backstage release notes](https://github.com/backstage/backstage/releases) for +the `@backstage/*` packages this plugin depends on. After a dependency bump, +review those notes and decide whether additional validation is warranted. + +## Full workspace app evaluation + +Bump default for this package is **plugin `dev/` + package tests**. The +orchestrator workspace also includes `packages/app` and `packages/backend`, +started with [`yarn dev`](../../docs/local-development.md) when you need +SonataFlow container auto-start, workflow clone, and the full UI. + +Do **not** add a second full Backstage application under this package for +day-to-day contributor or CI work. Multi-user / credential-backed / +production-like e2e belongs in a consumer RHDH deployment or +[rhdh-plugin-export-overlays](https://github.com/redhat-developer/rhdh-plugin-export-overlays). + +## Optional manual smoke checklist + +Use when you change HTTP router / auth-policy code or are reviewing a Backstage +version bump: + +1. Start this harness with `--config app-config.yaml` (and local overrides if + needed). Ensure `autoStart` is false / unset unless you intentionally want a + SonataFlow container. +2. Hit health: + + ```bash + curl "http://localhost:7007/api/orchestrator/health" + ``` + +3. Protected execute/log routes require a running permission policy and (for + most paths) a reachable data-index / workflow services. Prefer the automated + DENY→403 cases in `src/plugin.test.ts` for bump confidence. Full SonataFlow + / Playwright coverage belongs in overlays or a consumer deployment. + +## Related packages + +- [@red-hat-developer-hub/backstage-plugin-orchestrator](../orchestrator) — + frontend plugin +- [@red-hat-developer-hub/backstage-plugin-orchestrator-backend-module-loki](../orchestrator-backend-module-loki) + — workflow log provider module +- [@red-hat-developer-hub/backstage-plugin-scaffolder-backend-module-orchestrator](../scaffolder-backend-module-orchestrator) + — scaffolder actions that call this API diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/README.md b/workspaces/orchestrator/plugins/orchestrator-backend/README.md index ccdd71d6d33..abd5befde88 100644 --- a/workspaces/orchestrator/plugins/orchestrator-backend/README.md +++ b/workspaces/orchestrator/plugins/orchestrator-backend/README.md @@ -3,3 +3,5 @@ Welcome to the backend package for the Orchestrator plugin! For more information about the Orchestrator plugin, see the [Orchestrator Plugin documentation](https://github.com/redhat-developer/rhdh-plugins/tree/main/workspaces/orchestrator/plugins/orchestrator) on GitHub. + +For local development and contributor workflows, see [CONTRIBUTING.md](./CONTRIBUTING.md). diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/plugin.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/plugin.test.ts new file mode 100644 index 00000000000..55bb649e285 --- /dev/null +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/plugin.test.ts @@ -0,0 +1,188 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { BackendFeature } from '@backstage/backend-plugin-api'; +import { + mockCredentials, + mockServices, + startTestBackend, +} from '@backstage/backend-test-utils'; +import { AuthorizeResult } from '@backstage/plugin-permission-common'; + +import request from 'supertest'; + +import { orchestratorPlugin } from './plugin'; + +jest.mock('./service/DataIndexService', () => ({ + DataIndexService: jest.fn().mockImplementation(() => ({})), +})); + +jest.mock('./service/SonataFlowService', () => ({ + SonataFlowService: jest.fn().mockImplementation(() => ({})), +})); + +jest.mock('./service/WorkflowCacheService', () => ({ + WorkflowCacheService: jest.fn().mockImplementation(() => ({ + schedule: jest.fn(), + })), +})); + +jest.mock('./service/OrchestratorService', () => ({ + OrchestratorService: jest.fn().mockImplementation(() => ({ + fetchWorkflowInfo: jest.fn().mockResolvedValue({ + id: 'workflow1', + serviceUrl: 'http://localhost:8080', + }), + fetchInstance: jest.fn().mockResolvedValue({ + id: 'instance1', + processId: 'workflow1', + processName: 'Workflow 1', + nodes: [], + state: 'ACTIVE', + start: '2024-01-01T00:00:00.000Z', + end: null, + endpoint: '/workflow1', + serviceUrl: 'http://localhost:8080', + variables: { + workflowdata: {}, + initiatorEntity: 'user:default/test', + }, + }), + hasLogProvider: jest.fn().mockReturnValue(true), + fetchWorkflowLogsByInstance: jest.fn().mockResolvedValue({ + instanceId: 'instance1', + logs: [], + }), + })), +})); + +jest.mock('./service/DataInputSchemaService', () => ({ + DataInputSchemaService: jest.fn().mockImplementation(() => ({ + extractWorkflowData: jest.fn().mockReturnValue({}), + })), +})); + +jest.mock('@red-hat-developer-hub/backstage-plugin-orchestrator-common', () => { + const actual = jest.requireActual( + '@red-hat-developer-hub/backstage-plugin-orchestrator-common', + ); + const openApiDocument = structuredClone(actual.openApiDocument); + if (openApiDocument.components?.schemas?.Filter) { + openApiDocument.components.schemas.Filter = { + type: 'object', + additionalProperties: true, + }; + } + if (openApiDocument.components?.schemas?.LogicalFilter) { + openApiDocument.components.schemas.LogicalFilter = { + type: 'object', + additionalProperties: true, + }; + } + if (openApiDocument.components?.schemas?.NestedFilter) { + openApiDocument.components.schemas.NestedFilter = { + type: 'object', + additionalProperties: true, + }; + } + return { ...actual, openApiDocument }; +}); + +const BASE_CONFIG = { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + orchestrator: { + dataIndexService: { url: 'http://localhost:8080' }, + sonataFlowService: { autoStart: false }, + }, +}; + +async function startOrchestratorBackend(options?: { + authorizeResult?: AuthorizeResult.ALLOW | AuthorizeResult.DENY; +}) { + const authorizeResult = options?.authorizeResult ?? AuthorizeResult.DENY; + const features: (BackendFeature | Promise<{ default: BackendFeature }>)[] = [ + orchestratorPlugin, + mockServices.rootLogger.factory(), + mockServices.rootConfig.factory({ data: BASE_CONFIG }), + mockServices.httpAuth.factory({ + defaultCredentials: mockCredentials.user('user:default/test'), + }), + mockServices.userInfo.factory(), + mockServices.permissions.mock({ + authorize: async requests => + requests.map(() => ({ result: authorizeResult })), + authorizeConditional: async requests => + requests.map(() => ({ result: authorizeResult })), + }).factory, + ]; + + return (await startTestBackend({ features })).server; +} + +describe('orchestratorPlugin (startTestBackend)', () => { + it('GET /api/orchestrator/health returns ok without authentication', async () => { + const server = await startOrchestratorBackend(); + + const response = await request(server) + .get('/api/orchestrator/health') + .set('Authorization', mockCredentials.none.header()); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + + it('GET /api/orchestrator/health returns ok when permissions deny', async () => { + const server = await startOrchestratorBackend({ + authorizeResult: AuthorizeResult.DENY, + }); + + const response = await request(server).get('/api/orchestrator/health'); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + + it.each([ + [ + 'POST /v2/workflows/:workflowId/execute', + (agent: ReturnType) => + agent + .post('/api/orchestrator/v2/workflows/workflow1/execute') + .send({ inputData: {} }), + ], + [ + 'GET /v2/workflows/instances/:instanceId/logs', + (agent: ReturnType) => + agent.get('/api/orchestrator/v2/workflows/instances/instance1/logs'), + ], + ] as const)( + '%s returns 403 when permissions deny', + async (_name, reqHandler) => { + const server = await startOrchestratorBackend({ + authorizeResult: AuthorizeResult.DENY, + }); + + const response = await reqHandler(request(server)); + + expect(response.status).toBe(403); + }, + ); +}); diff --git a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.test.ts b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.test.ts index fc39ec957d8..da69a422f5d 100644 --- a/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.test.ts +++ b/workspaces/orchestrator/plugins/orchestrator-backend/src/service/OrchestratorService.test.ts @@ -102,7 +102,11 @@ describe('OrchestratorService', () => { serviceUrl, }); - expect(sonataFlowServiceMock.abortInstance).toHaveBeenCalled(); + expect(sonataFlowServiceMock.abortInstance).toHaveBeenCalledWith({ + definitionId, + instanceId, + serviceUrl, + }); }); }); @@ -447,7 +451,12 @@ describe('OrchestratorService', () => { inputData, }); - expect(result).toBeDefined(); + expect(result).toEqual(executeResponse); + expect(sonataFlowServiceMock.executeWorkflow).toHaveBeenCalledWith({ + definitionId, + serviceUrl, + inputData, + }); }); }); diff --git a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/CONTRIBUTING.MD b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/CONTRIBUTING.MD new file mode 100644 index 00000000000..9e7181ddd85 --- /dev/null +++ b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/CONTRIBUTING.MD @@ -0,0 +1,142 @@ +# Contributing — Orchestrator scaffolder backend module + +Developer guide for +`@red-hat-developer-hub/backstage-plugin-scaffolder-backend-module-orchestrator`. +For operator install and configuration, see [README.md](./README.md). + +## Prerequisites + +- Node.js **22 or 24** (see workspace `engines` in + `workspaces/orchestrator/package.json`) +- Yarn (this workspace has its own `yarn.lock`; run commands from + `workspaces/orchestrator/`) + +## Development harness + +Start this module in isolation with the package’s minimal harness config: + +```bash +export BACKSTAGE_DEV_STATIC_TOKEN=dev-static-token-min-8 + +yarn workspace @red-hat-developer-hub/backstage-plugin-scaffolder-backend-module-orchestrator start \ + --config app-config.yaml +``` + +(`--config` paths are resolved from the plugins directory.) + +This runs [`dev/index.ts`](dev/index.ts): a minimal backend with +`@backstage/plugin-scaffolder-backend` and this module. Use it to verify action +registration and local scaffolder integration work. + +The harness listens on port **7007**. Only one plugin `dev/` harness should run +on that port at a time. + +### Environment setup + +Export these variables in your shell before starting the harness. Use local-only +placeholder values for development — do not commit secrets. + +| Variable | Purpose | +| ---------------------------- | --------------------------------------------------------------------- | +| `BACKSTAGE_DEV_STATIC_TOKEN` | Static bearer token for authenticated `curl` calls to the dev backend | + +[`app-config.yaml`](./app-config.yaml) in this package is the **minimal** config +required to run the dev harness (listen port and static auth). Prefer starting +with `--config app-config.yaml` as shown above. + +Optional overrides can go in an untracked `app-config.local.yaml` next to the +config file you pass. + +### API authentication for `curl` + +The harness [`app-config.yaml`](./app-config.yaml) registers a **static** +backend access token (see +[service-to-service auth](https://backstage.io/docs/auth/service-to-service-auth)): + +```yaml +backend: + auth: + externalAccess: + - type: static + options: + token: ${BACKSTAGE_DEV_STATIC_TOKEN} + subject: user:default/guest +``` + +Authenticated scaffolder API requests must send that token: + +```bash +curl -H "Authorization: Bearer ${BACKSTAGE_DEV_STATIC_TOKEN}" \ + "http://localhost:7007/api/scaffolder/v2/actions" +``` + +Requests without a valid `Authorization: Bearer …` header are rejected when the +default auth policy applies. + +## Validation commands + +From the workspace root (`workspaces/orchestrator`): + +```bash +yarn workspace @red-hat-developer-hub/backstage-plugin-scaffolder-backend-module-orchestrator test +yarn workspace @red-hat-developer-hub/backstage-plugin-scaffolder-backend-module-orchestrator lint:check +yarn tsc:full +``` + +## What automated tests cover + +CI exercises: + +- **Module wiring** — scaffolder extension-point registration of + `orchestrator:workflow:run` and `orchestrator:workflow:get_params` +- **Action handlers** — mocked discovery/auth happy paths, dry-run short-circuit + (run), and representative error mapping via `getError` +- **Utils** — discovery base URL, plugin request token / secret fallback, axios + error reshaping + +CI does **not** replace reading +[Backstage release notes](https://github.com/backstage/backstage/releases) for +the `@backstage/*` packages this module depends on. After a dependency bump, +review those notes and decide whether additional validation is warranted. + +## Full workspace app evaluation + +Bump default for this package is **plugin `dev/` + package tests**. The +orchestrator workspace also has a full app (`packages/app`, `packages/backend`) +started with [`yarn dev`](../../docs/local-development.md) for SonataFlow + UI +flows. Do **not** add a second full Backstage application under this package. + +End-to-end software-template runs against a live orchestrator backend belong in +a consumer Backstage / RHDH app or in +[rhdh-plugin-export-overlays](https://github.com/redhat-developer/rhdh-plugin-export-overlays). + +## Optional manual smoke checklist + +Use when you change scaffolder integration code or are reviewing a Backstage +version bump: + +1. Export the [environment variables](#environment-setup) and start this harness. +2. List registered scaffolder actions (requires Bearer token): + + ```bash + curl -H "Authorization: Bearer ${BACKSTAGE_DEV_STATIC_TOKEN}" \ + "http://localhost:7007/api/scaffolder/v2/actions" + ``` + + Expect these action IDs in the response: + + - `orchestrator:workflow:run` + - `orchestrator:workflow:get_params` + +3. End-to-end template execution (running a software template through the UI or a + full consumer Backstage app) is not covered by this harness alone. Confirm + template output under `result` in a consumer app when needed. Live SonataFlow + / Playwright coverage belongs in overlays or a consumer deployment — not as a + required signal for this package’s bump trust. + +## Related packages + +- [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend) + — host scaffolder backend plugin for this module +- [@red-hat-developer-hub/backstage-plugin-orchestrator-backend](../orchestrator-backend) + — HTTP API these actions call via discovery diff --git a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/README.md b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/README.md index f3c309b9f8a..bf3b7ae1adb 100644 --- a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/README.md +++ b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/README.md @@ -4,6 +4,8 @@ The Orchestrator module [@backstage/plugin-scaffolder-backend](https://www.npmjs An example of using these actions can be found in the `workspaces/orchestrator/entities/convertWorkflowToTemplate.yaml` template. +For local development and contributor workflows, see [CONTRIBUTING.md](./CONTRIBUTING.md). + ## Installation ### Static Plugin Installation diff --git a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/app-config.yaml b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/app-config.yaml new file mode 100644 index 00000000000..693604570d0 --- /dev/null +++ b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/app-config.yaml @@ -0,0 +1,12 @@ +backend: + baseUrl: http://localhost:7007 + listen: + port: 7007 + auth: + # Dev-only static token for curl and other local API checks. Do not use in production. + # Export BACKSTAGE_DEV_STATIC_TOKEN (min 8 chars) before starting the harness. + externalAccess: + - type: static + options: + token: ${BACKSTAGE_DEV_STATIC_TOKEN} + subject: user:default/guest diff --git a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/dev/index.ts b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/dev/index.ts new file mode 100644 index 00000000000..bbe90dfb2bc --- /dev/null +++ b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/dev/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createBackend } from '@backstage/backend-defaults'; + +import scaffolderBackendModuleOrchestratorPlugin from '../src/'; + +const backend = createBackend(); + +backend.add(import('@backstage/plugin-scaffolder-backend')); +backend.add(scaffolderBackendModuleOrchestratorPlugin); + +backend.start(); diff --git a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/package.json b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/package.json index 841a15c9e4d..922abb0cba1 100644 --- a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/package.json +++ b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/package.json @@ -65,7 +65,10 @@ "js-yaml": "^4.1.0" }, "devDependencies": { + "@backstage/backend-defaults": "^0.17.6", + "@backstage/backend-test-utils": "^1.11.4", "@backstage/cli": "^0.36.3", + "@backstage/plugin-scaffolder-backend": "^4.0.2", "@backstage/plugin-scaffolder-node-test-utils": "^0.3.12", "@janus-idp/cli": "3.7.0", "@spotify/prettier-config": "^15.0.0", diff --git a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/src/module.test.ts b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/src/module.test.ts new file mode 100644 index 00000000000..fb7f5c3ccbb --- /dev/null +++ b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/src/module.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { startTestBackend } from '@backstage/backend-test-utils'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node'; + +import { scaffolderModule } from './module'; + +describe('scaffolderModule', () => { + it('registers orchestrator scaffolder actions', async () => { + const scaffolderActions = { + addActions: jest.fn(), + }; + + await startTestBackend({ + extensionPoints: [[scaffolderActionsExtensionPoint, scaffolderActions]], + features: [scaffolderModule], + }); + + expect(scaffolderActions.addActions).toHaveBeenCalledTimes(2); + + const registeredIds = scaffolderActions.addActions.mock.calls.map( + ([action]) => action.id, + ); + expect(registeredIds).toEqual( + expect.arrayContaining([ + 'orchestrator:workflow:run', + 'orchestrator:workflow:get_params', + ]), + ); + }); +}); diff --git a/workspaces/orchestrator/yarn.lock b/workspaces/orchestrator/yarn.lock index 6fd6e70f3fe..573db75ad79 100644 --- a/workspaces/orchestrator/yarn.lock +++ b/workspaces/orchestrator/yarn.lock @@ -2771,9 +2771,9 @@ __metadata: languageName: node linkType: hard -"@backstage/backend-defaults@npm:^0.17.3, @backstage/backend-defaults@npm:^0.17.5": - version: 0.17.5 - resolution: "@backstage/backend-defaults@npm:0.17.5" +"@backstage/backend-defaults@npm:^0.17.3, @backstage/backend-defaults@npm:^0.17.5, @backstage/backend-defaults@npm:^0.17.6": + version: 0.17.6 + resolution: "@backstage/backend-defaults@npm:0.17.6" dependencies: "@aws-sdk/abort-controller": "npm:^3.347.0" "@aws-sdk/client-codecommit": "npm:^3.350.0" @@ -2788,7 +2788,7 @@ __metadata: "@backstage/backend-plugin-api": "npm:^1.9.3" "@backstage/cli-node": "npm:^0.3.4" "@backstage/config": "npm:^1.3.8" - "@backstage/config-loader": "npm:^1.11.0" + "@backstage/config-loader": "npm:^1.11.1" "@backstage/errors": "npm:^1.3.1" "@backstage/integration": "npm:^2.0.3" "@backstage/integration-aws-node": "npm:^0.2.0" @@ -2856,7 +2856,7 @@ __metadata: optional: true better-sqlite3: optional: true - checksum: 10c0/bd37cfb65c181ec91941cbc183d3d520bb571e60a73d8f150afb2aa44593c77d00cc0a3b0b73ed9b87acd43df0534c84ca2e6224fa94798daca014519e424421 + checksum: 10c0/a9a2d68c086a7dd626bf928df887af87a689740fafbe3016739505e69d1a8b64d2d2cc219ddef5cdf7f536ec83c523679b06103454ec4c511340ccde8db3f447 languageName: node linkType: hard @@ -4677,7 +4677,7 @@ __metadata: languageName: node linkType: hard -"@backstage/plugin-scaffolder-backend@npm:^4.0.1": +"@backstage/plugin-scaffolder-backend@npm:^4.0.1, @backstage/plugin-scaffolder-backend@npm:^4.0.2": version: 4.0.2 resolution: "@backstage/plugin-scaffolder-backend@npm:4.0.2" dependencies: @@ -11238,10 +11238,13 @@ __metadata: version: 0.0.0-use.local resolution: "@red-hat-developer-hub/backstage-plugin-scaffolder-backend-module-orchestrator@workspace:plugins/scaffolder-backend-module-orchestrator" dependencies: + "@backstage/backend-defaults": "npm:^0.17.6" "@backstage/backend-plugin-api": "npm:^1.9.2" + "@backstage/backend-test-utils": "npm:^1.11.4" "@backstage/catalog-model": "npm:^1.9.0" "@backstage/cli": "npm:^0.36.3" "@backstage/plugin-permission-common": "npm:^0.9.9" + "@backstage/plugin-scaffolder-backend": "npm:^4.0.2" "@backstage/plugin-scaffolder-node": "npm:^0.13.4" "@backstage/plugin-scaffolder-node-test-utils": "npm:^0.3.12" "@backstage/types": "npm:^1.2.2" From 269e08dcdc0dbc119577b1b374110dac1fab54c1 Mon Sep 17 00:00:00 2001 From: Lucas Holmquist Date: Wed, 5 Aug 2026 14:04:41 -0400 Subject: [PATCH 2/2] squash: lower case file extension --- .../{CONTRIBUTING.MD => CONTRIBUTING.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/{CONTRIBUTING.MD => CONTRIBUTING.md} (100%) diff --git a/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/CONTRIBUTING.MD b/workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/CONTRIBUTING.md similarity index 100% rename from workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/CONTRIBUTING.MD rename to workspaces/orchestrator/plugins/scaffolder-backend-module-orchestrator/CONTRIBUTING.md