Added examples for sentry:project:create action and unit tests.

Signed-off-by: Diego Mondragon <mondragdiego@gmail.com>
This commit is contained in:
Diego Mondragon
2023-10-16 12:00:24 -07:00
committed by Fredrik Adelöw
parent 0c930f8df1
commit 7f8a801e6d
5 changed files with 273 additions and 4 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-sentry': patch
---
Added examples for `sentry:project:create` scaffolder action and unit tests.
@@ -30,10 +30,12 @@
"dependencies": {
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^"
"@backstage/plugin-scaffolder-node": "workspace:^",
"yaml": "^2.3.3"
},
"devDependencies": {
"@backstage/cli": "workspace:^"
"@backstage/cli": "workspace:^",
"@backstage/types": "workspace:^"
},
"files": [
"dist"
@@ -0,0 +1,53 @@
/*
* Copyright 2021 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 { TemplateExample } from '@backstage/plugin-scaffolder-node';
import yaml from 'yaml';
export const examples: TemplateExample[] = [
{
description: 'Creates a Sentry project with the specified parameters.',
example: yaml.stringify({
steps: [
{
id: 'create-sentry-project',
action: 'sentry:project:create',
name: 'Create a Sentry project with provided project slug.',
input: {
organizationSlug: 'my-org',
teamSlug: 'team-a',
name: 'Scaffolded project A',
slug: 'scaff-proj-a',
authToken:
'a14711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d5df96',
},
},
{
id: 'create-sentry-project',
action: 'sentry:project:create',
name: 'Create a Sentry project without providing a project slug.',
input: {
organizationSlug: 'my-org',
teamSlug: 'team-b',
name: 'Scaffolded project B',
authToken:
'b15711beb516e1e910d2ede554dc1bf725654ef3c75e5a9106de9aec13d4gf93',
},
},
],
}),
},
];
@@ -0,0 +1,207 @@
/*
* Copyright 2021 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 { ConfigReader } from '@backstage/config';
import { JsonObject } from '@backstage/types';
import { createSentryCreateProjectAction } from './createProject';
import { ActionContext } from '@backstage/plugin-scaffolder-node';
import { InputError } from '@backstage/errors';
describe('sentry:project:create action', () => {
const createScaffolderConfig = (configData: JsonObject = {}) => ({
config: new ConfigReader({
scaffolder: {
...configData,
},
}),
});
const mockFetch = (response = {}) => {
const mockedResponse = {
status: 201,
headers: {
get: () => 'application/json',
},
json: async () =>
Promise.resolve({
detail: 'project creation mocked result',
}),
text: async () => Promise.resolve('Unexpected error.'),
...response,
};
global.fetch = jest
.fn()
.mockImplementation(() => Promise.resolve(mockedResponse));
return mockedResponse;
};
const getActionContext = (): ActionContext<{
organizationSlug: string;
teamSlug: string;
name: string;
slug?: string;
authToken?: string;
}> => ({
workspacePath: './dev/proj',
createTemporaryDirectory: jest.fn(),
logger: jest.createMockFromModule('winston'),
logStream: jest.createMockFromModule('stream'),
input: {
organizationSlug: 'org',
teamSlug: 'team',
name: 'test project',
authToken: '008hsd7f7123hhdsfhfds7123123881239889fdsaf1g',
},
output: jest.fn(),
});
beforeEach(() => {
mockFetch();
});
test('should request sentry project create with specified parameters.', async () => {
const action = createSentryCreateProjectAction(createScaffolderConfig());
const actionContext = getActionContext();
await action.handler(actionContext);
expect(fetch).toHaveBeenNthCalledWith(
1,
`https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${actionContext.input.authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: actionContext.input.name,
}),
},
);
});
test('should request sentry project create with added optional specified project slug', async () => {
const action = createSentryCreateProjectAction(createScaffolderConfig());
const actionContext = getActionContext();
actionContext.input = { ...actionContext.input, slug: 'project-slug' };
await action.handler(actionContext);
expect(global.fetch).toHaveBeenNthCalledWith(
1,
`https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${actionContext.input.authToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: actionContext.input.name,
slug: actionContext.input.slug,
}),
},
);
});
test('should take Sentry auth token from scaffolder config when input authToken is missing.', async () => {
const sentryScaffolderConfigToken =
'scaffolder app-config.yaml scaffolder token';
const action = createSentryCreateProjectAction(
createScaffolderConfig({
sentry: {
token: sentryScaffolderConfigToken,
},
}),
);
const actionContext = getActionContext();
actionContext.input.authToken = undefined;
await action.handler(actionContext);
expect(fetch).toHaveBeenNthCalledWith(
1,
`https://sentry.io/api/0/teams/${actionContext.input.organizationSlug}/${actionContext.input.teamSlug}/projects/`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${sentryScaffolderConfigToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: actionContext.input.name,
}),
},
);
});
test('should throw InputError when auth token is missing from input parameters and scaffolder config.', async () => {
const action = createSentryCreateProjectAction(createScaffolderConfig());
const actionContext = getActionContext();
actionContext.input.authToken = undefined;
expect.assertions(1);
await expect(async () => {
await action.handler(actionContext);
}).rejects.toThrow(new InputError('No valid sentry token given'));
});
test('should throw InputError when sentry API returns unexpected content-type.', async () => {
const action = createSentryCreateProjectAction(createScaffolderConfig());
const actionContext = getActionContext();
const mockedFetchResponse = mockFetch({
headers: {
get: () => 'text/html',
},
});
expect.assertions(1);
await expect(async () => {
await action.handler(actionContext);
}).rejects.toThrow(
new InputError(
`Unexpected Sentry Response Type: ${await mockedFetchResponse.text()}`,
),
);
});
test('should throw InputError when sentry API returns unexpected status code.', async () => {
const action = createSentryCreateProjectAction(createScaffolderConfig());
const actionContext = getActionContext();
const mockedFetchResponse = mockFetch({
status: 400,
});
expect.assertions(1);
await expect(async () => {
await action.handler(actionContext);
}).rejects.toThrow(
new InputError(
`Sentry Response was: ${(await mockedFetchResponse.json()).detail}`,
),
);
});
});
@@ -17,9 +17,10 @@
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { InputError } from '@backstage/errors';
import { Config } from '@backstage/config';
import { examples } from './createProject.examples';
/**
* Creates the `sentry:craete-project` Scaffolder action.
* Creates the `sentry:create-project` Scaffolder action.
*
* @remarks
*
@@ -39,6 +40,7 @@ export function createSentryCreateProjectAction(options: { config: Config }) {
authToken?: string;
}>({
id: 'sentry:project:create',
examples,
schema: {
input: {
required: ['organizationSlug', 'teamSlug', 'name'],
@@ -112,7 +114,7 @@ export function createSentryCreateProjectAction(options: { config: Config }) {
const result = await response.json();
if (code !== 201) {
throw new InputError(`Sentry Response was: ${await result.detail}`);
throw new InputError(`Sentry Response was: ${result.detail}`);
}
ctx.output('id', result.id);