mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-27 07:50:47 +00:00
5ad36a2387
* ci(linter): add renovate/no-host-rules-mock rule Flags vi.mock()/vi.doMock() calls whose module specifier contains "host-rules" in spec files. Tests should configure real rules via hostRules.add() instead of mocking the host-rules module. Enabled at "warn" severity instead of "error": the repo currently has 35 pre-existing violations across 34 spec files, which exceeds the agreed fix threshold of 12 files. No violation fixes are included; migrating each spec to real hostRules.add()/clear() setup is a semantic test rewrite that should be done incrementally, after which the rule can be promoted to "error". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linter): enforce renovate/no-host-rules-mock at error Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(datasource): use real host-rules instead of mocking Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(manager): use real host-rules instead of mocking Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(platform): use real host-rules instead of mocking Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(util,workers): use real host-rules instead of mocking Also narrow renovate/no-host-rules-mock to the host-rules module itself so that mocking e.g. host-rules-from-env is not flagged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linter): mention '~test/host-rules.ts' util in no-host-rules-mock message Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
291 lines
9.6 KiB
TypeScript
291 lines
9.6 KiB
TypeScript
import { hostRules } from '~test/host-rules.ts';
|
|
import { logger } from '~test/util.ts';
|
|
import { GlobalConfig } from '../config/global.ts';
|
|
import { GithubReleasesDatasource } from '../modules/datasource/github-releases/index.ts';
|
|
import { GithubTagsDatasource } from '../modules/datasource/github-tags/index.ts';
|
|
import type { PackageFileContent } from '../modules/manager/types.ts';
|
|
import * as memCache from '../util/cache/memory/index.ts';
|
|
import {
|
|
checkGithubToken,
|
|
findGithubToken,
|
|
isGithubFineGrainedPersonalAccessToken,
|
|
isGithubPersonalAccessToken,
|
|
isGithubServerToServerToken,
|
|
takePersonalAccessTokenIfPossible,
|
|
} from './check-token.ts';
|
|
|
|
describe('util/check-token', () => {
|
|
describe('checkGithubToken', () => {
|
|
beforeEach(() => {
|
|
memCache.reset();
|
|
GlobalConfig.set({ githubTokenWarn: true });
|
|
});
|
|
|
|
it('does nothing if data is empty', () => {
|
|
checkGithubToken(undefined);
|
|
expect(logger.logger.trace).not.toHaveBeenCalled();
|
|
expect(logger.logger.warn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns early if GitHub token is found', () => {
|
|
hostRules.add({
|
|
hostType: 'github',
|
|
matchHost: 'api.github.com',
|
|
token: '123',
|
|
});
|
|
checkGithubToken({});
|
|
expect(logger.logger.trace).toHaveBeenCalledWith('GitHub token is found');
|
|
expect(logger.logger.warn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('returns early if token warnings are disabled', () => {
|
|
GlobalConfig.set({ githubTokenWarn: false });
|
|
checkGithubToken({});
|
|
expect(logger.logger.trace).toHaveBeenCalledWith(
|
|
'GitHub token warning is disabled',
|
|
);
|
|
expect(logger.logger.warn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('does not warn if there is dependencies with GitHub sourceUrl', () => {
|
|
checkGithubToken({
|
|
npm: [{ deps: [{ depName: 'renovatebot/renovate' }] }],
|
|
});
|
|
expect(logger.logger.warn).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('logs warning for github-tags datasource', () => {
|
|
checkGithubToken({
|
|
npm: [
|
|
{
|
|
deps: [
|
|
{
|
|
depName: 'foo/bar',
|
|
datasource: GithubTagsDatasource.id,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
expect(logger.logger.warn).toHaveBeenCalled();
|
|
});
|
|
|
|
it('logs warning for github-releases datasource', () => {
|
|
checkGithubToken({
|
|
npm: [
|
|
{
|
|
deps: [
|
|
{
|
|
depName: 'foo/bar',
|
|
datasource: GithubReleasesDatasource.id,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
expect(logger.logger.warn).toHaveBeenCalled();
|
|
});
|
|
|
|
it('logs warning once', () => {
|
|
const packageFiles: Record<string, PackageFileContent[]> = {
|
|
npm: [
|
|
{
|
|
deps: [
|
|
{
|
|
depName: 'foo/foo',
|
|
datasource: GithubTagsDatasource.id,
|
|
},
|
|
{
|
|
depName: 'bar/bar',
|
|
datasource: GithubReleasesDatasource.id,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
};
|
|
checkGithubToken(packageFiles);
|
|
|
|
expect(logger.logger.warn).toHaveBeenCalledWith(
|
|
{
|
|
githubDeps: ['foo/foo', 'bar/bar'],
|
|
},
|
|
'GitHub token is required for some dependencies',
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('isGithubPersonalAccessToken', () => {
|
|
it('returns true when string is a github personnal access token', () => {
|
|
expect(isGithubPersonalAccessToken('ghp_XXXXXX')).toBeTrue();
|
|
});
|
|
|
|
it('returns false when string is a github application token', () => {
|
|
expect(isGithubPersonalAccessToken('ghs_XXXXXX')).toBeFalse();
|
|
});
|
|
|
|
it('returns false when string is a github fine grained personal access token', () => {
|
|
expect(isGithubPersonalAccessToken('github_pat_XXXXXX')).toBeFalse();
|
|
});
|
|
|
|
it('returns false when string is not a token at all', () => {
|
|
expect(isGithubPersonalAccessToken('XXXXXX')).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('isGithubServerToServerToken', () => {
|
|
it('returns true when string is a github server to server token', () => {
|
|
expect(isGithubServerToServerToken('ghs_XXXXXX')).toBeTrue();
|
|
});
|
|
|
|
// via https://github.blog/changelog/2026-04-24-notice-about-upcoming-new-format-for-github-app-installation-tokens/
|
|
it('returns true when string is a 2026-style GitHub Installation Access Token', () => {
|
|
expect(
|
|
isGithubServerToServerToken('ghs_0123456_eyJhbGciOiJSUzI1NiJ9'),
|
|
).toBeTrue();
|
|
});
|
|
|
|
it('returns false when string is a github personal access token token', () => {
|
|
expect(isGithubServerToServerToken('ghp_XXXXXX')).toBeFalse();
|
|
});
|
|
|
|
it('returns false when string is a github fine grained personal access token', () => {
|
|
expect(isGithubPersonalAccessToken('github_pat_XXXXXX')).toBeFalse();
|
|
});
|
|
|
|
it('returns false when string is not a token at all', () => {
|
|
expect(isGithubServerToServerToken('XXXXXX')).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('isGithubFineGrainedPersonalAccessToken', () => {
|
|
it('returns true when string is a github fine grained personal access token', () => {
|
|
expect(
|
|
isGithubFineGrainedPersonalAccessToken('github_pat_XXXXXX'),
|
|
).toBeTrue();
|
|
});
|
|
|
|
it('returns false when string is a github personnal access token', () => {
|
|
expect(isGithubFineGrainedPersonalAccessToken('ghp_XXXXXX')).toBeFalse();
|
|
});
|
|
|
|
it('returns false when string is a github application token', () => {
|
|
expect(isGithubFineGrainedPersonalAccessToken('ghs_XXXXXX')).toBeFalse();
|
|
});
|
|
|
|
it('returns false when string is not a token at all', () => {
|
|
expect(isGithubFineGrainedPersonalAccessToken('XXXXXX')).toBeFalse();
|
|
});
|
|
});
|
|
|
|
describe('findGithubToken', () => {
|
|
it('returns the token string when hostRule match search with a valid personal access token', () => {
|
|
const TOKEN_STRING = 'ghp_TOKEN';
|
|
|
|
expect(findGithubToken({ token: TOKEN_STRING })).toBe(TOKEN_STRING);
|
|
});
|
|
|
|
it('returns undefined when no token is defined', () => {
|
|
expect(findGithubToken({})).toBeUndefined();
|
|
});
|
|
|
|
it('remove x-access-token token prefix', () => {
|
|
const TOKEN_STRING_WITH_PREFIX = 'x-access-token:ghp_TOKEN';
|
|
const TOKEN_STRING = 'ghp_TOKEN';
|
|
|
|
expect(findGithubToken({ token: TOKEN_STRING_WITH_PREFIX })).toBe(
|
|
TOKEN_STRING,
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('takePersonalAccessTokenIfPossible', () => {
|
|
it('returns undefined when both token are undefined', () => {
|
|
const githubToken = undefined;
|
|
const gitTagsGithubToken = undefined;
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBeUndefined();
|
|
});
|
|
|
|
it('returns gitTagsToken when both token are PAT', () => {
|
|
const githubToken = 'ghp_github';
|
|
const gitTagsGithubToken = 'ghp_gitTags';
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(gitTagsGithubToken);
|
|
});
|
|
|
|
it('returns githubToken is PAT and gitTagsGithubToken is not a PAT', () => {
|
|
const githubToken = 'ghp_github';
|
|
const gitTagsGithubToken = 'ghs_gitTags';
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(githubToken);
|
|
});
|
|
|
|
it('returns gitTagsToken when both token are set but not PAT', () => {
|
|
const githubToken = 'ghs_github';
|
|
const gitTagsGithubToken = 'ghs_gitTags';
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(gitTagsGithubToken);
|
|
});
|
|
|
|
it('returns gitTagsToken when gitTagsToken not PAT and gitTagsGithubToken is not set', () => {
|
|
const githubToken = undefined;
|
|
const gitTagsGithubToken = 'ghs_gitTags';
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(gitTagsGithubToken);
|
|
});
|
|
|
|
it('returns githubToken when githubToken not PAT and gitTagsGithubToken is not set', () => {
|
|
const githubToken = 'ghs_gitTags';
|
|
const gitTagsGithubToken = undefined;
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(githubToken);
|
|
});
|
|
|
|
it('take personal access token over fine grained token', () => {
|
|
const githubToken = 'ghp_github';
|
|
const gitTagsGithubToken = 'github_pat_gitTags';
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(githubToken);
|
|
});
|
|
|
|
it('take fine grained token over server to server token', () => {
|
|
const githubToken = 'github_pat_github';
|
|
const gitTagsGithubToken = 'ghs_gitTags';
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(githubToken);
|
|
});
|
|
|
|
it('take git-tags fine grained token', () => {
|
|
const githubToken = undefined;
|
|
const gitTagsGithubToken = 'github_pat_gitTags';
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(gitTagsGithubToken);
|
|
});
|
|
|
|
it('take git-tags unknown token type when no other token is set', () => {
|
|
const githubToken = undefined;
|
|
const gitTagsGithubToken = 'unknownTokenType_gitTags';
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(gitTagsGithubToken);
|
|
});
|
|
|
|
it('take github unknown token type when no other token is set', () => {
|
|
const githubToken = 'unknownTokenType';
|
|
const gitTagsGithubToken = undefined;
|
|
expect(
|
|
takePersonalAccessTokenIfPossible(githubToken, gitTagsGithubToken),
|
|
).toBe(githubToken);
|
|
});
|
|
});
|
|
});
|