feat(scaffolder): implement scaffolderServiceRef for credentials aware communication (#33044)
* add scaffolderServiceRef to scaffolder-node Signed-off-by: benjdlambert <ben@blam.sh> * make ScaffolderApi methods required, add tests Signed-off-by: benjdlambert <ben@blam.sh> * use request objects for single-string params in ScaffolderService Signed-off-by: benjdlambert <ben@blam.sh> * add scaffolderServiceMock test utility Signed-off-by: benjdlambert <ben@blam.sh> * adjust review comments Signed-off-by: benjdlambert <ben@blam.sh> * add scaffolderApiMock test utility to scaffolder-react Signed-off-by: benjdlambert <ben@blam.sh> * use items/totalItems response shape for listTasks Signed-off-by: benjdlambert <ben@blam.sh> * pass credentials through for autocomplete Signed-off-by: benjdlambert <ben@blam.sh> --------- Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-common': minor
|
||||
---
|
||||
|
||||
**BREAKING PRODUCERS**: Made `retry`, `listTasks`, `listTemplatingExtensions`, `dryRun`, and `autocomplete` required methods on the `ScaffolderApi` interface. Implementations of `ScaffolderApi` must now provide these methods.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-react': patch
|
||||
---
|
||||
|
||||
Added `scaffolderApiMock` test utility, exported from `@backstage/plugin-scaffolder-react/testUtils`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-node': patch
|
||||
---
|
||||
|
||||
Added `scaffolderServiceRef` and `ScaffolderService` interface for backend plugins that need to interact with the scaffolder API using `BackstageCredentials` instead of raw tokens.
|
||||
@@ -58,7 +58,7 @@ export type LogEvent = {
|
||||
// @public
|
||||
export interface ScaffolderApi {
|
||||
// (undocumented)
|
||||
autocomplete?(
|
||||
autocomplete(
|
||||
request: {
|
||||
token: string;
|
||||
provider: string;
|
||||
@@ -79,94 +79,6 @@ export interface ScaffolderApi {
|
||||
status?: ScaffolderTaskStatus;
|
||||
}>;
|
||||
// (undocumented)
|
||||
dryRun?(
|
||||
request: ScaffolderDryRunOptions,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ScaffolderDryRunResponse>;
|
||||
// (undocumented)
|
||||
getIntegrationsList(
|
||||
options: ScaffolderGetIntegrationsListOptions,
|
||||
): Promise<ScaffolderGetIntegrationsListResponse>;
|
||||
// (undocumented)
|
||||
getTask(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ScaffolderTask>;
|
||||
// (undocumented)
|
||||
getTemplateParameterSchema(
|
||||
templateRef: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<TemplateParameterSchema>;
|
||||
listActions(options?: ScaffolderRequestOptions): Promise<ListActionsResponse>;
|
||||
// (undocumented)
|
||||
listTasks?(
|
||||
request: {
|
||||
filterByOwnership: 'owned' | 'all';
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
},
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{
|
||||
tasks: ScaffolderTask[];
|
||||
totalTasks?: number;
|
||||
}>;
|
||||
listTemplatingExtensions?(
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ListTemplatingExtensionsResponse>;
|
||||
retry?(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
scaffold(
|
||||
request: ScaffolderScaffoldOptions,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ScaffolderScaffoldResponse>;
|
||||
// (undocumented)
|
||||
streamLogs(
|
||||
request: ScaffolderStreamLogsOptions,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Observable<LogEvent>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class ScaffolderClient implements ScaffolderApi {
|
||||
constructor(options: {
|
||||
discoveryApi: {
|
||||
getBaseUrl(pluginId: string): Promise<string>;
|
||||
};
|
||||
fetchApi: {
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
identityApi?: {
|
||||
getBackstageIdentity(): Promise<{
|
||||
type: 'user';
|
||||
userEntityRef: string;
|
||||
ownershipEntityRefs: string[];
|
||||
}>;
|
||||
};
|
||||
scmIntegrationsApi: ScmIntegrationRegistry;
|
||||
useLongPollingLogs?: boolean;
|
||||
});
|
||||
autocomplete(input: {
|
||||
token: string;
|
||||
provider: string;
|
||||
resource: string;
|
||||
context: Record<string, string>;
|
||||
}): Promise<{
|
||||
results: {
|
||||
title?: string;
|
||||
id: string;
|
||||
}[];
|
||||
}>;
|
||||
cancelTask(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{
|
||||
status?: ScaffolderTaskStatus;
|
||||
}>;
|
||||
// (undocumented)
|
||||
dryRun(
|
||||
request: ScaffolderDryRunOptions,
|
||||
options?: ScaffolderRequestOptions,
|
||||
@@ -201,7 +113,98 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
listTemplatingExtensions(
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ListTemplatingExtensionsResponse>;
|
||||
retry?(
|
||||
retry(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
scaffold(
|
||||
request: ScaffolderScaffoldOptions,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ScaffolderScaffoldResponse>;
|
||||
// (undocumented)
|
||||
streamLogs(
|
||||
request: ScaffolderStreamLogsOptions,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Observable<LogEvent>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class ScaffolderClient implements ScaffolderApi {
|
||||
constructor(options: {
|
||||
discoveryApi: {
|
||||
getBaseUrl(pluginId: string): Promise<string>;
|
||||
};
|
||||
fetchApi: {
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
identityApi?: {
|
||||
getBackstageIdentity(): Promise<{
|
||||
type: 'user';
|
||||
userEntityRef: string;
|
||||
ownershipEntityRefs: string[];
|
||||
}>;
|
||||
};
|
||||
scmIntegrationsApi: ScmIntegrationRegistry;
|
||||
useLongPollingLogs?: boolean;
|
||||
});
|
||||
autocomplete(
|
||||
input: {
|
||||
token: string;
|
||||
provider: string;
|
||||
resource: string;
|
||||
context: Record<string, string>;
|
||||
},
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{
|
||||
results: {
|
||||
title?: string;
|
||||
id: string;
|
||||
}[];
|
||||
}>;
|
||||
cancelTask(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{
|
||||
status?: ScaffolderTaskStatus;
|
||||
}>;
|
||||
// (undocumented)
|
||||
dryRun(
|
||||
request: ScaffolderDryRunOptions,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ScaffolderDryRunResponse>;
|
||||
// (undocumented)
|
||||
getIntegrationsList(
|
||||
options: ScaffolderGetIntegrationsListOptions,
|
||||
): Promise<ScaffolderGetIntegrationsListResponse>;
|
||||
// (undocumented)
|
||||
getTask(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ScaffolderTask>;
|
||||
// (undocumented)
|
||||
getTemplateParameterSchema(
|
||||
templateRef: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<TemplateParameterSchema>;
|
||||
listActions(options?: ScaffolderRequestOptions): Promise<ListActionsResponse>;
|
||||
// (undocumented)
|
||||
listTasks(
|
||||
request: {
|
||||
filterByOwnership: 'owned' | 'all';
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
},
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{
|
||||
tasks: ScaffolderTask[];
|
||||
totalTasks?: number;
|
||||
}>;
|
||||
listTemplatingExtensions(
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ListTemplatingExtensionsResponse>;
|
||||
retry(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{
|
||||
|
||||
@@ -381,7 +381,7 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
/**
|
||||
* {@inheritdoc ScaffolderApi.retry}
|
||||
*/
|
||||
async retry?(
|
||||
async retry(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{ id: string }> {
|
||||
@@ -393,22 +393,28 @@ export class ScaffolderClient implements ScaffolderApi {
|
||||
/**
|
||||
* {@inheritdoc ScaffolderApi.retry}
|
||||
*/
|
||||
async autocomplete({
|
||||
token,
|
||||
resource,
|
||||
provider,
|
||||
context,
|
||||
}: {
|
||||
token: string;
|
||||
provider: string;
|
||||
resource: string;
|
||||
context: Record<string, string>;
|
||||
}): Promise<{ results: { title?: string; id: string }[] }> {
|
||||
async autocomplete(
|
||||
{
|
||||
token,
|
||||
resource,
|
||||
provider,
|
||||
context,
|
||||
}: {
|
||||
token: string;
|
||||
provider: string;
|
||||
resource: string;
|
||||
context: Record<string, string>;
|
||||
},
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{ results: { title?: string; id: string }[] }> {
|
||||
return await this.requestRequired(
|
||||
await this.apiClient.autocomplete({
|
||||
path: { provider, resource },
|
||||
body: { token, context },
|
||||
}),
|
||||
await this.apiClient.autocomplete(
|
||||
{
|
||||
path: { provider, resource },
|
||||
body: { token, context },
|
||||
},
|
||||
options,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -285,12 +285,12 @@ export interface ScaffolderApi {
|
||||
*
|
||||
* @param taskId - the id of the task
|
||||
*/
|
||||
retry?(
|
||||
retry(
|
||||
taskId: string,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<{ id: string }>;
|
||||
|
||||
listTasks?(
|
||||
listTasks(
|
||||
request: {
|
||||
filterByOwnership: 'owned' | 'all';
|
||||
limit?: number;
|
||||
@@ -311,7 +311,7 @@ export interface ScaffolderApi {
|
||||
/**
|
||||
* Returns a structure describing the available templating extensions.
|
||||
*/
|
||||
listTemplatingExtensions?(
|
||||
listTemplatingExtensions(
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ListTemplatingExtensionsResponse>;
|
||||
|
||||
@@ -320,12 +320,12 @@ export interface ScaffolderApi {
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Observable<LogEvent>;
|
||||
|
||||
dryRun?(
|
||||
dryRun(
|
||||
request: ScaffolderDryRunOptions,
|
||||
options?: ScaffolderRequestOptions,
|
||||
): Promise<ScaffolderDryRunResponse>;
|
||||
|
||||
autocomplete?(
|
||||
autocomplete(
|
||||
request: {
|
||||
token: string;
|
||||
provider: string;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./alpha": "./src/alpha/index.ts",
|
||||
"./testUtils": "./src/testUtils.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
@@ -36,6 +37,9 @@
|
||||
"alpha": [
|
||||
"src/alpha/index.ts"
|
||||
],
|
||||
"testUtils": [
|
||||
"src/testUtils.ts"
|
||||
],
|
||||
"package.json": [
|
||||
"package.json"
|
||||
]
|
||||
@@ -79,6 +83,15 @@
|
||||
"@backstage/backend-test-utils": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@types/lodash": "^4.14.151"
|
||||
"@types/lodash": "^4.14.151",
|
||||
"msw": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@backstage/backend-test-utils": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
## API Report File for "@backstage/plugin-scaffolder-node"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { BackstageCredentials } from '@backstage/backend-plugin-api';
|
||||
import { ListActionsResponse } from '@backstage/plugin-scaffolder-common';
|
||||
import { ListTemplatingExtensionsResponse } from '@backstage/plugin-scaffolder-common';
|
||||
import { LogEvent } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderDryRunOptions } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderDryRunResponse } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderScaffoldOptions } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderScaffoldResponse } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderTask } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderTaskStatus } from '@backstage/plugin-scaffolder-common';
|
||||
import { ServiceMock } from '@backstage/backend-test-utils';
|
||||
import type { TemplateParameterSchema } from '@backstage/plugin-scaffolder-common';
|
||||
|
||||
// @public
|
||||
export interface ScaffolderService {
|
||||
// (undocumented)
|
||||
autocomplete(
|
||||
request: {
|
||||
token: string;
|
||||
provider: string;
|
||||
resource: string;
|
||||
context: Record<string, string>;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{
|
||||
results: {
|
||||
title?: string;
|
||||
id: string;
|
||||
}[];
|
||||
}>;
|
||||
// (undocumented)
|
||||
cancelTask(
|
||||
request: {
|
||||
taskId: string;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{
|
||||
status?: ScaffolderTaskStatus;
|
||||
}>;
|
||||
// (undocumented)
|
||||
dryRun(
|
||||
request: ScaffolderDryRunOptions,
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderDryRunResponse>;
|
||||
// (undocumented)
|
||||
getLogs(
|
||||
request: {
|
||||
taskId: string;
|
||||
after?: number;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<LogEvent[]>;
|
||||
// (undocumented)
|
||||
getTask(
|
||||
request: {
|
||||
taskId: string;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderTask>;
|
||||
// (undocumented)
|
||||
getTemplateParameterSchema(
|
||||
request: {
|
||||
templateRef: string;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<TemplateParameterSchema>;
|
||||
// (undocumented)
|
||||
listActions(
|
||||
request?: {},
|
||||
options?: ScaffolderServiceRequestOptions,
|
||||
): Promise<ListActionsResponse>;
|
||||
// (undocumented)
|
||||
listTasks(
|
||||
request: {
|
||||
createdBy?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{
|
||||
items: ScaffolderTask[];
|
||||
totalItems: number;
|
||||
}>;
|
||||
// (undocumented)
|
||||
listTemplatingExtensions(
|
||||
request?: {},
|
||||
options?: ScaffolderServiceRequestOptions,
|
||||
): Promise<ListTemplatingExtensionsResponse>;
|
||||
// (undocumented)
|
||||
retry(
|
||||
request: {
|
||||
taskId: string;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
// (undocumented)
|
||||
scaffold(
|
||||
request: ScaffolderScaffoldOptions,
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderScaffoldResponse>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export namespace scaffolderServiceMock {
|
||||
const mock: (
|
||||
partialImpl?: Partial<ScaffolderService> | undefined,
|
||||
) => ServiceMock<ScaffolderService>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ScaffolderServiceRequestOptions {
|
||||
// (undocumented)
|
||||
credentials: BackstageCredentials;
|
||||
}
|
||||
```
|
||||
@@ -9,15 +9,26 @@ import { Expand } from '@backstage/types';
|
||||
import { ExtensionPoint } from '@backstage/backend-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { ListActionsResponse } from '@backstage/plugin-scaffolder-common';
|
||||
import { ListTemplatingExtensionsResponse } from '@backstage/plugin-scaffolder-common';
|
||||
import { LogEvent } from '@backstage/plugin-scaffolder-common';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { PermissionCriteria } from '@backstage/plugin-permission-common';
|
||||
import { ScaffolderDryRunOptions } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderDryRunResponse } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderScaffoldOptions } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderScaffoldResponse } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderTask } from '@backstage/plugin-scaffolder-common';
|
||||
import { ScaffolderTaskStatus } from '@backstage/plugin-scaffolder-common';
|
||||
import { Schema } from 'jsonschema';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { ServiceRef } from '@backstage/backend-plugin-api';
|
||||
import { SpawnOptionsWithoutStdio } from 'node:child_process';
|
||||
import { TaskSpec } from '@backstage/plugin-scaffolder-common';
|
||||
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
|
||||
import type { TemplateParameterSchema } from '@backstage/plugin-scaffolder-common';
|
||||
import { UpdateTaskCheckpointOptions } from '@backstage/plugin-scaffolder-node/alpha';
|
||||
import { UrlReaderService } from '@backstage/backend-plugin-api';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
@@ -316,6 +327,110 @@ export interface ScaffolderActionsExtensionPoint {
|
||||
// @public
|
||||
export const scaffolderActionsExtensionPoint: ExtensionPoint<ScaffolderActionsExtensionPoint>;
|
||||
|
||||
// @public
|
||||
export interface ScaffolderService {
|
||||
// (undocumented)
|
||||
autocomplete(
|
||||
request: {
|
||||
token: string;
|
||||
provider: string;
|
||||
resource: string;
|
||||
context: Record<string, string>;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{
|
||||
results: {
|
||||
title?: string;
|
||||
id: string;
|
||||
}[];
|
||||
}>;
|
||||
// (undocumented)
|
||||
cancelTask(
|
||||
request: {
|
||||
taskId: string;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{
|
||||
status?: ScaffolderTaskStatus;
|
||||
}>;
|
||||
// (undocumented)
|
||||
dryRun(
|
||||
request: ScaffolderDryRunOptions,
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderDryRunResponse>;
|
||||
// (undocumented)
|
||||
getLogs(
|
||||
request: {
|
||||
taskId: string;
|
||||
after?: number;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<LogEvent[]>;
|
||||
// (undocumented)
|
||||
getTask(
|
||||
request: {
|
||||
taskId: string;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderTask>;
|
||||
// (undocumented)
|
||||
getTemplateParameterSchema(
|
||||
request: {
|
||||
templateRef: string;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<TemplateParameterSchema>;
|
||||
// (undocumented)
|
||||
listActions(
|
||||
request?: {},
|
||||
options?: ScaffolderServiceRequestOptions,
|
||||
): Promise<ListActionsResponse>;
|
||||
// (undocumented)
|
||||
listTasks(
|
||||
request: {
|
||||
createdBy?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{
|
||||
items: ScaffolderTask[];
|
||||
totalItems: number;
|
||||
}>;
|
||||
// (undocumented)
|
||||
listTemplatingExtensions(
|
||||
request?: {},
|
||||
options?: ScaffolderServiceRequestOptions,
|
||||
): Promise<ListTemplatingExtensionsResponse>;
|
||||
// (undocumented)
|
||||
retry(
|
||||
request: {
|
||||
taskId: string;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
// (undocumented)
|
||||
scaffold(
|
||||
request: ScaffolderScaffoldOptions,
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderScaffoldResponse>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const scaffolderServiceRef: ServiceRef<
|
||||
ScaffolderService,
|
||||
'plugin',
|
||||
'singleton'
|
||||
>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ScaffolderServiceRequestOptions {
|
||||
// (undocumented)
|
||||
credentials: BackstageCredentials;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface SerializedFile {
|
||||
// (undocumented)
|
||||
|
||||
@@ -25,3 +25,4 @@ export * from './tasks';
|
||||
export * from './files';
|
||||
export * from './types';
|
||||
export * from './extensions';
|
||||
export * from './scaffolderService';
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
createBackendModule,
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
ServiceFactoryTester,
|
||||
mockCredentials,
|
||||
mockServices,
|
||||
registerMswTestHooks,
|
||||
startTestBackend,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { scaffolderServiceRef } from './scaffolderService';
|
||||
|
||||
describe('scaffolderServiceRef', () => {
|
||||
const server = setupServer();
|
||||
registerMswTestHooks(server);
|
||||
|
||||
it('should return a scaffolder service', async () => {
|
||||
expect.assertions(1);
|
||||
const testModule = createBackendModule({
|
||||
moduleId: 'test',
|
||||
pluginId: 'test',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
scaffolder: scaffolderServiceRef,
|
||||
},
|
||||
async init({ scaffolder }) {
|
||||
expect(scaffolder.getTask).toBeDefined();
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
await startTestBackend({
|
||||
features: [testModule],
|
||||
});
|
||||
});
|
||||
|
||||
it('should inject token from user credentials', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
server.use(
|
||||
rest.get('*/api/scaffolder/v2/tasks/:taskId', (req, res, ctx) => {
|
||||
expect(req.headers.get('authorization')).toBe(
|
||||
mockCredentials.service.header({
|
||||
onBehalfOf: mockCredentials.user(),
|
||||
targetPluginId: 'scaffolder',
|
||||
}),
|
||||
);
|
||||
return res(
|
||||
ctx.json({
|
||||
id: 'task-1',
|
||||
spec: {},
|
||||
status: 'completed',
|
||||
createdAt: '2025-01-01T00:00:00Z',
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const tester = ServiceFactoryTester.from(
|
||||
createServiceFactory({
|
||||
service: createServiceRef<void>({ id: 'unused-dummy' }),
|
||||
deps: {},
|
||||
factory() {},
|
||||
}),
|
||||
{ dependencies: [mockServices.discovery.factory()] },
|
||||
);
|
||||
|
||||
const scaffolder = await tester.getService(scaffolderServiceRef);
|
||||
|
||||
await scaffolder.getTask(
|
||||
{ taskId: 'task-1' },
|
||||
{
|
||||
credentials: mockCredentials.user(),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should inject token from service credentials', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
server.use(
|
||||
rest.get('*/api/scaffolder/v2/tasks/:taskId', (req, res, ctx) => {
|
||||
expect(req.headers.get('authorization')).toBe(
|
||||
mockCredentials.service.header({
|
||||
onBehalfOf: mockCredentials.service(),
|
||||
targetPluginId: 'scaffolder',
|
||||
}),
|
||||
);
|
||||
return res(
|
||||
ctx.json({
|
||||
id: 'task-1',
|
||||
spec: {},
|
||||
status: 'completed',
|
||||
createdAt: '2025-01-01T00:00:00Z',
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
const tester = ServiceFactoryTester.from(
|
||||
createServiceFactory({
|
||||
service: createServiceRef<void>({ id: 'unused-dummy' }),
|
||||
deps: {},
|
||||
factory() {},
|
||||
}),
|
||||
{ dependencies: [mockServices.discovery.factory()] },
|
||||
);
|
||||
|
||||
const scaffolder = await tester.getService(scaffolderServiceRef);
|
||||
|
||||
await scaffolder.getTask(
|
||||
{ taskId: 'task-1' },
|
||||
{
|
||||
credentials: mockCredentials.service(),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass credentials for direct HTTP calls like getLogs', async () => {
|
||||
expect.assertions(1);
|
||||
|
||||
server.use(
|
||||
rest.get('*/api/scaffolder/v2/tasks/:taskId/events', (req, res, ctx) => {
|
||||
expect(req.headers.get('authorization')).toBe(
|
||||
mockCredentials.service.header({
|
||||
onBehalfOf: mockCredentials.user(),
|
||||
targetPluginId: 'scaffolder',
|
||||
}),
|
||||
);
|
||||
return res(ctx.json([]));
|
||||
}),
|
||||
);
|
||||
|
||||
const tester = ServiceFactoryTester.from(
|
||||
createServiceFactory({
|
||||
service: createServiceRef<void>({ id: 'unused-dummy' }),
|
||||
deps: {},
|
||||
factory() {},
|
||||
}),
|
||||
{ dependencies: [mockServices.discovery.factory()] },
|
||||
);
|
||||
|
||||
const scaffolder = await tester.getService(scaffolderServiceRef);
|
||||
|
||||
await scaffolder.getLogs(
|
||||
{ taskId: 'task-1' },
|
||||
{ credentials: mockCredentials.user() },
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass createdBy and pagination params for listTasks', async () => {
|
||||
expect.assertions(4);
|
||||
|
||||
server.use(
|
||||
rest.get('*/api/scaffolder/v2/tasks', (req, res, ctx) => {
|
||||
expect(req.url.searchParams.get('createdBy')).toBe(
|
||||
'user:default/guest',
|
||||
);
|
||||
expect(req.url.searchParams.get('limit')).toBe('10');
|
||||
expect(req.url.searchParams.get('offset')).toBe('5');
|
||||
return res(ctx.json({ tasks: [], totalTasks: 0 }));
|
||||
}),
|
||||
);
|
||||
|
||||
const tester = ServiceFactoryTester.from(
|
||||
createServiceFactory({
|
||||
service: createServiceRef<void>({ id: 'unused-dummy' }),
|
||||
deps: {},
|
||||
factory() {},
|
||||
}),
|
||||
{ dependencies: [mockServices.discovery.factory()] },
|
||||
);
|
||||
|
||||
const scaffolder = await tester.getService(scaffolderServiceRef);
|
||||
|
||||
const result = await scaffolder.listTasks(
|
||||
{ createdBy: 'user:default/guest', limit: 10, offset: 5 },
|
||||
{ credentials: mockCredentials.user() },
|
||||
);
|
||||
|
||||
expect(result).toEqual({ items: [], totalItems: 0 });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 {
|
||||
AuthService,
|
||||
BackstageCredentials,
|
||||
coreServices,
|
||||
createServiceFactory,
|
||||
createServiceRef,
|
||||
DiscoveryService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import {
|
||||
ListActionsResponse,
|
||||
ListTemplatingExtensionsResponse,
|
||||
LogEvent,
|
||||
ScaffolderClient,
|
||||
ScaffolderDryRunOptions,
|
||||
ScaffolderDryRunResponse,
|
||||
ScaffolderRequestOptions,
|
||||
ScaffolderScaffoldOptions,
|
||||
ScaffolderScaffoldResponse,
|
||||
ScaffolderTask,
|
||||
ScaffolderTaskStatus,
|
||||
} from '@backstage/plugin-scaffolder-common';
|
||||
import type { TemplateParameterSchema } from '@backstage/plugin-scaffolder-common';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ScaffolderServiceRequestOptions {
|
||||
credentials: BackstageCredentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* A backend service interface for the scaffolder that uses
|
||||
* {@link @backstage/backend-plugin-api#BackstageCredentials} instead of tokens.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface ScaffolderService {
|
||||
getTemplateParameterSchema(
|
||||
request: { templateRef: string },
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<TemplateParameterSchema>;
|
||||
|
||||
scaffold(
|
||||
request: ScaffolderScaffoldOptions,
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderScaffoldResponse>;
|
||||
|
||||
getTask(
|
||||
request: { taskId: string },
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderTask>;
|
||||
|
||||
cancelTask(
|
||||
request: { taskId: string },
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{ status?: ScaffolderTaskStatus }>;
|
||||
|
||||
retry(
|
||||
request: { taskId: string },
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{ id: string }>;
|
||||
|
||||
listTasks(
|
||||
request: {
|
||||
createdBy?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{ items: ScaffolderTask[]; totalItems: number }>;
|
||||
|
||||
listActions(
|
||||
request?: {},
|
||||
options?: ScaffolderServiceRequestOptions,
|
||||
): Promise<ListActionsResponse>;
|
||||
|
||||
listTemplatingExtensions(
|
||||
request?: {},
|
||||
options?: ScaffolderServiceRequestOptions,
|
||||
): Promise<ListTemplatingExtensionsResponse>;
|
||||
|
||||
getLogs(
|
||||
request: {
|
||||
taskId: string;
|
||||
after?: number;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<LogEvent[]>;
|
||||
|
||||
dryRun(
|
||||
request: ScaffolderDryRunOptions,
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderDryRunResponse>;
|
||||
|
||||
autocomplete(
|
||||
request: {
|
||||
token: string;
|
||||
provider: string;
|
||||
resource: string;
|
||||
context: Record<string, string>;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{ results: { title?: string; id: string }[] }>;
|
||||
}
|
||||
|
||||
class DefaultScaffolderService implements ScaffolderService {
|
||||
readonly #auth: AuthService;
|
||||
readonly #client: ScaffolderClient;
|
||||
readonly #discovery: DiscoveryService;
|
||||
|
||||
constructor(options: {
|
||||
auth: AuthService;
|
||||
client: ScaffolderClient;
|
||||
discovery: DiscoveryService;
|
||||
}) {
|
||||
this.#auth = options.auth;
|
||||
this.#client = options.client;
|
||||
this.#discovery = options.discovery;
|
||||
}
|
||||
|
||||
async getTemplateParameterSchema(
|
||||
request: { templateRef: string },
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<TemplateParameterSchema> {
|
||||
return this.#client.getTemplateParameterSchema(
|
||||
request.templateRef,
|
||||
await this.#getOptions(options),
|
||||
);
|
||||
}
|
||||
|
||||
async scaffold(
|
||||
request: ScaffolderScaffoldOptions,
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderScaffoldResponse> {
|
||||
return this.#client.scaffold(request, await this.#getOptions(options));
|
||||
}
|
||||
|
||||
async getTask(
|
||||
request: { taskId: string },
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderTask> {
|
||||
return this.#client.getTask(
|
||||
request.taskId,
|
||||
await this.#getOptions(options),
|
||||
);
|
||||
}
|
||||
|
||||
async cancelTask(
|
||||
request: { taskId: string },
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{ status?: ScaffolderTaskStatus }> {
|
||||
return this.#client.cancelTask(
|
||||
request.taskId,
|
||||
await this.#getOptions(options),
|
||||
);
|
||||
}
|
||||
|
||||
async retry(
|
||||
request: { taskId: string },
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{ id: string }> {
|
||||
return this.#client.retry(request.taskId, await this.#getOptions(options));
|
||||
}
|
||||
|
||||
async listTasks(
|
||||
request: {
|
||||
createdBy?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{ items: ScaffolderTask[]; totalItems: number }> {
|
||||
const { token } = await this.#getOptions(options);
|
||||
const baseUrl = await this.#discovery.getBaseUrl('scaffolder');
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (request.createdBy) {
|
||||
params.set('createdBy', request.createdBy);
|
||||
}
|
||||
if (request.limit !== undefined) {
|
||||
params.set('limit', String(request.limit));
|
||||
}
|
||||
if (request.offset !== undefined) {
|
||||
params.set('offset', String(request.offset));
|
||||
}
|
||||
|
||||
const query = params.toString();
|
||||
const url = `${baseUrl}/v2/tasks${query ? `?${query}` : ''}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
const body = await response.json();
|
||||
return {
|
||||
items: body.tasks,
|
||||
totalItems: body.totalTasks ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
async listActions(
|
||||
_request?: {},
|
||||
options?: ScaffolderServiceRequestOptions,
|
||||
): Promise<ListActionsResponse> {
|
||||
return this.#client.listActions(
|
||||
options ? await this.#getOptions(options) : {},
|
||||
);
|
||||
}
|
||||
|
||||
async listTemplatingExtensions(
|
||||
_request?: {},
|
||||
options?: ScaffolderServiceRequestOptions,
|
||||
): Promise<ListTemplatingExtensionsResponse> {
|
||||
return this.#client.listTemplatingExtensions(
|
||||
options ? await this.#getOptions(options) : {},
|
||||
);
|
||||
}
|
||||
|
||||
async getLogs(
|
||||
request: {
|
||||
taskId: string;
|
||||
after?: number;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<LogEvent[]> {
|
||||
const { token } = await this.#getOptions(options);
|
||||
const baseUrl = await this.#discovery.getBaseUrl('scaffolder');
|
||||
|
||||
const params = new URLSearchParams();
|
||||
if (request.after !== undefined) {
|
||||
params.set('after', String(request.after));
|
||||
}
|
||||
|
||||
const query = params.toString();
|
||||
const taskId = encodeURIComponent(request.taskId);
|
||||
const url = `${baseUrl}/v2/tasks/${taskId}/events${
|
||||
query ? `?${query}` : ''
|
||||
}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(token && { Authorization: `Bearer ${token}` }),
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async dryRun(
|
||||
request: ScaffolderDryRunOptions,
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderDryRunResponse> {
|
||||
return this.#client.dryRun(request, await this.#getOptions(options));
|
||||
}
|
||||
|
||||
async autocomplete(
|
||||
request: {
|
||||
token: string;
|
||||
provider: string;
|
||||
resource: string;
|
||||
context: Record<string, string>;
|
||||
},
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<{ results: { title?: string; id: string }[] }> {
|
||||
return this.#client.autocomplete(request, await this.#getOptions(options));
|
||||
}
|
||||
|
||||
async #getOptions(
|
||||
options: ScaffolderServiceRequestOptions,
|
||||
): Promise<ScaffolderRequestOptions> {
|
||||
return this.#auth.getPluginRequestToken({
|
||||
onBehalfOf: options.credentials,
|
||||
targetPluginId: 'scaffolder',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A service ref for the scaffolder client, to be used by backend plugins
|
||||
* and modules that need to interact with the scaffolder API.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const scaffolderServiceRef = createServiceRef<ScaffolderService>({
|
||||
id: 'scaffolder-client',
|
||||
defaultFactory: async service =>
|
||||
createServiceFactory({
|
||||
service,
|
||||
deps: {
|
||||
auth: coreServices.auth,
|
||||
discovery: coreServices.discovery,
|
||||
config: coreServices.rootConfig,
|
||||
},
|
||||
async factory({ auth, discovery, config }) {
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const client = new ScaffolderClient({
|
||||
discoveryApi: discovery,
|
||||
fetchApi: { fetch },
|
||||
scmIntegrationsApi: integrations,
|
||||
});
|
||||
return new DefaultScaffolderService({
|
||||
auth,
|
||||
client,
|
||||
discovery,
|
||||
});
|
||||
},
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Backend test helpers for the Scaffolder plugin.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export type {
|
||||
ScaffolderService,
|
||||
ScaffolderServiceRequestOptions,
|
||||
} from './scaffolderService';
|
||||
export { scaffolderServiceMock } from './testUtils/scaffolderServiceMock';
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { scaffolderServiceMock } from './scaffolderServiceMock';
|
||||
|
||||
describe('scaffolderServiceMock', () => {
|
||||
it('creates a mock with all methods as jest.fn()', () => {
|
||||
const mock = scaffolderServiceMock.mock();
|
||||
|
||||
expect(mock.getTask).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('supports overriding individual methods', async () => {
|
||||
const mock = scaffolderServiceMock.mock({
|
||||
getTask: jest.fn().mockResolvedValue({ id: 'task-1' }),
|
||||
});
|
||||
|
||||
await expect(
|
||||
mock.getTask({ taskId: 'task-1' }, { credentials: expect.anything() }),
|
||||
).resolves.toEqual(expect.objectContaining({ id: 'task-1' }));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { createServiceMock } from '@backstage/backend-test-utils';
|
||||
import { scaffolderServiceRef, ScaffolderService } from '../scaffolderService';
|
||||
|
||||
/**
|
||||
* A collection of mock functionality for the scaffolder service.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace scaffolderServiceMock {
|
||||
/**
|
||||
* Creates a scaffolder service whose methods are mock functions, possibly with
|
||||
* some of them overloaded by the caller.
|
||||
*/
|
||||
export const mock = createServiceMock<ScaffolderService>(
|
||||
scaffolderServiceRef,
|
||||
() => ({
|
||||
getTemplateParameterSchema: jest.fn(),
|
||||
scaffold: jest.fn(),
|
||||
getTask: jest.fn(),
|
||||
cancelTask: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
listActions: jest.fn(),
|
||||
listTemplatingExtensions: jest.fn(),
|
||||
getLogs: jest.fn(),
|
||||
dryRun: jest.fn(),
|
||||
autocomplete: jest.fn(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -31,6 +31,7 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./alpha": "./src/alpha.ts",
|
||||
"./testUtils": "./src/testUtils.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
@@ -40,6 +41,9 @@
|
||||
"alpha": [
|
||||
"src/alpha.ts"
|
||||
],
|
||||
"testUtils": [
|
||||
"src/testUtils.ts"
|
||||
],
|
||||
"package.json": [
|
||||
"package.json"
|
||||
]
|
||||
@@ -115,12 +119,16 @@
|
||||
"swr": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@backstage/frontend-test-utils": "workspace:^",
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "^6.30.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@backstage/frontend-test-utils": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## API Report File for "@backstage/plugin-scaffolder-react"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
import { ApiMock } from '@backstage/frontend-test-utils';
|
||||
import { ScaffolderApi } from '@backstage/plugin-scaffolder-common';
|
||||
|
||||
// @public
|
||||
export namespace scaffolderApiMock {
|
||||
const mock: (
|
||||
partialImpl?: Partial<ScaffolderApi> | undefined,
|
||||
) => ApiMock<ScaffolderApi>;
|
||||
}
|
||||
```
|
||||
@@ -37,6 +37,9 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
listActions: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
autocomplete: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
listTemplatingExtensions: jest.fn(),
|
||||
dryRun: jest.fn(),
|
||||
};
|
||||
|
||||
const catalogApi = catalogApiMock.mock();
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Frontend test helpers for the Scaffolder plugin.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { scaffolderApiMock } from './testUtils/scaffolderApiMock';
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { scaffolderApiMock } from './scaffolderApiMock';
|
||||
|
||||
describe('scaffolderApiMock', () => {
|
||||
it('creates a mock with all methods as jest.fn()', () => {
|
||||
const mock = scaffolderApiMock.mock();
|
||||
|
||||
expect(mock.getTask).toHaveBeenCalledTimes(0);
|
||||
expect(mock.scaffold).toHaveBeenCalledTimes(0);
|
||||
expect(mock.listActions).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('supports overriding individual methods', async () => {
|
||||
const mock = scaffolderApiMock.mock({
|
||||
getTask: jest.fn().mockResolvedValue({ id: 'task-1' }),
|
||||
});
|
||||
|
||||
await expect(mock.getTask('task-1')).resolves.toEqual(
|
||||
expect.objectContaining({ id: 'task-1' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* 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 { createApiMock } from '@backstage/frontend-test-utils';
|
||||
import { scaffolderApiRef } from '../api/ref';
|
||||
|
||||
/**
|
||||
* A collection of mock functionality for the scaffolder API.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export namespace scaffolderApiMock {
|
||||
/**
|
||||
* Creates a scaffolder API whose methods are mock functions, possibly with
|
||||
* some of them overloaded by the caller.
|
||||
*/
|
||||
export const mock = createApiMock(scaffolderApiRef, () => ({
|
||||
getTemplateParameterSchema: jest.fn(),
|
||||
scaffold: jest.fn(),
|
||||
getTask: jest.fn(),
|
||||
cancelTask: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
getIntegrationsList: jest.fn(),
|
||||
listActions: jest.fn(),
|
||||
listTemplatingExtensions: jest.fn(),
|
||||
streamLogs: jest.fn(),
|
||||
dryRun: jest.fn(),
|
||||
autocomplete: jest.fn(),
|
||||
}));
|
||||
}
|
||||
+3
@@ -38,6 +38,9 @@ describe('TemplateEditorToolbar', () => {
|
||||
listActions: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
autocomplete: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
listTemplatingExtensions: jest.fn(),
|
||||
dryRun: jest.fn(),
|
||||
};
|
||||
|
||||
scaffolderApiMock.listActions.mockResolvedValue([
|
||||
|
||||
@@ -54,6 +54,9 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
listActions: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
autocomplete: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
listTemplatingExtensions: jest.fn(),
|
||||
dryRun: jest.fn(),
|
||||
};
|
||||
|
||||
const scaffolderDecoratorsMock: jest.Mocked<ScaffolderFormDecoratorsApi> = {
|
||||
|
||||
@@ -34,6 +34,9 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
listActions: jest.fn(),
|
||||
listTasks: jest.fn(),
|
||||
autocomplete: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
listTemplatingExtensions: jest.fn(),
|
||||
dryRun: jest.fn(),
|
||||
};
|
||||
|
||||
const mockPermissionApi = { authorize: jest.fn() };
|
||||
|
||||
+2
@@ -40,6 +40,8 @@ const scaffolderApiMock: jest.Mocked<ScaffolderApi> = {
|
||||
listTemplatingExtensions,
|
||||
listTasks: jest.fn(),
|
||||
autocomplete: jest.fn(),
|
||||
retry: jest.fn(),
|
||||
dryRun: jest.fn(),
|
||||
};
|
||||
|
||||
const mockPermissionApi = { authorize: jest.fn() };
|
||||
|
||||
@@ -7104,12 +7104,18 @@ __metadata:
|
||||
isomorphic-git: "npm:^1.23.0"
|
||||
jsonschema: "npm:^1.5.0"
|
||||
lodash: "npm:^4.17.21"
|
||||
msw: "npm:^1.0.0"
|
||||
p-limit: "npm:^3.1.0"
|
||||
tar: "npm:^7.5.6"
|
||||
winston: "npm:^3.2.1"
|
||||
winston-transport: "npm:^4.7.0"
|
||||
zod: "npm:^3.25.76"
|
||||
zod-to-json-schema: "npm:^3.25.1"
|
||||
peerDependencies:
|
||||
"@backstage/backend-test-utils": "workspace:^"
|
||||
peerDependenciesMeta:
|
||||
"@backstage/backend-test-utils":
|
||||
optional: true
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -7171,11 +7177,14 @@ __metadata:
|
||||
zod: "npm:^3.25.76"
|
||||
zod-to-json-schema: "npm:^3.25.1"
|
||||
peerDependencies:
|
||||
"@backstage/frontend-test-utils": "workspace:^"
|
||||
"@types/react": ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
react-router-dom: ^6.30.2
|
||||
peerDependenciesMeta:
|
||||
"@backstage/frontend-test-utils":
|
||||
optional: true
|
||||
"@types/react":
|
||||
optional: true
|
||||
languageName: unknown
|
||||
|
||||
Reference in New Issue
Block a user