Update RepoUrlPicker validation for bitbucket fields
Also, - Add and update tests - Add changeset Signed-off-by: Mustansar Anwar ul Samad <mustansar.samad@gmail.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': minor
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Add bitbucket workspace and project fields to RepoUrlPicker to support bitbucket cloud and server
|
||||
@@ -68,21 +68,21 @@ describe('publish:azure', () => {
|
||||
await expect(
|
||||
action.handler({
|
||||
...mockContext,
|
||||
input: { repoUrl: 'azure.com?repo=bob' },
|
||||
input: { repoUrl: 'dev.azure.com?repo=bob' },
|
||||
}),
|
||||
).rejects.toThrow(/missing owner/);
|
||||
|
||||
await expect(
|
||||
action.handler({
|
||||
...mockContext,
|
||||
input: { repoUrl: 'azure.com?owner=owner' },
|
||||
input: { repoUrl: 'dev.azure.com?owner=owner' },
|
||||
}),
|
||||
).rejects.toThrow(/missing repo/);
|
||||
|
||||
await expect(
|
||||
action.handler({
|
||||
...mockContext,
|
||||
input: { repoUrl: 'azure.com?owner=owner&repo=repo' },
|
||||
input: { repoUrl: 'dev.azure.com?owner=owner&repo=repo' },
|
||||
}),
|
||||
).rejects.toThrow(/missing organization/);
|
||||
});
|
||||
|
||||
+26
-20
@@ -145,7 +145,13 @@ describe('publish:bitbucket', () => {
|
||||
),
|
||||
);
|
||||
|
||||
await action.handler(mockContext);
|
||||
await action.handler({
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should call the correct APIs when the host is hosted bitbucket', async () => {
|
||||
@@ -209,7 +215,7 @@ describe('publish:bitbucket', () => {
|
||||
expect.assertions(1);
|
||||
server.use(
|
||||
rest.post(
|
||||
'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos',
|
||||
'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos',
|
||||
(_, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(201),
|
||||
@@ -219,7 +225,7 @@ describe('publish:bitbucket', () => {
|
||||
},
|
||||
),
|
||||
rest.put(
|
||||
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/owner/repos/repo/enabled',
|
||||
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled',
|
||||
(req, res, ctx) => {
|
||||
expect(req.headers.get('Authorization')).toBe('Bearer thing');
|
||||
return res(ctx.status(204));
|
||||
@@ -231,7 +237,7 @@ describe('publish:bitbucket', () => {
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo',
|
||||
repoUrl: 'hosted.bitbucket.com?project=project&repo=repo',
|
||||
enableLFS: true,
|
||||
},
|
||||
});
|
||||
@@ -240,7 +246,7 @@ describe('publish:bitbucket', () => {
|
||||
it('should report an error if enabling LFS fails', async () => {
|
||||
server.use(
|
||||
rest.post(
|
||||
'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos',
|
||||
'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos',
|
||||
(_, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(201),
|
||||
@@ -250,7 +256,7 @@ describe('publish:bitbucket', () => {
|
||||
},
|
||||
),
|
||||
rest.put(
|
||||
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/owner/repos/repo/enabled',
|
||||
'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled',
|
||||
(_, res, ctx) => {
|
||||
return res(ctx.status(500));
|
||||
},
|
||||
@@ -262,7 +268,7 @@ describe('publish:bitbucket', () => {
|
||||
...mockContext,
|
||||
input: {
|
||||
...mockContext.input,
|
||||
repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo',
|
||||
repoUrl: 'hosted.bitbucket.com?project=project&repo=repo',
|
||||
enableLFS: true,
|
||||
},
|
||||
}),
|
||||
@@ -299,7 +305,7 @@ describe('publish:bitbucket', () => {
|
||||
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: mockContext.workspacePath,
|
||||
remoteUrl: 'https://bitbucket.org/owner/cloneurl',
|
||||
remoteUrl: 'https://bitbucket.org/workspace/cloneurl',
|
||||
defaultBranch: 'master',
|
||||
auth: { username: 'x-token-auth', password: 'tokenlols' },
|
||||
logger: mockContext.logger,
|
||||
@@ -310,7 +316,7 @@ describe('publish:bitbucket', () => {
|
||||
it('should call initAndPush with the correct default branch', async () => {
|
||||
server.use(
|
||||
rest.post(
|
||||
'https://api.bitbucket.org/2.0/repositories/owner/repo',
|
||||
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
@@ -318,12 +324,12 @@ describe('publish:bitbucket', () => {
|
||||
ctx.json({
|
||||
links: {
|
||||
html: {
|
||||
href: 'https://bitbucket.org/owner/repo',
|
||||
href: 'https://bitbucket.org/workspace/repo',
|
||||
},
|
||||
clone: [
|
||||
{
|
||||
name: 'https',
|
||||
href: 'https://bitbucket.org/owner/cloneurl',
|
||||
href: 'https://bitbucket.org/workspace/cloneurl',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -342,7 +348,7 @@ describe('publish:bitbucket', () => {
|
||||
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: mockContext.workspacePath,
|
||||
remoteUrl: 'https://bitbucket.org/owner/cloneurl',
|
||||
remoteUrl: 'https://bitbucket.org/workspace/cloneurl',
|
||||
defaultBranch: 'main',
|
||||
auth: { username: 'x-token-auth', password: 'tokenlols' },
|
||||
logger: mockContext.logger,
|
||||
@@ -385,7 +391,7 @@ describe('publish:bitbucket', () => {
|
||||
|
||||
server.use(
|
||||
rest.post(
|
||||
'https://api.bitbucket.org/2.0/repositories/owner/repo',
|
||||
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
@@ -393,12 +399,12 @@ describe('publish:bitbucket', () => {
|
||||
ctx.json({
|
||||
links: {
|
||||
html: {
|
||||
href: 'https://bitbucket.org/owner/repo',
|
||||
href: 'https://bitbucket.org/workspace/repo',
|
||||
},
|
||||
clone: [
|
||||
{
|
||||
name: 'https',
|
||||
href: 'https://bitbucket.org/owner/cloneurl',
|
||||
href: 'https://bitbucket.org/workspace/cloneurl',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -411,7 +417,7 @@ describe('publish:bitbucket', () => {
|
||||
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: mockContext.workspacePath,
|
||||
remoteUrl: 'https://bitbucket.org/owner/cloneurl',
|
||||
remoteUrl: 'https://bitbucket.org/workspace/cloneurl',
|
||||
auth: { username: 'x-token-auth', password: 'tokenlols' },
|
||||
logger: mockContext.logger,
|
||||
defaultBranch: 'master',
|
||||
@@ -451,7 +457,7 @@ describe('publish:bitbucket', () => {
|
||||
|
||||
server.use(
|
||||
rest.post(
|
||||
'https://api.bitbucket.org/2.0/repositories/owner/repo',
|
||||
'https://api.bitbucket.org/2.0/repositories/workspace/repo',
|
||||
(_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
@@ -459,12 +465,12 @@ describe('publish:bitbucket', () => {
|
||||
ctx.json({
|
||||
links: {
|
||||
html: {
|
||||
href: 'https://bitbucket.org/owner/repo',
|
||||
href: 'https://bitbucket.org/workspace/repo',
|
||||
},
|
||||
clone: [
|
||||
{
|
||||
name: 'https',
|
||||
href: 'https://bitbucket.org/owner/cloneurl',
|
||||
href: 'https://bitbucket.org/workspace/cloneurl',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -477,7 +483,7 @@ describe('publish:bitbucket', () => {
|
||||
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: mockContext.workspacePath,
|
||||
remoteUrl: 'https://bitbucket.org/owner/cloneurl',
|
||||
remoteUrl: 'https://bitbucket.org/workspace/cloneurl',
|
||||
auth: { username: 'x-token-auth', password: 'tokenlols' },
|
||||
logger: mockContext.logger,
|
||||
defaultBranch: 'master',
|
||||
|
||||
@@ -61,7 +61,9 @@ export const parseRepoUrl = (
|
||||
const type = integrations.byHost(host)?.type;
|
||||
|
||||
if (!type) {
|
||||
throw new InputError(`Unable to find host ${host} in integrations`);
|
||||
throw new InputError(
|
||||
`No matching integration configuration for host ${host}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
|
||||
if (type === 'bitbucket') {
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
import { repoPickerValidation } from './validation';
|
||||
import { FieldValidation } from '@rjsf/core';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { ConfigReader } from '@backstage/core-app-api';
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
|
||||
describe('RepoPicker Validation', () => {
|
||||
const fieldValidator = () =>
|
||||
@@ -23,30 +26,60 @@ describe('RepoPicker Validation', () => {
|
||||
addError: jest.fn(),
|
||||
} as unknown as FieldValidation);
|
||||
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
bitbucket: [
|
||||
{
|
||||
host: 'bitbucket.org',
|
||||
},
|
||||
{
|
||||
host: 'server.bitbucket.com',
|
||||
},
|
||||
],
|
||||
github: [
|
||||
{
|
||||
host: 'github.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const scmIntegrations = ScmIntegrations.fromConfig(config);
|
||||
|
||||
const apiHolderMock: jest.Mocked<ApiHolder> = {
|
||||
get: jest.fn().mockImplementation(() => {
|
||||
return scmIntegrations;
|
||||
}),
|
||||
};
|
||||
|
||||
it('validates when no repo', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation('github.com?owner=a', mockFieldValidation);
|
||||
repoPickerValidation('github.com?owner=a', mockFieldValidation, {
|
||||
apiHolder: apiHolderMock,
|
||||
});
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenCalledWith(
|
||||
'Incomplete repository location provided',
|
||||
'Incomplete repository location provided, repo not provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates when no owner', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation('github.com?repo=a', mockFieldValidation);
|
||||
repoPickerValidation('github.com?repo=a', mockFieldValidation, {
|
||||
apiHolder: apiHolderMock,
|
||||
});
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenCalledWith(
|
||||
'Incomplete repository location provided',
|
||||
'Incomplete repository location provided, owner not provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates when not a real url', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation('', mockFieldValidation);
|
||||
repoPickerValidation('', mockFieldValidation, { apiHolder: apiHolderMock });
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenCalledWith(
|
||||
'Unable to parse the Repository URL',
|
||||
@@ -56,8 +89,116 @@ describe('RepoPicker Validation', () => {
|
||||
it('validates properly with proper input', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation('github.com?owner=a&repo=b', mockFieldValidation);
|
||||
repoPickerValidation('github.com?owner=a&repo=b', mockFieldValidation, {
|
||||
apiHolder: apiHolderMock,
|
||||
});
|
||||
|
||||
expect(mockFieldValidation.addError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('validates when no workspace, project or repo provided for bitbucket cloud', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation('bitbucket.org', mockFieldValidation, {
|
||||
apiHolder: apiHolderMock,
|
||||
});
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'Incomplete repository location provided, workspace not provided',
|
||||
);
|
||||
expect(mockFieldValidation.addError).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'Incomplete repository location provided, project not provided',
|
||||
);
|
||||
expect(mockFieldValidation.addError).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'Incomplete repository location provided, repo not provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates when no workspace provided for bitbucket cloud', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation(
|
||||
'bitbucket.org?project=p&repo=r',
|
||||
mockFieldValidation,
|
||||
{ apiHolder: apiHolderMock },
|
||||
);
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenCalledWith(
|
||||
'Incomplete repository location provided, workspace not provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates when no project provided for bitbucket cloud', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation(
|
||||
'bitbucket.org?workspace=w&repo=r',
|
||||
mockFieldValidation,
|
||||
{ apiHolder: apiHolderMock },
|
||||
);
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenCalledWith(
|
||||
'Incomplete repository location provided, project not provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates when no repo provided for bitbucket cloud', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation(
|
||||
'bitbucket.org?workspace=w&project=p',
|
||||
mockFieldValidation,
|
||||
{ apiHolder: apiHolderMock },
|
||||
);
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenCalledWith(
|
||||
'Incomplete repository location provided, repo not provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates when no project or repo provided for bitbucket server', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation('server.bitbucket.com', mockFieldValidation, {
|
||||
apiHolder: apiHolderMock,
|
||||
});
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'Incomplete repository location provided, project not provided',
|
||||
);
|
||||
expect(mockFieldValidation.addError).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'Incomplete repository location provided, repo not provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates when no project provided for bitbucket server', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation('server.bitbucket.com?repo=r', mockFieldValidation, {
|
||||
apiHolder: apiHolderMock,
|
||||
});
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenCalledWith(
|
||||
'Incomplete repository location provided, project not provided',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates when no repo provided for bitbucket server', () => {
|
||||
const mockFieldValidation = fieldValidator();
|
||||
|
||||
repoPickerValidation(
|
||||
'server.bitbucket.com?project=p',
|
||||
mockFieldValidation,
|
||||
{ apiHolder: apiHolderMock },
|
||||
);
|
||||
|
||||
expect(mockFieldValidation.addError).toHaveBeenCalledWith(
|
||||
'Incomplete repository location provided, repo not provided',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,16 +15,56 @@
|
||||
*/
|
||||
|
||||
import { FieldValidation } from '@rjsf/core';
|
||||
import { ApiHolder } from '@backstage/core-plugin-api';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
|
||||
export const repoPickerValidation = (
|
||||
value: string,
|
||||
validation: FieldValidation,
|
||||
context: { apiHolder: ApiHolder },
|
||||
) => {
|
||||
try {
|
||||
const { host, searchParams } = new URL(`https://${value}`);
|
||||
if (!host || !searchParams.get('repo')) {
|
||||
validation.addError('Incomplete repository location provided');
|
||||
|
||||
const integrationApi = context.apiHolder.get(scmIntegrationsApiRef);
|
||||
|
||||
if (!host) {
|
||||
validation.addError(
|
||||
'Incomplete repository location provided, host not provided',
|
||||
);
|
||||
} else {
|
||||
if (integrationApi?.byHost(host)?.type === 'bitbucket') {
|
||||
// workspace is only applicable for bitbucket cloud
|
||||
if (host === 'bitbucket.org' && !searchParams.get('workspace')) {
|
||||
validation.addError(
|
||||
'Incomplete repository location provided, workspace not provided',
|
||||
);
|
||||
}
|
||||
|
||||
if (!searchParams.get('project')) {
|
||||
validation.addError(
|
||||
'Incomplete repository location provided, project not provided',
|
||||
);
|
||||
}
|
||||
}
|
||||
// For anything other than bitbucket
|
||||
else {
|
||||
if (!searchParams.get('owner')) {
|
||||
validation.addError(
|
||||
'Incomplete repository location provided, owner not provided',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Do this for all hosts
|
||||
if (!searchParams.get('repo')) {
|
||||
validation.addError(
|
||||
'Incomplete repository location provided, repo not provided',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if (!host || !searchParams.get('owner') || !searchParams.get('repo')) {
|
||||
} catch {
|
||||
validation.addError('Unable to parse the Repository URL');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user