yarn-plugin: add unit tests

Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
MT Lewis
2024-04-18 10:11:59 +01:00
parent 7763950ed5
commit c24bc371eb
5 changed files with 327 additions and 0 deletions
+1
View File
@@ -36,6 +36,7 @@
"semver": "^7.6.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@yarnpkg/builder": "^4.0.0",
"nodemon": "^3.0.1"
@@ -0,0 +1,120 @@
/*
* 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 { Manifest, Workspace } from '@yarnpkg/core';
import { npath, ppath } from '@yarnpkg/fslib';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { beforeWorkspacePacking } from './beforeWorkspacePacking';
jest.mock('@backstage/release-manifests', () => ({
getManifestByVersion: jest.fn().mockResolvedValue({
releaseVersion: '1.23.45',
packages: [
{
name: '@backstage/core',
version: '3.2.1',
},
],
}),
}));
const makeWorkspace = (manifest: object) => {
return {
manifest: Manifest.fromText(JSON.stringify(manifest)),
} as Workspace;
};
describe('beforeWorkspacePacking', () => {
const mockDir = createMockDirectory();
beforeEach(() => {
jest
.spyOn(ppath, 'cwd')
.mockReturnValue(npath.toPortablePath(mockDir.path));
mockDir.setContent({
'backstage.json': JSON.stringify({
version: '1.23.45',
}),
'package.json': JSON.stringify({
workspaces: {
packages: ['packages/*'],
},
}),
});
});
afterEach(() => {
jest.restoreAllMocks();
});
describe.each`
dependencyType
${'dependencies'}
${'devDependencies'}
${'optionalDependencies'}
`('$dependencyType', ({ dependencyType }) => {
it(`ignores ${dependencyType} that don't use the backstage: protocol`, () => {
const result = {
name: 'test-package',
[dependencyType]: {
foo: '^1.1.1',
},
};
beforeWorkspacePacking(makeWorkspace(result), result);
expect(result).toEqual({
name: 'test-package',
[dependencyType]: {
foo: '^1.1.1',
},
});
});
it(`throws an error for any backstage: versions with a selector other than ^`, async () => {
const result = {
name: 'test-package',
[dependencyType]: {
'@backstage/core': 'backstage:^1.1.1',
},
};
await expect(() =>
beforeWorkspacePacking(makeWorkspace(result), result),
).rejects.toThrow();
});
it('converts backstage:^ versions to the corresponding package version prefixed by ^', async () => {
const result = {
name: 'test-package',
[dependencyType]: {
'@backstage/core': 'backstage:^',
},
};
await beforeWorkspacePacking(makeWorkspace(result), result);
expect(result).toEqual({
name: 'test-package',
[dependencyType]: {
'@backstage/core': '^3.2.1',
},
});
});
});
});
@@ -0,0 +1,177 @@
/*
* 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 { structUtils } from '@yarnpkg/core';
import { npath, ppath } from '@yarnpkg/fslib';
import { BackstageResolver } from './BackstageResolver';
import { createMockDirectory } from '@backstage/backend-test-utils';
jest.mock('@backstage/release-manifests', () => ({
getManifestByVersion: jest.fn().mockResolvedValue({
releaseVersion: '1.23.45',
packages: [
{
name: '@backstage/core',
version: '6.7.8',
},
],
}),
}));
describe('BackstageResolver', () => {
const mockDir = createMockDirectory();
let backstageResolver: BackstageResolver;
beforeEach(() => {
jest
.spyOn(ppath, 'cwd')
.mockReturnValue(npath.toPortablePath(mockDir.path));
mockDir.setContent({
'backstage.json': JSON.stringify({
version: '1.23.45',
}),
'package.json': JSON.stringify({
workspaces: {
packages: ['packages/*'],
},
}),
packages: {
a: {
'package.json': JSON.stringify({
name: 'a',
dependencies: {
'@backstage/core': 'backstage:^',
},
}),
},
},
});
backstageResolver = new BackstageResolver();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('supportsDescriptor', () => {
it.each([
['backstage:^'],
['backstage:1.26.0-next.3'],
['backstage:anything'],
])('returns true for range "%s"', range => {
expect(
backstageResolver.supportsDescriptor(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
range,
),
),
).toEqual(true);
});
});
describe('bindDescriptor', () => {
describe('with range "backstage:^"', () => {
it('returns a descriptor basedwith a version range for the current Backstage version', () => {
expect(
backstageResolver.bindDescriptor(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
'backstage:^',
),
),
).toEqual(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
'backstage:1.23.45',
),
);
});
});
describe('with range "backstage:1.23.45"', () => {
it('throws an error', () => {
expect(() =>
backstageResolver.bindDescriptor(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
'backstage:1.23.45',
),
),
).toThrow(/unsupported version range/i);
});
});
});
describe('getCandidates', () => {
it('returns an npm: descriptor based on the manifest for the appropriate backstage version', async () => {
const descriptor = structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
'backstage:1.23.45',
);
await expect(
backstageResolver.getCandidates(descriptor),
).resolves.toEqual([structUtils.makeLocator(descriptor, 'npm:6.7.8')]);
});
it('rejects descriptors not using the backstage: protocol', async () => {
await expect(
backstageResolver.getCandidates(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
'npm:1.2.3',
),
),
).rejects.toThrow(/unsupported version protocol/i);
});
it('rejects backstage: ranges with a ^ shorthand version', async () => {
await expect(
backstageResolver.getCandidates(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
'backstage:^',
),
),
).rejects.toThrow(/invalid backstage version/i);
});
it('rejects backstage: ranges with a * shorthand version', async () => {
await expect(
backstageResolver.getCandidates(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
'backstage:*',
),
),
).rejects.toThrow(/invalid backstage version/i);
});
it('rejects backstage: ranges with an invalid version specified', async () => {
await expect(
backstageResolver.getCandidates(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
'backstage:latest',
),
),
).rejects.toThrow(/invalid backstage version/i);
});
});
});
@@ -21,6 +21,7 @@ import {
Package,
Resolver,
} from '@yarnpkg/core';
import semver from 'semver';
import { PROTOCOL } from '../constants';
import { getCurrentBackstageVersion, getPackageVersion } from '../util';
@@ -33,6 +34,16 @@ export class BackstageResolver implements Resolver {
shouldPersistResolution = () => true;
bindDescriptor(descriptor: Descriptor): Descriptor {
if (descriptor.range !== 'backstage:^') {
throw new Error(
`Unsupported version range "${
descriptor.range
}" for package ${structUtils.stringifyIdent(
descriptor,
)}. The backstage protocol only supports the range "backstage:^".`,
);
}
return structUtils.makeDescriptor(
descriptor,
`${PROTOCOL}${getCurrentBackstageVersion()}`,
@@ -40,6 +51,23 @@ export class BackstageResolver implements Resolver {
}
async getCandidates(descriptor: Descriptor): Promise<Locator[]> {
const range = structUtils.parseRange(descriptor.range);
if (range.protocol !== BackstageResolver.protocol) {
throw new Error(
`Unsupported version protocol in version range "${
descriptor.range
}" for package ${structUtils.stringifyIdent(descriptor)}`,
);
}
if (!semver.valid(range.selector)) {
throw new Error(
`Invalid Backstage version string when resolving version for ${structUtils.stringifyIdent(
descriptor,
)}`,
);
}
return [
structUtils.makeLocator(
descriptor,
+1
View File
@@ -45131,6 +45131,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "yarn-plugin-backstage@workspace:packages/yarn-plugin"
dependencies:
"@backstage/backend-test-utils": "workspace:^"
"@backstage/cli": "workspace:^"
"@backstage/release-manifests": "workspace:^"
"@yarnpkg/builder": ^4.0.0