mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-25 15:01:49 +00:00
a31f9cf0f9
* ci(linter): add renovate/prefer-zod-nullish rule Add a custom oxlint rule that flags zod chains calling .optional() and .nullable() adjacently (in either order) and suggests .nullish(), which is equivalent (both accept undefined | null in zod v4). Fix all 26 pre-existing violations across 6 schema files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(linter): prefer schema-utils Nullish over zod .nullish() Rename prefer-zod-nullish to prefer-nullish-util and extend it to flag .nullish() itself, including the standalone forms z.nullish(x) and z.optional(z.nullable(x)). Migrate all call sites to Nullish() and DeepNullish() from schema-utils, which normalize null to undefined in parsed output. Schemas with more than one nullish position are wrapped in DeepNullish(); nullish object properties with nullish members use Nullish(DeepNullish(...)) to keep the optional property type marker. Spec files are exempt (result.spec.ts intentionally tests null-preserving parses). Spec assertions expecting null in parsed output were updated to undefined. Note: docker manifests with an explicit "mediaType": null are now accepted and treated like an absent mediaType instead of failing the discriminated union. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
247 lines
9.4 KiB
TypeScript
247 lines
9.4 KiB
TypeScript
import { ZodError } from 'zod/v4';
|
|
import { logger } from '~test/util.ts';
|
|
import { GithubContentResponse, GithubVulnerabilityAlerts } from './schema.ts';
|
|
|
|
describe('modules/platform/github/schema', () => {
|
|
it('should be parse directory response', () => {
|
|
const { error } = GithubContentResponse.safeParse([
|
|
{
|
|
type: 'file',
|
|
size: 625,
|
|
name: 'octokit.rb',
|
|
path: 'lib/octokit.rb',
|
|
sha: 'fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b',
|
|
url: 'https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit.rb',
|
|
git_url:
|
|
'https://api.github.com/repos/octokit/octokit.rb/git/blobs/fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b',
|
|
html_url:
|
|
'https://github.com/octokit/octokit.rb/blob/master/lib/octokit.rb',
|
|
download_url:
|
|
'https://raw.githubusercontent.com/octokit/octokit.rb/master/lib/octokit.rb',
|
|
_links: {
|
|
self: 'https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit.rb',
|
|
git: 'https://api.github.com/repos/octokit/octokit.rb/git/blobs/fff6fe3a23bf1c8ea0692b4a883af99bee26fd3b',
|
|
html: 'https://github.com/octokit/octokit.rb/blob/master/lib/octokit.rb',
|
|
},
|
|
},
|
|
{
|
|
type: 'dir',
|
|
size: 0,
|
|
name: 'octokit',
|
|
path: 'lib/octokit',
|
|
sha: 'a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d',
|
|
url: 'https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit',
|
|
git_url:
|
|
'https://api.github.com/repos/octokit/octokit.rb/git/trees/a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d',
|
|
html_url:
|
|
'https://github.com/octokit/octokit.rb/tree/master/lib/octokit',
|
|
download_url: null,
|
|
_links: {
|
|
self: 'https://api.github.com/repos/octokit/octokit.rb/contents/lib/octokit',
|
|
git: 'https://api.github.com/repos/octokit/octokit.rb/git/trees/a84d88e7554fc1fa21bcbc4efae3c782a70d2b9d',
|
|
html: 'https://github.com/octokit/octokit.rb/tree/master/lib/octokit',
|
|
},
|
|
},
|
|
{
|
|
type: 'symlink',
|
|
target: '/path/to/symlink/target',
|
|
size: 23,
|
|
name: 'some-symlink',
|
|
path: 'bin/some-symlink',
|
|
sha: '452a98979c88e093d682cab404a3ec82babebb48',
|
|
url: 'https://api.github.com/repos/octokit/octokit.rb/contents/bin/some-symlink',
|
|
git_url:
|
|
'https://api.github.com/repos/octokit/octokit.rb/git/blobs/452a98979c88e093d682cab404a3ec82babebb48',
|
|
html_url:
|
|
'https://github.com/octokit/octokit.rb/blob/master/bin/some-symlink',
|
|
download_url:
|
|
'https://raw.githubusercontent.com/octokit/octokit.rb/master/bin/some-symlink',
|
|
_links: {
|
|
git: 'https://api.github.com/repos/octokit/octokit.rb/git/blobs/452a98979c88e093d682cab404a3ec82babebb48',
|
|
self: 'https://api.github.com/repos/octokit/octokit.rb/contents/bin/some-symlink',
|
|
html: 'https://github.com/octokit/octokit.rb/blob/master/bin/some-symlink',
|
|
},
|
|
},
|
|
{
|
|
type: 'submodule',
|
|
submodule_git_url: 'git://github.com/jquery/qunit.git',
|
|
size: 0,
|
|
name: 'qunit',
|
|
path: 'test/qunit',
|
|
sha: '6ca3721222109997540bd6d9ccd396902e0ad2f9',
|
|
url: 'https://api.github.com/repos/jquery/jquery/contents/test/qunit?ref=master',
|
|
git_url:
|
|
'https://api.github.com/repos/jquery/qunit/git/trees/6ca3721222109997540bd6d9ccd396902e0ad2f9',
|
|
html_url:
|
|
'https://github.com/jquery/qunit/tree/6ca3721222109997540bd6d9ccd396902e0ad2f9',
|
|
download_url: null,
|
|
_links: {
|
|
git: 'https://api.github.com/repos/jquery/qunit/git/trees/6ca3721222109997540bd6d9ccd396902e0ad2f9',
|
|
self: 'https://api.github.com/repos/jquery/jquery/contents/test/qunit?ref=master',
|
|
html: 'https://github.com/jquery/qunit/tree/6ca3721222109997540bd6d9ccd396902e0ad2f9',
|
|
},
|
|
},
|
|
]);
|
|
expect(error).toBeUndefined();
|
|
});
|
|
|
|
it('should parse response for single file', () => {
|
|
const { error } = GithubContentResponse.safeParse({
|
|
type: 'file',
|
|
encoding: 'base64',
|
|
size: 5362,
|
|
name: 'README.md',
|
|
path: 'README.md',
|
|
content: 'aaaaaaaaaa',
|
|
sha: '3d21ec53a331a6f037a91c368710b99387d012c1',
|
|
url: 'https://api.github.com/repos/octokit/octokit.rb/contents/README.md',
|
|
git_url:
|
|
'https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1',
|
|
html_url: 'https://github.com/octokit/octokit.rb/blob/master/README.md',
|
|
download_url:
|
|
'https://raw.githubusercontent.com/octokit/octokit.rb/master/README.md',
|
|
_links: {
|
|
git: 'https://api.github.com/repos/octokit/octokit.rb/git/blobs/3d21ec53a331a6f037a91c368710b99387d012c1',
|
|
self: 'https://api.github.com/repos/octokit/octokit.rb/contents/README.md',
|
|
html: 'https://github.com/octokit/octokit.rb/blob/master/README.md',
|
|
},
|
|
});
|
|
expect(error).toBeUndefined();
|
|
});
|
|
|
|
it('should skip vulnerability alerts with unsupported ecosystems', () => {
|
|
const result = GithubVulnerabilityAlerts.parse([
|
|
{
|
|
dismissed_reason: null,
|
|
security_advisory: {
|
|
ghsa_id: 'GHSA-1111-2222-3333',
|
|
summary: 'Test advisory',
|
|
description: 'Test advisory',
|
|
identifiers: [{ type: 'CVE', value: 'CVE-2024-1234' }],
|
|
severity: 'high',
|
|
},
|
|
security_vulnerability: {
|
|
first_patched_version: { identifier: '1.0.0' },
|
|
package: { ecosystem: 'dotnet', name: 'test-package' },
|
|
severity: 'high',
|
|
vulnerable_version_range: '< 1.0.0',
|
|
},
|
|
dependency: { manifest_path: 'package.json' },
|
|
},
|
|
{
|
|
dismissed_reason: null,
|
|
security_advisory: {
|
|
ghsa_id: 'GHSA-4444-5555-6666',
|
|
summary: 'Test advisory',
|
|
description: 'Test advisory',
|
|
identifiers: [{ type: 'CVE', value: 'CVE-2024-5678' }],
|
|
severity: 'medium',
|
|
},
|
|
security_vulnerability: {
|
|
first_patched_version: { identifier: '2.0.0' },
|
|
package: { ecosystem: 'npm', name: 'valid-package' },
|
|
severity: 'medium',
|
|
vulnerable_version_range: '< 2.0.0',
|
|
},
|
|
dependency: { manifest_path: 'package.json' },
|
|
},
|
|
]);
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].security_vulnerability?.package.ecosystem).toBe('npm');
|
|
});
|
|
|
|
it('should log vulnerability alerts with parse errors', () => {
|
|
const { data, success } = GithubVulnerabilityAlerts.safeParse([
|
|
{
|
|
dismissed_reason: null,
|
|
security_advisory: {
|
|
ghsa_id: 'GHSA-1111-2222-3333',
|
|
summary: 'Test advisory',
|
|
description: 'Test advisory',
|
|
identifiers: [{ type: 'CVE', value: 'CVE-2024-1234' }],
|
|
severity: 'high',
|
|
},
|
|
security_vulnerability: {
|
|
first_patched_version: { identifier: '1.0.0' },
|
|
package: { ecosystem: 'dotnet', name: 'test-package' },
|
|
severity: 'high',
|
|
vulnerable_version_range: '< 1.0.0',
|
|
},
|
|
},
|
|
]);
|
|
expect(success).toBe(true);
|
|
expect(data).toBeEmptyArray();
|
|
expect(logger.logger.debug).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
error: expect.any(ZodError),
|
|
}),
|
|
'Vulnerability Alert: Failed to parse some alerts',
|
|
);
|
|
});
|
|
|
|
it('should filter vulnerability alerts with missing security_vulnerability', () => {
|
|
const { data, success } = GithubVulnerabilityAlerts.safeParse([
|
|
{
|
|
dismissed_reason: null,
|
|
security_advisory: {
|
|
ghsa_id: 'GHSA-4444-5555-6666',
|
|
summary: 'Test advisory',
|
|
description: 'Test advisory',
|
|
identifiers: [{ type: 'CVE', value: 'CVE-2024-5678' }],
|
|
severity: 'high',
|
|
},
|
|
security_vulnerability: null,
|
|
dependency: { manifest_path: 'package.json' },
|
|
},
|
|
]);
|
|
expect(success).toBe(true);
|
|
expect(data).toBeEmptyArray();
|
|
expect(logger.logger.debug).not.toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
error: expect.any(ZodError),
|
|
}),
|
|
'Vulnerability Alert: Failed to parse some alerts',
|
|
);
|
|
});
|
|
|
|
it('should parse severity and cvss_severities fields', () => {
|
|
const result = GithubVulnerabilityAlerts.parse([
|
|
{
|
|
dismissed_reason: null,
|
|
security_advisory: {
|
|
ghsa_id: 'GHSA-1111-2222-3333',
|
|
summary: 'Test advisory',
|
|
description: 'Test advisory',
|
|
identifiers: [{ type: 'CVE', value: 'CVE-2024-1234' }],
|
|
severity: 'high',
|
|
cvss_severities: {
|
|
cvss_v3: {
|
|
vector_string: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H',
|
|
score: 9.8,
|
|
},
|
|
cvss_v4: null,
|
|
},
|
|
},
|
|
security_vulnerability: {
|
|
first_patched_version: { identifier: '2.0.0' },
|
|
package: { ecosystem: 'npm', name: 'test-package' },
|
|
severity: 'critical',
|
|
vulnerable_version_range: '< 2.0.0',
|
|
},
|
|
dependency: { manifest_path: 'package.json' },
|
|
},
|
|
]);
|
|
|
|
expect(result).toHaveLength(1);
|
|
expect(result[0].security_advisory.severity).toBe('high');
|
|
expect(result[0].security_vulnerability?.severity).toBe('critical');
|
|
expect(result[0].security_advisory.cvss_severities?.cvss_v3).toEqual({
|
|
vector_string: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H',
|
|
score: 9.8,
|
|
});
|
|
expect(
|
|
result[0].security_advisory.cvss_severities?.cvss_v4,
|
|
).toBeUndefined();
|
|
});
|
|
});
|