yarn-plugin: add unit tests
Signed-off-by: MT Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user