mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 10:09:50 +00:00
00043d193e
* ci(linter): add renovate/codeblock-in-spec-fixtures rule Add a custom oxlint rule that requires the codeBlock (or stripIndent) template helper from common-tags for multi-line string fixtures in spec files (enabled for **/*.spec.ts and test/** only). The rule only flags shapes whose value can round-trip through the common-tags helpers unchanged, since both codeBlock and stripIndent trim the end result and strip the shortest line indentation: - only literals spanning multiple source lines are flagged (escaped \n one-liners are left alone: tagging them is not value-preserving in general and gains nothing) - values with leading/trailing whitespace (e.g. fixtures ending in a newline) are not flagged - tagged templates, describe/it titles, and inline snapshot arguments are skipped All 55 pre-existing violations (22 files) were fixed with a one-off codemod that re-emits each fixture indented to the statement depth and verifies value preservation by evaluating codeBlock on the rewritten template (substitutions kept as opaque placeholders). One site (pip_requirements/artifacts.spec.ts) mixes line continuations with escaped newlines and was tagged verbatim instead of re-indented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linter): address review findings Make getSimpleRequirementsFile in pip-compile/extract.spec.ts value-preserving again: tagging the whole template with codeBlock trimmed the trailing blank line when deps is empty (the headerOnly.txt case), silently changing the fixture from ending in '#\n\n' to '#'. Tag only the static header and append the deps section via a single-line template, which reproduces the original string exactly for both empty and non-empty deps (verified by executing old vs new in node). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
140 lines
4.1 KiB
TypeScript
140 lines
4.1 KiB
TypeScript
import { codeBlock } from 'common-tags';
|
|
import * as httpMock from '~test/http-mock.ts';
|
|
import { logger } from '~test/util.ts';
|
|
import { getPkgReleases } from '../index.ts';
|
|
import { RustVersionDatasource } from './index.ts';
|
|
|
|
const datasource = RustVersionDatasource.id;
|
|
|
|
describe('modules/datasource/rust-version/index', () => {
|
|
describe('getReleases', () => {
|
|
it('fetches and parses manifest data', async () => {
|
|
const manifestsContent = codeBlock`
|
|
static.rust-lang.org/dist/2025-11-24/channel-rust-nightly.toml
|
|
static.rust-lang.org/dist/2024-10-17/channel-rust-1.82.0.toml
|
|
static.rust-lang.org/dist/2025-01-15/channel-rust-1.83.0-beta.5.toml
|
|
static.rust-lang.org/dist/2025-11-24/channel-rust-stable.toml
|
|
static.rust-lang.org/dist/2025-11-24/channel-rust-beta.toml
|
|
`;
|
|
|
|
httpMock
|
|
.scope('https://static.rust-lang.org')
|
|
.get('/manifests.txt')
|
|
.reply(200, manifestsContent);
|
|
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'rust',
|
|
});
|
|
|
|
expect(res).toMatchObject({
|
|
releases: expect.arrayContaining([
|
|
{
|
|
version: 'nightly-2025-11-24',
|
|
releaseTimestamp: '2025-11-24T00:00:00.000Z',
|
|
},
|
|
{
|
|
version: '1.82.0',
|
|
releaseTimestamp: '2024-10-17T00:00:00.000Z',
|
|
},
|
|
{
|
|
version: '1.83.0-beta.5',
|
|
releaseTimestamp: '2025-01-15T00:00:00.000Z',
|
|
},
|
|
]),
|
|
sourceUrl: 'https://github.com/rust-lang/rust',
|
|
});
|
|
expect(res?.releases).toHaveLength(3);
|
|
});
|
|
|
|
it('deduplicates versions with latest date', async () => {
|
|
const manifestsContent = codeBlock`
|
|
static.rust-lang.org/dist/2024-10-17/channel-rust-1.82.0.toml
|
|
static.rust-lang.org/dist/2024-10-18/channel-rust-1.82.0.toml
|
|
static.rust-lang.org/dist/2024-10-19/channel-rust-1.82.0.toml
|
|
`;
|
|
|
|
httpMock
|
|
.scope('https://static.rust-lang.org')
|
|
.get('/manifests.txt')
|
|
.reply(200, manifestsContent);
|
|
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'rust',
|
|
});
|
|
|
|
expect(res?.releases).toEqual([
|
|
{
|
|
version: '1.82.0',
|
|
releaseTimestamp: '2024-10-19T00:00:00.000Z',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('ignores unexpected URLs', async () => {
|
|
const manifestsContent = codeBlock`
|
|
static.rust-lang.org/dist/invalid.toml
|
|
static.rust-lang.org/dist/2024-10-17/channel-rust-1.82.0.toml
|
|
`;
|
|
|
|
httpMock
|
|
.scope('https://static.rust-lang.org')
|
|
.get('/manifests.txt')
|
|
.reply(200, manifestsContent);
|
|
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'rust',
|
|
});
|
|
|
|
expect(res?.releases).toEqual([
|
|
{
|
|
version: '1.82.0',
|
|
releaseTimestamp: '2024-10-17T00:00:00.000Z',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('ignores blank lines silently (no spurious warning)', async () => {
|
|
// `manifests.txt` ends with a trailing newline upstream, so the
|
|
// body's `split('\n')` always yields an empty final element. Also
|
|
// exercise a whitespace-only line in case upstream introduces one.
|
|
const manifestsContent =
|
|
'static.rust-lang.org/dist/2024-10-17/channel-rust-1.82.0.toml\n\n \n';
|
|
|
|
httpMock
|
|
.scope('https://static.rust-lang.org')
|
|
.get('/manifests.txt')
|
|
.reply(200, manifestsContent);
|
|
|
|
const res = await getPkgReleases({
|
|
datasource,
|
|
packageName: 'rust',
|
|
});
|
|
|
|
expect(res?.releases).toEqual([
|
|
{
|
|
version: '1.82.0',
|
|
releaseTimestamp: '2024-10-17T00:00:00.000Z',
|
|
},
|
|
]);
|
|
expect(logger.logger.warn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('throws for network error', async () => {
|
|
httpMock
|
|
.scope('https://static.rust-lang.org')
|
|
.get('/manifests.txt')
|
|
.reply(500);
|
|
|
|
await expect(
|
|
getPkgReleases({
|
|
datasource,
|
|
packageName: 'rust',
|
|
}),
|
|
).rejects.toThrow('external-host-error');
|
|
});
|
|
});
|
|
});
|