test: add tests for RepoBranchPicker

Signed-off-by: Benjamin Janssens <benji.janssens@gmail.com>
This commit is contained in:
Benjamin Janssens
2024-06-19 09:52:17 +02:00
parent 5588a64e80
commit 8329e930c6
2 changed files with 327 additions and 2 deletions
@@ -0,0 +1,324 @@
/*
* Copyright 2024 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 React from 'react';
import { Form } from '@backstage/plugin-scaffolder-react/alpha';
import validator from '@rjsf/validator-ajv8';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import {
scmIntegrationsApiRef,
ScmIntegrationsApi,
scmAuthApiRef,
ScmAuthApi,
} from '@backstage/integration-react';
import {
SecretsContextProvider,
scaffolderApiRef,
useTemplateSecrets,
ScaffolderRJSFField,
} from '@backstage/plugin-scaffolder-react';
import { act, fireEvent } from '@testing-library/react';
import { RepoBranchPicker } from './RepoBranchPicker';
describe('RepoBranchPicker', () => {
const mockIntegrationsApi: Partial<ScmIntegrationsApi> = {
byHost: () => ({ type: 'bitbucket' }),
};
let mockScmAuthApi: Partial<ScmAuthApi>;
beforeEach(() => {
mockScmAuthApi = {
getCredentials: jest.fn().mockResolvedValue({ token: 'abc123' }),
};
});
describe('happy path rendering', () => {
it('should render the repo branch picker with minimal props', async () => {
const onSubmit = jest.fn();
const { getByRole } = await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
[scmAuthApiRef, {}],
[scaffolderApiRef, {}],
]}
>
<SecretsContextProvider>
<Form
validator={validator}
schema={{ type: 'string' }}
uiSchema={{ 'ui:field': 'RepoBranchPicker' }}
fields={{
RepoBranchPicker:
RepoBranchPicker as ScaffolderRJSFField<string>,
}}
onSubmit={onSubmit}
formContext={{
formData: {
repoUrl: 'bitbucket.org',
},
}}
/>
</SecretsContextProvider>
</TestApiProvider>,
);
const input = getByRole('textbox');
const submitButton = getByRole('button');
act(() => {
input.focus();
fireEvent.change(input, { target: { value: 'branch1' } });
input.blur();
});
fireEvent.click(submitButton);
expect(onSubmit).toHaveBeenCalledWith(
expect.objectContaining({
formData: 'branch1',
}),
expect.anything(),
);
});
it('should render properly with title and description', async () => {
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
[scmAuthApiRef, {}],
[scaffolderApiRef, {}],
]}
>
<SecretsContextProvider>
<Form
validator={validator}
schema={{
type: 'string',
title: 'test title',
description: 'test description',
}}
uiSchema={{
'ui:field': 'RepoBranchPicker',
}}
fields={{
RepoBranchPicker:
RepoBranchPicker as ScaffolderRJSFField<string>,
}}
formContext={{
formData: {
repoUrl: 'bitbucket.org',
},
}}
/>
</SecretsContextProvider>
</TestApiProvider>,
);
expect(getByText('test title')).toBeInTheDocument();
expect(getByText('test description')).toBeInTheDocument();
});
});
describe('requestUserCredentials', () => {
it('should call the scmAuthApi with the correct params', async () => {
const SecretsComponent = () => {
const { secrets } = useTemplateSecrets();
return (
<div data-testid="current-secrets">{JSON.stringify({ secrets })}</div>
);
};
const { getByTestId } = await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
[scmAuthApiRef, mockScmAuthApi],
[scaffolderApiRef, {}],
]}
>
<SecretsContextProvider>
<Form
validator={validator}
schema={{ type: 'string' }}
uiSchema={{
'ui:field': 'RepoBranchPicker',
'ui:options': {
requestUserCredentials: {
secretsKey: 'testKey',
additionalScopes: { github: ['workflow'] },
},
},
}}
fields={{
RepoBranchPicker:
RepoBranchPicker as ScaffolderRJSFField<string>,
}}
formContext={{
formData: {
repoUrl: 'github.com',
},
}}
/>
<SecretsComponent />
</SecretsContextProvider>
</TestApiProvider>,
);
await act(async () => {
// need to wait for the debounce to finish
await new Promise(resolve => setTimeout(resolve, 600));
});
expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({
url: 'https://github.com',
additionalScope: {
repoWrite: true,
customScopes: {
github: ['workflow'],
},
},
});
const currentSecrets = JSON.parse(
getByTestId('current-secrets').textContent!,
);
expect(currentSecrets).toEqual({
secrets: { testKey: 'abc123' },
});
});
it('should call the scmAuthApi with the correct params if workspace is nested', async () => {
const SecretsComponent = () => {
const { secrets } = useTemplateSecrets();
return (
<div data-testid="current-secrets">{JSON.stringify({ secrets })}</div>
);
};
await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
[scmAuthApiRef, mockScmAuthApi],
[scaffolderApiRef, {}],
]}
>
<SecretsContextProvider>
<Form
validator={validator}
schema={{ type: 'string' }}
uiSchema={{
'ui:field': 'RepoBranchPicker',
'ui:options': {
allowedHosts: ['gitlab.example.com'],
requestUserCredentials: {
secretsKey: 'testKey',
},
},
}}
fields={{
RepoBranchPicker:
RepoBranchPicker as ScaffolderRJSFField<string>,
}}
formContext={{
formData: {
repoUrl: 'gitlab.example.com',
},
}}
/>
<SecretsComponent />
</SecretsContextProvider>
</TestApiProvider>,
);
await act(async () => {
// need to wait for the debounce to finish
await new Promise(resolve => setTimeout(resolve, 600));
});
expect(mockScmAuthApi.getCredentials).toHaveBeenCalledWith({
url: 'https://gitlab.example.com',
additionalScope: {
repoWrite: true,
},
});
});
it('should not call the scmAuthApi if secret is available in the state', async () => {
const SecretsComponent = () => {
const { secrets } = useTemplateSecrets();
return (
<div data-testid="current-secrets">{JSON.stringify({ secrets })}</div>
);
};
const { getByTestId } = await renderInTestApp(
<TestApiProvider
apis={[
[scmIntegrationsApiRef, mockIntegrationsApi],
[scmAuthApiRef, mockScmAuthApi],
[scaffolderApiRef, {}],
]}
>
<SecretsContextProvider initialSecrets={{ testKey: 'abc123' }}>
<Form
validator={validator}
schema={{ type: 'string' }}
uiSchema={{
'ui:field': 'RepoBranchPicker',
'ui:options': {
requestUserCredentials: {
secretsKey: 'testKey',
additionalScopes: { github: ['workflow'] },
},
},
}}
fields={{
RepoBranchPicker:
RepoBranchPicker as ScaffolderRJSFField<string>,
}}
formContext={{
formData: {
repoUrl: 'github.com',
},
}}
/>
<SecretsComponent />
</SecretsContextProvider>
</TestApiProvider>,
);
await act(async () => {
// need to wait for the debounce to finish
await new Promise(resolve => setTimeout(resolve, 600));
});
// as we already have a secret in the state, getCredentials should not be called again.
expect(mockScmAuthApi.getCredentials).toHaveBeenCalledTimes(0);
const currentSecrets = JSON.parse(
getByTestId('current-secrets').textContent!,
);
expect(currentSecrets).toEqual({
secrets: { testKey: 'abc123' },
});
});
});
});
@@ -20,8 +20,6 @@ import {
scmAuthApiRef,
} from '@backstage/integration-react';
import React, { useEffect, useState, useCallback } from 'react';
import { RepoBranchPickerProps } from './schema';
import { RepoBranchPickerState } from './types';
import useDebounce from 'react-use/esm/useDebounce';
import { useTemplateSecrets } from '@backstage/plugin-scaffolder-react';
import Box from '@material-ui/core/Box';
@@ -29,6 +27,9 @@ import Divider from '@material-ui/core/Divider';
import Typography from '@material-ui/core/Typography';
import { BitbucketRepoBranchPicker } from './BitbucketRepoBranchPicker';
import { RepoBranchPickerProps } from './schema';
import { RepoBranchPickerState } from './types';
/**
* The underlying component that is rendered in the form for the `RepoBranchPicker`
* field extension.