mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-24 22:48:06 +00:00
f711fd46e2
* test(workers/repository): mark seven unreachable guards for v8 These are defensive paths that no input reaches, and the guards stay as they are. What changes is that v8 now knows to skip them: the existing `istanbul ignore` comments are inert under the v8 provider, so the branches behind them were still being counted. - `versioning/index`: a string input can only fail the schema with the custom issue its own transform raises - `versioning/conda`: pep440 always answers for a well-formed `==X.Y.*` - `versioning/ruby`: callers only pass versions that have already parsed - `repository/index`: only called when the repository logged a problem - `config-migration/branch/rebase`: JSON5 output is always serializable back to JSON - `onboarding/pr/pr-list`: base branches are unique Record keys, so the sort never compares one with itself - `changelog/release-notes`: a matched release always carries its own url - `changelog/source`: `getInRangeReleases` only returns null on paths it already ignores Two notes on the hints. They have to be a single-line comment directly above the statement -- a two-line reason, or anything in between such as an oxlint directive, breaks the association silently. And `v8 ignore else` covers an `if` with no else, while `v8 ignore next` covers an expression-level branch such as `??` or a ternary. `versioning/index` also swaps `issue && issue.code` for `issue?.code`, which is what the oxlint directive sitting in the way was suppressing. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> * test(workers/repository): cover the last five pr worker guards None of these needed an ignore comment; each has a real input: - `updatePrDebugData` gets a describe of its own. It is exported but was only reached through a pr whose debug data already carried labels, so the case where an existing pr has none went untaken. - a `not-pending` branch whose status is no longer yellow - a changelog error that is not a missing GitHub token - an existing pr updated because a rebase was requested - a platform that answers `createPr` with null, so assignees and reviewers are skipped With these, `update/pr/index.ts` is fully covered. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> * test(workers/repository): cover the last four branch update guards - `bump-versions` reads the current version off disk when the pending change for that file carries no contents. All three guards involved are covered by one case where the file is pending in both the package file and the artifact list with no contents on either entry. An earlier attempt at this looked like it failed: the bump does happen, but the result is appended to `updatedPackageFiles` rather than `updatedArtifacts`, because the file is already pending there. - `get-updated` keeps a package file another upgrade already wrote, for a manager that is asked for a lockfile-only update but has no `updateLockedDependency`. With these, `lib/workers/repository` is fully covered. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> * ci: pin the branch coverage threshold at 100 A full run on this branch reports 100% on all four metrics: statements (49435/49435), branches (25727/25727), functions (7414/7414) and lines (48924/48924). Branches joins the other three at 100 rather than taking another rounding-down step. There is no longer a margin to leave: any new uncovered branch is a regression, and the threshold should say so. Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io> --------- Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
155 lines
5.0 KiB
TypeScript
155 lines
5.0 KiB
TypeScript
import type { RenovateConfig } from '~test/util.ts';
|
|
import { logger, partial } from '~test/util.ts';
|
|
import {
|
|
CONFIG_SECRETS_EXPOSED,
|
|
CONFIG_VALIDATION,
|
|
EXTERNAL_HOST_ERROR,
|
|
MANAGER_LOCKFILE_ERROR,
|
|
MISSING_API_CREDENTIALS,
|
|
NO_VULNERABILITY_ALERTS,
|
|
PLATFORM_AUTHENTICATION_ERROR,
|
|
PLATFORM_BAD_CREDENTIALS,
|
|
PLATFORM_INTEGRATION_UNAUTHORIZED,
|
|
PLATFORM_RATE_LIMIT_EXCEEDED,
|
|
REPOSITORY_ACCESS_FORBIDDEN,
|
|
REPOSITORY_ARCHIVED,
|
|
REPOSITORY_BLOCKED,
|
|
REPOSITORY_CANNOT_FORK,
|
|
REPOSITORY_CHANGED,
|
|
REPOSITORY_CLOSED_ONBOARDING,
|
|
REPOSITORY_DISABLED,
|
|
REPOSITORY_EMPTY,
|
|
REPOSITORY_FORKED,
|
|
REPOSITORY_FORK_MISSING,
|
|
REPOSITORY_FORK_MODE_FORKED,
|
|
REPOSITORY_MIRRORED,
|
|
REPOSITORY_NOT_FOUND,
|
|
REPOSITORY_NO_PACKAGE_FILES,
|
|
REPOSITORY_PENDING_DELETION,
|
|
REPOSITORY_RENAMED,
|
|
REPOSITORY_UNINITIATED,
|
|
SYSTEM_INSUFFICIENT_DISK_SPACE,
|
|
SYSTEM_INSUFFICIENT_MEMORY,
|
|
TEMPORARY_ERROR,
|
|
UNKNOWN_ERROR,
|
|
} from '../../constants/error-messages.ts';
|
|
import { ExternalHostError } from '../../types/errors/external-host-error.ts';
|
|
import handleError from './error.ts';
|
|
|
|
vi.mock('./error-config.ts');
|
|
|
|
let config: RenovateConfig;
|
|
|
|
beforeEach(() => {
|
|
config = partial<RenovateConfig>({ branchList: [] });
|
|
});
|
|
|
|
describe('workers/repository/error', () => {
|
|
describe('handleError()', () => {
|
|
const errors = [
|
|
REPOSITORY_UNINITIATED,
|
|
REPOSITORY_EMPTY,
|
|
REPOSITORY_CLOSED_ONBOARDING,
|
|
REPOSITORY_DISABLED,
|
|
REPOSITORY_CHANGED,
|
|
REPOSITORY_FORKED,
|
|
REPOSITORY_FORK_MISSING,
|
|
REPOSITORY_FORK_MODE_FORKED,
|
|
REPOSITORY_NO_PACKAGE_FILES,
|
|
CONFIG_SECRETS_EXPOSED,
|
|
CONFIG_VALIDATION,
|
|
REPOSITORY_ARCHIVED,
|
|
REPOSITORY_MIRRORED,
|
|
REPOSITORY_PENDING_DELETION,
|
|
REPOSITORY_RENAMED,
|
|
REPOSITORY_BLOCKED,
|
|
REPOSITORY_NOT_FOUND,
|
|
REPOSITORY_ACCESS_FORBIDDEN,
|
|
PLATFORM_BAD_CREDENTIALS,
|
|
PLATFORM_RATE_LIMIT_EXCEEDED,
|
|
MANAGER_LOCKFILE_ERROR,
|
|
MISSING_API_CREDENTIALS,
|
|
SYSTEM_INSUFFICIENT_DISK_SPACE,
|
|
SYSTEM_INSUFFICIENT_MEMORY,
|
|
NO_VULNERABILITY_ALERTS,
|
|
REPOSITORY_CANNOT_FORK,
|
|
PLATFORM_INTEGRATION_UNAUTHORIZED,
|
|
PLATFORM_AUTHENTICATION_ERROR,
|
|
TEMPORARY_ERROR,
|
|
];
|
|
errors.forEach((err) => {
|
|
it(`errors ${err}`, async () => {
|
|
const res = await handleError(config, new Error(err));
|
|
expect(res).toEqual(err);
|
|
});
|
|
});
|
|
|
|
it(`handles ExternalHostError`, async () => {
|
|
const res = await handleError(
|
|
config,
|
|
new ExternalHostError(new Error(), 'some-host-type'),
|
|
);
|
|
expect(res).toEqual(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('rewrites git 5xx error', async () => {
|
|
const gitError = new Error(
|
|
"fatal: unable to access 'https://**redacted**@gitlab.com/learnox/learnox.git/': The requested URL returned error: 500\n",
|
|
);
|
|
const res = await handleError(config, gitError);
|
|
expect(res).toEqual(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('rewrites git remote error', async () => {
|
|
const gitError = new Error(
|
|
'fatal: remote error: access denied or repository not exported: /b/nw/bd/27/47/159945428/108610112.git\n',
|
|
);
|
|
const res = await handleError(config, gitError);
|
|
expect(res).toEqual(EXTERNAL_HOST_ERROR);
|
|
});
|
|
|
|
it('rewrites git fatal error', async () => {
|
|
const gitError = new Error(
|
|
'fatal: not a git repository (or any parent up to mount point /mnt)\nStopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set).\n',
|
|
);
|
|
const res = await handleError(config, gitError);
|
|
expect(res).toEqual(TEMPORARY_ERROR);
|
|
});
|
|
|
|
it('handles unknown error', async () => {
|
|
const res = await handleError(config, new Error('abcdefg'));
|
|
expect(res).toEqual(UNKNOWN_ERROR);
|
|
});
|
|
|
|
it('logs config validation errors as warnings by default', async () => {
|
|
const error = new Error(CONFIG_VALIDATION);
|
|
await handleError(config, error);
|
|
expect(logger.logger.warn).toHaveBeenCalledExactlyOnceWith(
|
|
{ err: error },
|
|
'Repository has invalid config',
|
|
);
|
|
expect(logger.logger.error).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('logs config validation errors as warnings when configValidationError is false', async () => {
|
|
const error = new Error(CONFIG_VALIDATION);
|
|
await handleError({ ...config, configValidationError: false }, error);
|
|
expect(logger.logger.warn).toHaveBeenCalledExactlyOnceWith(
|
|
{ err: error },
|
|
'Repository has invalid config',
|
|
);
|
|
expect(logger.logger.error).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('logs config validation errors as errors when configValidationError is true', async () => {
|
|
const error = new Error(CONFIG_VALIDATION);
|
|
await handleError({ ...config, configValidationError: true }, error);
|
|
expect(logger.logger.error).toHaveBeenCalledExactlyOnceWith(
|
|
{ err: error },
|
|
'Repository has invalid config',
|
|
);
|
|
expect(logger.logger.warn).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|