Skip to content
Snippets Groups Projects
Select Git revision
  • 2b95d7bb34c0472ebb73ede35075473cff3a1e37
  • master default protected
  • docs/remove-link
  • v2.31.0
  • v2.30.0
  • v2.29.0
  • v2.28.0
  • v2.27.1
  • v2.27.0
  • v2.26.1
  • v2.26.0
  • v2.25.0
  • v2.24.1
  • v2.24.0
  • v2.23.1
  • v2.23.0
  • v2.22.4
  • v2.22.3
  • v2.22.2
  • v2.22.1
  • v2.22.0
  • v2.21.0
  • v2.20.2
23 results

CODE_OF_CONDUCT.md

Blame
  • validation.spec.ts 20.45 KiB
    import type { RenovateConfig } from './types';
    import * as configValidation from './validation';
    
    describe('config/validation', () => {
      describe('getParentName()', () => {
        it('ignores encrypted in root', () => {
          expect(configValidation.getParentName('encrypted')).toBeEmptyString();
        });
        it('handles array types', () => {
          expect(configValidation.getParentName('hostRules[1]')).toBe('hostRules');
        });
        it('handles encrypted within array types', () => {
          expect(configValidation.getParentName('hostRules[0].encrypted')).toBe(
            'hostRules'
          );
        });
      });
      describe('validateConfig(config)', () => {
        it('returns deprecation warnings', async () => {
          const config = {
            prTitle: 'something',
          };
          const { warnings } = await configValidation.validateConfig(config);
          expect(warnings).toHaveLength(1);
          expect(warnings).toMatchSnapshot();
        });
        it('catches invalid templates', async () => {
          const config = {
            commitMessage: '{{{something}}',
          };
          const { errors } = await configValidation.validateConfig(config);
          expect(errors).toHaveLength(1);
          expect(errors).toMatchSnapshot();
        });
        it('catches invalid allowedVersions regex', async () => {
          const config = {
            packageRules: [
              {
                matchPackageNames: ['foo'],
                allowedVersions: '/^2/',
              },
              {
                matchPackageNames: ['bar'],
                allowedVersions: '/***$}{]][/',
              },
              {
                matchPackageNames: ['baz'],
                allowedVersions: '!/^2/',
              },
              {
                matchPackageNames: ['quack'],
                allowedVersions: '!/***$}{]][/',
              },
            ],
          };
          const { errors } = await configValidation.validateConfig(config);
          expect(errors).toHaveLength(2);
          expect(errors).toMatchSnapshot();
        });
        it('catches invalid matchCurrentVersion regex', async () => {
          const config = {
            packageRules: [
              {
                matchPackageNames: ['foo'],
                matchCurrentVersion: '/^2/',
                enabled: true,
              },
              {
                matchPackageNames: ['bar'],
                matchCurrentVersion: '/***$}{]][/',
                enabled: true,
              },
              {
                matchPackageNames: ['baz'],
                matchCurrentVersion: '!/^2/',
                enabled: true,
              },
              {
                matchPackageNames: ['quack'],
                matchCurrentVersion: '!/***$}{]][/',
                enabled: true,
              },
            ],
          };
          const { errors } = await configValidation.validateConfig(config);
          expect(errors).toHaveLength(2);
          expect(errors).toMatchSnapshot();
        });
        it('returns nested errors', async () => {
          const config: RenovateConfig = {
            foo: 1,
            schedule: ['after 5pm'],
            timezone: 'Asia/Singapore',
            packageRules: [
              {
                matchPackagePatterns: ['*'],
                excludePackagePatterns: ['abc ([a-z]+) ([a-z]+))'],
                enabled: true,
              },
            ],
            lockFileMaintenance: {
              bar: 2,
            },
            major: null,
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(3);
          expect(errors).toMatchSnapshot();
        });
        it('included unsupported manager', async () => {
          const config = {
            packageRules: [
              {
                matchManagers: ['foo'],
                enabled: true,
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(1);
          expect(errors[0].message).toContain('ansible');
        });
        it('included managers of the wrong type', async () => {
          const config = {
            packageRules: [
              {
                matchManagers: 'string not an array',
                enabled: true,
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(2);
          expect(errors).toMatchSnapshot();
        });
    
        it.each([
          ['empty configuration', {}],
          ['configuration with enabledManagers empty', { enabledManagers: [] }],
          ['single enabled manager', { enabledManagers: ['npm'] }],
          [
            'multiple enabled managers',
            { enabledManagers: ['npm', 'gradle', 'maven'] },
          ],
        ])('validates enabled managers for %s', async (_case, config) => {
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(0);
        });
    
        it.each([
          ['single not supported manager', { enabledManagers: ['foo'] }],
          ['multiple not supported managers', { enabledManagers: ['foo', 'bar'] }],
          [
            'combined supported and not supported managers',
            { enabledManagers: ['foo', 'npm', 'gradle', 'maven'] },
          ],
        ])(
          'errors if included not supported enabled managers for %s',
          async (_case, config) => {
            const { warnings, errors } = await configValidation.validateConfig(
              config
            );
            expect(warnings).toHaveLength(0);
            expect(errors).toHaveLength(1);
            expect(errors).toMatchSnapshot();
          }
        );
        it('errors for all types', async () => {
          const config: RenovateConfig = {
            allowedVersions: 'foo',
            enabled: 1 as any,
            enabledManagers: ['npm'],
            schedule: ['every 15 mins every weekday'],
            timezone: 'Asia',
            labels: 5 as any,
            semanticCommitType: 7 as any,
            lockFileMaintenance: false as any,
            extends: [':timezone(Europe/Brussel)'],
            packageRules: [
              {
                excludePackageNames: ['foo'],
                enabled: true,
              },
              {
                foo: 1,
              },
              'what?' as any,
              {
                matchPackagePatterns: 'abc ([a-z]+) ([a-z]+))',
                excludePackagePatterns: ['abc ([a-z]+) ([a-z]+))'],
                enabled: false,
              },
            ],
            major: null,
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(1);
          expect(errors).toMatchSnapshot();
          expect(errors).toHaveLength(12);
        });
        it('selectors outside packageRules array trigger errors', async () => {
          const config = {
            matchPackageNames: ['angular'],
            meteor: {
              packageRules: [
                {
                  matchPackageNames: ['meteor'],
                  enabled: true,
                },
              ],
            },
            docker: {
              minor: {
                matchPackageNames: ['testPackage'],
              },
            },
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(2);
          expect(errors).toMatchSnapshot();
          expect(errors).toHaveLength(2);
        });
        it('ignore packageRule nesting validation for presets', async () => {
          const config = {
            description: ['All angular.js packages'],
            matchPackageNames: [
              'angular',
              'angular-animate',
              'angular-scroll',
              'angular-sanitize',
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toMatchSnapshot();
          expect(errors).toHaveLength(0);
        });
    
        it('errors for unsafe fileMatches', async () => {
          const config = {
            npm: {
              fileMatch: ['abc ([a-z]+) ([a-z]+))'],
            },
            dockerfile: {
              fileMatch: ['x?+'],
            },
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(2);
          expect(errors).toMatchSnapshot();
        });
    
        it('validates regEx for each fileMatch', async () => {
          const config = {
            regexManagers: [
              {
                fileMatch: ['js', '***$}{]]['],
                matchStrings: ['^(?<depName>foo)(?<currentValue>bar)$'],
                datasourceTemplate: 'maven',
                versioningTemplate: 'gradle',
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(1);
          expect(errors).toMatchSnapshot();
        });
        it('errors if no regexManager matchStrings', async () => {
          const config = {
            regexManagers: [
              {
                fileMatch: [],
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config as any,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(1);
          expect(errors).toMatchInlineSnapshot(`
            Array [
              Object {
                "message": "Each Regex Manager must contain a non-empty fileMatch array",
                "topic": "Configuration Error",
              },
            ]
          `);
        });
        it('errors if empty regexManager matchStrings', async () => {
          const config = {
            regexManagers: [
              {
                fileMatch: ['foo'],
                matchStrings: [],
              },
              {
                fileMatch: ['foo'],
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config as RenovateConfig,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(2);
          expect(errors).toMatchInlineSnapshot(`
            Array [
              Object {
                "message": "Each Regex Manager must contain a non-empty matchStrings array",
                "topic": "Configuration Error",
              },
              Object {
                "message": "Each Regex Manager must contain a non-empty matchStrings array",
                "topic": "Configuration Error",
              },
            ]
          `);
        });
        it('errors if no regexManager fileMatch', async () => {
          const config = {
            regexManagers: [
              {
                matchStrings: ['^(?<depName>foo)(?<currentValue>bar)$'],
                datasourceTemplate: 'maven',
                versioningTemplate: 'gradle',
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config as any,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(1);
        });
        it('validates regEx for each matchStrings', async () => {
          const config = {
            regexManagers: [
              {
                fileMatch: ['Dockerfile'],
                matchStrings: ['***$}{]]['],
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(1);
        });
        it('passes if regexManager fields are present', async () => {
          const config = {
            regexManagers: [
              {
                fileMatch: ['Dockerfile'],
                matchStrings: ['ENV (?<currentValue>.*?)\\s'],
                depNameTemplate: 'foo',
                datasourceTemplate: 'bar',
                registryUrlTemplate: 'foobar',
                extractVersionTemplate: '^(?<version>v\\d+\\.\\d+)',
                depTypeTemplate: 'apple',
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(0);
        });
        it('errors if extra regexManager fields are present', async () => {
          const config = {
            regexManagers: [
              {
                fileMatch: ['Dockerfile'],
                matchStrings: ['ENV (?<currentValue>.*?)\\s'],
                depNameTemplate: 'foo',
                datasourceTemplate: 'bar',
                depTypeTemplate: 'apple',
                automerge: true,
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(1);
        });
        it('errors if regexManager fields are missing', async () => {
          const config = {
            regexManagers: [
              {
                fileMatch: ['Dockerfile'],
                matchStrings: ['ENV (.*?)\\s'],
                depNameTemplate: 'foo',
                datasourceTemplate: 'bar',
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toMatchSnapshot();
          expect(errors).toHaveLength(1);
        });
        it('ignore keys', async () => {
          const config = {
            $schema: 'renovate.json',
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(0);
        });
    
        it('validates timezone preset', async () => {
          const config = {
            extends: [':timezone', ':timezone(Europe/Berlin)'],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(0);
        });
    
        it('does not validate constraints children', async () => {
          const config = {
            constraints: { packageRules: [{}] },
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(0);
        });
    
        it('validates object with ignored children', async () => {
          const config = {
            prBodyDefinitions: {},
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(0);
        });
    
        it('validates valid alias objects', async () => {
          const config = {
            aliases: {
              example1: 'http://www.example.com',
              example2: 'https://www.example2.com/example',
            },
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(0);
        });
    
        it('errors if aliases depth is more than 1', async () => {
          const config = {
            aliases: {
              sample: {
                example1: 'http://www.example.com',
              },
            },
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toMatchObject([
            {
              message:
                'Invalid `aliases.aliases.sample` configuration: value is not a url',
              topic: 'Configuration Error',
            },
          ]);
        });
    
        it('errors if aliases have invalid url', async () => {
          const config = {
            aliases: {
              example1: 'noturl',
              example2: 'http://www.example.com',
            },
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toMatchObject([
            {
              message:
                'Invalid `aliases.aliases.example1` configuration: value is not a url',
              topic: 'Configuration Error',
            },
          ]);
        });
    
        it('errors if fileMatch has wrong parent', async () => {
          const config = {
            fileMatch: ['foo'],
            npm: {
              fileMatch: ['package\\.json'],
              minor: {
                fileMatch: ['bar'],
              },
            },
            regexManagers: [
              {
                fileMatch: ['build.gradle'],
                matchStrings: ['^(?<depName>foo)(?<currentValue>bar)$'],
                datasourceTemplate: 'maven',
                versioningTemplate: 'gradle',
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(errors).toHaveLength(1);
          expect(warnings).toHaveLength(1);
          expect(errors).toMatchSnapshot();
          expect(warnings).toMatchSnapshot();
        });
    
        it('errors if language or manager objects are nested', async () => {
          const config = {
            python: {
              enabled: false,
            },
            java: {
              gradle: {
                enabled: false,
              },
            },
            major: {
              minor: {
                docker: {
                  automerge: true,
                },
              },
            },
          } as never;
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(errors).toHaveLength(2);
          expect(warnings).toHaveLength(0);
          expect(errors).toMatchSnapshot();
        });
    
        it('warns if hostType has the wrong parent', async () => {
          const config = {
            hostType: 'npm',
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(errors).toHaveLength(0);
          expect(warnings).toHaveLength(1);
          expect(warnings).toMatchSnapshot();
        });
    
        it('validates preset values', async () => {
          const config = {
            extends: ['foo', 'bar', 42] as never,
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(1);
        });
    
        it('warns if only selectors in packageRules', async () => {
          const config = {
            packageRules: [
              { matchDepTypes: ['foo'], excludePackageNames: ['bar'] },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(1);
          expect(warnings).toMatchSnapshot();
          expect(errors).toHaveLength(0);
        });
        it('errors if invalid combinations in packageRules', async () => {
          const config = {
            packageRules: [
              {
                matchUpdateTypes: ['major'],
                registryUrls: ['https://registry.npmjs.org'],
              },
            ],
          } as any;
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(1);
          expect(errors).toMatchSnapshot();
        });
        it('warns on nested group packageRules', async () => {
          const config = {
            extends: ['group:fortawesome'],
            packageRules: [
              {
                automerge: true,
                extends: ['group:fortawesome'],
              },
            ],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config,
            true
          );
          expect(errors).toHaveLength(0);
          expect(warnings).toHaveLength(1);
        });
    
        it('validates valid customEnvVariables objects', async () => {
          const config = {
            customEnvVariables: {
              example1: 'abc',
              example2: 'https://www.example2.com/example',
            },
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toHaveLength(0);
        });
        it('errors on invalid customEnvVariables objects', async () => {
          const config = {
            customEnvVariables: {
              example1: 'abc',
              example2: 123,
            },
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toMatchObject([
            {
              message:
                'Invalid `customEnvVariables.customEnvVariables.example2` configuration: value is not a string',
              topic: 'Configuration Error',
            },
          ]);
        });
    
        it('errors if schedule is cron and has no * minutes', async () => {
          const config = {
            schedule: ['30 5 * * *'],
          };
          const { warnings, errors } = await configValidation.validateConfig(
            config
          );
          expect(warnings).toHaveLength(0);
          expect(errors).toMatchObject([
            {
              message:
                'Invalid schedule: `Invalid schedule: "30 5 * * *" has cron syntax, but doesn\'t have * as minutes`',
              topic: 'Configuration Error',
            },
          ]);
        });
      });
    });