Files

149 lines
5.3 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import {
parseCargoPackageVersion,
setCargoLockPackageVersion,
setCargoPackageVersion,
validateAppVersion,
} from '../scripts/version.mjs';
import { applyAppVersion, runCargoMetadata } from '../scripts/set-version.mjs';
describe('app version', () => {
it('reads the version from the package section', () => {
expect(parseCargoPackageVersion(`
[package]
name = "cpa-gui"
version = "1.2.3-beta.1+build.7"
[dependencies]
example = "9.9.9"
`)).toBe('1.2.3-beta.1+build.7');
});
it('rejects a missing package version', () => {
expect(() => parseCargoPackageVersion('[package]\nname = "cpa-gui"\n'))
.toThrow('Missing package version');
});
it('rejects an invalid semantic version', () => {
expect(() => parseCargoPackageVersion('[package]\nversion = "v1.2.3"\n'))
.toThrow('Invalid package version');
});
it('updates only the package version', () => {
const manifest = `[package]
name = "cpa-gui"
version = "1.2.3"
[dependencies]
example = "9.9.9"
`;
const updated = setCargoPackageVersion(manifest, '2.0.0-rc.1');
expect(parseCargoPackageVersion(updated)).toBe('2.0.0-rc.1');
expect(updated).toContain('example = "9.9.9"');
});
it('validates a version supplied by a release tag', () => {
expect(validateAppVersion('0.2.15')).toBe('0.2.15');
expect(() => validateAppVersion('v0.2.15')).toThrow('Invalid app version');
});
it('updates only the local app entry in Cargo.lock', () => {
const lockfile = `[[package]]
name = "cpa-gui"
version = "1.2.3"
[[package]]
name = "dependency"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
`;
const updated = setCargoLockPackageVersion(lockfile, 'cpa-gui', '2.0.0');
expect(updated).toContain('name = "cpa-gui"\nversion = "2.0.0"');
expect(updated).toContain('name = "dependency"\nversion = "1.2.3"');
});
it('repairs a stale lockfile even when Cargo.toml already has the requested version', async () => {
const files = new Map<string, string>([
['Cargo.toml', '[package]\nname = "cpa-gui"\nversion = "2.0.0"\n'],
['Cargo.lock', '[[package]]\nname = "cpa-gui"\nversion = "1.0.0"\n'],
]);
const writes: string[] = [];
const result = await applyAppVersion('2.0.0', {
rootDir: 'test-root',
readText: async (path: string) => files.get(
path.endsWith('Cargo.toml') ? 'Cargo.toml' : 'Cargo.lock',
)!,
writeText: async (path: string, contents: string) => {
const name = path.endsWith('Cargo.toml') ? 'Cargo.toml' : 'Cargo.lock';
writes.push(name);
files.set(name, contents);
},
validateProject: async () => {},
});
expect(result).toMatchObject({ version: '2.0.0', previousVersion: '2.0.0', changed: true });
expect(writes).toEqual(['Cargo.lock']);
expect(files.get('Cargo.lock')).toContain('version = "2.0.0"');
});
it('restores both files when cargo metadata validation fails', async () => {
const originalManifest = '[package]\nname = "cpa-gui"\nversion = "1.0.0"\n';
const originalLockfile = '[[package]]\nname = "cpa-gui"\nversion = "1.0.0"\n';
const files = new Map<string, string>([
['Cargo.toml', originalManifest],
['Cargo.lock', originalLockfile],
]);
await expect(applyAppVersion('2.0.0', {
rootDir: 'test-root',
readText: async (path: string) => files.get(
path.endsWith('Cargo.toml') ? 'Cargo.toml' : 'Cargo.lock',
)!,
writeText: async (path: string, contents: string) => {
files.set(path.endsWith('Cargo.toml') ? 'Cargo.toml' : 'Cargo.lock', contents);
},
validateProject: async () => { throw new Error('metadata validation failed'); },
})).rejects.toThrow('metadata validation failed');
expect(files.get('Cargo.toml')).toBe(originalManifest);
expect(files.get('Cargo.lock')).toBe(originalLockfile);
});
it('restores the manifest when writing the lockfile fails', async () => {
const originalManifest = '[package]\nname = "cpa-gui"\nversion = "1.0.0"\n';
const originalLockfile = '[[package]]\nname = "cpa-gui"\nversion = "1.0.0"\n';
const files = new Map<string, string>([
['Cargo.toml', originalManifest],
['Cargo.lock', originalLockfile],
]);
let lockfileWrites = 0;
await expect(applyAppVersion('2.0.0', {
rootDir: 'test-root',
readText: async (path: string) => files.get(
path.endsWith('Cargo.toml') ? 'Cargo.toml' : 'Cargo.lock',
)!,
writeText: async (path: string, contents: string) => {
const name = path.endsWith('Cargo.toml') ? 'Cargo.toml' : 'Cargo.lock';
if (name === 'Cargo.lock' && lockfileWrites++ === 0) {
throw new Error('lockfile write failed');
}
files.set(name, contents);
},
validateProject: async () => {},
})).rejects.toThrow('lockfile write failed');
expect(files.get('Cargo.toml')).toBe(originalManifest);
expect(files.get('Cargo.lock')).toBe(originalLockfile);
});
it('includes cargo stderr when metadata validation fails', () => {
expect(() => runCargoMetadata('Cargo.toml', () => ({
status: 101,
stderr: 'manifest parse error',
stdout: '',
}))).toThrow('cargo metadata failed with exit code 101: manifest parse error');
});
});