From 64f5bd5f03f977ee860a13cb77d0cb52b73380e6 Mon Sep 17 00:00:00 2001 From: Michael Kriese <michael.kriese@visualon.de> Date: Tue, 12 Apr 2022 16:49:49 +0200 Subject: [PATCH] test: enforce lint style rules (#15085) --- .eslintrc.js | 3 +- lib/config/decrypt.spec.ts | 11 ++ lib/config/index.spec.ts | 5 + lib/config/massage.spec.ts | 4 + lib/config/migrate-validate.spec.ts | 3 + lib/config/migration.spec.ts | 18 +++ .../presets/bitbucket-server/index.spec.ts | 1 + lib/config/presets/gitea/index.spec.ts | 1 + lib/config/presets/index.spec.ts | 59 ++++++++++ lib/config/presets/local/index.spec.ts | 9 ++ lib/config/presets/npm/index.spec.ts | 5 + lib/config/presets/util.spec.ts | 1 + lib/config/secrets.spec.ts | 15 +++ lib/config/validation.spec.ts | 23 ++++ lib/logger/config-serializer.spec.ts | 1 + lib/logger/err-serializer.spec.ts | 1 + lib/logger/index.spec.ts | 4 + lib/logger/pretty-stdout.spec.ts | 11 ++ .../aws-machine-image/index.spec.ts | 9 ++ .../datasource/bitbucket-tags/index.spec.ts | 3 + lib/modules/datasource/cdnjs/index.spec.ts | 9 ++ lib/modules/datasource/conan/index.spec.ts | 1 + lib/modules/datasource/crate/index.spec.ts | 15 +++ lib/modules/datasource/dart/index.spec.ts | 5 + lib/modules/datasource/docker/index.spec.ts | 4 + .../datasource/flutter-version/index.spec.ts | 3 + lib/modules/datasource/galaxy/index.spec.ts | 8 ++ lib/modules/datasource/git-refs/index.spec.ts | 7 ++ lib/modules/datasource/git-tags/index.spec.ts | 5 + .../datasource/github-releases/common.spec.ts | 1 + .../datasource/github-releases/index.spec.ts | 1 + .../datasource/github-tags/index.spec.ts | 6 + lib/modules/datasource/go/index.spec.ts | 5 + .../datasource/go/releases-direct.spec.ts | 11 ++ .../datasource/golang-version/index.spec.ts | 2 + lib/modules/datasource/helm/index.spec.ts | 11 ++ lib/modules/datasource/hex/index.spec.ts | 7 ++ lib/modules/datasource/maven/index.spec.ts | 1 + lib/modules/datasource/metadata.spec.ts | 7 ++ lib/modules/datasource/npm/npmrc.spec.ts | 11 ++ lib/modules/datasource/nuget/index.spec.ts | 19 ++++ lib/modules/datasource/orb/index.spec.ts | 5 + .../datasource/packagist/index.spec.ts | 13 +++ lib/modules/datasource/pod/index.spec.ts | 16 +++ lib/modules/datasource/pypi/index.spec.ts | 22 ++++ .../datasource/ruby-version/index.spec.ts | 1 + lib/modules/datasource/rubygems/index.spec.ts | 1 + .../datasource/sbt-plugin/index.spec.ts | 1 + .../datasource/terraform-module/index.spec.ts | 7 ++ .../terraform-provider/index.spec.ts | 7 ++ .../manager/ansible-galaxy/extract.spec.ts | 11 ++ lib/modules/manager/ansible/extract.spec.ts | 2 + .../manager/azure-pipelines/extract.spec.ts | 3 + lib/modules/manager/bazel/extract.spec.ts | 5 + lib/modules/manager/bazel/update.spec.ts | 6 + lib/modules/manager/buildkite/extract.spec.ts | 6 + lib/modules/manager/bundler/artifacts.spec.ts | 10 ++ lib/modules/manager/bundler/extract.spec.ts | 7 ++ .../manager/bundler/host-rules.spec.ts | 10 ++ .../manager/bundler/locked-version.spec.ts | 4 + lib/modules/manager/bundler/range.spec.ts | 1 + .../manager/bundler/update-locked.spec.ts | 1 + lib/modules/manager/cargo/artifacts.spec.ts | 2 + lib/modules/manager/cargo/extract.spec.ts | 16 +++ lib/modules/manager/circleci/extract.spec.ts | 3 + .../manager/cloudbuild/extract.spec.ts | 1 + .../manager/cocoapods/artifacts.spec.ts | 1 + lib/modules/manager/composer/extract.spec.ts | 9 ++ lib/modules/manager/composer/range.spec.ts | 6 + .../manager/composer/update-locked.spec.ts | 1 + lib/modules/manager/composer/utils.spec.ts | 10 ++ lib/modules/manager/conan/extract.spec.ts | 3 + .../manager/docker-compose/extract.spec.ts | 6 + .../manager/dockerfile/extract.spec.ts | 1 + lib/modules/manager/flux/extract.spec.ts | 18 +++ lib/modules/manager/fvm/extract.spec.ts | 4 + .../manager/git-submodules/artifact.spec.ts | 1 + .../manager/git-submodules/extract.spec.ts | 1 + .../manager/git-submodules/update.spec.ts | 4 + .../manager/github-actions/extract.spec.ts | 3 + .../manager/gitlabci-include/extract.spec.ts | 4 + lib/modules/manager/gitlabci/extract.spec.ts | 2 + lib/modules/manager/gomod/artifacts.spec.ts | 1 + lib/modules/manager/gomod/extract.spec.ts | 3 + lib/modules/manager/gomod/update.spec.ts | 21 ++++ .../manager/helm-requirements/extract.spec.ts | 8 ++ .../manager/helm-values/extract.spec.ts | 5 + lib/modules/manager/helmv3/artifacts.spec.ts | 1 + lib/modules/manager/helmv3/extract.spec.ts | 10 ++ lib/modules/manager/helmv3/update.spec.ts | 4 + lib/modules/manager/homebrew/extract.spec.ts | 12 ++ lib/modules/manager/homebrew/update.spec.ts | 10 ++ lib/modules/manager/html/extract.spec.ts | 1 + lib/modules/manager/index.spec.ts | 9 ++ .../manager/jsonnet-bundler/extract.spec.ts | 5 + .../manager/kubernetes/extract.spec.ts | 3 + lib/modules/manager/kustomize/extract.spec.ts | 29 +++++ lib/modules/manager/leiningen/extract.spec.ts | 3 + lib/modules/manager/maven/extract.spec.ts | 2 + lib/modules/manager/maven/index.spec.ts | 3 + lib/modules/manager/metadata.spec.ts | 1 + lib/modules/manager/meteor/extract.spec.ts | 1 + lib/modules/manager/mix/extract.spec.ts | 1 + lib/modules/manager/nodenv/extract.spec.ts | 2 + lib/modules/manager/npm/detect.spec.ts | 1 + lib/modules/manager/npm/extract/index.spec.ts | 24 ++++ .../npm/extract/locked-versions.spec.ts | 5 + lib/modules/manager/npm/extract/npm.spec.ts | 3 + lib/modules/manager/npm/extract/type.spec.ts | 2 + .../manager/npm/post-update/lerna.spec.ts | 12 ++ .../npm/post-update/node-version.spec.ts | 5 + .../manager/npm/post-update/npm.spec.ts | 11 ++ .../manager/npm/post-update/pnpm.spec.ts | 5 + .../manager/npm/post-update/rules.spec.ts | 4 + lib/modules/manager/npm/range.spec.ts | 6 + .../npm/update/dependency/index.spec.ts | 13 +++ .../update/locked-dependency/index.spec.ts | 19 ++++ .../package-lock/dep-constraints.spec.ts | 3 + .../package-lock/get-locked.spec.ts | 5 + .../yarn-lock/get-locked.spec.ts | 1 + .../locked-dependency/yarn-lock/index.spec.ts | 7 ++ .../yarn-lock/replace.spec.ts | 5 + .../npm/update/package-version/index.spec.ts | 6 + lib/modules/manager/nuget/artifacts.spec.ts | 9 ++ lib/modules/manager/nuget/extract.spec.ts | 11 ++ lib/modules/manager/nvm/extract.spec.ts | 2 + .../manager/pip_requirements/extract.spec.ts | 15 +++ .../manager/pip_requirements/range.spec.ts | 1 + lib/modules/manager/pipenv/artifacts.spec.ts | 1 + lib/modules/manager/pipenv/extract.spec.ts | 15 +++ lib/modules/manager/poetry/artifacts.spec.ts | 11 ++ lib/modules/manager/poetry/extract.spec.ts | 18 +++ .../manager/poetry/update-locked.spec.ts | 1 + .../manager/pre-commit/extract.spec.ts | 11 ++ lib/modules/manager/pub/extract.spec.ts | 2 + lib/modules/manager/pyenv/extract.spec.ts | 2 + lib/modules/manager/range.spec.ts | 3 + lib/modules/manager/regex/index.spec.ts | 14 +++ .../manager/ruby-version/extract.spec.ts | 2 + lib/modules/manager/sbt/extract.spec.ts | 9 ++ lib/modules/manager/sbt/update.spec.ts | 3 + lib/modules/manager/setup-cfg/extract.spec.ts | 1 + lib/modules/manager/setup-cfg/range.spec.ts | 1 + lib/modules/manager/swift/index.spec.ts | 4 + .../manager/terraform-version/extract.spec.ts | 1 + lib/modules/manager/terraform/extract.spec.ts | 1 + lib/modules/manager/terraform/modules.spec.ts | 2 + lib/modules/manager/terraform/util.spec.ts | 5 + .../manager/terragrunt/extract.spec.ts | 2 + .../manager/terragrunt/modules.spec.ts | 1 + lib/modules/manager/terragrunt/util.spec.ts | 3 + .../platform/azure/azure-got-wrapper.spec.ts | 3 + .../platform/azure/azure-helper.spec.ts | 7 ++ lib/modules/platform/azure/index.spec.ts | 37 ++++++ lib/modules/platform/azure/util.spec.ts | 19 ++++ .../platform/bitbucket-server/index.spec.ts | 12 ++ lib/modules/platform/bitbucket/index.spec.ts | 32 ++++++ lib/modules/platform/comment.spec.ts | 1 + lib/modules/platform/gitea/index.spec.ts | 5 + lib/modules/platform/github/index.spec.ts | 94 +++++++++++++++ lib/modules/platform/gitlab/index.spec.ts | 65 +++++++++++ lib/modules/platform/index.spec.ts | 2 + lib/modules/platform/util.spec.ts | 1 + .../aws-machine-image/index.spec.ts | 9 ++ lib/modules/versioning/generic.spec.ts | 15 +++ .../versioning/hashicorp/index.spec.ts | 1 + lib/modules/versioning/index.spec.ts | 1 + lib/modules/versioning/node/index.spec.ts | 2 + lib/modules/versioning/poetry/index.spec.ts | 1 + .../versioning/versioning-metadata.spec.ts | 1 + lib/proxy.spec.ts | 4 + lib/util/cache/memory/index.spec.ts | 2 + lib/util/cache/package/file.spec.ts | 1 + lib/util/cache/package/index.spec.ts | 2 + lib/util/cache/repository/index.spec.ts | 6 + lib/util/exec/buildpack.spec.ts | 9 ++ lib/util/exec/docker/index.spec.ts | 1 + lib/util/exec/env.spec.ts | 5 + lib/util/exec/index.spec.ts | 2 + lib/util/fs/index.spec.ts | 3 + lib/util/git/auth.spec.ts | 1 + lib/util/git/author.spec.ts | 6 + lib/util/git/index.spec.ts | 33 ++++++ lib/util/git/private-key.spec.ts | 3 + lib/util/host-rules.spec.ts | 18 +++ lib/util/html.spec.ts | 1 + lib/util/http/bitbucket-server.spec.ts | 1 + lib/util/http/bitbucket.spec.ts | 2 + lib/util/http/gitea.spec.ts | 1 + lib/util/http/github.spec.ts | 31 +++++ lib/util/http/gitlab.spec.ts | 3 + lib/util/http/host-rules.spec.ts | 1 + lib/util/http/index.spec.ts | 11 ++ lib/util/index.spec.ts | 7 ++ lib/util/object.spec.ts | 2 + lib/util/package-rules.spec.ts | 43 +++++++ lib/util/regex.spec.ts | 1 + lib/util/sanitize.spec.ts | 2 + lib/util/template/index.spec.ts | 8 ++ lib/workers/global/autodiscover.spec.ts | 9 ++ lib/workers/global/config/parse/cli.spec.ts | 26 +++++ lib/workers/global/config/parse/env.spec.ts | 30 +++++ .../config/parse/host-rules-from-env.spec.ts | 6 + lib/workers/global/config/parse/index.spec.ts | 7 ++ lib/workers/global/index.spec.ts | 4 + lib/workers/repository/configured.spec.ts | 3 + .../repository/dependency-dashboard.spec.ts | 3 + lib/workers/repository/error-config.spec.ts | 5 + lib/workers/repository/error.spec.ts | 6 + .../repository/extract/file-match.spec.ts | 9 ++ lib/workers/repository/extract/index.spec.ts | 3 + .../repository/extract/manager-files.spec.ts | 6 + lib/workers/repository/finalise/prune.spec.ts | 12 ++ lib/workers/repository/index.spec.ts | 2 + lib/workers/repository/init/apis.spec.ts | 9 ++ lib/workers/repository/init/cache.spec.ts | 2 + lib/workers/repository/init/index.spec.ts | 2 + lib/workers/repository/init/merge.spec.ts | 15 +++ lib/workers/repository/init/semantic.spec.ts | 3 + .../repository/init/vulnerability.spec.ts | 5 + .../onboarding/branch/config.spec.ts | 5 + .../onboarding/branch/create.spec.ts | 12 ++ .../onboarding/branch/index.spec.ts | 11 ++ .../onboarding/branch/rebase.spec.ts | 7 ++ .../onboarding/pr/base-branch.spec.ts | 4 + .../onboarding/pr/config-description.spec.ts | 7 ++ .../onboarding/pr/errors-warnings.spec.ts | 7 ++ .../repository/onboarding/pr/index.spec.ts | 9 ++ .../repository/onboarding/pr/pr-list.spec.ts | 4 + .../repository/process/deprecated.spec.ts | 2 + .../repository/process/extract-update.spec.ts | 2 + lib/workers/repository/process/fetch.spec.ts | 5 + lib/workers/repository/process/index.spec.ts | 3 + lib/workers/repository/process/limits.spec.ts | 10 ++ .../process/lookup/filter-checks.spec.ts | 1 + .../repository/process/lookup/index.spec.ts | 107 ++++++++++++++++++ lib/workers/repository/process/sort.spec.ts | 2 + lib/workers/repository/process/write.spec.ts | 2 + lib/workers/repository/result.spec.ts | 1 + .../update/branch/artifacts.spec.ts | 1 + .../update/branch/auto-replace.spec.ts | 10 ++ .../update/branch/automerge.spec.ts | 9 ++ .../update/branch/check-existing.spec.ts | 4 + .../repository/update/branch/commit.spec.ts | 5 + .../update/branch/get-updated.spec.ts | 23 ++++ .../repository/update/branch/index.spec.ts | 37 ++++++ .../update/branch/lock-files/index.spec.ts | 7 ++ .../repository/update/branch/reuse.spec.ts | 12 ++ .../repository/update/branch/schedule.spec.ts | 47 ++++++++ .../update/branch/status-checks.spec.ts | 8 ++ .../repository/update/pr/automerge.spec.ts | 12 ++ .../update/pr/body/config-description.spec.ts | 1 + .../update/pr/body/controls.spec.ts | 4 + .../update/pr/body/updates-table.spec.ts | 1 + .../update/pr/changelog/gitlab.spec.ts | 12 ++ .../update/pr/changelog/index.spec.ts | 14 +++ .../update/pr/changelog/release-notes.spec.ts | 8 ++ .../update/pr/changelog/releases.spec.ts | 5 + .../repository/update/pr/code-owners.spec.ts | 8 ++ .../repository/update/pr/index.spec.ts | 52 +++++++++ .../repository/updates/branch-name.spec.ts | 7 ++ .../repository/updates/branchify.spec.ts | 6 + .../repository/updates/flatten.spec.ts | 1 + .../repository/updates/generate.spec.ts | 24 ++++ package.json | 1 + test/website-docs.spec.ts | 5 + yarn.lock | 6 +- 267 files changed, 2198 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 71e038c4d7..fdbe786f15 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { env: { node: true, }, - plugins: ['@renovate', 'typescript-enum'], + plugins: ['@renovate', 'typescript-enum', 'jest-formatting'], extends: [ 'eslint:recommended', 'plugin:import/errors', @@ -15,6 +15,7 @@ module.exports = { 'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended-requiring-type-checking', 'plugin:promise/recommended', + 'plugin:jest-formatting/recommended', 'prettier', ], parserOptions: { diff --git a/lib/config/decrypt.spec.ts b/lib/config/decrypt.spec.ts index d702240093..92365f2ef4 100644 --- a/lib/config/decrypt.spec.ts +++ b/lib/config/decrypt.spec.ts @@ -10,27 +10,32 @@ const repository = 'abc/def'; describe('config/decrypt', () => { describe('decryptConfig()', () => { let config: RenovateConfig; + beforeEach(() => { config = {}; GlobalConfig.reset(); }); + it('returns empty with no privateKey', async () => { delete config.encrypted; const res = await decryptConfig(config, repository); expect(res).toMatchObject(config); }); + it('warns if no privateKey found', async () => { config.encrypted = { a: '1' }; const res = await decryptConfig(config, repository); expect(res.encrypted).toBeUndefined(); expect(res.a).toBeUndefined(); }); + it('handles invalid encrypted type', async () => { config.encrypted = 1; GlobalConfig.set({ privateKey }); const res = await decryptConfig(config, repository); expect(res.encrypted).toBeUndefined(); }); + it('handles invalid encrypted value', async () => { config.encrypted = { a: 1 }; GlobalConfig.set({ privateKey, privateKeyOld: 'invalid-key' }); @@ -38,6 +43,7 @@ describe('config/decrypt', () => { 'config-validation' ); }); + it('replaces npm token placeholder in npmrc', async () => { GlobalConfig.set({ privateKey: 'invalid-key', @@ -56,6 +62,7 @@ describe('config/decrypt', () => { '//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n' ); }); + it('appends npm token in npmrc', async () => { GlobalConfig.set({ privateKey }); config.npmrc = 'foo=bar\n'; @@ -68,6 +75,7 @@ describe('config/decrypt', () => { expect(res.npmToken).toBeUndefined(); expect(res.npmrc).toMatchSnapshot(); }); + it('decrypts nested', async () => { GlobalConfig.set({ privateKey }); config.packageFiles = [ @@ -95,6 +103,7 @@ describe('config/decrypt', () => { '//registry.npmjs.org/:_authToken=abcdef-ghijklm-nopqf-stuvwxyz\n' ); }); + it('rejects invalid PGP message', async () => { GlobalConfig.set({ privateKey: privateKeyPgp }); config.encrypted = { @@ -135,6 +144,7 @@ describe('config/decrypt', () => { 'config-validation' ); }); + it('handles PGP org constraint', async () => { GlobalConfig.set({ privateKey: privateKeyPgp }); config.encrypted = { @@ -148,6 +158,7 @@ describe('config/decrypt', () => { 'config-validation' ); }); + it('handles PGP org/repo constraint', async () => { GlobalConfig.set({ privateKey: privateKeyPgp }); config.encrypted = { diff --git a/lib/config/index.spec.ts b/lib/config/index.spec.ts index c1a2aa40f6..3b73bbca77 100644 --- a/lib/config/index.spec.ts +++ b/lib/config/index.spec.ts @@ -27,6 +27,7 @@ describe('config/index', () => { expect(config.lockFileMaintenance.schedule).toEqual(['on monday']); expect(config.lockFileMaintenance).toMatchSnapshot(); }); + it('merges packageRules', async () => { const parentConfig = { ...defaultConfig }; Object.assign(parentConfig, { @@ -41,6 +42,7 @@ describe('config/index', () => { 1, 2, 3, 4, ]); }); + it('merges constraints', async () => { const parentConfig = { ...defaultConfig }; Object.assign(parentConfig, { @@ -59,6 +61,7 @@ describe('config/index', () => { expect(config.constraints).toMatchSnapshot(); expect(config.constraints.node).toBe('<15'); }); + it('handles null parent packageRules', async () => { const parentConfig = { ...defaultConfig }; Object.assign(parentConfig, { @@ -71,6 +74,7 @@ describe('config/index', () => { const config = configParser.mergeChildConfig(parentConfig, childConfig); expect(config.packageRules).toHaveLength(2); }); + it('handles null child packageRules', async () => { const parentConfig = { ...defaultConfig }; parentConfig.packageRules = [{ a: 3 }, { a: 4 }]; @@ -78,6 +82,7 @@ describe('config/index', () => { const config = configParser.mergeChildConfig(parentConfig, {}); expect(config.packageRules).toHaveLength(2); }); + it('handles undefined childConfig', async () => { const parentConfig = { ...defaultConfig }; const configParser = await import('./index'); diff --git a/lib/config/massage.spec.ts b/lib/config/massage.spec.ts index ad95cb4041..0d5ac6b4fd 100644 --- a/lib/config/massage.spec.ts +++ b/lib/config/massage.spec.ts @@ -8,6 +8,7 @@ describe('config/massage', () => { const res = massage.massageConfig(config); expect(res).toEqual({}); }); + it('massages strings to array', () => { const config: RenovateConfig = { schedule: 'before 5am' as never, @@ -15,6 +16,7 @@ describe('config/massage', () => { const res = massage.massageConfig(config); expect(Array.isArray(res.schedule)).toBeTrue(); }); + it('massages npmToken', () => { const config: RenovateConfig = { npmToken: 'some-token', @@ -23,6 +25,7 @@ describe('config/massage', () => { npmrc: '//registry.npmjs.org/:_authToken=some-token\n', }); }); + it('massages packageRules matchUpdateTypes', () => { const config: RenovateConfig = { packageRules: [ @@ -42,6 +45,7 @@ describe('config/massage', () => { expect(res).toMatchSnapshot(); expect(res.packageRules).toHaveLength(3); }); + it('does not massage lockFileMaintenance', () => { const config: RenovateConfig = { packageRules: [ diff --git a/lib/config/migrate-validate.spec.ts b/lib/config/migrate-validate.spec.ts index c6c40f7004..eb3754620a 100644 --- a/lib/config/migrate-validate.spec.ts +++ b/lib/config/migrate-validate.spec.ts @@ -2,6 +2,7 @@ import { RenovateConfig, getConfig } from '../../test/util'; import { migrateAndValidate } from './migrate-validate'; let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -16,6 +17,7 @@ describe('config/migrate-validate', () => { warnings: [], }); }); + it('handles migration', async () => { const input: RenovateConfig = { automerge: 'none' as any }; const res = await migrateAndValidate(config, input); @@ -25,6 +27,7 @@ describe('config/migrate-validate', () => { warnings: [], }); }); + it('handles invalid', async () => { const input: RenovateConfig = { foo: 'none' }; const res = await migrateAndValidate(config, input); diff --git a/lib/config/migration.spec.ts b/lib/config/migration.spec.ts index 3d1e89696a..54a9199024 100644 --- a/lib/config/migration.spec.ts +++ b/lib/config/migration.spec.ts @@ -164,6 +164,7 @@ describe('config/migration', () => { expect(migratedConfig.packageRules).toHaveLength(9); expect(migratedConfig.hostRules).toHaveLength(1); }); + it('migrates before and after schedules', () => { const config = { major: { @@ -185,6 +186,7 @@ describe('config/migration', () => { expect(migratedConfig.minor.schedule[0]).toBe('after 10pm every weekday'); expect(migratedConfig.minor.schedule[1]).toBe('before 7am every weekday'); }); + it('migrates every friday', () => { const config = { schedule: 'every friday' as never, @@ -194,6 +196,7 @@ describe('config/migration', () => { expect(isMigrated).toBeTrue(); expect(migratedConfig.schedule).toBe('on friday'); }); + it('migrates semantic prefix with no scope', () => { const config = { semanticPrefix: 'fix', @@ -203,6 +206,7 @@ describe('config/migration', () => { expect(isMigrated).toBeTrue(); expect(migratedConfig.semanticCommitScope).toBeNull(); }); + it('does not migrate every weekday', () => { const config = { schedule: 'every weekday' as never, @@ -212,6 +216,7 @@ describe('config/migration', () => { expect(isMigrated).toBeFalse(); expect(migratedConfig.schedule).toEqual(config.schedule); }); + it('does not migrate multi days', () => { const config = { schedule: 'after 5:00pm on wednesday and thursday' as never, @@ -222,6 +227,7 @@ describe('config/migration', () => { expect(isMigrated).toBeFalse(); expect(migratedConfig.schedule).toEqual(config.schedule); }); + it('does not migrate hour range', () => { const config = { schedule: 'after 1:00pm and before 5:00pm' as never, @@ -231,6 +237,7 @@ describe('config/migration', () => { expect(migratedConfig.schedule).toEqual(config.schedule); expect(isMigrated).toBeFalse(); }); + it('migrates packages', () => { const config = { packages: [ @@ -252,6 +259,7 @@ describe('config/migration', () => { ], }); }); + it('overrides existing automerge setting', () => { const config: TestRenovateConfig = { automerge: 'minor' as never, @@ -268,6 +276,7 @@ describe('config/migration', () => { expect(migratedConfig).toMatchSnapshot(); expect(migratedConfig.packageRules[0].minor.automerge).toBeFalse(); }); + it('does not migrate config', () => { const config: TestRenovateConfig = { enabled: true, @@ -278,6 +287,7 @@ describe('config/migration', () => { expect(isMigrated).toBeFalse(); expect(migratedConfig).toMatchObject(config); }); + it('migrates subconfig', () => { const config: TestRenovateConfig = { lockFileMaintenance: { @@ -318,6 +328,7 @@ describe('config/migration', () => { true ); }); + it('migrates packageFiles', () => { const config: TestRenovateConfig = { packageFiles: [ @@ -341,6 +352,7 @@ describe('config/migration', () => { expect(migratedConfig.packageRules[0].rangeStrategy).toBe('replace'); expect(migratedConfig.packageRules[1].rangeStrategy).toBe('pin'); }); + it('migrates more packageFiles', () => { const config: TestRenovateConfig = { packageFiles: [ @@ -501,6 +513,7 @@ describe('config/migration', () => { extends: [':unpublishSafeDisabled', 'npm:unpublishSafe'], }); }); + it('migrates combinations of packageRules', () => { let config: TestRenovateConfig; let res: MigratedConfig; @@ -521,6 +534,7 @@ describe('config/migration', () => { expect(res.isMigrated).toBeTrue(); expect(res.migratedConfig.packageRules).toHaveLength(2); }); + it('it migrates packageRules', () => { const config: TestRenovateConfig = { packageRules: [ @@ -563,6 +577,7 @@ describe('config/migration', () => { }); }); }); + it('it migrates nested packageRules', () => { const config: TestRenovateConfig = { packageRules: [ @@ -592,6 +607,7 @@ describe('config/migration', () => { expect(migratedConfig).toMatchSnapshot(); expect(migratedConfig.packageRules).toHaveLength(3); }); + it('it migrates presets', () => { GlobalConfig.set({ migratePresets: { @@ -607,6 +623,7 @@ describe('config/migration', () => { expect(isMigrated).toBeTrue(); expect(migratedConfig).toEqual({ extends: ['local>org/renovate-config'] }); }); + it('it migrates regexManagers', () => { const config: RenovateConfig = { regexManagers: [ @@ -652,6 +669,7 @@ describe('config/migration', () => { expect(isMigrated).toBeTrue(); expect(migratedConfig).toMatchSnapshot(); }); + it('migrates empty requiredStatusChecks', () => { const config: RenovateConfig = { requiredStatusChecks: [], diff --git a/lib/config/presets/bitbucket-server/index.spec.ts b/lib/config/presets/bitbucket-server/index.spec.ts index 37cf4d02c7..000e1a2370 100644 --- a/lib/config/presets/bitbucket-server/index.spec.ts +++ b/lib/config/presets/bitbucket-server/index.spec.ts @@ -129,6 +129,7 @@ describe('config/presets/bitbucket-server/index', () => { ) ).toEqual({ from: 'api' }); }); + it('uses custom path', async () => { httpMock .scope('https://api.github.example.org') diff --git a/lib/config/presets/gitea/index.spec.ts b/lib/config/presets/gitea/index.spec.ts index 10b7a4e5e4..5930999071 100644 --- a/lib/config/presets/gitea/index.spec.ts +++ b/lib/config/presets/gitea/index.spec.ts @@ -211,6 +211,7 @@ describe('config/presets/gitea/index', () => { ) ).toEqual({ from: 'api' }); }); + it('uses custom endpoint with a tag', async () => { httpMock .scope('https://api.gitea.example.org') diff --git a/lib/config/presets/index.spec.ts b/lib/config/presets/index.spec.ts index e560f956d5..5c662c0245 100644 --- a/lib/config/presets/index.spec.ts +++ b/lib/config/presets/index.spec.ts @@ -43,9 +43,11 @@ npm.getPreset = jest.fn(({ repo, presetName }) => { describe('config/presets/index', () => { describe('resolvePreset', () => { let config: RenovateConfig; + beforeEach(() => { config = {}; }); + it('returns same if no presets', async () => { config.foo = 1; config.extends = []; @@ -53,6 +55,7 @@ describe('config/presets/index', () => { expect(config).toMatchObject(res); expect(res).toEqual({ foo: 1 }); }); + it('throws if invalid preset file', async () => { config.foo = 1; config.extends = ['notfound']; @@ -67,6 +70,7 @@ describe('config/presets/index', () => { expect(e.validationError).toBe("Cannot find preset's package (notfound)"); expect(e.validationMessage).toBeUndefined(); }); + it('throws if invalid preset', async () => { config.foo = 1; config.extends = ['wrongpreset:invalid-preset']; @@ -178,6 +182,7 @@ describe('config/presets/index', () => { }); expect(res.rangeStrategy).toBe('pin'); }); + it('throws if valid and invalid', async () => { config.foo = 1; config.extends = ['wrongpreset:invalid-preset', ':pinVersions']; @@ -194,6 +199,7 @@ describe('config/presets/index', () => { ); expect(e.validationMessage).toBeUndefined(); }); + it('combines two package alls', async () => { config.extends = ['packages:eslint', 'packages:stylelint']; const res = await presets.resolveConfigPresets(config); @@ -202,6 +208,7 @@ describe('config/presets/index', () => { matchPackagePrefixes: ['@typescript-eslint/', 'eslint', 'stylelint'], }); }); + it('resolves packageRule', async () => { config.packageRules = [ { @@ -220,12 +227,14 @@ describe('config/presets/index', () => { ], }); }); + it('resolves eslint', async () => { config.extends = ['packages:eslint']; const res = await presets.resolveConfigPresets(config); expect(res).toMatchSnapshot(); expect(res.matchPackagePrefixes).toHaveLength(2); }); + it('resolves linters', async () => { config.extends = ['packages:linters']; const res = await presets.resolveConfigPresets(config); @@ -234,6 +243,7 @@ describe('config/presets/index', () => { expect(res.matchPackagePatterns).toHaveLength(1); expect(res.matchPackagePrefixes).toHaveLength(4); }); + it('resolves nested groups', async () => { config.extends = [':automergeLinters']; const res = await presets.resolveConfigPresets(config); @@ -244,6 +254,7 @@ describe('config/presets/index', () => { expect(rule.matchPackagePatterns).toHaveLength(1); expect(rule.matchPackagePrefixes).toHaveLength(4); }); + it('migrates automerge in presets', async () => { config.extends = ['ikatyang:library']; const res = await presets.resolveConfigPresets(config); @@ -275,22 +286,26 @@ describe('config/presets/index', () => { expect(res).toMatchSnapshot(); }); }); + describe('replaceArgs', () => { const argMappings = { arg0: 'a', arg1: 'b', arg2: 'c', }; + it('replaces args in strings', () => { const str = '{{arg2}} foo {{arg0}}{{arg1}}'; const res = presets.replaceArgs(str, argMappings); expect(res).toBe('c foo ab'); }); + it('replaces args twice in same string', () => { const str = '{{arg2}}{{arg0}} foo {{arg0}}{{arg1}}'; const res = presets.replaceArgs(str, argMappings); expect(res).toBe('ca foo ab'); }); + it('replaces objects', () => { const obj = { foo: 'ha {{arg0}}', @@ -307,6 +322,7 @@ describe('config/presets/index', () => { foo: 'ha a', }); }); + it('replaces arrays', () => { const obj = { foo: ['{{arg0}}', { bar: '{{arg1}}', baz: 5 }], @@ -317,6 +333,7 @@ describe('config/presets/index', () => { }); }); }); + describe('parsePreset', () => { // default namespace it('returns default package name', () => { @@ -328,6 +345,7 @@ describe('config/presets/index', () => { presetSource: 'internal', }); }); + it('parses github', () => { expect(presets.parsePreset('github>some/repo')).toEqual({ repo: 'some/repo', @@ -337,6 +355,7 @@ describe('config/presets/index', () => { presetSource: 'github', }); }); + it('handles special chars', () => { expect(presets.parsePreset('github>some/repo:foo+bar')).toEqual({ repo: 'some/repo', @@ -346,6 +365,7 @@ describe('config/presets/index', () => { presetSource: 'github', }); }); + it('parses github subfiles', () => { expect(presets.parsePreset('github>some/repo:somefile')).toEqual({ repo: 'some/repo', @@ -355,6 +375,7 @@ describe('config/presets/index', () => { presetSource: 'github', }); }); + it('parses github subfiles with preset name', () => { expect( presets.parsePreset('github>some/repo:somefile/somepreset') @@ -366,6 +387,7 @@ describe('config/presets/index', () => { presetSource: 'github', }); }); + it('parses github subfiles with preset and sub-preset name', () => { expect( presets.parsePreset( @@ -379,6 +401,7 @@ describe('config/presets/index', () => { presetSource: 'github', }); }); + it('parses github subdirectories', () => { expect( presets.parsePreset('github>some/repo//somepath/somesubpath/somefile') @@ -390,6 +413,7 @@ describe('config/presets/index', () => { presetSource: 'github', }); }); + it('parses github toplevel file using subdirectory syntax', () => { expect(presets.parsePreset('github>some/repo//somefile')).toEqual({ repo: 'some/repo', @@ -399,6 +423,7 @@ describe('config/presets/index', () => { presetSource: 'github', }); }); + it('parses gitlab', () => { expect(presets.parsePreset('gitlab>some/repo')).toEqual({ repo: 'some/repo', @@ -408,6 +433,7 @@ describe('config/presets/index', () => { presetSource: 'gitlab', }); }); + it('parses gitea', () => { expect(presets.parsePreset('gitea>some/repo')).toEqual({ repo: 'some/repo', @@ -417,6 +443,7 @@ describe('config/presets/index', () => { presetSource: 'gitea', }); }); + it('parses local', () => { expect(presets.parsePreset('local>some/repo')).toEqual({ repo: 'some/repo', @@ -426,6 +453,7 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('parses local with spaces', () => { expect(presets.parsePreset('local>A2B CD/A2B_Renovate')).toEqual({ repo: 'A2B CD/A2B_Renovate', @@ -435,6 +463,7 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('parses local with subdirectory', () => { expect( presets.parsePreset('local>some-group/some-repo//some-dir/some-file') @@ -446,6 +475,7 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('parses local with spaces and subdirectory', () => { expect( presets.parsePreset('local>A2B CD/A2B_Renovate//some-dir/some-file') @@ -457,6 +487,7 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('parses local with sub preset and tag', () => { expect( presets.parsePreset( @@ -471,6 +502,7 @@ describe('config/presets/index', () => { tag: '1.2.3', }); }); + it('parses local with subdirectory and tag', () => { expect( presets.parsePreset( @@ -525,6 +557,7 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('parses local Bitbucket user repo with preset name', () => { expect(presets.parsePreset('local>~john_doe/repo//somefile')).toEqual({ repo: '~john_doe/repo', @@ -534,6 +567,7 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('parses local Bitbucket user repo', () => { expect(presets.parsePreset('local>~john_doe/renovate-config')).toEqual({ repo: '~john_doe/renovate-config', @@ -543,6 +577,7 @@ describe('config/presets/index', () => { presetSource: 'local', }); }); + it('returns default package name with params', () => { expect(presets.parsePreset(':group(packages/eslint, eslint)')).toEqual({ repo: 'default', @@ -552,6 +587,7 @@ describe('config/presets/index', () => { presetSource: 'internal', }); }); + // scoped namespace it('returns simple scope', () => { expect(presets.parsePreset('@somescope')).toEqual({ @@ -562,6 +598,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns simple scope and params', () => { expect(presets.parsePreset('@somescope(param1)')).toEqual({ repo: '@somescope/renovate-config', @@ -571,6 +608,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns scope with repo and default', () => { expect(presets.parsePreset('@somescope/somepackagename')).toEqual({ repo: '@somescope/somepackagename', @@ -580,6 +618,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns scope with repo and params and default', () => { expect( presets.parsePreset( @@ -593,6 +632,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns scope with presetName', () => { expect(presets.parsePreset('@somescope:somePresetName')).toEqual({ repo: '@somescope/renovate-config', @@ -602,6 +642,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns scope with presetName and params', () => { expect(presets.parsePreset('@somescope:somePresetName(param1)')).toEqual({ repo: '@somescope/renovate-config', @@ -611,6 +652,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns scope with repo and presetName', () => { expect( presets.parsePreset('@somescope/somepackagename:somePresetName') @@ -622,6 +664,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns scope with repo and presetName and params', () => { expect( presets.parsePreset( @@ -635,6 +678,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + // non-scoped namespace it('returns non-scoped default', () => { expect(presets.parsePreset('somepackage')).toEqual({ @@ -645,6 +689,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns non-scoped package name', () => { expect(presets.parsePreset('somepackage:webapp')).toEqual({ repo: 'renovate-config-somepackage', @@ -654,6 +699,7 @@ describe('config/presets/index', () => { presetSource: 'npm', }); }); + it('returns non-scoped package name full', () => { expect(presets.parsePreset('renovate-config-somepackage:webapp')).toEqual( { @@ -665,6 +711,7 @@ describe('config/presets/index', () => { } ); }); + it('returns non-scoped package name with params', () => { expect(presets.parsePreset('somepackage:webapp(param1)')).toEqual({ repo: 'renovate-config-somepackage', @@ -675,6 +722,7 @@ describe('config/presets/index', () => { }); }); }); + describe('getPreset', () => { it('handles removed presets with a migration', async () => { const res = await presets.getPreset(':base', {}); @@ -692,10 +740,12 @@ describe('config/presets/index', () => { ], }); }); + it('handles removed presets with no migration', async () => { const res = await presets.getPreset('helpers:oddIsUnstable', {}); expect(res).toEqual({}); }); + it('handles renamed monorepos', async () => { const res = await presets.getPreset('monorepo:opentelemetry', {}); expect(res).toMatchInlineSnapshot(` @@ -709,6 +759,7 @@ Object { } `); }); + it('handles renamed monorepo groups', async () => { const res = await presets.getPreset('group:opentelemetryMonorepo', {}); expect(res).toMatchInlineSnapshot(` @@ -733,12 +784,14 @@ Object { } `); }); + it('gets linters', async () => { const res = await presets.getPreset('packages:linters', {}); expect(res).toMatchSnapshot(); expect(res.matchPackageNames).toHaveLength(1); expect(res.extends).toHaveLength(4); }); + it('gets parameterised configs', async () => { const res = await presets.getPreset( ':group(packages:eslint, eslint)', @@ -754,6 +807,7 @@ Object { ], }); }); + it('handles missing params', async () => { const res = await presets.getPreset(':group()', {}); expect(res).toEqual({ @@ -766,6 +820,7 @@ Object { ], }); }); + it('ignores irrelevant params', async () => { const res = await presets.getPreset(':pinVersions(foo, bar)', {}); expect(res).toEqual({ @@ -775,6 +830,7 @@ Object { rangeStrategy: 'pin', }); }); + it('handles 404 packages', async () => { let e: Error; try { @@ -787,6 +843,7 @@ Object { expect(e.validationError).toMatchSnapshot(); expect(e.validationMessage).toMatchSnapshot(); }); + it('handles no config', async () => { let e: Error; try { @@ -799,6 +856,7 @@ Object { expect(e.validationError).toBeUndefined(); expect(e.validationMessage).toBeUndefined(); }); + it('handles throw errors', async () => { let e: Error; try { @@ -811,6 +869,7 @@ Object { expect(e.validationError).toBeUndefined(); expect(e.validationMessage).toBeUndefined(); }); + it('handles preset not found', async () => { let e: Error; try { diff --git a/lib/config/presets/local/index.spec.ts b/lib/config/presets/local/index.spec.ts index 66013435b2..44aec13e44 100644 --- a/lib/config/presets/local/index.spec.ts +++ b/lib/config/presets/local/index.spec.ts @@ -32,6 +32,7 @@ describe('config/presets/local/index', () => { github.getPresetFromEndpoint.mockResolvedValueOnce(preset); gitlab.getPresetFromEndpoint.mockResolvedValueOnce(preset); }); + describe('getPreset()', () => { it('throws for unsupported platform', async () => { await expect(async () => { @@ -44,6 +45,7 @@ describe('config/presets/local/index', () => { }); }).rejects.toThrow(); }); + it('throws for missing platform', async () => { await expect(async () => { await local.getPreset({ @@ -105,6 +107,7 @@ describe('config/presets/local/index', () => { expect(gitea.getPresetFromEndpoint.mock.calls).toMatchSnapshot(); expect(content).toEqual({ resolved: 'preset' }); }); + it('forwards to custom gitea', async () => { const content = await local.getPreset({ repo: 'some/repo', @@ -128,6 +131,7 @@ describe('config/presets/local/index', () => { expect(github.getPresetFromEndpoint.mock.calls).toMatchSnapshot(); expect(content).toEqual({ resolved: 'preset' }); }); + it('forwards to custom github', async () => { const content = await local.getPreset({ repo: 'some/repo', @@ -140,6 +144,7 @@ describe('config/presets/local/index', () => { expect(github.getPresetFromEndpoint.mock.calls).toMatchSnapshot(); expect(content).toEqual({ resolved: 'preset' }); }); + it('forwards to github with a tag', async () => { const content = await local.getPreset({ repo: 'some/repo', @@ -151,6 +156,7 @@ describe('config/presets/local/index', () => { expect(github.getPresetFromEndpoint.mock.calls).toMatchSnapshot(); expect(content).toEqual({ resolved: 'preset' }); }); + it('forwards to custom github with a tag', async () => { const content = await local.getPreset({ repo: 'some/repo', @@ -176,6 +182,7 @@ describe('config/presets/local/index', () => { expect(gitlab.getPresetFromEndpoint.mock.calls).toMatchSnapshot(); expect(content).toEqual({ resolved: 'preset' }); }); + it('forwards to custom gitlab', async () => { const content = await local.getPreset({ repo: 'some/repo', @@ -188,6 +195,7 @@ describe('config/presets/local/index', () => { expect(gitlab.getPresetFromEndpoint.mock.calls).toMatchSnapshot(); expect(content).toEqual({ resolved: 'preset' }); }); + it('forwards to gitlab with a tag', async () => { const content = await local.getPreset({ repo: 'some/repo', @@ -200,6 +208,7 @@ describe('config/presets/local/index', () => { expect(gitlab.getPresetFromEndpoint.mock.calls).toMatchSnapshot(); expect(content).toEqual({ resolved: 'preset' }); }); + it('forwards to custom gitlab with a tag', async () => { const content = await local.getPreset({ repo: 'some/repo', diff --git a/lib/config/presets/npm/index.spec.ts b/lib/config/presets/npm/index.spec.ts index 611475fdd6..e3c1853e0e 100644 --- a/lib/config/presets/npm/index.spec.ts +++ b/lib/config/presets/npm/index.spec.ts @@ -9,15 +9,18 @@ describe('config/presets/npm/index', () => { jest.resetAllMocks(); GlobalConfig.reset(); }); + afterEach(() => { delete process.env.RENOVATE_CACHE_NPM_MINUTES; }); + it('should throw if no package', async () => { httpMock.scope('https://registry.npmjs.org').get('/nopackage').reply(404); await expect( npm.getPreset({ repo: 'nopackage', presetName: 'default' }) ).rejects.toThrow(/dep not found/); }); + it('should throw if no renovate-config', async () => { const presetPackage = { name: 'norenovateconfig', @@ -50,6 +53,7 @@ describe('config/presets/npm/index', () => { npm.getPreset({ repo: 'norenovateconfig', presetName: 'default' }) ).rejects.toThrow(/preset renovate-config not found/); }); + it('should throw if preset name not found', async () => { const presetPackage = { name: 'presetnamenotfound', @@ -86,6 +90,7 @@ describe('config/presets/npm/index', () => { }) ).rejects.toThrow(/preset not found/); }); + it('should return preset', async () => { const presetPackage = { name: 'workingpreset', diff --git a/lib/config/presets/util.spec.ts b/lib/config/presets/util.spec.ts index 92b9390097..93c1fc6b73 100644 --- a/lib/config/presets/util.spec.ts +++ b/lib/config/presets/util.spec.ts @@ -14,6 +14,7 @@ describe('config/presets/util', () => { beforeEach(() => { fetch.mockReset(); }); + it('works', async () => { fetch.mockResolvedValueOnce({ sub: { preset: { foo: true } } }); expect(await fetchPreset({ ...config, fetch })).toEqual({ diff --git a/lib/config/secrets.spec.ts b/lib/config/secrets.spec.ts index 1d568d4b8c..ea5b1b10c8 100644 --- a/lib/config/secrets.spec.ts +++ b/lib/config/secrets.spec.ts @@ -10,24 +10,29 @@ describe('config/secrets', () => { it('works with default config', () => { expect(() => validateConfigSecrets(defaultConfig)).not.toThrow(); }); + it('returns if no secrets', () => { expect(validateConfigSecrets({})).toBeUndefined(); }); + it('throws if secrets is not an object', () => { expect(() => validateConfigSecrets({ secrets: 'hello' } as any)).toThrow( CONFIG_SECRETS_INVALID ); }); + it('throws for invalid secret names', () => { expect(() => validateConfigSecrets({ secrets: { '123': 'abc' } }) ).toThrow(CONFIG_SECRETS_INVALID); }); + it('throws for non-string secret', () => { expect(() => validateConfigSecrets({ secrets: { abc: 123 } } as any) ).toThrow(CONFIG_SECRETS_INVALID); }); + it('throws for secrets inside repositories', () => { expect(() => validateConfigSecrets({ @@ -53,12 +58,14 @@ describe('config/secrets', () => { }; expect(() => applySecretsToConfig(config)).toThrow(CONFIG_VALIDATION); }); + it('throws if an unknown secret is used', () => { const config = { npmToken: '{{ secrets.ARTIFACTORY_TOKEN }}', }; expect(() => applySecretsToConfig(config)).toThrow(CONFIG_VALIDATION); }); + it('replaces secrets in the top level', () => { const config = { secrets: { ARTIFACTORY_TOKEN: '123test==' }, @@ -70,6 +77,7 @@ describe('config/secrets', () => { }); expect(Object.keys(res)).not.toContain('secrets'); }); + it('replaces secrets in a subobject', () => { const config = { secrets: { ARTIFACTORY_TOKEN: '123test==' }, @@ -83,6 +91,7 @@ describe('config/secrets', () => { }); expect(Object.keys(res)).not.toContain('secrets'); }); + it('replaces secrets in a array of objects', () => { const config = { secrets: { ARTIFACTORY_TOKEN: '123test==' }, @@ -96,6 +105,7 @@ describe('config/secrets', () => { }); expect(Object.keys(res)).not.toContain('secrets'); }); + it('replaces secrets in a array of strings', () => { const config = { secrets: { SECRET_MANAGER: 'npm' }, @@ -107,6 +117,7 @@ describe('config/secrets', () => { }); expect(Object.keys(res)).not.toContain('secrets'); }); + it('replaces secrets in a array of objects without deleting them', () => { const config = { secrets: { ARTIFACTORY_TOKEN: '123test==' }, @@ -121,6 +132,7 @@ describe('config/secrets', () => { }); expect(Object.keys(res)).toContain('secrets'); }); + it('replaces secrets in a array of strings without deleting them', () => { const config = { secrets: { SECRET_MANAGER: 'npm' }, @@ -133,6 +145,7 @@ describe('config/secrets', () => { }); expect(Object.keys(res)).toContain('secrets'); }); + it('{} as secrets will result in an error', () => { const config = { secrets: { SECRET_MANAGER: 'npm' }, @@ -142,6 +155,7 @@ describe('config/secrets', () => { CONFIG_VALIDATION ); }); + it('undefined as secrets will result replace the secret', () => { const config = { secrets: { SECRET_MANAGER: 'npm' }, @@ -154,6 +168,7 @@ describe('config/secrets', () => { }); expect(Object.keys(res)).toContain('secrets'); }); + it('null as secrets will result in an error', () => { const config = { secrets: { SECRET_MANAGER: 'npm' }, diff --git a/lib/config/validation.spec.ts b/lib/config/validation.spec.ts index 1f31f88bd5..31f564af38 100644 --- a/lib/config/validation.spec.ts +++ b/lib/config/validation.spec.ts @@ -6,15 +6,18 @@ describe('config/validation', () => { 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 = { @@ -24,6 +27,7 @@ describe('config/validation', () => { expect(warnings).toHaveLength(1); expect(warnings).toMatchSnapshot(); }); + it('catches invalid templates', async () => { const config = { commitMessage: '{{{something}}', @@ -32,6 +36,7 @@ describe('config/validation', () => { expect(errors).toHaveLength(1); expect(errors).toMatchSnapshot(); }); + it('catches invalid allowedVersions regex', async () => { const config = { packageRules: [ @@ -57,6 +62,7 @@ describe('config/validation', () => { expect(errors).toHaveLength(2); expect(errors).toMatchSnapshot(); }); + it('catches invalid matchCurrentVersion regex', async () => { const config = { packageRules: [ @@ -86,6 +92,7 @@ describe('config/validation', () => { expect(errors).toHaveLength(2); expect(errors).toMatchSnapshot(); }); + it('returns nested errors', async () => { const config: RenovateConfig = { foo: 1, @@ -110,6 +117,7 @@ describe('config/validation', () => { expect(errors).toHaveLength(3); expect(errors).toMatchSnapshot(); }); + it('included unsupported manager', async () => { const config = { packageRules: [ @@ -126,6 +134,7 @@ describe('config/validation', () => { expect(errors).toHaveLength(1); expect(errors[0].message).toContain('ansible'); }); + it('included managers of the wrong type', async () => { const config = { packageRules: [ @@ -177,6 +186,7 @@ describe('config/validation', () => { expect(errors).toMatchSnapshot(); } ); + it('errors for all types', async () => { const config: RenovateConfig = { allowedVersions: 'foo', @@ -212,6 +222,7 @@ describe('config/validation', () => { expect(errors).toMatchSnapshot(); expect(errors).toHaveLength(12); }); + it('selectors outside packageRules array trigger errors', async () => { const config = { matchPackageNames: ['angular'], @@ -236,6 +247,7 @@ describe('config/validation', () => { expect(errors).toMatchSnapshot(); expect(errors).toHaveLength(2); }); + it('ignore packageRule nesting validation for presets', async () => { const config = { description: ['All angular.js packages'], @@ -291,6 +303,7 @@ describe('config/validation', () => { expect(errors).toHaveLength(1); expect(errors).toMatchSnapshot(); }); + it('errors if no regexManager matchStrings', async () => { const config = { regexManagers: [ @@ -314,6 +327,7 @@ describe('config/validation', () => { ] `); }); + it('errors if empty regexManager matchStrings', async () => { const config = { regexManagers: [ @@ -345,6 +359,7 @@ describe('config/validation', () => { ] `); }); + it('errors if no regexManager fileMatch', async () => { const config = { regexManagers: [ @@ -362,6 +377,7 @@ describe('config/validation', () => { expect(warnings).toHaveLength(0); expect(errors).toHaveLength(1); }); + it('validates regEx for each matchStrings', async () => { const config = { regexManagers: [ @@ -378,6 +394,7 @@ describe('config/validation', () => { expect(warnings).toHaveLength(0); expect(errors).toHaveLength(1); }); + it('passes if regexManager fields are present', async () => { const config = { regexManagers: [ @@ -399,6 +416,7 @@ describe('config/validation', () => { expect(warnings).toHaveLength(0); expect(errors).toHaveLength(0); }); + it('errors if extra regexManager fields are present', async () => { const config = { regexManagers: [ @@ -419,6 +437,7 @@ describe('config/validation', () => { expect(warnings).toHaveLength(0); expect(errors).toHaveLength(1); }); + it('errors if regexManager fields are missing', async () => { const config = { regexManagers: [ @@ -438,6 +457,7 @@ describe('config/validation', () => { expect(errors).toMatchSnapshot(); expect(errors).toHaveLength(1); }); + it('ignore keys', async () => { const config = { $schema: 'renovate.json', @@ -632,6 +652,7 @@ describe('config/validation', () => { expect(warnings).toMatchSnapshot(); expect(errors).toHaveLength(0); }); + it('errors if invalid combinations in packageRules', async () => { const config = { packageRules: [ @@ -649,6 +670,7 @@ describe('config/validation', () => { expect(errors).toHaveLength(1); expect(errors).toMatchSnapshot(); }); + it('warns on nested group packageRules', async () => { const config = { extends: ['group:fortawesome'], @@ -680,6 +702,7 @@ describe('config/validation', () => { expect(warnings).toHaveLength(0); expect(errors).toHaveLength(0); }); + it('errors on invalid customEnvVariables objects', async () => { const config = { customEnvVariables: { diff --git a/lib/logger/config-serializer.spec.ts b/lib/logger/config-serializer.spec.ts index 8a0e53b35e..8a2dfd95ef 100644 --- a/lib/logger/config-serializer.spec.ts +++ b/lib/logger/config-serializer.spec.ts @@ -10,6 +10,7 @@ describe('logger/config-serializer', () => { prBody: '[Template]', }); }); + it('suppresses content', () => { const config = { content: {}, diff --git a/lib/logger/err-serializer.spec.ts b/lib/logger/err-serializer.spec.ts index e737e8c611..75d0b6b371 100644 --- a/lib/logger/err-serializer.spec.ts +++ b/lib/logger/err-serializer.spec.ts @@ -36,6 +36,7 @@ describe('logger/err-serializer', () => { }, }); }); + it('handles missing fields', () => { const err = partial<Error & Record<string, unknown>>({ a: 1, diff --git a/lib/logger/index.spec.ts b/lib/logger/index.spec.ts index 4a1173ebe1..9b66e5c4c4 100644 --- a/lib/logger/index.spec.ts +++ b/lib/logger/index.spec.ts @@ -23,16 +23,20 @@ describe('logger/index', () => { it('inits', () => { expect(logger).toBeDefined(); }); + it('sets and gets context', () => { setContext('123test'); expect(getContext()).toBe('123test'); }); + it('supports logging with metadata', () => { expect(() => logger.debug({ some: 'meta' }, 'some meta')).not.toThrow(); }); + it('supports logging with only metadata', () => { expect(() => logger.debug({ some: 'meta' })).not.toThrow(); }); + it('supports logging without metadata', () => { expect(() => logger.debug('some meta')).not.toThrow(); }); diff --git a/lib/logger/pretty-stdout.spec.ts b/lib/logger/pretty-stdout.spec.ts index 4a9a850678..2c402c17c9 100644 --- a/lib/logger/pretty-stdout.spec.ts +++ b/lib/logger/pretty-stdout.spec.ts @@ -14,15 +14,18 @@ describe('logger/pretty-stdout', () => { it('returns empty string if null rec', () => { expect(prettyStdout.getMeta(null as any)).toBeEmptyString(); }); + it('returns empty string if empty rec', () => { expect(prettyStdout.getMeta({} as any)).toBeEmptyString(); }); + it('returns empty string if no meta fields', () => { const rec = { foo: 'bar', }; expect(prettyStdout.getMeta(rec as any)).toBeEmptyString(); }); + it('supports single meta', () => { const rec = { foo: 'bar', @@ -32,6 +35,7 @@ describe('logger/pretty-stdout', () => { chalk.gray(' (repository=a/b)') ); }); + it('supports multi meta', () => { const rec = { foo: 'bar', @@ -44,13 +48,16 @@ describe('logger/pretty-stdout', () => { ); }); }); + describe('getDetails(rec)', () => { it('returns empty string if null rec', () => { expect(prettyStdout.getDetails(null as any)).toBeEmptyString(); }); + it('returns empty string if empty rec', () => { expect(prettyStdout.getDetails({} as any)).toBeEmptyString(); }); + it('returns empty string if all are meta fields', () => { const rec = { branch: 'bar', @@ -58,6 +65,7 @@ describe('logger/pretty-stdout', () => { }; expect(prettyStdout.getDetails(rec as any)).toBeEmptyString(); }); + it('supports a config', () => { const rec = { v: 0, @@ -71,13 +79,16 @@ describe('logger/pretty-stdout', () => { ); }); }); + describe('formatRecord(rec)', () => { beforeEach(() => { process.env.FORCE_COLOR = '1'; }); + afterEach(() => { delete process.env.FORCE_COLOR; }); + it('formats record', () => { const rec: BunyanRecord = { level: 10, diff --git a/lib/modules/datasource/aws-machine-image/index.spec.ts b/lib/modules/datasource/aws-machine-image/index.spec.ts index 90525141a1..fffda991f6 100644 --- a/lib/modules/datasource/aws-machine-image/index.spec.ts +++ b/lib/modules/datasource/aws-machine-image/index.spec.ts @@ -147,6 +147,7 @@ describe('modules/datasource/aws-machine-image/index', () => { expect(res).toStrictEqual([image1, image2, image3]); expect(ec2Mock.calls()).toMatchSnapshot(); }); + it('with 1 returned image', async () => { mockDescribeImagesCommand(mock1Image); const ec2DataSource = new AwsMachineImageDataSource(); @@ -156,6 +157,7 @@ describe('modules/datasource/aws-machine-image/index', () => { expect(res).toStrictEqual([image3]); expect(ec2Mock.calls()).toMatchSnapshot(); }); + it('without returned images', async () => { mockDescribeImagesCommand(mockEmpty); const ec2DataSource = new AwsMachineImageDataSource(); @@ -177,6 +179,7 @@ describe('modules/datasource/aws-machine-image/index', () => { }); expect(res).toBeNull(); }); + it('without newValue, with one matching image to return that image', async () => { mockDescribeImagesCommand(mock1Image); const res = await getDigest({ @@ -186,6 +189,7 @@ describe('modules/datasource/aws-machine-image/index', () => { }); expect(res).toStrictEqual(image3.Name); }); + it('without newValue, with 3 matching image to return the newest image', async () => { mockDescribeImagesCommand(mock3Images); const res = await getDigest({ @@ -195,6 +199,7 @@ describe('modules/datasource/aws-machine-image/index', () => { }); expect(res).toStrictEqual(image3.Name); }); + it('with matching newValue, with 3 matching image to return the matching image', async () => { mockDescribeImagesCommand(mock3Images); const res = await getDigest( @@ -207,6 +212,7 @@ describe('modules/datasource/aws-machine-image/index', () => { ); expect(res).toStrictEqual(image1.Name); }); + it('with not matching newValue, with 3 matching images to return the matching image', async () => { mockDescribeImagesCommand(mock3Images); const res = await getDigest( @@ -231,6 +237,7 @@ describe('modules/datasource/aws-machine-image/index', () => { }); expect(res).toBeNull(); }); + it('with one matching image to return that image', async () => { mockDescribeImagesCommand(mock1Image); const res = await getPkgReleases({ @@ -249,6 +256,7 @@ describe('modules/datasource/aws-machine-image/index', () => { ], }); }); + it('with one deprecated matching image to return that image', async () => { mockDescribeImagesCommand({ Images: [image2] }); const res = await getPkgReleases({ @@ -267,6 +275,7 @@ describe('modules/datasource/aws-machine-image/index', () => { ], }); }); + it('with 3 matching image to return the newest image', async () => { mockDescribeImagesCommand(mock3Images); const res = await getPkgReleases({ diff --git a/lib/modules/datasource/bitbucket-tags/index.spec.ts b/lib/modules/datasource/bitbucket-tags/index.spec.ts index 3f33e023f2..ade861718f 100644 --- a/lib/modules/datasource/bitbucket-tags/index.spec.ts +++ b/lib/modules/datasource/bitbucket-tags/index.spec.ts @@ -38,6 +38,7 @@ describe('modules/datasource/bitbucket-tags/index', () => { expect(res.releases).toHaveLength(3); }); }); + describe('getDigest', () => { it('returns commits from bitbucket cloud', async () => { const body = { @@ -75,6 +76,7 @@ describe('modules/datasource/bitbucket-tags/index', () => { expect(res).toBe('123'); }); }); + describe('getDigest with no commits', () => { it('returns commits from bitbucket cloud', async () => { const body = { @@ -97,6 +99,7 @@ describe('modules/datasource/bitbucket-tags/index', () => { expect(res).toBeNull(); }); }); + describe('getTagCommit', () => { it('returns tags commit hash from bitbucket cloud', async () => { const body = { diff --git a/lib/modules/datasource/cdnjs/index.spec.ts b/lib/modules/datasource/cdnjs/index.spec.ts index e65d5246d5..84744a0080 100644 --- a/lib/modules/datasource/cdnjs/index.spec.ts +++ b/lib/modules/datasource/cdnjs/index.spec.ts @@ -24,6 +24,7 @@ describe('modules/datasource/cdnjs/index', () => { }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('throws for error', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).replyWithError('error'); await expect( @@ -33,6 +34,7 @@ describe('modules/datasource/cdnjs/index', () => { }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('returns null for 404', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(404); expect( @@ -42,6 +44,7 @@ describe('modules/datasource/cdnjs/index', () => { }) ).toBeNull(); }); + it('returns null for empty 200 OK', async () => { httpMock .scope(baseUrl) @@ -54,6 +57,7 @@ describe('modules/datasource/cdnjs/index', () => { }) ).toBeNull(); }); + it('throws for 401', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(401); await expect( @@ -63,6 +67,7 @@ describe('modules/datasource/cdnjs/index', () => { }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('throws for 429', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(429); await expect( @@ -72,6 +77,7 @@ describe('modules/datasource/cdnjs/index', () => { }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('throws for 5xx', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).reply(502); await expect( @@ -81,6 +87,7 @@ describe('modules/datasource/cdnjs/index', () => { }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('throws for unknown error', async () => { httpMock.scope(baseUrl).get(pathFor('foo/bar')).replyWithError('error'); await expect( @@ -90,6 +97,7 @@ describe('modules/datasource/cdnjs/index', () => { }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('processes real data', async () => { httpMock .scope(baseUrl) @@ -101,6 +109,7 @@ describe('modules/datasource/cdnjs/index', () => { }); expect(res).toMatchSnapshot(); }); + it('filters releases by asset presence', async () => { httpMock .scope(baseUrl) diff --git a/lib/modules/datasource/conan/index.spec.ts b/lib/modules/datasource/conan/index.spec.ts index 04cd190cee..c8f3802145 100644 --- a/lib/modules/datasource/conan/index.spec.ts +++ b/lib/modules/datasource/conan/index.spec.ts @@ -158,6 +158,7 @@ describe('modules/datasource/conan/index', () => { ], }); }); + it('rejects userAndChannel for Conan Center', async () => { expect( await getPkgReleases({ diff --git a/lib/modules/datasource/crate/index.spec.ts b/lib/modules/datasource/crate/index.spec.ts index 84f3449c2c..6bc0c4d7b2 100644 --- a/lib/modules/datasource/crate/index.spec.ts +++ b/lib/modules/datasource/crate/index.spec.ts @@ -121,6 +121,7 @@ describe('modules/datasource/crate/index', () => { }) ).toBeNull(); }); + it('returns null for invalid registry url', async () => { expect( await getPkgReleases({ @@ -130,6 +131,7 @@ describe('modules/datasource/crate/index', () => { }) ).toBeNull(); }); + it('returns null for empty result', async () => { httpMock.scope(baseUrl).get('/no/n_/non_existent_crate').reply(200, {}); expect( @@ -140,6 +142,7 @@ describe('modules/datasource/crate/index', () => { }) ).toBeNull(); }); + it('returns null for missing fields', async () => { httpMock .scope(baseUrl) @@ -153,6 +156,7 @@ describe('modules/datasource/crate/index', () => { }) ).toBeNull(); }); + it('returns null for empty list', async () => { httpMock.scope(baseUrl).get('/no/n_/non_existent_crate').reply(200, '\n'); expect( @@ -163,6 +167,7 @@ describe('modules/datasource/crate/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock.scope(baseUrl).get('/so/me/some_crate').reply(404); expect( @@ -173,6 +178,7 @@ describe('modules/datasource/crate/index', () => { }) ).toBeNull(); }); + it('throws for 5xx', async () => { httpMock.scope(baseUrl).get('/so/me/some_crate').reply(502); let e; @@ -188,6 +194,7 @@ describe('modules/datasource/crate/index', () => { expect(e).toBeDefined(); expect(e).toMatchSnapshot(); }); + it('returns null for unknown error', async () => { httpMock.scope(baseUrl).get('/so/me/some_crate').replyWithError(''); expect( @@ -198,6 +205,7 @@ describe('modules/datasource/crate/index', () => { }) ).toBeNull(); }); + it('processes real data: libc', async () => { httpMock .scope(baseUrl) @@ -212,6 +220,7 @@ describe('modules/datasource/crate/index', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('processes real data: amethyst', async () => { httpMock .scope(baseUrl) @@ -226,6 +235,7 @@ describe('modules/datasource/crate/index', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('refuses to clone if allowCustomCrateRegistries is not true', async () => { const { mockClone } = setupGitMocks(); @@ -238,6 +248,7 @@ describe('modules/datasource/crate/index', () => { expect(mockClone).toHaveBeenCalledTimes(0); expect(res).toBeNull(); }); + it('clones cloudsmith private registry', async () => { const { mockClone } = setupGitMocks(); GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); @@ -252,6 +263,7 @@ describe('modules/datasource/crate/index', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('clones other private registry', async () => { const { mockClone } = setupGitMocks(); GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); @@ -266,6 +278,7 @@ describe('modules/datasource/crate/index', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('clones once then reuses the cache', async () => { const { mockClone } = setupGitMocks(); GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); @@ -282,6 +295,7 @@ describe('modules/datasource/crate/index', () => { }); expect(mockClone).toHaveBeenCalledTimes(1); }); + it('guards against race conditions while cloning', async () => { const { mockClone } = setupGitMocks(250); GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); @@ -308,6 +322,7 @@ describe('modules/datasource/crate/index', () => { expect(mockClone).toHaveBeenCalledTimes(1); }); + it('returns null when git clone fails', async () => { setupErrorGitMock(); GlobalConfig.set({ ...adminConfig, allowCustomCrateRegistries: true }); diff --git a/lib/modules/datasource/dart/index.spec.ts b/lib/modules/datasource/dart/index.spec.ts index 9b2ea28b85..526618f03b 100644 --- a/lib/modules/datasource/dart/index.spec.ts +++ b/lib/modules/datasource/dart/index.spec.ts @@ -18,6 +18,7 @@ describe('modules/datasource/dart/index', () => { }) ).toBeNull(); }); + it('returns null for empty fields', async () => { const withoutVersions = { ...body, @@ -49,6 +50,7 @@ describe('modules/datasource/dart/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock.scope(baseUrl).get('/shared_preferences').reply(404); expect( @@ -58,6 +60,7 @@ describe('modules/datasource/dart/index', () => { }) ).toBeNull(); }); + it('throws for 5xx', async () => { httpMock.scope(baseUrl).get('/shared_preferences').reply(502); let e; @@ -72,6 +75,7 @@ describe('modules/datasource/dart/index', () => { expect(e).toBeDefined(); expect(e).toMatchSnapshot(); }); + it('returns null for unknown error', async () => { httpMock.scope(baseUrl).get('/shared_preferences').replyWithError(''); expect( @@ -81,6 +85,7 @@ describe('modules/datasource/dart/index', () => { }) ).toBeNull(); }); + it('processes real data', async () => { httpMock.scope(baseUrl).get('/shared_preferences').reply(200, body); const res = await getPkgReleases({ diff --git a/lib/modules/datasource/docker/index.spec.ts b/lib/modules/datasource/docker/index.spec.ts index 4c2f84af97..c5234c1c79 100644 --- a/lib/modules/datasource/docker/index.spec.ts +++ b/lib/modules/datasource/docker/index.spec.ts @@ -69,6 +69,7 @@ describe('modules/datasource/docker/index', () => { registryHost: 'https://registry:5000', }); }); + it('supports registryUrls', () => { const res = getRegistryRepository( 'my.local.registry/prefix/image', @@ -79,6 +80,7 @@ describe('modules/datasource/docker/index', () => { registryHost: 'https://my.local.registry', }); }); + it('supports http registryUrls', () => { const res = getRegistryRepository( 'my.local.registry/prefix/image', @@ -89,6 +91,7 @@ describe('modules/datasource/docker/index', () => { registryHost: 'http://my.local.registry', }); }); + it('supports schemeless registryUrls', () => { const res = getRegistryRepository( 'my.local.registry/prefix/image', @@ -100,6 +103,7 @@ describe('modules/datasource/docker/index', () => { }); }); }); + describe('getAuthHeaders', () => { beforeEach(() => { httpMock diff --git a/lib/modules/datasource/flutter-version/index.spec.ts b/lib/modules/datasource/flutter-version/index.spec.ts index d41adf4092..7bc6a5d4e4 100644 --- a/lib/modules/datasource/flutter-version/index.spec.ts +++ b/lib/modules/datasource/flutter-version/index.spec.ts @@ -20,6 +20,7 @@ describe('modules/datasource/flutter-version/index', () => { }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('returns null for error', async () => { httpMock.scope(baseUrl).get(urlPath).replyWithError('error'); expect( @@ -29,6 +30,7 @@ describe('modules/datasource/flutter-version/index', () => { }) ).toBeNull(); }); + it('returns null for empty 200 OK', async () => { httpMock.scope(baseUrl).get(urlPath).reply(200, []); expect( @@ -38,6 +40,7 @@ describe('modules/datasource/flutter-version/index', () => { }) ).toBeNull(); }); + it('processes real data', async () => { httpMock .scope(baseUrl) diff --git a/lib/modules/datasource/galaxy/index.spec.ts b/lib/modules/datasource/galaxy/index.spec.ts index 13fee711ed..1216c5716b 100644 --- a/lib/modules/datasource/galaxy/index.spec.ts +++ b/lib/modules/datasource/galaxy/index.spec.ts @@ -19,6 +19,7 @@ describe('modules/datasource/galaxy/index', () => { }) ).toBeNull(); }); + it('returns null for missing fields', async () => { httpMock .scope(baseUrl) @@ -31,6 +32,7 @@ describe('modules/datasource/galaxy/index', () => { }) ).toBeNull(); }); + it('returns null for empty list', async () => { httpMock .scope(baseUrl) @@ -43,6 +45,7 @@ describe('modules/datasource/galaxy/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock .scope(baseUrl) @@ -55,6 +58,7 @@ describe('modules/datasource/galaxy/index', () => { }) ).toBeNull(); }); + it('returns null for unknown error', async () => { httpMock .scope(baseUrl) @@ -67,6 +71,7 @@ describe('modules/datasource/galaxy/index', () => { }) ).toBeNull(); }); + it('processes real data', async () => { httpMock .scope(baseUrl) @@ -80,6 +85,7 @@ describe('modules/datasource/galaxy/index', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('return null if searching random username and project name', async () => { httpMock .scope(baseUrl) @@ -91,6 +97,7 @@ describe('modules/datasource/galaxy/index', () => { }); expect(res).toBeNull(); }); + it('throws for 5xx', async () => { httpMock .scope(baseUrl) @@ -108,6 +115,7 @@ describe('modules/datasource/galaxy/index', () => { expect(e).toBeDefined(); expect(e).toMatchSnapshot(); }); + it('throws for 404', async () => { httpMock .scope(baseUrl) diff --git a/lib/modules/datasource/git-refs/index.spec.ts b/lib/modules/datasource/git-refs/index.spec.ts index 4154c5d02e..5524c9814b 100644 --- a/lib/modules/datasource/git-refs/index.spec.ts +++ b/lib/modules/datasource/git-refs/index.spec.ts @@ -26,6 +26,7 @@ describe('modules/datasource/git-refs/index', () => { }); expect(versions).toBeNull(); }); + it('returns nil if response is malformed', async () => { simpleGit.mockReturnValue({ listRemote() { @@ -38,6 +39,7 @@ describe('modules/datasource/git-refs/index', () => { }); expect(releases).toBeEmpty(); }); + it('returns nil if remote call throws exception', async () => { simpleGit.mockReturnValue({ listRemote() { @@ -50,6 +52,7 @@ describe('modules/datasource/git-refs/index', () => { }); expect(versions).toBeNull(); }); + it('returns versions filtered from tags', async () => { simpleGit.mockReturnValue({ listRemote() { @@ -66,6 +69,7 @@ describe('modules/datasource/git-refs/index', () => { expect(result).toHaveLength(6); }); }); + describe('getDigest()', () => { it('returns null if not found', async () => { simpleGit.mockReturnValue({ @@ -79,6 +83,7 @@ describe('modules/datasource/git-refs/index', () => { ); expect(digest).toBeNull(); }); + it('returns digest for tag', async () => { simpleGit.mockReturnValue({ listRemote() { @@ -91,6 +96,7 @@ describe('modules/datasource/git-refs/index', () => { ); expect(digest).toMatchSnapshot(); }); + it('ignores refs/for/', async () => { simpleGit.mockReturnValue({ listRemote() { @@ -103,6 +109,7 @@ describe('modules/datasource/git-refs/index', () => { ); expect(digest).toBe('a9920c014aebc28dc1b23e7efcc006d0455cc710'); }); + it('returns digest for HEAD', async () => { simpleGit.mockReturnValue({ listRemote() { diff --git a/lib/modules/datasource/git-tags/index.spec.ts b/lib/modules/datasource/git-tags/index.spec.ts index c7c9f6d897..a9d45d400f 100644 --- a/lib/modules/datasource/git-tags/index.spec.ts +++ b/lib/modules/datasource/git-tags/index.spec.ts @@ -24,6 +24,7 @@ describe('modules/datasource/git-tags/index', () => { const versions = await getPkgReleases({ datasource, depName }); expect(versions).toBeNull(); }); + it('returns nil if remote call throws exception', async () => { simpleGit.mockReturnValue({ listRemote() { @@ -33,6 +34,7 @@ describe('modules/datasource/git-tags/index', () => { const versions = await getPkgReleases({ datasource, depName }); expect(versions).toBeNull(); }); + it('returns versions filtered from tags', async () => { simpleGit.mockReturnValue({ listRemote() { @@ -47,6 +49,7 @@ describe('modules/datasource/git-tags/index', () => { expect(versions).toMatchSnapshot(); }); }); + describe('getDigest()', () => { it('returns null if not found', async () => { simpleGit.mockReturnValue({ @@ -60,6 +63,7 @@ describe('modules/datasource/git-tags/index', () => { ); expect(digest).toBeNull(); }); + it('returns digest for tag', async () => { simpleGit.mockReturnValue({ listRemote() { @@ -72,6 +76,7 @@ describe('modules/datasource/git-tags/index', () => { ); expect(digest).toMatchSnapshot(); }); + it('returns digest for HEAD', async () => { simpleGit.mockReturnValue({ listRemote() { diff --git a/lib/modules/datasource/github-releases/common.spec.ts b/lib/modules/datasource/github-releases/common.spec.ts index d7a58883f3..e386f33750 100644 --- a/lib/modules/datasource/github-releases/common.spec.ts +++ b/lib/modules/datasource/github-releases/common.spec.ts @@ -6,6 +6,7 @@ describe('modules/datasource/github-releases/common', () => { const sourceUrl = getSourceUrlBase('https://gh.my-company.com'); expect(sourceUrl).toBe('https://gh.my-company.com/'); }); + it('defaults to github.com', () => { const sourceUrl = getSourceUrlBase(null); expect(sourceUrl).toBe('https://github.com/'); diff --git a/lib/modules/datasource/github-releases/index.spec.ts b/lib/modules/datasource/github-releases/index.spec.ts index ccf00f310f..2c0908962d 100644 --- a/lib/modules/datasource/github-releases/index.spec.ts +++ b/lib/modules/datasource/github-releases/index.spec.ts @@ -56,6 +56,7 @@ describe('modules/datasource/github-releases/index', () => { res.releases.find((release) => release.version === '2.0.0').isStable ).toBeFalse(); }); + it('supports ghe', async () => { const packageName = 'some/dep'; httpMock diff --git a/lib/modules/datasource/github-tags/index.spec.ts b/lib/modules/datasource/github-tags/index.spec.ts index 9dd1fa64f6..e8b978e5ae 100644 --- a/lib/modules/datasource/github-tags/index.spec.ts +++ b/lib/modules/datasource/github-tags/index.spec.ts @@ -32,6 +32,7 @@ describe('modules/datasource/github-tags/index', () => { const res = await github.getDigest({ packageName }, null); expect(res).toBeNull(); }); + it('returns digest', async () => { httpMock .scope(githubApiHost) @@ -40,6 +41,7 @@ describe('modules/datasource/github-tags/index', () => { const res = await github.getDigest({ packageName }, null); expect(res).toBe('abcdef'); }); + it('returns commit digest', async () => { httpMock .scope(githubApiHost) @@ -48,6 +50,7 @@ describe('modules/datasource/github-tags/index', () => { const res = await github.getDigest({ packageName }, tag); expect(res).toBe('ddd111'); }); + it('returns tagged commit digest', async () => { httpMock .scope(githubApiHost) @@ -60,6 +63,7 @@ describe('modules/datasource/github-tags/index', () => { const res = await github.getDigest({ packageName }, tag); expect(res).toBe('ddd111'); }); + it('warns if unknown ref', async () => { httpMock .scope(githubApiHost) @@ -68,6 +72,7 @@ describe('modules/datasource/github-tags/index', () => { const res = await github.getDigest({ packageName }, tag); expect(res).toBeNull(); }); + it('returns null for missed tagged digest', async () => { httpMock .scope(githubApiHost) @@ -98,6 +103,7 @@ describe('modules/datasource/github-tags/index', () => { expect(sha2).toBe('ddd111'); }); }); + describe('getReleases', () => { beforeEach(() => { jest.resetAllMocks(); diff --git a/lib/modules/datasource/go/index.spec.ts b/lib/modules/datasource/go/index.spec.ts index 37ae1d378c..6ce14c1b13 100644 --- a/lib/modules/datasource/go/index.spec.ts +++ b/lib/modules/datasource/go/index.spec.ts @@ -102,6 +102,7 @@ describe('modules/datasource/go/index', () => { ); expect(res).toBeNull(); }); + it('returns null for wrong name', async () => { httpMock .scope('https://golang.org/') @@ -113,6 +114,7 @@ describe('modules/datasource/go/index', () => { ); expect(res).toBeNull(); }); + it('supports gitlab digest', async () => { httpMock .scope('https://gitlab.com/') @@ -125,6 +127,7 @@ describe('modules/datasource/go/index', () => { ); expect(res).toBe('abcdefabcdefabcdefabcdef'); }); + it('supports gitlab digest with a specific branch', async () => { const branch = 'some-branch'; httpMock @@ -138,6 +141,7 @@ describe('modules/datasource/go/index', () => { ); expect(res).toBe('abcdefabcdefabcdefabcdef'); }); + it('returns digest', async () => { httpMock .scope('https://golang.org/') @@ -150,6 +154,7 @@ describe('modules/datasource/go/index', () => { ); expect(res).toBe('abcdefabcdefabcdefabcdef'); }); + it('support bitbucket digest', async () => { getDigestBitbucketMock.mockResolvedValueOnce('123'); const res = await datasource.getDigest( diff --git a/lib/modules/datasource/go/releases-direct.spec.ts b/lib/modules/datasource/go/releases-direct.spec.ts index 7b5b16c00d..6c6c1159e2 100644 --- a/lib/modules/datasource/go/releases-direct.spec.ts +++ b/lib/modules/datasource/go/releases-direct.spec.ts @@ -30,6 +30,7 @@ describe('modules/datasource/go/releases-direct', () => { }); expect(res).toBeNull(); }); + it('throws for getDatasource error', async () => { getDatasourceSpy.mockRejectedValueOnce(new Error('unknown')); await expect( @@ -38,6 +39,7 @@ describe('modules/datasource/go/releases-direct', () => { }) ).rejects.toThrow(); }); + it('processes real data', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'github-tags', @@ -57,6 +59,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('support gitlab', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'gitlab-tags', @@ -74,6 +77,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('support self hosted gitlab private repositories', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'gitlab-tags', @@ -92,6 +96,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('support bitbucket tags', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'bitbucket-tags', @@ -113,6 +118,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('support ghe', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'github-tags', @@ -132,6 +138,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('works for known servers', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'github-tags', @@ -172,6 +179,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(res.releases).toBeEmpty(); } }); + it('support gitlab subgroups', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'gitlab-tags', @@ -191,6 +199,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('works for nested modules on github', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'github-tags', @@ -227,6 +236,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(result.releases[0].version.startsWith(prefix)).toBeFalse(); } }); + it('returns none if no tags match submodules', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'github-tags', @@ -256,6 +266,7 @@ describe('modules/datasource/go/releases-direct', () => { expect(result.releases).toHaveLength(0); } }); + it('works for nested modules on github v2+ major upgrades', async () => { getDatasourceSpy.mockResolvedValueOnce({ datasource: 'github-tags', diff --git a/lib/modules/datasource/golang-version/index.spec.ts b/lib/modules/datasource/golang-version/index.spec.ts index 9fe3e9d937..39ea17852c 100644 --- a/lib/modules/datasource/golang-version/index.spec.ts +++ b/lib/modules/datasource/golang-version/index.spec.ts @@ -98,6 +98,7 @@ describe('modules/datasource/golang-version/index', () => { await getPkgReleases({ datasource, depName: 'golang' }) ).toBeNull(); }); + it('throws ExternalHostError for invalid release format beginning ', async () => { httpMock .scope('https://raw.githubusercontent.com') @@ -107,6 +108,7 @@ describe('modules/datasource/golang-version/index', () => { getPkgReleases({ datasource, depName: 'golang' }) ).rejects.toThrow(ExternalHostError); }); + it('throws ExternalHostError for invalid release format', async () => { httpMock .scope('https://raw.githubusercontent.com') diff --git a/lib/modules/datasource/helm/index.spec.ts b/lib/modules/datasource/helm/index.spec.ts index e0c58b92c3..6280a5f3d3 100644 --- a/lib/modules/datasource/helm/index.spec.ts +++ b/lib/modules/datasource/helm/index.spec.ts @@ -21,6 +21,7 @@ describe('modules/datasource/helm/index', () => { }) ).toBeNull(); }); + it('returns null if repository was not provided', async () => { // FIXME: should it call default rtegisty? httpMock @@ -35,6 +36,7 @@ describe('modules/datasource/helm/index', () => { }) ).toBeNull(); }); + it('returns null for empty response', async () => { httpMock .scope('https://example-repository.com') @@ -48,6 +50,7 @@ describe('modules/datasource/helm/index', () => { }) ).toBeNull(); }); + it('returns null for missing response body', async () => { httpMock .scope('https://example-repository.com') @@ -61,6 +64,7 @@ describe('modules/datasource/helm/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock .scope('https://example-repository.com') @@ -74,6 +78,7 @@ describe('modules/datasource/helm/index', () => { }) ).toBeNull(); }); + it('throws for 5xx', async () => { httpMock .scope('https://example-repository.com') @@ -92,6 +97,7 @@ describe('modules/datasource/helm/index', () => { expect(e).toBeDefined(); expect(e).toMatchSnapshot(); }); + it('returns null for unknown error', async () => { httpMock .scope('https://example-repository.com') @@ -105,6 +111,7 @@ describe('modules/datasource/helm/index', () => { }) ).toBeNull(); }); + it('returns null if index.yaml in response is empty', async () => { httpMock .scope('https://example-repository.com') @@ -117,6 +124,7 @@ describe('modules/datasource/helm/index', () => { }); expect(releases).toBeNull(); }); + it('returns null if index.yaml in response is invalid', async () => { const res = { body: `some @@ -135,6 +143,7 @@ describe('modules/datasource/helm/index', () => { }); expect(releases).toBeNull(); }); + it('returns null if packageName is not in index.yaml', async () => { httpMock .scope('https://example-repository.com') @@ -147,6 +156,7 @@ describe('modules/datasource/helm/index', () => { }); expect(releases).toBeNull(); }); + it('returns list of versions for normal response', async () => { httpMock .scope('https://example-repository.com') @@ -160,6 +170,7 @@ describe('modules/datasource/helm/index', () => { expect(releases).not.toBeNull(); expect(releases).toMatchSnapshot(); }); + it('adds trailing slash to subdirectories', async () => { httpMock .scope('https://example-repository.com') diff --git a/lib/modules/datasource/hex/index.spec.ts b/lib/modules/datasource/hex/index.spec.ts index 1ad085bc70..669faf63f4 100644 --- a/lib/modules/datasource/hex/index.spec.ts +++ b/lib/modules/datasource/hex/index.spec.ts @@ -35,6 +35,7 @@ describe('modules/datasource/hex/index', () => { }) ).toBeNull(); }); + it('returns null for missing fields', async () => { httpMock .scope(baseUrl) @@ -47,30 +48,35 @@ describe('modules/datasource/hex/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock.scope(baseUrl).get('/packages/some_package').reply(404); expect( await getPkgReleases({ datasource, depName: 'some_package' }) ).toBeNull(); }); + it('returns null for 401', async () => { httpMock.scope(baseUrl).get('/packages/some_package').reply(401); expect( await getPkgReleases({ datasource, depName: 'some_package' }) ).toBeNull(); }); + it('throws for 429', async () => { httpMock.scope(baseUrl).get('/packages/some_crate').reply(429); await expect( getPkgReleases({ datasource, depName: 'some_crate' }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('throws for 5xx', async () => { httpMock.scope(baseUrl).get('/packages/some_crate').reply(502); await expect( getPkgReleases({ datasource, depName: 'some_crate' }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('returns null for unknown error', async () => { httpMock.scope(baseUrl).get('/packages/some_package').replyWithError(''); expect( @@ -114,6 +120,7 @@ describe('modules/datasource/hex/index', () => { expect(res).not.toBeNull(); expect(res).toBeDefined(); }); + it('process public repo without auth', async () => { httpMock .scope(baseUrl) diff --git a/lib/modules/datasource/maven/index.spec.ts b/lib/modules/datasource/maven/index.spec.ts index 1af3e51adb..a7f1cec64f 100644 --- a/lib/modules/datasource/maven/index.spec.ts +++ b/lib/modules/datasource/maven/index.spec.ts @@ -587,6 +587,7 @@ describe('modules/datasource/maven/index', () => { sourceUrl: 'https://github.com/child-scm/child', }); }); + it('should be able to detect git://@github.com/child-scm as valid sourceUrl', async () => { mockGenericPackage({ meta: loadFixture('child-scm-gitprotocol/meta.xml'), diff --git a/lib/modules/datasource/metadata.spec.ts b/lib/modules/datasource/metadata.spec.ts index e227bda05d..c07d5df1fc 100644 --- a/lib/modules/datasource/metadata.spec.ts +++ b/lib/modules/datasource/metadata.spec.ts @@ -113,6 +113,7 @@ describe('modules/datasource/metadata', () => { sourceUrl: 'https://gitlab.com/meno/dropzone', }); }); + it('Should handle failed parsing of sourceUrls for GitLab', () => { const dep = { sourceUrl: 'https://gitlab-nope', @@ -132,6 +133,7 @@ describe('modules/datasource/metadata', () => { sourceUrl: 'https://gitlab-nope', }); }); + it('Should handle failed parsing of sourceUrls for other', () => { const dep = { sourceUrl: 'https://nope-nope-nope', @@ -151,6 +153,7 @@ describe('modules/datasource/metadata', () => { sourceUrl: 'https://nope-nope-nope', }); }); + it('Should handle non-url', () => { const dep = { sourceUrl: 'not-a-url', @@ -229,21 +232,25 @@ describe('modules/datasource/metadata', () => { 'https://example.com/foo/bar' ); }); + it('Should massage github http url to valid https url', () => { expect(massageGithubUrl('http://example.com/foo/bar')).toMatch( 'https://example.com/foo/bar' ); }); + it('Should massage github http and git url to valid https url', () => { expect(massageGithubUrl('http+git://example.com/foo/bar')).toMatch( 'https://example.com/foo/bar' ); }); + it('Should massage github ssh git@ url to valid https url', () => { expect(massageGithubUrl('ssh://git@example.com/foo/bar')).toMatch( 'https://example.com/foo/bar' ); }); + it('Should massage github git url to valid https url', () => { expect(massageGithubUrl('git://example.com/foo/bar')).toMatch( 'https://example.com/foo/bar' diff --git a/lib/modules/datasource/npm/npmrc.spec.ts b/lib/modules/datasource/npm/npmrc.spec.ts index 21cf223072..1a95d52309 100644 --- a/lib/modules/datasource/npm/npmrc.spec.ts +++ b/lib/modules/datasource/npm/npmrc.spec.ts @@ -18,23 +18,27 @@ describe('modules/datasource/npm/npmrc', () => { GlobalConfig.reset(); jest.resetAllMocks(); }); + describe('getMatchHostFromNpmrcHost()', () => { it('parses //host', () => { expect(getMatchHostFromNpmrcHost('//registry.npmjs.org')).toBe( 'registry.npmjs.org' ); }); + it('parses //host/path', () => { expect( getMatchHostFromNpmrcHost('//registry.company.com/some/path') ).toBe('https://registry.company.com/some/path'); }); + it('parses https://host', () => { expect(getMatchHostFromNpmrcHost('https://registry.npmjs.org')).toBe( 'https://registry.npmjs.org' ); }); }); + describe('convertNpmrcToRules()', () => { it('rejects invalid registries', () => { const res = convertNpmrcToRules( @@ -43,6 +47,7 @@ describe('modules/datasource/npm/npmrc', () => { expect(res.hostRules).toHaveLength(0); expect(res.packageRules).toHaveLength(0); }); + it('handles naked auth', () => { expect(convertNpmrcToRules(ini.parse('_auth=abc123\n'))) .toMatchInlineSnapshot(` @@ -58,6 +63,7 @@ describe('modules/datasource/npm/npmrc', () => { } `); }); + it('handles host, path and auth', () => { expect( convertNpmrcToRules(ini.parse('//some.test/with/path:_auth=abc123')) @@ -75,6 +81,7 @@ describe('modules/datasource/npm/npmrc', () => { } `); }); + it('handles host, path, port and auth', () => { expect( convertNpmrcToRules( @@ -93,6 +100,7 @@ describe('modules/datasource/npm/npmrc', () => { } `); }); + it('handles naked authToken', () => { expect(convertNpmrcToRules(ini.parse('_authToken=abc123\n'))) .toMatchInlineSnapshot(` @@ -107,6 +115,7 @@ describe('modules/datasource/npm/npmrc', () => { } `); }); + it('handles host authToken', () => { expect( convertNpmrcToRules( @@ -139,6 +148,7 @@ describe('modules/datasource/npm/npmrc', () => { } `); }); + it('handles username and _password', () => { expect( convertNpmrcToRules( @@ -161,6 +171,7 @@ describe('modules/datasource/npm/npmrc', () => { `); }); }); + it('sanitize _auth', () => { setNpmrc('_auth=test'); expect(sanitize.addSecretForSanitizing).toHaveBeenCalledWith('test'); diff --git a/lib/modules/datasource/nuget/index.spec.ts b/lib/modules/datasource/nuget/index.spec.ts index 4abb4fe7a8..5e0cc41b28 100644 --- a/lib/modules/datasource/nuget/index.spec.ts +++ b/lib/modules/datasource/nuget/index.spec.ts @@ -179,6 +179,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toBeNull(); }); + it(`empty packages list (v3)`, async () => { httpMock .scope('https://api.nuget.org') @@ -211,6 +212,7 @@ describe('modules/datasource/nuget/index', () => { }) ).toBeNull(); }); + it('returns null for empty result (v2)', async () => { httpMock .scope('https://www.nuget.org') @@ -224,6 +226,7 @@ describe('modules/datasource/nuget/index', () => { }) ).toBeNull(); }); + it('returns null for empty result (v3)', async () => { httpMock .scope('https://api.nuget.org') @@ -249,6 +252,7 @@ describe('modules/datasource/nuget/index', () => { }) ).toBeNull(); }); + it('returns null for non 200 (v3)', async () => { httpMock.scope('https://api.nuget.org').get('/v3/index.json').reply(500); expect( @@ -257,6 +261,7 @@ describe('modules/datasource/nuget/index', () => { }) ).toBeNull(); }); + it('returns null for non 200 (v2)', async () => { httpMock .scope('https://www.nuget.org') @@ -288,6 +293,7 @@ describe('modules/datasource/nuget/index', () => { }) ).toBeNull(); }); + it('returns deduplicated results', async () => { httpMock .scope('https://api.nuget.org') @@ -313,6 +319,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toMatchSnapshot(); expect(res.releases).toHaveLength(45); }); + it('returns null for unknown error in getReleasesFromV3Feed (v3)', async () => { httpMock .scope('https://api.nuget.org') @@ -324,6 +331,7 @@ describe('modules/datasource/nuget/index', () => { }) ).toBeNull(); }); + it('returns null for unknown error in getQueryUrlForV3Feed (v3)', async () => { httpMock .scope('https://api.nuget.org') @@ -337,6 +345,7 @@ describe('modules/datasource/nuget/index', () => { }) ).toBeNull(); }); + it('returns null for unknown error (v2)', async () => { httpMock .scope('https://www.nuget.org') @@ -350,6 +359,7 @@ describe('modules/datasource/nuget/index', () => { }) ).toBeNull(); }); + it('processes real data (v3) feed is a nuget.org', async () => { httpMock .scope('https://api.nuget.org') @@ -367,6 +377,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toMatchSnapshot(); expect(res.sourceUrl).toBeDefined(); }); + it('processes real data (v3) for several catalog pages', async () => { const scope = httpMock .scope('https://api.nuget.org') @@ -384,6 +395,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toMatchSnapshot(); expect(res.sourceUrl).toBeDefined(); }); + it('processes real data (v3) feed is not a nuget.org', async () => { httpMock .scope('https://api.nuget.org') @@ -412,6 +424,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toMatchSnapshot(); expect(res.sourceUrl).toBeDefined(); }); + it('processes real data (v3) nuspec fetch error', async () => { httpMock .scope('https://api.nuget.org') @@ -429,6 +442,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toMatchSnapshot(); expect(res.sourceUrl).toBeUndefined(); }); + it('processes real data (v3) nuspec fetch 404 error', async () => { httpMock .scope('https://api.nuget.org') @@ -446,6 +460,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toMatchSnapshot(); expect(res.sourceUrl).toBeUndefined(); }); + it('processes real data (v2)', async () => { httpMock .scope('https://www.nuget.org') @@ -460,6 +475,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toMatchSnapshot(); expect(res.sourceUrl).toBeDefined(); }); + it('processes real data no relase (v2)', async () => { httpMock .scope('https://www.nuget.org') @@ -472,6 +488,7 @@ describe('modules/datasource/nuget/index', () => { }); expect(res).toBeNull(); }); + it('processes real data without project url (v2)', async () => { httpMock .scope('https://www.nuget.org') @@ -486,6 +503,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).toMatchSnapshot(); expect(res.sourceUrl).toBeUndefined(); }); + it('processes real data with no github project url (v2)', async () => { httpMock .scope('https://www.nuget.org') @@ -499,6 +517,7 @@ describe('modules/datasource/nuget/index', () => { expect(res).not.toBeNull(); expect(res).toMatchSnapshot(); }); + it('handles paginated results (v2)', async () => { httpMock .scope('https://www.nuget.org') diff --git a/lib/modules/datasource/orb/index.spec.ts b/lib/modules/datasource/orb/index.spec.ts index 671444d735..e75e118438 100644 --- a/lib/modules/datasource/orb/index.spec.ts +++ b/lib/modules/datasource/orb/index.spec.ts @@ -42,6 +42,7 @@ describe('modules/datasource/orb/index', () => { }) ).toBeNull(); }); + it('returns null for missing orb', async () => { httpMock .scope(baseUrl) @@ -54,6 +55,7 @@ describe('modules/datasource/orb/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock.scope(baseUrl).post('/graphql-unstable').reply(404); expect( @@ -63,6 +65,7 @@ describe('modules/datasource/orb/index', () => { }) ).toBeNull(); }); + it('returns null for unknown error', async () => { httpMock.scope(baseUrl).post('/graphql-unstable').replyWithError(''); expect( @@ -72,6 +75,7 @@ describe('modules/datasource/orb/index', () => { }) ).toBeNull(); }); + it('processes real data', async () => { httpMock.scope(baseUrl).post('/graphql-unstable').reply(200, orbData); const res = await getPkgReleases({ @@ -81,6 +85,7 @@ describe('modules/datasource/orb/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('processes homeUrl', async () => { orbData.data.orb.homeUrl = 'https://google.com'; httpMock.scope(baseUrl).post('/graphql-unstable').reply(200, orbData); diff --git a/lib/modules/datasource/packagist/index.spec.ts b/lib/modules/datasource/packagist/index.spec.ts index 7d9720b575..2a1d34ef8d 100644 --- a/lib/modules/datasource/packagist/index.spec.ts +++ b/lib/modules/datasource/packagist/index.spec.ts @@ -22,6 +22,7 @@ const datasource = PackagistDatasource.id; describe('modules/datasource/packagist/index', () => { describe('getReleases', () => { let config: any; + beforeEach(() => { jest.resetAllMocks(); hostRules.find = jest.fn((input: HostRule) => input); @@ -75,6 +76,7 @@ describe('modules/datasource/packagist/index', () => { }); expect(res).toMatchSnapshot(); }); + it('handles timeouts', async () => { httpMock .scope('https://composer.renovatebot.com') @@ -89,6 +91,7 @@ describe('modules/datasource/packagist/index', () => { }); expect(res).toBeNull(); }); + it('handles auth rejections', async () => { httpMock .scope('https://composer.renovatebot.com') @@ -103,6 +106,7 @@ describe('modules/datasource/packagist/index', () => { }); expect(res).toBeNull(); }); + it('handles not found registries', async () => { httpMock .scope('https://composer.renovatebot.com') @@ -117,6 +121,7 @@ describe('modules/datasource/packagist/index', () => { }); expect(res).toBeNull(); }); + it('supports includes packages', async () => { hostRules.find = jest.fn(() => ({ username: 'some-username', @@ -145,6 +150,7 @@ describe('modules/datasource/packagist/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('supports lazy repositories', async () => { const packagesJson = { packages: [], @@ -183,6 +189,7 @@ describe('modules/datasource/packagist/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('supports provider-includes', async () => { const packagesJson = { packages: [], @@ -227,6 +234,7 @@ describe('modules/datasource/packagist/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('handles provider-includes miss', async () => { const packagesJson = { packages: [], @@ -267,6 +275,7 @@ describe('modules/datasource/packagist/index', () => { }); expect(res).toBeNull(); }); + it('supports providers', async () => { const packagesJson = { packages: [], @@ -299,6 +308,7 @@ describe('modules/datasource/packagist/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('supports providers without a hash', async () => { const packagesJson = { packages: [], @@ -327,6 +337,7 @@ describe('modules/datasource/packagist/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('handles providers miss', async () => { const packagesJson = { packages: [], @@ -355,6 +366,7 @@ describe('modules/datasource/packagist/index', () => { }); expect(res).toBeNull(); }); + it('processes real versioned data', async () => { httpMock .scope(baseUrl) @@ -374,6 +386,7 @@ describe('modules/datasource/packagist/index', () => { }) ).toMatchSnapshot(); }); + it('adds packagist source implicitly', async () => { httpMock .scope(baseUrl) diff --git a/lib/modules/datasource/pod/index.spec.ts b/lib/modules/datasource/pod/index.spec.ts index 71b1dd1918..96d71bdf59 100644 --- a/lib/modules/datasource/pod/index.spec.ts +++ b/lib/modules/datasource/pod/index.spec.ts @@ -36,6 +36,7 @@ describe('modules/datasource/pod/index', () => { }) ).toBeNull(); }); + it('returns null for empty result', async () => { // FIXME: why get request? httpMock @@ -44,6 +45,7 @@ describe('modules/datasource/pod/index', () => { .reply(404); expect(await getPkgReleases(config)).toBeNull(); }); + it('returns null for 404', async () => { httpMock .scope(githubApiHost) @@ -61,6 +63,7 @@ describe('modules/datasource/pod/index', () => { }); expect(res).toBeNull(); }); + it('returns null for 404 Github enterprise', async () => { httpMock .scope(githubEntApiHost) @@ -81,6 +84,7 @@ describe('modules/datasource/pod/index', () => { }); expect(res).toBeNull(); }); + it('returns null for 404 Github enterprise with different url style', async () => { httpMock .scope(githubEntApiHost2) @@ -98,6 +102,7 @@ describe('modules/datasource/pod/index', () => { }); expect(res).toBeNull(); }); + it('returns null for 401', async () => { httpMock .scope(cocoapodsHost) @@ -105,6 +110,7 @@ describe('modules/datasource/pod/index', () => { .reply(401); expect(await getPkgReleases(config)).toBeNull(); }); + it('throws for 429', async () => { httpMock .scope(cocoapodsHost) @@ -112,6 +118,7 @@ describe('modules/datasource/pod/index', () => { .reply(429); await expect(getPkgReleases(config)).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('returns null for unknown error', async () => { httpMock .scope(cocoapodsHost) @@ -119,6 +126,7 @@ describe('modules/datasource/pod/index', () => { .replyWithError('foobar'); expect(await getPkgReleases(config)).toBeNull(); }); + it('processes real data from CDN', async () => { httpMock .scope(cocoapodsHost) @@ -138,6 +146,7 @@ describe('modules/datasource/pod/index', () => { ], }); }); + it('processes real data from Github with shard with specs', async () => { httpMock .scope(githubApiHost) @@ -156,6 +165,7 @@ describe('modules/datasource/pod/index', () => { ], }); }); + it('processes real data from Github with shard without specs', async () => { httpMock .scope(githubApiHost) @@ -176,6 +186,7 @@ describe('modules/datasource/pod/index', () => { ], }); }); + it('processes real data from Github with specs without shard', async () => { httpMock .scope(githubApiHost) @@ -198,6 +209,7 @@ describe('modules/datasource/pod/index', () => { ], }); }); + it('processes real data from Github without specs without shard', async () => { httpMock .scope(githubApiHost) @@ -222,6 +234,7 @@ describe('modules/datasource/pod/index', () => { ], }); }); + it('processes real data from Github Enterprise with shard with specs', async () => { httpMock .scope(githubEntApiHost) @@ -240,6 +253,7 @@ describe('modules/datasource/pod/index', () => { ], }); }); + it('processes real data from Github Enterprise with shard without specs', async () => { httpMock .scope(githubEntApiHost) @@ -260,6 +274,7 @@ describe('modules/datasource/pod/index', () => { ], }); }); + it('processes real data from Github Enterprise with specs without shard', async () => { httpMock .scope(githubEntApiHost) @@ -282,6 +297,7 @@ describe('modules/datasource/pod/index', () => { ], }); }); + it('processes real data from Github Enterprise without specs without shard', async () => { httpMock .scope(githubEntApiHost) diff --git a/lib/modules/datasource/pypi/index.spec.ts b/lib/modules/datasource/pypi/index.spec.ts index 417aeb261e..e5720cb3cc 100644 --- a/lib/modules/datasource/pypi/index.spec.ts +++ b/lib/modules/datasource/pypi/index.spec.ts @@ -42,6 +42,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock.scope(baseUrl).get('/something/json').reply(404); httpMock.scope(baseUrl).get('/something/').reply(404); @@ -52,6 +53,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toBeNull(); }); + it('processes real data', async () => { httpMock .scope(baseUrl) @@ -64,6 +66,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toMatchSnapshot(); }); + it('supports custom datasource url', async () => { httpMock .scope('https://custom.pypi.net/foo') @@ -101,6 +104,7 @@ describe('modules/datasource/pypi/index', () => { }); expect(res.isPrivate).toBeTrue(); }); + it('supports multiple custom datasource urls', async () => { httpMock .scope('https://custom.pypi.net/foo') @@ -131,6 +135,7 @@ describe('modules/datasource/pypi/index', () => { releaseTimestamp: '2019-06-18T13:58:55.000Z', }); }); + it('returns non-github home_page', async () => { httpMock .scope(baseUrl) @@ -151,6 +156,7 @@ describe('modules/datasource/pypi/index', () => { ).homepage ).toMatchSnapshot(); }); + it('find url from project_urls', async () => { const info = { name: 'flexget', @@ -174,6 +180,7 @@ describe('modules/datasource/pypi/index', () => { expect(result.sourceUrl).toBe(info.project_urls.Repository); expect(result.changelogUrl).toBe(info.project_urls.changelog); }); + it('normalizes the package name according to PEP 503', async () => { const expectedHttpCall = httpMock .scope(baseUrl) @@ -188,6 +195,7 @@ describe('modules/datasource/pypi/index', () => { expect(expectedHttpCall.isDone()).toBeTrue(); }); + it('normalizes the package name according to PEP 503 when falling back to simple endpoint', async () => { httpMock .scope(baseUrl) @@ -206,6 +214,7 @@ describe('modules/datasource/pypi/index', () => { expect(expectedFallbackHttpCall.isDone()).toBeTrue(); }); + it('normalizes the package name according to PEP 503 querying a simple endpoint', async () => { const simpleRegistryUrl = 'https://pypi.org/simple/'; const expectedHttpCall = httpMock @@ -249,6 +258,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toMatchSnapshot(); }); + it('process data from simple endpoint', async () => { httpMock .scope('https://pypi.org/simple/') @@ -266,6 +276,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toMatchSnapshot(); }); + it('process data from +simple endpoint', async () => { httpMock .scope('https://some.registry.org/+simple/') @@ -283,6 +294,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toMatchSnapshot(); }); + it('sets private simple if authorization provided', async () => { hostRules.add({ matchHost: 'some.private.registry.org', @@ -303,6 +315,7 @@ describe('modules/datasource/pypi/index', () => { }); expect(res.isPrivate).toBeTrue(); }); + it('process data from simple endpoint with hyphens', async () => { httpMock .scope('https://pypi.org/simple/') @@ -322,6 +335,7 @@ describe('modules/datasource/pypi/index', () => { { version: '2.0.2' }, ]); }); + it('process data from simple endpoint with hyphens replaced with underscores', async () => { httpMock .scope('https://pypi.org/simple/') @@ -339,6 +353,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toMatchSnapshot(); }); + it('process data from simple endpoint with mixed-case characters', async () => { httpMock .scope('https://pypi.org/simple/') @@ -358,6 +373,7 @@ describe('modules/datasource/pypi/index', () => { { version: '2.0.2' }, ]); }); + it('process data from simple endpoint with mixed-case characters when using lower case dependency name', async () => { httpMock .scope('https://pypi.org/simple/') @@ -377,6 +393,7 @@ describe('modules/datasource/pypi/index', () => { { version: '2.0.2' }, ]); }); + it('process data from simple endpoint with periods', async () => { httpMock .scope('https://pypi.org/simple/') @@ -396,6 +413,7 @@ describe('modules/datasource/pypi/index', () => { { version: '2.0.2' }, ]); }); + it('returns null for empty response', async () => { httpMock .scope('https://pypi.org/simple/') @@ -413,6 +431,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toBeNull(); }); + it('returns null for 404 response from simple endpoint', async () => { httpMock .scope('https://pypi.org/simple/') @@ -430,6 +449,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toBeNull(); }); + it('returns null for response with no versions', async () => { httpMock .scope('https://pypi.org/simple/') @@ -447,6 +467,7 @@ describe('modules/datasource/pypi/index', () => { }) ).toBeNull(); }); + it('fall back from json and process data from simple endpoint', async () => { httpMock .scope('https://custom.pypi.net/foo') @@ -466,6 +487,7 @@ describe('modules/datasource/pypi/index', () => { }); expect(result).toMatchSnapshot(); }); + it('parses data-requires-python and respects constraints from simple endpoint', async () => { httpMock .scope('https://pypi.org/simple/') diff --git a/lib/modules/datasource/ruby-version/index.spec.ts b/lib/modules/datasource/ruby-version/index.spec.ts index 70ebc97c46..25f5533314 100644 --- a/lib/modules/datasource/ruby-version/index.spec.ts +++ b/lib/modules/datasource/ruby-version/index.spec.ts @@ -18,6 +18,7 @@ describe('modules/datasource/ruby-version/index', () => { }); expect(res).toMatchSnapshot(); }); + it('throws for empty result', async () => { httpMock .scope('https://www.ruby-lang.org') diff --git a/lib/modules/datasource/rubygems/index.spec.ts b/lib/modules/datasource/rubygems/index.spec.ts index 5e268a3662..ad41ab5580 100644 --- a/lib/modules/datasource/rubygems/index.spec.ts +++ b/lib/modules/datasource/rubygems/index.spec.ts @@ -160,6 +160,7 @@ describe('modules/datasource/rubygems/index', () => { expect(res.releases).toHaveLength(1); expect(res.releases[0].version).toBe(railsInfo.version); }); + it('errors when version request fails with anything other than 400 or 404', async () => { httpMock .scope('https://thirdparty.com/') diff --git a/lib/modules/datasource/sbt-plugin/index.spec.ts b/lib/modules/datasource/sbt-plugin/index.spec.ts index 1eb6f005eb..1d70d672d2 100644 --- a/lib/modules/datasource/sbt-plugin/index.spec.ts +++ b/lib/modules/datasource/sbt-plugin/index.spec.ts @@ -166,6 +166,7 @@ describe('modules/datasource/sbt-plugin/index', () => { releases: [{ version: '0.5.5' }], }); }); + it('fetches sbt plugins 2', async () => { expect( await getPkgReleases({ diff --git a/lib/modules/datasource/terraform-module/index.spec.ts b/lib/modules/datasource/terraform-module/index.spec.ts index a06f670fc2..8abd841fbd 100644 --- a/lib/modules/datasource/terraform-module/index.spec.ts +++ b/lib/modules/datasource/terraform-module/index.spec.ts @@ -33,6 +33,7 @@ describe('modules/datasource/terraform-module/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock .scope(baseUrl) @@ -47,6 +48,7 @@ describe('modules/datasource/terraform-module/index', () => { }) ).toBeNull(); }); + it('returns null for unknown error', async () => { httpMock .scope(baseUrl) @@ -61,6 +63,7 @@ describe('modules/datasource/terraform-module/index', () => { }) ).toBeNull(); }); + it('processes real data', async () => { httpMock .scope(baseUrl) @@ -75,6 +78,7 @@ describe('modules/datasource/terraform-module/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('processes with registry in name', async () => { httpMock .scope(baseUrl) @@ -89,6 +93,7 @@ describe('modules/datasource/terraform-module/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('rejects mismatch', async () => { httpMock .scope('https://terraform.company.com') @@ -103,6 +108,7 @@ describe('modules/datasource/terraform-module/index', () => { }); expect(res).toBeNull(); }); + it('rejects servicediscovery', async () => { httpMock .scope('https://terraform.company.com') @@ -115,6 +121,7 @@ describe('modules/datasource/terraform-module/index', () => { }); expect(res).toBeNull(); }); + it('processes real data on changed subpath', async () => { httpMock .scope(localTerraformEnterprisebaseUrl) diff --git a/lib/modules/datasource/terraform-provider/index.spec.ts b/lib/modules/datasource/terraform-provider/index.spec.ts index 3d16eccfc2..570b987043 100644 --- a/lib/modules/datasource/terraform-provider/index.spec.ts +++ b/lib/modules/datasource/terraform-provider/index.spec.ts @@ -31,6 +31,7 @@ describe('modules/datasource/terraform-provider/index', () => { }) ).toBeNull(); }); + it('returns null for 404', async () => { httpMock .scope(primaryUrl) @@ -46,6 +47,7 @@ describe('modules/datasource/terraform-provider/index', () => { }) ).toBeNull(); }); + it('returns null for unknown error', async () => { httpMock .scope(primaryUrl) @@ -61,6 +63,7 @@ describe('modules/datasource/terraform-provider/index', () => { }) ).toBeNull(); }); + it('processes real data', async () => { httpMock .scope(primaryUrl) @@ -92,6 +95,7 @@ describe('modules/datasource/terraform-provider/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('processes data with alternative backend', async () => { httpMock .scope(primaryUrl) @@ -113,6 +117,7 @@ describe('modules/datasource/terraform-provider/index', () => { expect(res).toMatchSnapshot(); expect(res).not.toBeNull(); }); + it('simulate failing secondary release source', async () => { httpMock .scope(primaryUrl) @@ -130,6 +135,7 @@ describe('modules/datasource/terraform-provider/index', () => { }); expect(res).toBeNull(); }); + it('returns null for error in service discovery', async () => { httpMock.scope(primaryUrl).get('/.well-known/terraform.json').reply(404); httpMock.scope(secondaryUrl).get('/index.json').replyWithError(''); @@ -141,6 +147,7 @@ describe('modules/datasource/terraform-provider/index', () => { ).toBeNull(); }); }); + describe('getBuilds', () => { it('returns null for empty result', async () => { httpMock diff --git a/lib/modules/manager/ansible-galaxy/extract.spec.ts b/lib/modules/manager/ansible-galaxy/extract.spec.ts index 21d5c8ad16..cc7dc519e7 100644 --- a/lib/modules/manager/ansible-galaxy/extract.spec.ts +++ b/lib/modules/manager/ansible-galaxy/extract.spec.ts @@ -13,54 +13,65 @@ describe('modules/manager/ansible-galaxy/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here', 'requirements.yml')).toBeNull(); }); + it('extracts multiple dependencies from requirements.yml', () => { const res = extractPackageFile(yamlFile1, 'requirements.yml'); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(12); }); + it('extracts dependencies from a not beautified requirements file', () => { const res = extractPackageFile(yamlFile2, 'requirements.yml'); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(2); }); + it('check if an empty file returns null', () => { const res = extractPackageFile('\n', 'requirements.yml'); expect(res).toBeNull(); }); + it('check if a requirements file of other systems returns null', () => { const res = extractPackageFile(helmRequirements, 'requirements.yml'); expect(res).toBeNull(); }); + it('check collection style requirements file', () => { const res = extractPackageFile(collections1, 'requirements.yml'); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(13); expect(res.deps.filter((value) => value.skipReason)).toHaveLength(6); }); + it('check collection style requirements file in reverse order and missing empty line', () => { const res = extractPackageFile(collections2, 'requirements.yml'); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(4); }); + it('check galaxy definition file', () => { const res = extractPackageFile(galaxy, 'galaxy.yml'); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(2); }); }); + describe('getSliceEndNumber()', () => { it('negative start number returns -1', () => { const res = getSliceEndNumber(-1, 10, 5); expect(res).toBe(-1); }); + it('a start number bigger then number of lines return -1', () => { const res = getSliceEndNumber(20, 10, 5); expect(res).toBe(-1); }); + it('choose first block', () => { const res = getSliceEndNumber(0, 10, 5); expect(res).toBe(5); }); + it('choose second block', () => { const res = getSliceEndNumber(5, 10, 5); expect(res).toBe(9); diff --git a/lib/modules/manager/ansible/extract.spec.ts b/lib/modules/manager/ansible/extract.spec.ts index 75fda6b369..4296d8b09d 100644 --- a/lib/modules/manager/ansible/extract.spec.ts +++ b/lib/modules/manager/ansible/extract.spec.ts @@ -6,11 +6,13 @@ describe('modules/manager/ansible/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts multiple image lines from docker_container', () => { const res = extractPackageFile(Fixtures.get('main1.yaml')); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(9); }); + it('extracts multiple image lines from docker_service', () => { const res = extractPackageFile(Fixtures.get('main2.yaml')); expect(res.deps).toMatchSnapshot(); diff --git a/lib/modules/manager/azure-pipelines/extract.spec.ts b/lib/modules/manager/azure-pipelines/extract.spec.ts index d84ebbf009..6e9ff77d85 100644 --- a/lib/modules/manager/azure-pipelines/extract.spec.ts +++ b/lib/modules/manager/azure-pipelines/extract.spec.ts @@ -82,6 +82,7 @@ describe('modules/manager/azure-pipelines/extract', () => { datasource: 'docker', }); }); + it('should return null if image field is missing', () => { expect(extractContainer({ image: null })).toBeNull(); }); @@ -91,11 +92,13 @@ describe('modules/manager/azure-pipelines/extract', () => { it('returns null for invalid azure pipelines files', () => { expect(extractPackageFile('', 'some-file')).toBeNull(); }); + it('extracts dependencies', () => { const res = extractPackageFile(azurePipelines, 'some-file'); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(3); }); + it('should return null when there is no dependency found', () => { expect( extractPackageFile(azurePipelinesNoDependency, 'some-file') diff --git a/lib/modules/manager/bazel/extract.spec.ts b/lib/modules/manager/bazel/extract.spec.ts index 9f685e3be9..c7fc036dc5 100644 --- a/lib/modules/manager/bazel/extract.spec.ts +++ b/lib/modules/manager/bazel/extract.spec.ts @@ -7,15 +7,18 @@ describe('modules/manager/bazel/extract', () => { const res = extractPackageFile('blahhhhh:foo:@what\n'); expect(res).toBeNull(); }); + it('returns empty if cannot parse dependency', () => { const res = extractPackageFile('git_repository(\n nothing\n)\n'); expect(res).toBeNull(); }); + it('extracts multiple types of dependencies', () => { const res = extractPackageFile(Fixtures.get('WORKSPACE1')); expect(res.deps).toHaveLength(14); expect(res.deps).toMatchSnapshot(); }); + it('extracts github tags', () => { const res = extractPackageFile(Fixtures.get('WORKSPACE2')); expect(res.deps).toMatchSnapshot([ @@ -25,12 +28,14 @@ describe('modules/manager/bazel/extract', () => { { packageName: 'nelhage/rules_boost' }, ]); }); + it('handle comments and strings', () => { const res = extractPackageFile(Fixtures.get('WORKSPACE3')); expect(res.deps).toMatchSnapshot([ { packageName: 'nelhage/rules_boost' }, ]); }); + it('extracts dependencies from *.bzl files', () => { const res = extractPackageFile(Fixtures.get('repositories.bzl')); expect(res.deps).toMatchSnapshot([ diff --git a/lib/modules/manager/bazel/update.spec.ts b/lib/modules/manager/bazel/update.spec.ts index 5c6f6451c0..82240d48ac 100644 --- a/lib/modules/manager/bazel/update.spec.ts +++ b/lib/modules/manager/bazel/update.spec.ts @@ -97,6 +97,7 @@ describe('modules/manager/bazel/update', () => { '"aaa09d789f3dba190787f8b4454c7d3c936fe123", # v1.0.3' ); }); + it('updates commit-based http archive', async () => { const upgrade = { depName: 'distroless', @@ -120,6 +121,7 @@ describe('modules/manager/bazel/update', () => { }); expect(res).not.toEqual(content); }); + it('updates http archive with content other then WORKSPACE', async () => { const upgrade = { depName: 'bazel_skylib', @@ -147,6 +149,7 @@ describe('modules/manager/bazel/update', () => { expect(res).not.toEqual(fileWithBzlExtension); expect(res.indexOf('0.8.0')).not.toBe(-1); }); + it('updates finds url instead of urls', async () => { const upgrade = { depName: 'bazel_skylib', @@ -174,6 +177,7 @@ describe('modules/manager/bazel/update', () => { expect(res).not.toEqual(fileWithBzlExtension); expect(res.indexOf('0.8.0')).not.toBe(-1); }); + it('returns null if no urls resolve hashes', async () => { const upgrade = { depName: 'bazel_skylib', @@ -202,6 +206,7 @@ describe('modules/manager/bazel/update', () => { }); expect(res).toBeNull(); }); + it('errors for http_archive without urls', async () => { const upgrade = { depName: 'bazel_skylib', @@ -226,6 +231,7 @@ http_archive( }); expect(res).toBeNull(); }); + it('updates http_archive with urls array', async () => { const upgrade = { depName: 'bazel_skylib', diff --git a/lib/modules/manager/buildkite/extract.spec.ts b/lib/modules/manager/buildkite/extract.spec.ts index 84cca6b293..6fd8c5fe1e 100644 --- a/lib/modules/manager/buildkite/extract.spec.ts +++ b/lib/modules/manager/buildkite/extract.spec.ts @@ -7,31 +7,37 @@ describe('modules/manager/buildkite/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts simple single plugin', () => { const res = extractPackageFile(Fixtures.get('pipeline1.yml')).deps; expect(res).toMatchSnapshot(); expect(res).toHaveLength(1); }); + it('extracts multiple plugins in same file', () => { const res = extractPackageFile(Fixtures.get('pipeline2.yml')).deps; expect(res).toMatchSnapshot(); expect(res).toHaveLength(2); }); + it('adds skipReason', () => { const res = extractPackageFile(Fixtures.get('pipeline3.yml')).deps; expect(res).toMatchSnapshot(); expect(res).toHaveLength(2); }); + it('extracts arrays of plugins', () => { const res = extractPackageFile(Fixtures.get('pipeline4.yml')).deps; expect(res).toMatchSnapshot(); expect(res).toHaveLength(4); }); + it('extracts git-based plugins', () => { const res = extractPackageFile(Fixtures.get('pipeline5.yml')).deps; expect(res).toMatchSnapshot(); expect(res).toHaveLength(2); }); + it('extracts git-based plugin with .git at the end of its name', () => { const expectedPackageDependency: PackageDependency = { currentValue: 'v3.2.7', diff --git a/lib/modules/manager/bundler/artifacts.spec.ts b/lib/modules/manager/bundler/artifacts.spec.ts index 6021eacc9a..6ee5333d3e 100644 --- a/lib/modules/manager/bundler/artifacts.spec.ts +++ b/lib/modules/manager/bundler/artifacts.spec.ts @@ -56,9 +56,11 @@ describe('modules/manager/bundler/artifacts', () => { GlobalConfig.set(adminConfig); fs.ensureCacheDir.mockResolvedValue('/tmp/cache/others/gem'); }); + afterEach(() => { GlobalConfig.reset(); }); + it('returns null by default', async () => { expect( await updateArtifacts({ @@ -69,6 +71,7 @@ describe('modules/manager/bundler/artifacts', () => { }) ).toBeNull(); }); + it('returns null if Gemfile.lock was not changed', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); @@ -87,6 +90,7 @@ describe('modules/manager/bundler/artifacts', () => { ).toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('works for default binarySource', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); @@ -106,6 +110,7 @@ describe('modules/manager/bundler/artifacts', () => { ).toEqual([updatedGemfileLock]); expect(execSnapshots).toMatchSnapshot(); }); + it('works explicit global binarySource', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); @@ -126,6 +131,7 @@ describe('modules/manager/bundler/artifacts', () => { ).toEqual([updatedGemfileLock]); expect(execSnapshots).toMatchSnapshot(); }); + describe('Docker', () => { beforeEach(() => { GlobalConfig.set({ @@ -133,6 +139,7 @@ describe('modules/manager/bundler/artifacts', () => { binarySource: 'docker', }); }); + it('.ruby-version', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); @@ -162,6 +169,7 @@ describe('modules/manager/bundler/artifacts', () => { ).toEqual([updatedGemfileLock]); expect(execSnapshots).toMatchSnapshot(); }); + it('constraints options', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); @@ -197,6 +205,7 @@ describe('modules/manager/bundler/artifacts', () => { ).toEqual([updatedGemfileLock]); expect(execSnapshots).toMatchSnapshot(); }); + it('invalid constraints options', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); @@ -439,6 +448,7 @@ describe('modules/manager/bundler/artifacts', () => { ]); expect(execSnapshots).toMatchSnapshot(); }); + it('performs lockFileMaintenance', async () => { fs.readLocalFile.mockResolvedValueOnce('Current Gemfile.lock'); fs.writeLocalFile.mockResolvedValueOnce(null as never); diff --git a/lib/modules/manager/bundler/extract.spec.ts b/lib/modules/manager/bundler/extract.spec.ts index 754619b6ea..834dbd66dc 100644 --- a/lib/modules/manager/bundler/extract.spec.ts +++ b/lib/modules/manager/bundler/extract.spec.ts @@ -36,6 +36,7 @@ describe('modules/manager/bundler/extract', () => { it('returns null for empty', async () => { expect(await extractPackageFile('nothing here', 'Gemfile')).toBeNull(); }); + it('parses rails Gemfile', async () => { fs.readLocalFile.mockResolvedValueOnce(railsGemfileLock); const res = await extractPackageFile(railsGemfile, 'Gemfile'); @@ -54,11 +55,13 @@ describe('modules/manager/bundler/extract', () => { ).toBeTrue(); validateGems(railsGemfile, res); }); + it('parses sourceGroups', async () => { const res = await extractPackageFile(sourceGroupGemfile, 'Gemfile'); expect(res).toMatchSnapshot(); validateGems(sourceGroupGemfile, res); }); + it('parse webpacker Gemfile', async () => { fs.readLocalFile.mockResolvedValueOnce(webPackerGemfileLock); const res = await extractPackageFile(webPackerGemfile, 'Gemfile'); @@ -72,6 +75,7 @@ describe('modules/manager/bundler/extract', () => { ).toBeTrue(); validateGems(webPackerGemfile, res); }); + it('parse mastodon Gemfile', async () => { fs.readLocalFile.mockResolvedValueOnce(mastodonGemfileLock); const res = await extractPackageFile(mastodonGemfile, 'Gemfile'); @@ -89,6 +93,7 @@ describe('modules/manager/bundler/extract', () => { ).toBeTrue(); validateGems(mastodonGemfile, res); }); + it('parse Ruby CI Gemfile', async () => { fs.readLocalFile.mockResolvedValueOnce(rubyCIGemfileLock); const res = await extractPackageFile(rubyCIGemfile, 'Gemfile'); @@ -103,6 +108,7 @@ describe('modules/manager/bundler/extract', () => { validateGems(rubyCIGemfile, res); }); }); + it('parse Gitlab Foss Gemfile', async () => { fs.readLocalFile.mockResolvedValueOnce(gitlabFossGemfileLock); const res = await extractPackageFile(gitlabFossGemfile, 'Gemfile'); @@ -122,6 +128,7 @@ describe('modules/manager/bundler/extract', () => { const res = await extractPackageFile(sourceBlockGemfile, 'Gemfile'); expect(res).toMatchSnapshot(); }); + it('parse source blocks with spaces in Gemfile', async () => { fs.readLocalFile.mockResolvedValueOnce(sourceBlockWithNewLinesGemfileLock); const res = await extractPackageFile( diff --git a/lib/modules/manager/bundler/host-rules.spec.ts b/lib/modules/manager/bundler/host-rules.spec.ts index 8ba4548084..943bca95e2 100644 --- a/lib/modules/manager/bundler/host-rules.spec.ts +++ b/lib/modules/manager/bundler/host-rules.spec.ts @@ -10,6 +10,7 @@ describe('modules/manager/bundler/host-rules', () => { beforeEach(() => { clear(); }); + describe('getAuthenticationHeaderValue()', () => { it('returns the authentication header with the password', () => { expect( @@ -19,6 +20,7 @@ describe('modules/manager/bundler/host-rules', () => { }) ).toBe('test:password'); }); + it('returns the authentication header with the token', () => { expect( getAuthenticationHeaderValue({ @@ -27,6 +29,7 @@ describe('modules/manager/bundler/host-rules', () => { ).toBe('token'); }); }); + describe('findAllAuthenticatable()', () => { let hostRule: HostRule; @@ -39,6 +42,7 @@ describe('modules/manager/bundler/host-rules', () => { token: 'token', }; }); + it('returns an empty array if matchHost is missing', () => { delete hostRule.matchHost; add(hostRule); @@ -46,6 +50,7 @@ describe('modules/manager/bundler/host-rules', () => { findAllAuthenticatable({ hostType: 'nuget' } as any) ).toBeEmptyArray(); }); + it('returns an empty array if username is missing and password is present', () => { delete hostRule.username; delete hostRule.token; @@ -55,6 +60,7 @@ describe('modules/manager/bundler/host-rules', () => { findAllAuthenticatable({ hostType: 'nuget' } as any) ).toBeEmptyArray(); }); + it('returns an empty array if password and token are missing', () => { delete hostRule.password; delete hostRule.token; @@ -64,6 +70,7 @@ describe('modules/manager/bundler/host-rules', () => { findAllAuthenticatable({ hostType: 'nuget' } as any) ).toBeEmptyArray(); }); + it('returns the hostRule if using matchHost and password', () => { delete hostRule.token; @@ -72,6 +79,7 @@ describe('modules/manager/bundler/host-rules', () => { findAllAuthenticatable({ hostType: 'nuget' } as any) ).toMatchObject([hostRule]); }); + it('returns the hostRule if using matchHost and token', () => { delete hostRule.password; @@ -80,6 +88,7 @@ describe('modules/manager/bundler/host-rules', () => { findAllAuthenticatable({ hostType: 'nuget' } as any) ).toMatchObject([hostRule]); }); + it('returns the hostRule if using baseUrl and password', () => { hostRule.matchHost = 'https://nuget.com'; @@ -88,6 +97,7 @@ describe('modules/manager/bundler/host-rules', () => { findAllAuthenticatable({ hostType: 'nuget' } as any) ).toMatchObject([hostRule]); }); + it('returns the hostRule if using baseUrl and token', () => { hostRule.matchHost = 'https://nuget.com'; diff --git a/lib/modules/manager/bundler/locked-version.spec.ts b/lib/modules/manager/bundler/locked-version.spec.ts index fdc5020d27..3843a3a150 100644 --- a/lib/modules/manager/bundler/locked-version.spec.ts +++ b/lib/modules/manager/bundler/locked-version.spec.ts @@ -13,21 +13,25 @@ describe('modules/manager/bundler/locked-version', () => { expect(parsedLockEntries.size).toBe(185); expect(parsedLockEntries).toMatchSnapshot(); }); + test('Parse WebPacker Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(webPackerGemfileLock); expect(parsedLockEntries.size).toBe(53); expect(parsedLockEntries).toMatchSnapshot(); }); + test('Parse Mastodon Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(mastodonGemfileLock); expect(parsedLockEntries.size).toBe(266); expect(parsedLockEntries).toMatchSnapshot(); }); + test('Parse Ruby CI Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(rubyCIGemfileLock); expect(parsedLockEntries.size).toBe(64); expect(parsedLockEntries).toMatchSnapshot(); }); + test('Parse Gitlab Foss Gem Lock File', () => { const parsedLockEntries = extractLockFileEntries(gitlabFossGemfileLock); expect(parsedLockEntries.size).toBe(478); diff --git a/lib/modules/manager/bundler/range.spec.ts b/lib/modules/manager/bundler/range.spec.ts index 2461376b01..13c1de9937 100644 --- a/lib/modules/manager/bundler/range.spec.ts +++ b/lib/modules/manager/bundler/range.spec.ts @@ -7,6 +7,7 @@ describe('modules/manager/bundler/range', () => { const config: RangeConfig = { rangeStrategy: 'auto' }; expect(getRangeStrategy(config)).toBe('replace'); }); + it('returns the config value when rangeStrategy is different than auto', () => { const config: RangeConfig = { rangeStrategy: 'update-lockfile' }; expect(getRangeStrategy(config)).toBe('update-lockfile'); diff --git a/lib/modules/manager/bundler/update-locked.spec.ts b/lib/modules/manager/bundler/update-locked.spec.ts index fc188d173f..92840526f8 100644 --- a/lib/modules/manager/bundler/update-locked.spec.ts +++ b/lib/modules/manager/bundler/update-locked.spec.ts @@ -13,6 +13,7 @@ describe('modules/manager/bundler/update-locked', () => { }; expect(updateLockedDependency(config).status).toBe('already-updated'); }); + it('returns unsupported', () => { const config: UpdateLockedConfig = { lockFileContent, diff --git a/lib/modules/manager/cargo/artifacts.spec.ts b/lib/modules/manager/cargo/artifacts.spec.ts index 97777ca6a8..818e39e45d 100644 --- a/lib/modules/manager/cargo/artifacts.spec.ts +++ b/lib/modules/manager/cargo/artifacts.spec.ts @@ -29,6 +29,7 @@ describe('modules/manager/cargo/artifacts', () => { GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); + afterEach(() => { GlobalConfig.reset(); }); @@ -195,6 +196,7 @@ describe('modules/manager/cargo/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('catches errors', async () => { fs.stat.mockResolvedValueOnce({ name: 'Cargo.lock' } as any); fs.findLocalSiblingOrParent.mockResolvedValueOnce('Cargo.lock'); diff --git a/lib/modules/manager/cargo/extract.spec.ts b/lib/modules/manager/cargo/extract.spec.ts index 42723d2ccb..896ca48afc 100644 --- a/lib/modules/manager/cargo/extract.spec.ts +++ b/lib/modules/manager/cargo/extract.spec.ts @@ -31,58 +31,69 @@ describe('modules/manager/cargo/extract', () => { GlobalConfig.set(adminConfig); }); + afterEach(async () => { await tmpDir.cleanup(); GlobalConfig.reset(); }); + it('returns null for invalid toml', async () => { expect( await extractPackageFile('invalid toml', 'Cargo.toml', config) ).toBeNull(); }); + it('returns null for empty dependencies', async () => { const cargotoml = '[dependencies]\n'; expect( await extractPackageFile(cargotoml, 'Cargo.toml', config) ).toBeNull(); }); + it('returns null for empty dev-dependencies', async () => { const cargotoml = '[dev-dependencies]\n'; expect( await extractPackageFile(cargotoml, 'Cargo.toml', config) ).toBeNull(); }); + it('returns null for empty custom target', async () => { const cargotoml = '[target."foo".dependencies]\n'; expect( await extractPackageFile(cargotoml, 'Cargo.toml', config) ).toBeNull(); }); + it('extracts multiple dependencies simple', async () => { const res = await extractPackageFile(cargo1toml, 'Cargo.toml', config); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(15); }); + it('extracts multiple dependencies advanced', async () => { const res = await extractPackageFile(cargo2toml, 'Cargo.toml', config); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(18 + 6 + 1); }); + it('handles inline tables', async () => { const res = await extractPackageFile(cargo3toml, 'Cargo.toml', config); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(8); }); + it('handles standard tables', async () => { const res = await extractPackageFile(cargo4toml, 'Cargo.toml', config); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(6); }); + it('extracts platform specific dependencies', async () => { const res = await extractPackageFile(cargo5toml, 'Cargo.toml', config); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(4); }); + it('extracts registry urls from .cargo/config.toml', async () => { await writeLocalFile('.cargo/config.toml', cargo6configtoml); const res = await extractPackageFile(cargo6toml, 'Cargo.toml', { @@ -91,6 +102,7 @@ describe('modules/manager/cargo/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(3); }); + it('extracts registry urls from .cargo/config (legacy path)', async () => { await writeLocalFile('.cargo/config', cargo6configtoml); const res = await extractPackageFile(cargo6toml, 'Cargo.toml', { @@ -99,6 +111,7 @@ describe('modules/manager/cargo/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(3); }); + it('skips unknown registries', async () => { const cargotoml = '[dependencies]\nfoobar = { version = "0.1.0", registry = "not-listed" }'; @@ -106,6 +119,7 @@ describe('modules/manager/cargo/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('fails to parse cargo config with invalid TOML', async () => { await writeLocalFile('.cargo/config', '[registries'); @@ -115,6 +129,7 @@ describe('modules/manager/cargo/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(3); }); + it('ignore cargo config registries with missing index', async () => { await writeLocalFile('.cargo/config', '[registries.mine]\nfoo = "bar"'); @@ -124,6 +139,7 @@ describe('modules/manager/cargo/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(3); }); + it('extracts original package name of renamed dependencies', async () => { const cargotoml = '[dependencies]\nboolector-solver = { package = "boolector", version = "0.4.0" }'; diff --git a/lib/modules/manager/circleci/extract.spec.ts b/lib/modules/manager/circleci/extract.spec.ts index 6e48dc7ac6..1d0ba0275a 100644 --- a/lib/modules/manager/circleci/extract.spec.ts +++ b/lib/modules/manager/circleci/extract.spec.ts @@ -10,11 +10,13 @@ describe('modules/manager/circleci/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts multiple image lines', () => { const res = extractPackageFile(file1); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(4); }); + it('extracts orbs too', () => { const res = extractPackageFile(file2); expect(res.deps).toMatchSnapshot([ @@ -44,6 +46,7 @@ describe('modules/manager/circleci/extract', () => { {}, ]); }); + it('extracts image without leading dash', () => { const res = extractPackageFile(file3); expect(res.deps).toMatchSnapshot([ diff --git a/lib/modules/manager/cloudbuild/extract.spec.ts b/lib/modules/manager/cloudbuild/extract.spec.ts index 8d51697af5..2067164916 100644 --- a/lib/modules/manager/cloudbuild/extract.spec.ts +++ b/lib/modules/manager/cloudbuild/extract.spec.ts @@ -6,6 +6,7 @@ describe('modules/manager/cloudbuild/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts multiple image lines', () => { const res = extractPackageFile(Fixtures.get('cloudbuild.yml')); expect(res.deps).toMatchSnapshot(); diff --git a/lib/modules/manager/cocoapods/artifacts.spec.ts b/lib/modules/manager/cocoapods/artifacts.spec.ts index e48566c43f..04cffde3c7 100644 --- a/lib/modules/manager/cocoapods/artifacts.spec.ts +++ b/lib/modules/manager/cocoapods/artifacts.spec.ts @@ -44,6 +44,7 @@ describe('modules/manager/cocoapods/artifacts', () => { ], }); }); + afterEach(() => { GlobalConfig.reset(); }); diff --git a/lib/modules/manager/composer/extract.spec.ts b/lib/modules/manager/composer/extract.spec.ts index 21fd08c070..97375edfd2 100644 --- a/lib/modules/manager/composer/extract.spec.ts +++ b/lib/modules/manager/composer/extract.spec.ts @@ -13,41 +13,50 @@ const requirements5Lock = loadFixture('composer5.lock'); describe('modules/manager/composer/extract', () => { describe('extractPackageFile()', () => { let packageFile; + beforeEach(() => { packageFile = 'composer.json'; }); + it('returns null for invalid json', async () => { expect(await extractPackageFile('nothing here', packageFile)).toBeNull(); }); + it('returns null for empty deps', async () => { expect(await extractPackageFile('{}', packageFile)).toBeNull(); }); + it('extracts dependencies with no lock file', async () => { const res = await extractPackageFile(requirements1, packageFile); expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(32); }); + it('extracts registryUrls', async () => { const res = await extractPackageFile(requirements2, packageFile); expect(res).toMatchSnapshot(); expect(res.registryUrls).toHaveLength(1); }); + it('extracts object registryUrls', async () => { const res = await extractPackageFile(requirements3, packageFile); expect(res).toMatchSnapshot(); expect(res.registryUrls).toHaveLength(1); }); + it('extracts repositories and registryUrls', async () => { const res = await extractPackageFile(requirements4, packageFile); expect(res).toMatchSnapshot(); expect(res.registryUrls).toHaveLength(3); }); + it('extracts object repositories and registryUrls with lock file', async () => { fs.readLocalFile.mockResolvedValue(requirements5Lock); const res = await extractPackageFile(requirements5, packageFile); expect(res).toMatchSnapshot(); expect(res.registryUrls).toHaveLength(2); }); + it('extracts dependencies with lock file', async () => { fs.readLocalFile.mockResolvedValue('some content'); const res = await extractPackageFile(requirements1, packageFile); diff --git a/lib/modules/manager/composer/range.spec.ts b/lib/modules/manager/composer/range.spec.ts index 8ce56b1036..3c5b42f495 100644 --- a/lib/modules/manager/composer/range.spec.ts +++ b/lib/modules/manager/composer/range.spec.ts @@ -6,6 +6,7 @@ describe('modules/manager/composer/range', () => { const config: RangeConfig = { rangeStrategy: 'widen' }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('pins require-dev', () => { const config: RangeConfig = { rangeStrategy: 'auto', @@ -13,6 +14,7 @@ describe('modules/manager/composer/range', () => { }; expect(getRangeStrategy(config)).toBe('pin'); }); + it('pins project require', () => { const config: RangeConfig = { rangeStrategy: 'auto', @@ -21,6 +23,7 @@ describe('modules/manager/composer/range', () => { }; expect(getRangeStrategy(config)).toBe('pin'); }); + it('widens complex ranges', () => { const config: RangeConfig = { rangeStrategy: 'auto', @@ -29,6 +32,7 @@ describe('modules/manager/composer/range', () => { }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('widens complex bump', () => { const config: RangeConfig = { rangeStrategy: 'bump', @@ -37,10 +41,12 @@ describe('modules/manager/composer/range', () => { }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('defaults to replace', () => { const config: RangeConfig = { rangeStrategy: 'auto', depType: 'require' }; expect(getRangeStrategy(config)).toBe('replace'); }); + it('defaults to widen for TYPO3 extensions', () => { const config: RangeConfig = { managerData: { diff --git a/lib/modules/manager/composer/update-locked.spec.ts b/lib/modules/manager/composer/update-locked.spec.ts index 3adcd0da5f..d797e732f9 100644 --- a/lib/modules/manager/composer/update-locked.spec.ts +++ b/lib/modules/manager/composer/update-locked.spec.ts @@ -13,6 +13,7 @@ describe('modules/manager/composer/update-locked', () => { }; expect(updateLockedDependency(config).status).toBe('already-updated'); }); + it('returns unsupported', () => { const config: UpdateLockedConfig = { lockFileContent, diff --git a/lib/modules/manager/composer/utils.spec.ts b/lib/modules/manager/composer/utils.spec.ts index 8f85601027..eda723cc40 100644 --- a/lib/modules/manager/composer/utils.spec.ts +++ b/lib/modules/manager/composer/utils.spec.ts @@ -80,6 +80,7 @@ describe('modules/manager/composer/utils', () => { ' --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); + it('disables platform requirements', () => { expect( getComposerArguments( @@ -92,6 +93,7 @@ describe('modules/manager/composer/utils', () => { ' --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); + it('disables all platform requirements with 2.1.0', () => { expect( getComposerArguments( @@ -104,6 +106,7 @@ describe('modules/manager/composer/utils', () => { ' --ignore-platform-reqs --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); + it('disables only extension and library platform requirements with 2.2.0', () => { expect( getComposerArguments( @@ -116,6 +119,7 @@ describe('modules/manager/composer/utils', () => { " --ignore-platform-req='ext-*' --ignore-platform-req='lib-*' --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins" ); }); + it('disables only extension and library platform requirements with ^2.2', () => { expect( getComposerArguments( @@ -128,6 +132,7 @@ describe('modules/manager/composer/utils', () => { " --ignore-platform-req='ext-*' --ignore-platform-req='lib-*' --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins" ); }); + it('disables single platform requirement', () => { expect( getComposerArguments( @@ -140,6 +145,7 @@ describe('modules/manager/composer/utils', () => { ' --ignore-platform-req ext-intl --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); + it('disables multiple platform requirement', () => { expect( getComposerArguments( @@ -152,6 +158,7 @@ describe('modules/manager/composer/utils', () => { ' --ignore-platform-req ext-intl --ignore-platform-req ext-icu --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); + it('allows scripts when configured', () => { GlobalConfig.set({ allowScripts: true, @@ -160,6 +167,7 @@ describe('modules/manager/composer/utils', () => { getComposerArguments({}, { toolName: 'composer', constraint: '1.*' }) ).toBe(' --no-ansi --no-interaction --no-plugins'); }); + it('disables scripts when configured locally', () => { GlobalConfig.set({ allowScripts: true, @@ -175,6 +183,7 @@ describe('modules/manager/composer/utils', () => { ' --no-ansi --no-interaction --no-scripts --no-autoloader --no-plugins' ); }); + it('allows plugins when configured', () => { GlobalConfig.set({ allowPlugins: true, @@ -183,6 +192,7 @@ describe('modules/manager/composer/utils', () => { getComposerArguments({}, { toolName: 'composer', constraint: '1.*' }) ).toBe(' --no-ansi --no-interaction --no-scripts --no-autoloader'); }); + it('disables plugins when configured locally', () => { GlobalConfig.set({ allowPlugins: true, diff --git a/lib/modules/manager/conan/extract.spec.ts b/lib/modules/manager/conan/extract.spec.ts index b24ae08cf5..c988c8ef6a 100644 --- a/lib/modules/manager/conan/extract.spec.ts +++ b/lib/modules/manager/conan/extract.spec.ts @@ -10,6 +10,7 @@ describe('modules/manager/conan/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts multiple image lines from conanfile.txt', () => { const res = extractPackageFile(conanfile1); expect(res?.deps).toEqual([ @@ -87,10 +88,12 @@ describe('modules/manager/conan/extract', () => { }, ]); }); + it('extracts multiple 0 lines from conanfile.txt', () => { const res = extractPackageFile(conanfile2); expect(res).toBeNull(); }); + it('extracts multiple image lines from conanfile.py', () => { const res = extractPackageFile(conanfile3); expect(res?.deps).toEqual([ diff --git a/lib/modules/manager/docker-compose/extract.spec.ts b/lib/modules/manager/docker-compose/extract.spec.ts index 85a40de787..ebfb2708da 100644 --- a/lib/modules/manager/docker-compose/extract.spec.ts +++ b/lib/modules/manager/docker-compose/extract.spec.ts @@ -11,27 +11,33 @@ describe('modules/manager/docker-compose/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('')).toBeNull(); }); + it('returns null for non-object YAML', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('returns null for malformed YAML', () => { expect(extractPackageFile('nothing here\n:::::::')).toBeNull(); }); + it('extracts multiple image lines for version 1', () => { const res = extractPackageFile(yamlFile1); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(8); }); + it('extracts multiple image lines for version 3', () => { const res = extractPackageFile(yamlFile3); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(8); }); + it('extracts multiple image lines for version 3 without set version key', () => { const res = extractPackageFile(yamlFile3NoVersion); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(8); }); + it('extracts default variable values for version 3', () => { const res = extractPackageFile(yamlFile3DefaultValue); expect(res.deps).toMatchInlineSnapshot(` diff --git a/lib/modules/manager/dockerfile/extract.spec.ts b/lib/modules/manager/dockerfile/extract.spec.ts index 37a6514ee4..9f64b74af4 100644 --- a/lib/modules/manager/dockerfile/extract.spec.ts +++ b/lib/modules/manager/dockerfile/extract.spec.ts @@ -613,6 +613,7 @@ describe('modules/manager/dockerfile/extract', () => { `); }); }); + describe('getDep()', () => { it('rejects null', () => { expect(getDep(null)).toEqual({ skipReason: 'invalid-value' }); diff --git a/lib/modules/manager/flux/extract.spec.ts b/lib/modules/manager/flux/extract.spec.ts index 2956dc2251..ced3bd438b 100644 --- a/lib/modules/manager/flux/extract.spec.ts +++ b/lib/modules/manager/flux/extract.spec.ts @@ -29,6 +29,7 @@ describe('modules/manager/flux/extract', () => { ], }); }); + it('extracts version and components from system manifests', () => { const result = extractPackageFile( loadFixture('system.yaml'), @@ -48,6 +49,7 @@ describe('modules/manager/flux/extract', () => { ], }); }); + it('considers components optional in system manifests', () => { const result = extractPackageFile( `# Flux Version: v0.27.0`, @@ -55,6 +57,7 @@ describe('modules/manager/flux/extract', () => { ); expect(result.deps[0].managerData.components).toBeUndefined(); }); + it('ignores system manifests without a version', () => { const result = extractPackageFile( 'not actually a system manifest!', @@ -62,6 +65,7 @@ describe('modules/manager/flux/extract', () => { ); expect(result).toBeNull(); }); + it('extracts releases without repositories', () => { const result = extractPackageFile( loadFixture('release.yaml'), @@ -69,14 +73,17 @@ describe('modules/manager/flux/extract', () => { ); expect(result.deps[0].skipReason).toBe('unknown-registry'); }); + it('ignores HelmRelease resources without an apiVersion', () => { const result = extractPackageFile('kind: HelmRelease', 'test.yaml'); expect(result).toBeNull(); }); + it('ignores HelmRepository resources without an apiVersion', () => { const result = extractPackageFile('kind: HelmRepository', 'test.yaml'); expect(result).toBeNull(); }); + it('ignores HelmRepository resources without metadata', () => { const result = extractPackageFile( `${loadFixture('release.yaml')} @@ -88,6 +95,7 @@ kind: HelmRepository ); expect(result.deps[0].skipReason).toBe('unknown-registry'); }); + it('ignores HelmRelease resources without a chart name', () => { const result = extractPackageFile( `apiVersion: helm.toolkit.fluxcd.io/v2beta1 @@ -107,6 +115,7 @@ spec: ); expect(result).toBeNull(); }); + it('does not match HelmRelease resources without a namespace to HelmRepository resources without a namespace', () => { const result = extractPackageFile( `apiVersion: source.toolkit.fluxcd.io/v1beta1 @@ -131,6 +140,7 @@ spec: ); expect(result.deps[0].skipReason).toBe('unknown-registry'); }); + it('does not match HelmRelease resources without a sourceRef', () => { const result = extractPackageFile( `${loadFixture('source.yaml')} @@ -149,6 +159,7 @@ spec: ); expect(result.deps[0].skipReason).toBe('unknown-registry'); }); + it('does not match HelmRelease resources without a namespace', () => { const result = extractPackageFile( `${loadFixture('source.yaml')} @@ -168,6 +179,7 @@ spec: ); expect(result.deps[0].skipReason).toBe('unknown-registry'); }); + it('ignores HelmRepository resources without a namespace', () => { const result = extractPackageFile( `${loadFixture('release.yaml')} @@ -181,6 +193,7 @@ metadata: ); expect(result.deps[0].skipReason).toBe('unknown-registry'); }); + it('ignores HelmRepository resources without a URL', () => { const result = extractPackageFile( `${loadFixture('release.yaml')} @@ -195,6 +208,7 @@ metadata: ); expect(result.deps[0].skipReason).toBe('unknown-registry'); }); + it('ignores resources of an unknown kind', () => { const result = extractPackageFile( `kind: SomethingElse @@ -203,6 +217,7 @@ apiVersion: helm.toolkit.fluxcd.io/v2beta1`, ); expect(result).toBeNull(); }); + it('ignores resources without a kind', () => { const result = extractPackageFile( 'apiVersion: helm.toolkit.fluxcd.io/v2beta1', @@ -210,10 +225,12 @@ apiVersion: helm.toolkit.fluxcd.io/v2beta1`, ); expect(result).toBeNull(); }); + it('ignores bad manifests', () => { const result = extractPackageFile('"bad YAML', 'test.yaml'); expect(result).toBeNull(); }); + it('ignores null resources', () => { const result = extractPackageFile('null', 'test.yaml'); expect(result).toBeNull(); @@ -240,6 +257,7 @@ apiVersion: helm.toolkit.fluxcd.io/v2beta1`, }, ]); }); + it('ignores files that do not exist', async () => { const result = await extractAllPackageFiles(config, [ 'lib/modules/manager/flux/__fixtures__/bogus.yaml', diff --git a/lib/modules/manager/fvm/extract.spec.ts b/lib/modules/manager/fvm/extract.spec.ts index 4565acc447..e3b9341940 100644 --- a/lib/modules/manager/fvm/extract.spec.ts +++ b/lib/modules/manager/fvm/extract.spec.ts @@ -9,9 +9,11 @@ describe('modules/manager/fvm/extract', () => { extractPackageFile('clearly invalid json', packageFile) ).toBeNull(); }); + it('returns null for empty flutter sdk version', () => { expect(extractPackageFile('{}', packageFile)).toBeNull(); }); + it('returns null for non string flutter sdk version', () => { expect( extractPackageFile( @@ -20,6 +22,7 @@ describe('modules/manager/fvm/extract', () => { ) ).toBeNull(); }); + it('returns a result', () => { const res = extractPackageFile( '{"flutterSdkVersion": "2.10.1", "flavors": {}}', @@ -34,6 +37,7 @@ describe('modules/manager/fvm/extract', () => { }, ]); }); + it('supports non range', () => { const res = extractPackageFile( '{"flutterSdkVersion": "stable", "flavors": {}}', diff --git a/lib/modules/manager/git-submodules/artifact.spec.ts b/lib/modules/manager/git-submodules/artifact.spec.ts index b3fd8f454c..806db009b7 100644 --- a/lib/modules/manager/git-submodules/artifact.spec.ts +++ b/lib/modules/manager/git-submodules/artifact.spec.ts @@ -14,6 +14,7 @@ describe('modules/manager/git-submodules/artifact', () => { { file: { type: 'addition', path: '', contents: '' } }, ]); }); + it('returns two modules', () => { expect( updateArtifacts({ diff --git a/lib/modules/manager/git-submodules/extract.spec.ts b/lib/modules/manager/git-submodules/extract.spec.ts index f105bf257f..fd44c667e3 100644 --- a/lib/modules/manager/git-submodules/extract.spec.ts +++ b/lib/modules/manager/git-submodules/extract.spec.ts @@ -42,6 +42,7 @@ describe('modules/manager/git-submodules/extract', () => { }; }); }); + describe('extractPackageFile()', () => { it('extracts submodules', async () => { GlobalConfig.set({ localDir: `${__dirname}/__fixtures__` }); diff --git a/lib/modules/manager/git-submodules/update.spec.ts b/lib/modules/manager/git-submodules/update.spec.ts index a14df2c962..e3b5fb3ff4 100644 --- a/lib/modules/manager/git-submodules/update.spec.ts +++ b/lib/modules/manager/git-submodules/update.spec.ts @@ -14,6 +14,7 @@ describe('modules/manager/git-submodules/update', () => { let upgrade: Upgrade; let adminConfig: RepoGlobalConfig; let tmpDir: DirectoryResult; + beforeAll(async () => { upgrade = { depName: 'renovate' }; @@ -21,10 +22,12 @@ describe('modules/manager/git-submodules/update', () => { adminConfig = { localDir: join(tmpDir.path) }; GlobalConfig.set(adminConfig); }); + afterAll(async () => { await tmpDir.cleanup(); GlobalConfig.reset(); }); + it('returns null on error', async () => { simpleGit.mockReturnValue({ submoduleUpdate() { @@ -37,6 +40,7 @@ describe('modules/manager/git-submodules/update', () => { }); expect(update).toBeNull(); }); + it('returns content on update', async () => { simpleGit.mockReturnValue({ submoduleUpdate() { diff --git a/lib/modules/manager/github-actions/extract.spec.ts b/lib/modules/manager/github-actions/extract.spec.ts index 625b93e373..a3f129c54b 100644 --- a/lib/modules/manager/github-actions/extract.spec.ts +++ b/lib/modules/manager/github-actions/extract.spec.ts @@ -6,11 +6,13 @@ describe('modules/manager/github-actions/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts multiple docker image lines from yaml configuration file', () => { const res = extractPackageFile(Fixtures.get('workflow_1.yml')); expect(res.deps).toMatchSnapshot(); expect(res.deps.filter((d) => d.datasource === 'docker')).toHaveLength(2); }); + it('extracts multiple action tag lines from yaml configuration file', () => { const res = extractPackageFile(Fixtures.get('workflow_2.yml')); expect(res.deps).toMatchSnapshot(); @@ -18,6 +20,7 @@ describe('modules/manager/github-actions/extract', () => { res.deps.filter((d) => d.datasource === 'github-tags') ).toHaveLength(8); }); + it('extracts multiple action tag lines with double quotes and comments', () => { const res = extractPackageFile(Fixtures.get('workflow_3.yml')); expect(res.deps).toMatchSnapshot([ diff --git a/lib/modules/manager/gitlabci-include/extract.spec.ts b/lib/modules/manager/gitlabci-include/extract.spec.ts index 7ca3a93bb3..201ff682a6 100644 --- a/lib/modules/manager/gitlabci-include/extract.spec.ts +++ b/lib/modules/manager/gitlabci-include/extract.spec.ts @@ -12,6 +12,7 @@ describe('modules/manager/gitlabci-include/extract', () => { extractPackageFile('nothing here', '.gitlab-ci.yml', {}) ).toBeNull(); }); + it('returns null for include block without any actual includes', () => { const res = extractPackageFile( yamlWithEmptyIncludeConfig, @@ -20,6 +21,7 @@ describe('modules/manager/gitlabci-include/extract', () => { ); expect(res).toBeNull(); }); + it('extracts single include block', () => { const res = extractPackageFile( yamlFileSingleConfig, @@ -29,11 +31,13 @@ describe('modules/manager/gitlabci-include/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('extracts multiple include blocks', () => { const res = extractPackageFile(yamlFileMultiConfig, '.gitlab-ci.yml', {}); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(3); }); + it('normalizes configured endpoints', () => { const endpoints = [ 'http://gitlab.test/api/v4', diff --git a/lib/modules/manager/gitlabci/extract.spec.ts b/lib/modules/manager/gitlabci/extract.spec.ts index 2eedb77994..b3f3b5a72d 100644 --- a/lib/modules/manager/gitlabci/extract.spec.ts +++ b/lib/modules/manager/gitlabci/extract.spec.ts @@ -160,6 +160,7 @@ describe('modules/manager/gitlabci/extract', () => { }, ]); }); + it('extracts from image', () => { let expectedRes = { autoReplaceStringTemplate: @@ -214,6 +215,7 @@ describe('modules/manager/gitlabci/extract', () => { extractFromServices([{ name: 'image:test' }, { name: 'image2:test2' }]) ).toEqual(expectedRes); }); + it('extracts from job object', () => { const expectedRes = [ { diff --git a/lib/modules/manager/gomod/artifacts.spec.ts b/lib/modules/manager/gomod/artifacts.spec.ts index 3936ce9ab8..d32eabb6bc 100644 --- a/lib/modules/manager/gomod/artifacts.spec.ts +++ b/lib/modules/manager/gomod/artifacts.spec.ts @@ -62,6 +62,7 @@ describe('modules/manager/gomod/artifacts', () => { GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); + afterEach(() => { GlobalConfig.reset(); }); diff --git a/lib/modules/manager/gomod/extract.spec.ts b/lib/modules/manager/gomod/extract.spec.ts index 57e7d3b603..42d332dfe5 100644 --- a/lib/modules/manager/gomod/extract.spec.ts +++ b/lib/modules/manager/gomod/extract.spec.ts @@ -10,6 +10,7 @@ describe('modules/manager/gomod/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts single-line requires', () => { const res = extractPackageFile(gomod1).deps; expect(res).toMatchSnapshot(); @@ -17,11 +18,13 @@ describe('modules/manager/gomod/extract', () => { expect(res.filter((e) => e.skipReason)).toHaveLength(1); expect(res.filter((e) => e.depType === 'replace')).toHaveLength(1); }); + it('extracts constraints', () => { const res = extractPackageFile(gomod3); expect(res).toMatchSnapshot(); expect(res.constraints.go).toBe('^1.13'); }); + it('extracts multi-line requires', () => { const res = extractPackageFile(gomod2).deps; expect(res).toMatchSnapshot(); diff --git a/lib/modules/manager/gomod/update.spec.ts b/lib/modules/manager/gomod/update.spec.ts index ebe31cba2c..0142d85ee9 100644 --- a/lib/modules/manager/gomod/update.spec.ts +++ b/lib/modules/manager/gomod/update.spec.ts @@ -18,6 +18,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod1); expect(res).toContain(upgrade.newValue); }); + it('replaces two values in one file', () => { const upgrade1 = { depName: 'github.com/pkg/errors', @@ -44,6 +45,7 @@ describe('modules/manager/gomod/update', () => { expect(res2).not.toEqual(res1); expect(res2).toMatchSnapshot(); }); + it('returns same', () => { const upgrade = { depName: 'github.com/pkg/errors', @@ -53,6 +55,7 @@ describe('modules/manager/gomod/update', () => { const res = updateDependency({ fileContent: gomod1, upgrade }); expect(res).toEqual(gomod1); }); + it('bumps major v0 > v1', () => { const upgrade = { depName: 'github.com/pkg/errors', @@ -67,6 +70,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod1); expect(res).toContain('github.com/pkg/errors v1.0.0'); }); + it('replaces major updates > 1', () => { const upgrade = { depName: 'github.com/pkg/errors', @@ -81,6 +85,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod2); expect(res).toContain('github.com/pkg/errors/v2 v2.0.0'); }); + it('replaces major gopkg.in updates', () => { const upgrade = { depName: 'gopkg.in/russross/blackfriday.v1', @@ -96,6 +101,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod2); expect(res).toContain('gopkg.in/russross/blackfriday.v2 v2.0.0'); }); + it('returns null if mismatch', () => { const upgrade = { depName: 'github.com/aws/aws-sdk-go', @@ -105,10 +111,12 @@ describe('modules/manager/gomod/update', () => { const res = updateDependency({ fileContent: gomod1, upgrade }); expect(res).toBeNull(); }); + it('returns null if error', () => { const res = updateDependency({ fileContent: null, upgrade: null }); expect(res).toBeNull(); }); + it('replaces multiline', () => { const upgrade = { depName: 'github.com/fatih/color', @@ -120,6 +128,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod2); expect(res).toContain('github.com/fatih/color v1.8.0'); }); + it('replaces quoted multiline', () => { const upgrade = { depName: 'gopkg.in/src-d/go-billy.v4', @@ -132,6 +141,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod2); expect(res).toContain(upgrade.newValue); }); + it('replaces major multiline', () => { const upgrade = { depName: 'github.com/emirpasic/gods', @@ -146,6 +156,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod2); expect(res).toContain('github.com/emirpasic/gods/v2 v2.0.0'); }); + it('bumps major multiline', () => { const upgrade = { depName: 'github.com/src-d/gcfg/v2', @@ -160,6 +171,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod2); expect(res).toContain('github.com/src-d/gcfg/v3 v3.0.0'); }); + it('bumps major v0 > v1 multiline', () => { const upgrade = { depName: 'golang.org/x/text', @@ -174,6 +186,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod2); expect(res).toContain('golang.org/x/text v1.0.0'); }); + it('update multiline digest', () => { const upgrade = { depName: 'github.com/spf13/jwalterweatherman', @@ -188,6 +201,7 @@ describe('modules/manager/gomod/update', () => { expect(res).toContain('github.com/spf13/jwalterweatherman 123456123456'); expect(res).toContain(upgrade.newDigest.substring(0, 12)); }); + it('skips already-updated multiline digest', () => { const upgrade = { depName: 'github.com/spf13/jwalterweatherman', @@ -200,6 +214,7 @@ describe('modules/manager/gomod/update', () => { const res = updateDependency({ fileContent: gomod2, upgrade }); expect(res).toEqual(gomod2); }); + it('handles multiline mismatch', () => { const upgrade = { depName: 'github.com/fatih/color', @@ -210,6 +225,7 @@ describe('modules/manager/gomod/update', () => { const res = updateDependency({ fileContent: gomod2, upgrade }); expect(res).toBeNull(); }); + it('handles +incompatible tag', () => { const upgrade = { depName: 'github.com/Azure/azure-sdk-for-go', @@ -224,6 +240,7 @@ describe('modules/manager/gomod/update', () => { 'github.com/Azure/azure-sdk-for-go v26.0.0+incompatible' ); }); + it('handles replace line with minor version update', () => { const upgrade = { depName: 'github.com/pravesht/gocql', @@ -235,6 +252,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod1); expect(res).toContain('github.com/pravesht/gocql v0.0.1'); }); + it('handles replace line with major version update', () => { const upgrade = { depName: 'github.com/pravesht/gocql', @@ -249,6 +267,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod1); expect(res).toContain('github.com/pravesht/gocql/v2 v2.0.0'); }); + it('handles replace line with digest', () => { const upgrade = { depName: 'github.com/pravesht/gocql', @@ -265,6 +284,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod1); expect(res).toContain(upgrade.newDigest.substring(0, 12)); }); + it('handles no pinned version to latest available version', () => { const upgrade = { depName: 'github.com/caarlos0/env', @@ -279,6 +299,7 @@ describe('modules/manager/gomod/update', () => { expect(res).not.toEqual(gomod1); expect(res).toContain('github.com/caarlos0/env/v6 v6.1.0'); }); + it('should return null for replacement', () => { const res = updateDependency({ fileContent: undefined, diff --git a/lib/modules/manager/helm-requirements/extract.spec.ts b/lib/modules/manager/helm-requirements/extract.spec.ts index 6f884caefb..657f64dc6f 100644 --- a/lib/modules/manager/helm-requirements/extract.spec.ts +++ b/lib/modules/manager/helm-requirements/extract.spec.ts @@ -65,6 +65,7 @@ describe('modules/manager/helm-requirements/extract', () => { expect(result).toMatchSnapshot(); expect(result.deps.every((dep) => dep.skipReason)).toBe(true); }); + it('parses simple requirements.yaml correctly', () => { fs.readLocalFile.mockResolvedValueOnce(` apiVersion: v1 @@ -96,6 +97,7 @@ describe('modules/manager/helm-requirements/extract', () => { ], }); }); + it('parses simple requirements.yaml but skips if necessary fields missing', () => { fs.readLocalFile.mockResolvedValueOnce(` apiVersion: v1 @@ -111,6 +113,7 @@ describe('modules/manager/helm-requirements/extract', () => { }); expect(result).toBeNull(); }); + it('resolves aliased registry urls', () => { fs.readLocalFile.mockResolvedValueOnce(` apiVersion: v1 @@ -139,6 +142,7 @@ describe('modules/manager/helm-requirements/extract', () => { expect(result).toMatchSnapshot(); expect(result.deps.every((dep) => dep.skipReason)).toBe(false); }); + it('skips local dependencies', () => { fs.readLocalFile.mockResolvedValueOnce(` apiVersion: v1 @@ -169,6 +173,7 @@ describe('modules/manager/helm-requirements/extract', () => { ], }); }); + it('returns null if no dependencies', () => { fs.readLocalFile.mockResolvedValueOnce(` apiVersion: v1 @@ -188,6 +193,7 @@ describe('modules/manager/helm-requirements/extract', () => { }); expect(result).toBeNull(); }); + it('returns null if requirements.yaml is invalid', () => { fs.readLocalFile.mockResolvedValueOnce(` apiVersion: v1 @@ -209,6 +215,7 @@ describe('modules/manager/helm-requirements/extract', () => { }); expect(result).toBeNull(); }); + it('returns null if Chart.yaml is empty', () => { const content = ''; const fileName = 'requirements.yaml'; @@ -287,6 +294,7 @@ describe('modules/manager/helm-requirements/extract', () => { expect(result).toEqual(params.want); }); }); + it('skips only invalid dependences', () => { fs.readLocalFile.mockResolvedValueOnce(` apiVersion: v1 diff --git a/lib/modules/manager/helm-values/extract.spec.ts b/lib/modules/manager/helm-values/extract.spec.ts index e9546024c2..9ab2ef5afc 100644 --- a/lib/modules/manager/helm-values/extract.spec.ts +++ b/lib/modules/manager/helm-values/extract.spec.ts @@ -14,18 +14,22 @@ describe('modules/manager/helm-values/extract', () => { beforeEach(() => { jest.resetAllMocks(); }); + it('returns null for invalid yaml file content', () => { const result = extractPackageFile('nothing here: ['); expect(result).toBeNull(); }); + it('returns null for empty yaml file content', () => { const result = extractPackageFile(''); expect(result).toBeNull(); }); + it('returns null for no file content', () => { const result = extractPackageFile(null); expect(result).toBeNull(); }); + it('extracts from values.yaml correctly with same structure as "helm create"', () => { const result = extractPackageFile(helmDefaultChartInitValues); expect(result).toMatchSnapshot({ @@ -37,6 +41,7 @@ describe('modules/manager/helm-values/extract', () => { ], }); }); + it('extracts from complex values file correctly"', () => { const result = extractPackageFile(helmMultiAndNestedImageValues); expect(result).toMatchSnapshot(); diff --git a/lib/modules/manager/helmv3/artifacts.spec.ts b/lib/modules/manager/helmv3/artifacts.spec.ts index 217fdd9f4d..715343be12 100644 --- a/lib/modules/manager/helmv3/artifacts.spec.ts +++ b/lib/modules/manager/helmv3/artifacts.spec.ts @@ -42,6 +42,7 @@ describe('modules/manager/helmv3/artifacts', () => { docker.resetPrefetchedImages(); hostRules.clear(); }); + afterEach(() => { GlobalConfig.reset(); }); diff --git a/lib/modules/manager/helmv3/extract.spec.ts b/lib/modules/manager/helmv3/extract.spec.ts index bb02c1cd89..9cb3978559 100644 --- a/lib/modules/manager/helmv3/extract.spec.ts +++ b/lib/modules/manager/helmv3/extract.spec.ts @@ -10,6 +10,7 @@ describe('modules/manager/helmv3/extract', () => { jest.resetAllMocks(); fs.readLocalFile = jest.fn(); }); + it('skips invalid registry urls', async () => { const content = ` apiVersion: v2 @@ -37,6 +38,7 @@ describe('modules/manager/helmv3/extract', () => { expect(result).toMatchSnapshot(); expect(result.deps.every((dep) => dep.skipReason)).toBe(true); }); + it('parses simple Chart.yaml correctly', async () => { const content = ` apiVersion: v2 @@ -135,6 +137,7 @@ describe('modules/manager/helmv3/extract', () => { expect(result).toMatchSnapshot(); expect(result.deps.every((dep) => dep.skipReason)).toBe(false); }); + it("doesn't fail if Chart.yaml is invalid", async () => { const content = ` Invalid Chart.yaml content. @@ -149,6 +152,7 @@ describe('modules/manager/helmv3/extract', () => { }); expect(result).toBeNull(); }); + it('skips local dependencies', async () => { const content = ` apiVersion: v2 @@ -177,6 +181,7 @@ describe('modules/manager/helmv3/extract', () => { ], }); }); + it('returns null if no dependencies key', async () => { fs.readLocalFile.mockResolvedValueOnce(` `); @@ -196,6 +201,7 @@ describe('modules/manager/helmv3/extract', () => { }); expect(result).toBeNull(); }); + it('returns null if dependencies are an empty list', async () => { fs.readLocalFile.mockResolvedValueOnce(` `); @@ -215,6 +221,7 @@ describe('modules/manager/helmv3/extract', () => { }); expect(result).toBeNull(); }); + it('returns null if dependencies key is invalid', async () => { const content = ` apiVersion: v2 @@ -234,6 +241,7 @@ describe('modules/manager/helmv3/extract', () => { }); expect(result).toBeNull(); }); + it('returns null if Chart.yaml is empty', async () => { const content = ''; const fileName = 'Chart.yaml'; @@ -244,6 +252,7 @@ describe('modules/manager/helmv3/extract', () => { }); expect(result).toBeNull(); }); + it('returns null if Chart.yaml uses an unsupported apiVersion', async () => { const content = ` apiVersion: v1 @@ -260,6 +269,7 @@ describe('modules/manager/helmv3/extract', () => { }); expect(result).toBeNull(); }); + it('returns null if name and version are missing for all dependencies', async () => { const content = ` apiVersion: v2 diff --git a/lib/modules/manager/helmv3/update.spec.ts b/lib/modules/manager/helmv3/update.spec.ts index 2b92fec006..1dc8f0d727 100644 --- a/lib/modules/manager/helmv3/update.spec.ts +++ b/lib/modules/manager/helmv3/update.spec.ts @@ -8,6 +8,7 @@ describe('modules/manager/helmv3/update', () => { name: 'test', version: '0.0.2', }); + it('increments', () => { const { bumpedContent } = helmv3Updater.bumpPackageVersion( content, @@ -17,6 +18,7 @@ describe('modules/manager/helmv3/update', () => { const expected = content.replace('0.0.2', '0.0.3'); expect(bumpedContent).toEqual(expected); }); + it('no ops', () => { const { bumpedContent } = helmv3Updater.bumpPackageVersion( content, @@ -25,6 +27,7 @@ describe('modules/manager/helmv3/update', () => { ); expect(bumpedContent).toEqual(content); }); + it('updates', () => { const { bumpedContent } = helmv3Updater.bumpPackageVersion( content, @@ -34,6 +37,7 @@ describe('modules/manager/helmv3/update', () => { const expected = content.replace('0.0.2', '0.1.0'); expect(bumpedContent).toEqual(expected); }); + it('returns content if bumping errors', () => { const { bumpedContent } = helmv3Updater.bumpPackageVersion( content, diff --git a/lib/modules/manager/homebrew/extract.spec.ts b/lib/modules/manager/homebrew/extract.spec.ts index 4759b73f70..6922488ff9 100644 --- a/lib/modules/manager/homebrew/extract.spec.ts +++ b/lib/modules/manager/homebrew/extract.spec.ts @@ -15,30 +15,35 @@ describe('modules/manager/homebrew/extract', () => { expect(res.deps[0].skipReason).toBe('unsupported-url'); expect(res).toMatchSnapshot(); }); + it('skips sourceforge dependency 2', () => { const res = extractPackageFile(aap); expect(res).not.toBeNull(); expect(res.deps[0].skipReason).toBe('unsupported-url'); expect(res).toMatchSnapshot(); }); + it('skips github dependency with wrong format', () => { const res = extractPackageFile(acmetool); expect(res).not.toBeNull(); expect(res.deps[0].skipReason).toBe('unsupported-url'); expect(res).toMatchSnapshot(); }); + it('extracts "releases" github dependency', () => { const res = extractPackageFile(aide); expect(res).not.toBeNull(); expect(res.deps[0].skipReason).toBeUndefined(); expect(res).toMatchSnapshot(); }); + it('extracts "archive" github dependency', () => { const res = extractPackageFile(ibazel); expect(res).not.toBeNull(); expect(res.deps[0].skipReason).toBeUndefined(); expect(res).toMatchSnapshot(); }); + it('handles no space before class header', () => { const content = `class Ibazel < Formula desc 'IBazel is a tool for building Bazel targets when source files change.' @@ -52,6 +57,7 @@ describe('modules/manager/homebrew/extract', () => { expect(res.deps[0].skipReason).toBeUndefined(); expect(res).toMatchSnapshot(); }); + it('returns null for invalid class header 1', () => { const content = ` class Ibazel !?# Formula @@ -63,6 +69,7 @@ describe('modules/manager/homebrew/extract', () => { `; expect(extractPackageFile(content)).toBeNull(); }); + it('returns null for invalid class header 2', () => { const content = ` class Ibazel < NotFormula @@ -74,6 +81,7 @@ describe('modules/manager/homebrew/extract', () => { `; expect(extractPackageFile(content)).toBeNull(); }); + it('skips if there is no url field', () => { const content = ` class Ibazel < Formula @@ -88,6 +96,7 @@ describe('modules/manager/homebrew/extract', () => { expect(res.deps[0].skipReason).toBe('unsupported-url'); expect(res).toMatchSnapshot(); }); + it('skips if invalid url protocol', () => { const content = ` class Ibazel < Formula @@ -102,6 +111,7 @@ describe('modules/manager/homebrew/extract', () => { deps: [{ depName: 'Ibazel', skipReason: 'unsupported-url' }], }); }); + it('skips if invalid url', () => { const content = ` class Ibazel < Formula @@ -116,6 +126,7 @@ describe('modules/manager/homebrew/extract', () => { deps: [{ depName: 'Ibazel', skipReason: 'unsupported-url' }], }); }); + it('skips if there is no sha256 field', () => { const content = ` class Ibazel < Formula @@ -130,6 +141,7 @@ describe('modules/manager/homebrew/extract', () => { expect(res.deps[0].skipReason).toBe('invalid-sha256'); expect(res).toMatchSnapshot(); }); + it('skips if sha256 field is invalid', () => { const content = ` class Ibazel < Formula diff --git a/lib/modules/manager/homebrew/update.spec.ts b/lib/modules/manager/homebrew/update.spec.ts index 0ffee0ddf8..a7f8a88ae2 100644 --- a/lib/modules/manager/homebrew/update.spec.ts +++ b/lib/modules/manager/homebrew/update.spec.ts @@ -39,6 +39,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBe(aide); expect(newContent).toMatchSnapshot(); }); + it('updates "archive" github dependency', async () => { const upgrade = { currentValue: 'v0.8.2', @@ -66,6 +67,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBe(ibazel); expect(newContent).toMatchSnapshot(); }); + it('returns unchanged content if fromStream promise rejects', async () => { const upgrade = { currentValue: 'v0.8.2', @@ -94,6 +96,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBeNull(); expect(newContent).toBe(ibazel); }); + it('returns unchanged content if url field in upgrade object is invalid', async () => { const content = ibazel; const upgrade = { @@ -115,6 +118,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBeNull(); expect(newContent).toBe(content); }); + it('returns unchanged content if repoName in upgrade object is invalid', async () => { const content = ibazel; const upgrade = { @@ -144,6 +148,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBeNull(); expect(newContent).toBe(content); }); + it('returns unchanged content if repoName in upgrade object is wrong', async () => { const content = ibazel; const upgrade = { @@ -175,6 +180,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBeNull(); expect(newContent).toBe(content); }); + it('returns unchanged content if url field in Formula file is invalid', async () => { const content = ` class Ibazel < Formula @@ -209,6 +215,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBeNull(); expect(newContent).toBe(content); }); + it('returns unchanged content if url field in Formula file is missing', async () => { const content = ` class Ibazel < Formula @@ -242,6 +249,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBeNull(); expect(newContent).toBe(content); }); + it('returns unchanged content if sha256 field in Formula file is invalid', async () => { const content = ` class Ibazel < Formula @@ -276,6 +284,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBeNull(); expect(newContent).toBe(content); }); + it('returns unchanged content if sha256 field in Formula file is missing', async () => { const content = ` class Ibazel < Formula @@ -309,6 +318,7 @@ describe('modules/manager/homebrew/update', () => { expect(newContent).not.toBeNull(); expect(newContent).toBe(content); }); + it('returns unchanged content if both got requests fail', async () => { const upgrade = { currentValue: 'v0.16.1', diff --git a/lib/modules/manager/html/extract.spec.ts b/lib/modules/manager/html/extract.spec.ts index cbd95e417a..30694e7e2e 100644 --- a/lib/modules/manager/html/extract.spec.ts +++ b/lib/modules/manager/html/extract.spec.ts @@ -25,6 +25,7 @@ describe('modules/manager/html/extract', () => { ], }); }); + it('returns null', () => { expect(extractPackageFile(nothing)).toBeNull(); }); diff --git a/lib/modules/manager/index.spec.ts b/lib/modules/manager/index.spec.ts index 6a6bc76d83..dac8175de3 100644 --- a/lib/modules/manager/index.spec.ts +++ b/lib/modules/manager/index.spec.ts @@ -15,6 +15,7 @@ describe('modules/manager/index', () => { continue; } const supportedDatasources = manager.get(m, 'supportedDatasources'); + it(`has valid supportedDatasources for ${m}`, () => { expect(supportedDatasources).toBeNonEmptyArray(); supportedDatasources.every((d) => { @@ -23,16 +24,19 @@ describe('modules/manager/index', () => { }); } }); + describe('get()', () => { it('gets something', () => { expect(manager.get('dockerfile', 'extractPackageFile')).not.toBeNull(); }); }); + describe('getLanguageList()', () => { it('gets', () => { expect(manager.getLanguageList()).not.toBeNull(); }); }); + describe('getManagerList()', () => { it('gets', () => { expect(manager.getManagerList()).not.toBeNull(); @@ -82,6 +86,7 @@ describe('modules/manager/index', () => { await manager.extractAllPackageFiles('dummy', {} as any, []) ).toBeNull(); }); + it('returns non-null', async () => { manager.getManagers().set('dummy', { defaultConfig: {}, @@ -92,6 +97,7 @@ describe('modules/manager/index', () => { await manager.extractAllPackageFiles('dummy', {} as any, []) ).not.toBeNull(); }); + afterEach(() => { manager.getManagers().delete('dummy'); }); @@ -106,6 +112,7 @@ describe('modules/manager/index', () => { expect(manager.extractPackageFile('unknown', null)).toBeNull(); expect(manager.extractPackageFile('dummy', null)).toBeNull(); }); + it('returns non-null', () => { manager.getManagers().set('dummy', { defaultConfig: {}, @@ -115,6 +122,7 @@ describe('modules/manager/index', () => { expect(manager.extractPackageFile('dummy', null)).not.toBeNull(); }); + afterEach(() => { manager.getManagers().delete('dummy'); }); @@ -130,6 +138,7 @@ describe('modules/manager/index', () => { manager.getRangeStrategy({ manager: 'unknown', rangeStrategy: 'auto' }) ).toBeNull(); }); + it('returns non-null', () => { manager.getManagers().set('dummy', { defaultConfig: {}, diff --git a/lib/modules/manager/jsonnet-bundler/extract.spec.ts b/lib/modules/manager/jsonnet-bundler/extract.spec.ts index c225dd9cef..3c0145c2f0 100644 --- a/lib/modules/manager/jsonnet-bundler/extract.spec.ts +++ b/lib/modules/manager/jsonnet-bundler/extract.spec.ts @@ -17,21 +17,25 @@ describe('modules/manager/jsonnet-bundler/extract', () => { extractPackageFile('this is not a jsonnetfile', 'jsonnetfile.json') ).toBeNull(); }); + it('returns null for jsonnetfile with no dependencies', () => { expect( extractPackageFile(jsonnetfileNoDependencies, 'jsonnetfile.json') ).toBeNull(); }); + it('returns null for local dependencies', () => { expect( extractPackageFile(jsonnetfileLocalDependencies, 'jsonnetfile.json') ).toBeNull(); }); + it('returns null for vendored dependencies', () => { expect( extractPackageFile(jsonnetfile, 'vendor/jsonnetfile.json') ).toBeNull(); }); + it('extracts dependency', () => { const res = extractPackageFile(jsonnetfile, 'jsonnetfile.json'); expect(res).toMatchSnapshot({ @@ -51,6 +55,7 @@ describe('modules/manager/jsonnet-bundler/extract', () => { ], }); }); + it('extracts dependency with custom name', () => { const res = extractPackageFile(jsonnetfileWithName, 'jsonnetfile.json'); expect(res).toMatchSnapshot({ diff --git a/lib/modules/manager/kubernetes/extract.spec.ts b/lib/modules/manager/kubernetes/extract.spec.ts index 6dab936bfe..2f4117fc14 100644 --- a/lib/modules/manager/kubernetes/extract.spec.ts +++ b/lib/modules/manager/kubernetes/extract.spec.ts @@ -11,16 +11,19 @@ describe('modules/manager/kubernetes/extract', () => { it('returns null for empty', () => { expect(extractPackageFile(kubernetesConfigMapFile)).toBeNull(); }); + it('extracts multiple image lines', () => { const res = extractPackageFile(kubernetesImagesFile); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(2); }); + it('extracts image line in a YAML array', () => { const res = extractPackageFile(kubernetesArraySyntaxFile); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('ignores non-Kubernetes YAML files', () => { expect(extractPackageFile(otherYamlFile)).toBeNull(); }); diff --git a/lib/modules/manager/kustomize/extract.spec.ts b/lib/modules/manager/kustomize/extract.spec.ts index 29c11bf5ef..320adaefe8 100644 --- a/lib/modules/manager/kustomize/extract.spec.ts +++ b/lib/modules/manager/kustomize/extract.spec.ts @@ -30,10 +30,12 @@ describe('modules/manager/kustomize/extract', () => { const file = parseKustomize(kustomizeGitSSHBase); expect(file).not.toBeNull(); }); + it('return null on an invalid file', () => { const file = parseKustomize(''); expect(file).toBeNull(); }); + it('should return null when header has invalid resource kind', () => { const file = parseKustomize(` kind: NoKustomization @@ -42,6 +44,7 @@ describe('modules/manager/kustomize/extract', () => { `); expect(file).toBeNull(); }); + it('should fall back to default resource kind when header is missing', () => { const file = parseKustomize(` bases: @@ -50,11 +53,13 @@ describe('modules/manager/kustomize/extract', () => { expect(file).not.toBeNull(); expect(file.kind).toBe('Kustomization'); }); + describe('extractBase', () => { it('should return null for a local base', () => { const res = extractResource('./service-1'); expect(res).toBeNull(); }); + it('should extract out the version of an http base', () => { const base = 'https://github.com/user/test-repo.git'; const version = 'v1.0.0'; @@ -67,6 +72,7 @@ describe('modules/manager/kustomize/extract', () => { const pkg = extractResource(`${base}?ref=${version}`); expect(pkg).toEqual(sample); }); + it('should extract the version of a non http base', () => { const pkg = extractResource( 'ssh://git@bitbucket.com/user/test-repo?ref=v1.2.3' @@ -78,6 +84,7 @@ describe('modules/manager/kustomize/extract', () => { packageName: 'ssh://git@bitbucket.com/user/test-repo', }); }); + it('should extract the depName if the URL includes a port number', () => { const pkg = extractResource( 'ssh://git@bitbucket.com:7999/user/test-repo?ref=v1.2.3' @@ -89,6 +96,7 @@ describe('modules/manager/kustomize/extract', () => { packageName: 'ssh://git@bitbucket.com:7999/user/test-repo', }); }); + it('should extract the version of a non http base with subdir', () => { const pkg = extractResource( 'ssh://git@bitbucket.com/user/test-repo/subdir?ref=v1.2.3' @@ -100,6 +108,7 @@ describe('modules/manager/kustomize/extract', () => { packageName: 'ssh://git@bitbucket.com/user/test-repo', }); }); + it('should extract out the version of an github base', () => { const base = 'github.com/fluxcd/flux/deploy'; const version = 'v1.0.0'; @@ -112,6 +121,7 @@ describe('modules/manager/kustomize/extract', () => { const pkg = extractResource(`${base}?ref=${version}`); expect(pkg).toEqual(sample); }); + it('should extract out the version of a git base', () => { const base = 'git@github.com:user/repo.git'; const version = 'v1.0.0'; @@ -124,6 +134,7 @@ describe('modules/manager/kustomize/extract', () => { const pkg = extractResource(`${base}?ref=${version}`); expect(pkg).toEqual(sample); }); + it('should extract out the version of a git base with subdir', () => { const base = 'git@github.com:user/repo.git/subdir'; const version = 'v1.0.0'; @@ -137,6 +148,7 @@ describe('modules/manager/kustomize/extract', () => { expect(pkg).toEqual(sample); }); }); + describe('extractHelmChart', () => { it('should return null on a null input', () => { const pkg = extractHelmChart({ @@ -146,6 +158,7 @@ describe('modules/manager/kustomize/extract', () => { }); expect(pkg).toBeNull(); }); + it('should correctly extract a chart', () => { const registryUrl = 'https://docs.renovatebot.com/helm-charts'; const sample = { @@ -162,6 +175,7 @@ describe('modules/manager/kustomize/extract', () => { expect(pkg).toEqual(sample); }); }); + describe('image extraction', () => { it('should return null on a null input', () => { const pkg = extractImage({ @@ -170,6 +184,7 @@ describe('modules/manager/kustomize/extract', () => { }); expect(pkg).toBeNull(); }); + it('should correctly extract a default image', () => { const sample = { currentDigest: undefined, @@ -184,6 +199,7 @@ describe('modules/manager/kustomize/extract', () => { }); expect(pkg).toEqual(sample); }); + it('should correctly extract an image in a repo', () => { const sample = { currentDigest: undefined, @@ -198,6 +214,7 @@ describe('modules/manager/kustomize/extract', () => { }); expect(pkg).toEqual(sample); }); + it('should correctly extract from a different registry', () => { const sample = { currentDigest: undefined, @@ -212,6 +229,7 @@ describe('modules/manager/kustomize/extract', () => { }); expect(pkg).toEqual(sample); }); + it('should correctly extract from a different port', () => { const sample = { currentDigest: undefined, @@ -226,6 +244,7 @@ describe('modules/manager/kustomize/extract', () => { }); expect(pkg).toEqual(sample); }); + it('should correctly extract from a multi-depth registry', () => { const sample = { currentDigest: undefined, @@ -241,25 +260,30 @@ describe('modules/manager/kustomize/extract', () => { expect(pkg).toEqual(sample); }); }); + describe('extractPackageFile()', () => { it('returns null for non kustomize kubernetes files', () => { expect(extractPackageFile(nonKustomize)).toBeNull(); }); + it('extracts multiple image lines', () => { const res = extractPackageFile(kustomizeWithLocal); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(2); }); + it('extracts ssh dependency', () => { const res = extractPackageFile(kustomizeGitSSHBase); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('extracts ssh dependency with a subdir', () => { const res = extractPackageFile(kustomizeGitSSHSubdir); expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('extracts http dependency', () => { const res = extractPackageFile(kustomizeHTTP); expect(res.deps).toMatchSnapshot(); @@ -268,6 +292,7 @@ describe('modules/manager/kustomize/extract', () => { expect(res.deps[1].currentValue).toBe('1.19.0'); expect(res.deps[1].depName).toBe('fluxcd/flux'); }); + it('should extract out image versions', () => { const res = extractPackageFile(gitImages); expect(res.deps).toMatchSnapshot(); @@ -276,12 +301,15 @@ describe('modules/manager/kustomize/extract', () => { expect(res.deps[1].currentValue).toBe('v0.0.1'); expect(res.deps[5].skipReason).toBe('invalid-value'); }); + it('ignores non-Kubernetes empty files', () => { expect(extractPackageFile('')).toBeNull(); }); + it('does nothing with kustomize empty kustomize files', () => { expect(extractPackageFile(kustomizeEmpty)).toBeNull(); }); + it('should extract bases resources and components from their respective blocks', () => { const res = extractPackageFile(kustomizeDepsInResources); expect(res).not.toBeNull(); @@ -297,6 +325,7 @@ describe('modules/manager/kustomize/extract', () => { expect(res.deps[1].depType).toBe('Kustomization'); expect(res.deps[2].depType).toBe('Kustomization'); }); + it('should extract dependencies when kind is Component', () => { const res = extractPackageFile(kustomizeComponent); expect(res).not.toBeNull(); diff --git a/lib/modules/manager/leiningen/extract.spec.ts b/lib/modules/manager/leiningen/extract.spec.ts index 5c257f4081..f4bb40db7c 100644 --- a/lib/modules/manager/leiningen/extract.spec.ts +++ b/lib/modules/manager/leiningen/extract.spec.ts @@ -15,6 +15,7 @@ describe('modules/manager/leiningen/extract', () => { expect(trimAtKey(':dependencies ', 'dependencies')).toBeNull(); expect(trimAtKey(':dependencies \nfoobar', 'dependencies')).toBe('foobar'); }); + it('extractFromVectors', () => { expect(extractFromVectors('')).toBeEmptyArray(); expect(extractFromVectors('[]')).toBeEmptyArray(); @@ -51,6 +52,7 @@ describe('modules/manager/leiningen/extract', () => { }, ]); }); + it('extractPackageFile', () => { expect(extractPackageFile(leinProjectClj)).toMatchSnapshot({ deps: [ @@ -126,6 +128,7 @@ describe('modules/manager/leiningen/extract', () => { ], }); }); + it('extractVariables', () => { expect(extractVariables('(def foo "1")')).toEqual({ foo: '1' }); expect(extractVariables('(def foo"2")')).toEqual({ foo: '2' }); diff --git a/lib/modules/manager/maven/extract.spec.ts b/lib/modules/manager/maven/extract.spec.ts index 3fe7e54044..60f925a758 100644 --- a/lib/modules/manager/maven/extract.spec.ts +++ b/lib/modules/manager/maven/extract.spec.ts @@ -123,6 +123,7 @@ describe('modules/manager/maven/extract', () => { packageFile: null, }); }); + it('tries minimum manifests', () => { const res = extractPackage(minimumContent); expect(res).toEqual({ @@ -134,6 +135,7 @@ describe('modules/manager/maven/extract', () => { }); }); }); + describe('extractRegistries', () => { it('returns null for invalid XML', () => { expect(extractRegistries(undefined)).toBeEmptyArray(); diff --git a/lib/modules/manager/maven/index.spec.ts b/lib/modules/manager/maven/index.spec.ts index 6f7b4d5768..3cdc1c070c 100644 --- a/lib/modules/manager/maven/index.spec.ts +++ b/lib/modules/manager/maven/index.spec.ts @@ -307,6 +307,7 @@ describe('modules/manager/maven/index', () => { expect(updatedContent).toBeNull(); }); + it('should update ranges', () => { const newValue = '[1.2.3]'; const select = (depSet: PackageFile) => @@ -320,6 +321,7 @@ describe('modules/manager/maven/index', () => { const newDep = select(newContent); expect(newDep.currentValue).toEqual(newValue); }); + it('should preserve ranges', () => { const newValue = '[1.0.0]'; const select = (depSet: PackageFile) => @@ -332,6 +334,7 @@ describe('modules/manager/maven/index', () => { pomContent ); }); + it('should return null for replacement', () => { const res = updateDependency({ fileContent: undefined, diff --git a/lib/modules/manager/metadata.spec.ts b/lib/modules/manager/metadata.spec.ts index 0706f31c6a..50b9e36935 100644 --- a/lib/modules/manager/metadata.spec.ts +++ b/lib/modules/manager/metadata.spec.ts @@ -7,6 +7,7 @@ describe('modules/manager/metadata', () => { .map((dirent) => dirent.name) .filter((name) => !name.startsWith('__')) .sort(); + test.each(managerList)('%s has readme with no h1 or h2', async (manager) => { let readme: string; try { diff --git a/lib/modules/manager/meteor/extract.spec.ts b/lib/modules/manager/meteor/extract.spec.ts index f7df1acb2a..608e15e9fe 100644 --- a/lib/modules/manager/meteor/extract.spec.ts +++ b/lib/modules/manager/meteor/extract.spec.ts @@ -9,6 +9,7 @@ describe('modules/manager/meteor/extract', () => { const res = extractPackageFile('blahhhhh:foo:@what\n'); expect(res).toBeNull(); }); + it('returns results', () => { const res = extractPackageFile(input01Content); expect(res).toMatchSnapshot(); diff --git a/lib/modules/manager/mix/extract.spec.ts b/lib/modules/manager/mix/extract.spec.ts index 7ca4495494..c2a99b1c04 100644 --- a/lib/modules/manager/mix/extract.spec.ts +++ b/lib/modules/manager/mix/extract.spec.ts @@ -12,6 +12,7 @@ describe('modules/manager/mix/extract', () => { const { deps } = await extractPackageFile('nothing here', 'mix.exs'); expect(deps).toBeEmpty(); }); + it('extracts all dependencies', async () => { const res = await extractPackageFile(Fixtures.get('mix.exs'), 'mix.exs'); expect(res).toMatchSnapshot({ diff --git a/lib/modules/manager/nodenv/extract.spec.ts b/lib/modules/manager/nodenv/extract.spec.ts index 719ab24d65..40b8b939e7 100644 --- a/lib/modules/manager/nodenv/extract.spec.ts +++ b/lib/modules/manager/nodenv/extract.spec.ts @@ -13,6 +13,7 @@ describe('modules/manager/nodenv/extract', () => { }, ]); }); + it('supports ranges', () => { const res = extractPackageFile('8.4\n'); expect(res.deps).toEqual([ @@ -24,6 +25,7 @@ describe('modules/manager/nodenv/extract', () => { }, ]); }); + it('skips non ranges', () => { const res = extractPackageFile('latestn'); expect(res.deps).toEqual([ diff --git a/lib/modules/manager/npm/detect.spec.ts b/lib/modules/manager/npm/detect.spec.ts index 5605c96af3..f6bf78700c 100644 --- a/lib/modules/manager/npm/detect.spec.ts +++ b/lib/modules/manager/npm/detect.spec.ts @@ -20,6 +20,7 @@ Object { expect(res.npmrc).toBeDefined(); expect(res.npmrcMerge).toBe(true); }); + it('handles no .npmrc', async () => { fs.readFile.mockImplementationOnce(() => Promise.reject()); const res = await detectGlobalConfig(); diff --git a/lib/modules/manager/npm/extract/index.spec.ts b/lib/modules/manager/npm/extract/index.spec.ts index 734463dfe6..016f8cdc72 100644 --- a/lib/modules/manager/npm/extract/index.spec.ts +++ b/lib/modules/manager/npm/extract/index.spec.ts @@ -24,6 +24,7 @@ describe('modules/manager/npm/extract/index', () => { fs.readLocalFile = jest.fn(() => null); fs.localPathExists = jest.fn(() => false); }); + it('returns null if cannot parse', async () => { const res = await npmExtract.extractPackageFile( 'not json', @@ -32,6 +33,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res).toBeNull(); }); + it('catches invalid names', async () => { const res = await npmExtract.extractPackageFile( invalidNameContent, @@ -42,6 +44,7 @@ describe('modules/manager/npm/extract/index', () => { deps: [{ skipReason: 'invalid-name' }], }); }); + it('ignores vendorised package.json', async () => { const res = await npmExtract.extractPackageFile( vendorisedContent, @@ -50,6 +53,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res).toBeNull(); }); + it('throws error if non-root renovate config', async () => { await expect( npmExtract.extractPackageFile( @@ -59,6 +63,7 @@ describe('modules/manager/npm/extract/index', () => { ) ).rejects.toThrow(); }); + it('returns null if no deps', async () => { const res = await npmExtract.extractPackageFile( '{ "renovate": {} }', @@ -67,6 +72,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res).toBeNull(); }); + it('handles invalid', async () => { const res = await npmExtract.extractPackageFile( '{"dependencies": true, "devDependencies": []}', @@ -75,6 +81,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res).toBeNull(); }); + it('returns an array of dependencies', async () => { const res = await npmExtract.extractPackageFile( input01Content, @@ -101,6 +108,7 @@ describe('modules/manager/npm/extract/index', () => { ], }); }); + it('returns an array of dependencies with resolution comments', async () => { const res = await npmExtract.extractPackageFile( input01GlobContent, @@ -129,6 +137,7 @@ describe('modules/manager/npm/extract/index', () => { ], }); }); + it('finds a lock file', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === 'yarn.lock') { @@ -143,6 +152,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res).toMatchSnapshot({ yarnLock: 'yarn.lock' }); }); + it('finds and filters .npmrc', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === '.npmrc') { @@ -157,6 +167,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res.npmrc).toBeDefined(); }); + it('ignores .npmrc when config.npmrc is defined and npmrcMerge=false', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === '.npmrc') { @@ -171,6 +182,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res.npmrc).toBeUndefined(); }); + it('reads .npmrc when config.npmrc is merged', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === '.npmrc') { @@ -185,6 +197,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res.npmrc).toBe(`config-npmrc\nrepo-npmrc\n`); }); + it('finds and filters .npmrc with variables', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === '.npmrc') { @@ -199,6 +212,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res.npmrc).toBe('registry=https://registry.npmjs.org\n'); }); + it('finds lerna', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === 'lerna.json') { @@ -217,6 +231,7 @@ describe('modules/manager/npm/extract/index', () => { managerData: { lernaJsonFile: 'lerna.json' }, }); }); + it('finds "npmClient":"npm" in lerna.json', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === 'lerna.json') { @@ -235,6 +250,7 @@ describe('modules/manager/npm/extract/index', () => { managerData: { lernaJsonFile: 'lerna.json' }, }); }); + it('finds "npmClient":"yarn" in lerna.json', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === 'lerna.json') { @@ -253,6 +269,7 @@ describe('modules/manager/npm/extract/index', () => { managerData: { lernaJsonFile: 'lerna.json' }, }); }); + it('finds simple yarn workspaces', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === 'lerna.json') { @@ -267,6 +284,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res).toMatchSnapshot({ yarnWorkspacesPackages: ['packages/*'] }); }); + it('finds simple yarn workspaces with lerna.json and useWorkspaces: true', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === 'lerna.json') { @@ -281,6 +299,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res).toMatchSnapshot({ yarnWorkspacesPackages: ['packages/*'] }); }); + it('finds complex yarn workspaces', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === 'lerna.json') { @@ -295,6 +314,7 @@ describe('modules/manager/npm/extract/index', () => { ); expect(res).toMatchSnapshot({ yarnWorkspacesPackages: ['packages/*'] }); }); + it('extracts engines', async () => { const pJson = { dependencies: { @@ -386,6 +406,7 @@ describe('modules/manager/npm/extract/index', () => { ], }); }); + it('extracts volta', async () => { const pJson = { main: 'index.js', @@ -461,6 +482,7 @@ describe('modules/manager/npm/extract/index', () => { ], }); }); + it('extracts non-npmjs', async () => { const pJson = { dependencies: { @@ -573,6 +595,7 @@ describe('modules/manager/npm/extract/index', () => { ], }); }); + it('extracts npm package alias', async () => { fs.readLocalFile = jest.fn((fileName) => { if (fileName === 'package-lock.json') { @@ -647,6 +670,7 @@ describe('modules/manager/npm/extract/index', () => { }); }); }); + describe('.postExtract()', () => { it('runs', async () => { await expect(npmExtract.postExtract([], false)).resolves.not.toThrow(); diff --git a/lib/modules/manager/npm/extract/locked-versions.spec.ts b/lib/modules/manager/npm/extract/locked-versions.spec.ts index 6b9caf5a43..8b90657b7b 100644 --- a/lib/modules/manager/npm/extract/locked-versions.spec.ts +++ b/lib/modules/manager/npm/extract/locked-versions.spec.ts @@ -77,6 +77,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }, ]); }); + it('uses yarn.lock with yarn v2.1.0', async () => { const yarnVersion = '2.1.0'; const lockfileVersion = undefined; @@ -123,6 +124,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }, ]); }); + it('uses yarn.lock with yarn v2.2.0', async () => { const yarnVersion = '2.2.0'; const lockfileVersion = 6; @@ -169,6 +171,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }, ]); }); + it('uses yarn.lock with yarn v3.0.0', async () => { const yarnVersion = '3.0.0'; const lockfileVersion = 8; @@ -236,6 +239,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }, ]); }); + it('uses package-lock.json with npm v7.0.0', async () => { npm.getNpmLock.mockReturnValue({ lockedVersions: { a: '1.0.0', b: '2.0.0', c: '3.0.0' }, @@ -305,6 +309,7 @@ describe('modules/manager/npm/extract/locked-versions', () => { }, ]); }); + it('ignores pnpm', async () => { const packageFiles = [ { diff --git a/lib/modules/manager/npm/extract/npm.spec.ts b/lib/modules/manager/npm/extract/npm.spec.ts index a85ec1d365..3ccb36abac 100644 --- a/lib/modules/manager/npm/extract/npm.spec.ts +++ b/lib/modules/manager/npm/extract/npm.spec.ts @@ -10,6 +10,7 @@ describe('modules/manager/npm/extract/npm', () => { const res = await getNpmLock('package.json'); expect(Object.keys(res.lockedVersions)).toHaveLength(0); }); + it('extracts', async () => { const plocktest1Lock = loadFixture('plocktest1/package-lock.json', '..'); fs.readLocalFile.mockResolvedValueOnce(plocktest1Lock as never); @@ -17,6 +18,7 @@ describe('modules/manager/npm/extract/npm', () => { expect(res).toMatchSnapshot(); expect(Object.keys(res.lockedVersions)).toHaveLength(7); }); + it('extracts npm 7 lockfile', async () => { const npm7Lock = loadFixture('npm7/package-lock.json', '..'); fs.readLocalFile.mockResolvedValueOnce(npm7Lock as never); @@ -25,6 +27,7 @@ describe('modules/manager/npm/extract/npm', () => { expect(Object.keys(res.lockedVersions)).toHaveLength(7); expect(res.lockfileVersion).toBe(2); }); + it('returns empty if no deps', async () => { fs.readLocalFile.mockResolvedValueOnce('{}'); const res = await getNpmLock('package.json'); diff --git a/lib/modules/manager/npm/extract/type.spec.ts b/lib/modules/manager/npm/extract/type.spec.ts index aac96b5585..df2b6b4a97 100644 --- a/lib/modules/manager/npm/extract/type.spec.ts +++ b/lib/modules/manager/npm/extract/type.spec.ts @@ -6,10 +6,12 @@ describe('modules/manager/npm/extract/type', () => { const isLibrary = mightBeABrowserLibrary({ private: true }); expect(isLibrary).toBeFalse(); }); + it('is not a library if no main', () => { const isLibrary = mightBeABrowserLibrary({}); expect(isLibrary).toBeFalse(); }); + it('is a library if has a main', () => { const isLibrary = mightBeABrowserLibrary({ main: 'index.js ' }); expect(isLibrary).toBeTrue(); diff --git a/lib/modules/manager/npm/post-update/lerna.spec.ts b/lib/modules/manager/npm/post-update/lerna.spec.ts index 78982115cf..1017651f35 100644 --- a/lib/modules/manager/npm/post-update/lerna.spec.ts +++ b/lib/modules/manager/npm/post-update/lerna.spec.ts @@ -25,6 +25,7 @@ function lernaPkgFileWithoutLernaDep(lernaClient: string) { lernaClient, }; } + describe('modules/manager/npm/post-update/lerna', () => { describe('generateLockFiles()', () => { beforeEach(() => { @@ -32,10 +33,12 @@ describe('modules/manager/npm/post-update/lerna', () => { jest.resetModules(); env.getChildProcessEnv.mockReturnValue(envMock.basic); }); + it('returns if no lernaClient', async () => { const res = await lernaHelper.generateLockFiles({}, 'some-dir', {}, {}); expect(res.error).toBeFalse(); }); + it('returns if invalid lernaClient', async () => { const res = await lernaHelper.generateLockFiles( lernaPkgFile('foo'), @@ -45,6 +48,7 @@ describe('modules/manager/npm/post-update/lerna', () => { ); expect(res.error).toBeFalse(); }); + it('generates package-lock.json files', async () => { const execSnapshots = mockExecAll(exec); const skipInstalls = true; @@ -58,6 +62,7 @@ describe('modules/manager/npm/post-update/lerna', () => { expect(res.error).toBeFalse(); expect(execSnapshots).toMatchSnapshot(); }); + it('performs full npm install', async () => { const execSnapshots = mockExecAll(exec); const skipInstalls = false; @@ -71,6 +76,7 @@ describe('modules/manager/npm/post-update/lerna', () => { expect(res.error).toBeFalse(); expect(execSnapshots).toMatchSnapshot(); }); + it('generates yarn.lock files', async () => { const execSnapshots = mockExecAll(exec); const res = await lernaHelper.generateLockFiles( @@ -82,6 +88,7 @@ describe('modules/manager/npm/post-update/lerna', () => { expect(execSnapshots).toMatchSnapshot(); expect(res.error).toBeFalse(); }); + it('defaults to latest if lerna version unspecified', async () => { const execSnapshots = mockExecAll(exec); const res = await lernaHelper.generateLockFiles( @@ -93,6 +100,7 @@ describe('modules/manager/npm/post-update/lerna', () => { expect(res.error).toBeFalse(); expect(execSnapshots).toMatchSnapshot(); }); + it('allows scripts for trust level high', async () => { const execSnapshots = mockExecAll(exec); GlobalConfig.set({ allowScripts: true }); @@ -114,6 +122,7 @@ describe('modules/manager/npm/post-update/lerna', () => { }; expect(lernaHelper.getLernaVersion(pkg)).toBe('2.0.0'); }); + it('returns specified range', () => { const pkg = { deps: [ @@ -124,16 +133,19 @@ describe('modules/manager/npm/post-update/lerna', () => { '1.x || >=2.5.0 || 5.0.0 - 7.2.3' ); }); + it('returns latest if no lerna dep is specified', () => { const pkg = { deps: [{ depName: 'something-else', currentValue: '1.2.3' }], }; expect(lernaHelper.getLernaVersion(pkg)).toBe('latest'); }); + it('returns latest if pkg has no deps at all', () => { const pkg = {}; expect(lernaHelper.getLernaVersion(pkg)).toBe('latest'); }); + it('returns latest if specified lerna version is not a valid semVer range', () => { const pkg = { deps: [{ depName: 'lerna', currentValue: '[a.b.c;' }], diff --git a/lib/modules/manager/npm/post-update/node-version.spec.ts b/lib/modules/manager/npm/post-update/node-version.spec.ts index f24aa58224..55bba94a62 100644 --- a/lib/modules/manager/npm/post-update/node-version.spec.ts +++ b/lib/modules/manager/npm/post-update/node-version.spec.ts @@ -8,6 +8,7 @@ describe('modules/manager/npm/post-update/node-version', () => { packageFile: 'package.json', constraints: { node: '^12.16.0' }, }; + it('returns package.json range', async () => { fs.readLocalFile = jest.fn(); fs.readLocalFile.mockResolvedValueOnce(null); @@ -15,6 +16,7 @@ describe('modules/manager/npm/post-update/node-version', () => { const res = await getNodeConstraint(config); expect(res).toBe('^12.16.0'); }); + it('returns .node-version value', async () => { fs.readLocalFile = jest.fn(); fs.readLocalFile.mockResolvedValueOnce(null); @@ -22,12 +24,14 @@ describe('modules/manager/npm/post-update/node-version', () => { const res = await getNodeConstraint(config); expect(res).toBe('12.16.1'); }); + it('returns .nvmrc value', async () => { fs.readLocalFile = jest.fn(); fs.readLocalFile.mockResolvedValueOnce('12.16.2\n'); const res = await getNodeConstraint(config); expect(res).toBe('12.16.2'); }); + it('ignores unusable ranges in dotfiles', async () => { fs.readLocalFile = jest.fn(); fs.readLocalFile.mockResolvedValueOnce('latest'); @@ -35,6 +39,7 @@ describe('modules/manager/npm/post-update/node-version', () => { const res = await getNodeConstraint(config); expect(res).toBe('^12.16.0'); }); + it('returns no constraint', async () => { fs.readLocalFile = jest.fn(); fs.readLocalFile.mockResolvedValueOnce(null); diff --git a/lib/modules/manager/npm/post-update/npm.spec.ts b/lib/modules/manager/npm/post-update/npm.spec.ts index 8cb1a14cd6..a1a19e76fc 100644 --- a/lib/modules/manager/npm/post-update/npm.spec.ts +++ b/lib/modules/manager/npm/post-update/npm.spec.ts @@ -22,6 +22,7 @@ describe('modules/manager/npm/post-update/npm', () => { jest.resetModules(); env.getChildProcessEnv.mockReturnValue(envMock.basic); }); + it('generates lock files', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; @@ -42,6 +43,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('performs lock file updates', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; @@ -61,6 +63,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('performs lock file updates retaining the package.json counterparts', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => @@ -88,6 +91,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toMatchSnapshot(); expect(execSnapshots).toMatchSnapshot(); }); + it('performs npm-shrinkwrap.json updates', async () => { const execSnapshots = mockExecAll(exec); fs.pathExists.mockResolvedValueOnce(true); @@ -117,6 +121,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('performs npm-shrinkwrap.json updates (no package-lock.json)', async () => { const execSnapshots = mockExecAll(exec); fs.pathExists.mockResolvedValueOnce(false); @@ -142,6 +147,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('performs full install', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; @@ -158,6 +164,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('runs twice if remediating', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; @@ -174,6 +181,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toHaveLength(2); }); + it('catches errors', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => { @@ -189,6 +197,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBeUndefined(); expect(execSnapshots).toMatchSnapshot(); }); + it('finds npm globally', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; @@ -201,6 +210,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('uses docker npm', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; @@ -214,6 +224,7 @@ describe('modules/manager/npm/post-update/npm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('performs lock file maintenance', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; diff --git a/lib/modules/manager/npm/post-update/pnpm.spec.ts b/lib/modules/manager/npm/post-update/pnpm.spec.ts index d5dba94c8a..6a38875fbb 100644 --- a/lib/modules/manager/npm/post-update/pnpm.spec.ts +++ b/lib/modules/manager/npm/post-update/pnpm.spec.ts @@ -20,10 +20,12 @@ delete process.env.NPM_CONFIG_CACHE; describe('modules/manager/npm/post-update/pnpm', () => { let config: PostUpdateConfig; + beforeEach(() => { config = { cacheDir: 'some-cache-dir', constraints: { pnpm: '^2.0.0' } }; env.getChildProcessEnv.mockReturnValue(envMock.basic); }); + it('generates lock files', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; @@ -32,6 +34,7 @@ describe('modules/manager/npm/post-update/pnpm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('catches errors', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => { @@ -43,6 +46,7 @@ describe('modules/manager/npm/post-update/pnpm', () => { expect(res.lockFile).toBeUndefined(); expect(execSnapshots).toMatchSnapshot(); }); + it('finds pnpm globally', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; @@ -51,6 +55,7 @@ describe('modules/manager/npm/post-update/pnpm', () => { expect(res.lockFile).toBe('package-lock-contents'); expect(execSnapshots).toMatchSnapshot(); }); + it('performs lock file maintenance', async () => { const execSnapshots = mockExecAll(exec); fs.readFile = jest.fn(() => 'package-lock-contents') as never; diff --git a/lib/modules/manager/npm/post-update/rules.spec.ts b/lib/modules/manager/npm/post-update/rules.spec.ts index af6fde0162..dec854c976 100644 --- a/lib/modules/manager/npm/post-update/rules.spec.ts +++ b/lib/modules/manager/npm/post-update/rules.spec.ts @@ -6,17 +6,20 @@ describe('modules/manager/npm/post-update/rules', () => { beforeEach(() => { hostRules.clear(); }); + it('returns empty if no rules', () => { const res = processHostRules(); expect(res.additionalNpmrcContent).toHaveLength(0); expect(res.additionalYarnRcYml).toBeUndefined(); }); + it('returns empty if no resolvedHost', () => { hostRules.add({ hostType: 'npm', token: '123test' }); const res = processHostRules(); expect(res.additionalNpmrcContent).toHaveLength(0); expect(res.additionalYarnRcYml).toBeUndefined(); }); + it('returns rules content', () => { hostRules.add({ hostType: 'npm', @@ -57,6 +60,7 @@ describe('modules/manager/npm/post-update/rules', () => { ` ); }); + it('returns mixed rules content', () => { hostRules.add({ hostType: 'npm', diff --git a/lib/modules/manager/npm/range.spec.ts b/lib/modules/manager/npm/range.spec.ts index 650548d3a9..c50ae01976 100644 --- a/lib/modules/manager/npm/range.spec.ts +++ b/lib/modules/manager/npm/range.spec.ts @@ -6,6 +6,7 @@ describe('modules/manager/npm/range', () => { const config: RangeConfig = { rangeStrategy: 'widen' }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('pins devDependencies', () => { const config: RangeConfig = { rangeStrategy: 'auto', @@ -13,6 +14,7 @@ describe('modules/manager/npm/range', () => { }; expect(getRangeStrategy(config)).toBe('pin'); }); + it('pins app dependencies', () => { const config: RangeConfig = { rangeStrategy: 'auto', @@ -21,6 +23,7 @@ describe('modules/manager/npm/range', () => { }; expect(getRangeStrategy(config)).toBe('pin'); }); + it('widens peerDependencies', () => { const config: RangeConfig = { rangeStrategy: 'auto', @@ -28,6 +31,7 @@ describe('modules/manager/npm/range', () => { }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('widens complex ranges', () => { const config: RangeConfig = { rangeStrategy: 'auto', @@ -36,6 +40,7 @@ describe('modules/manager/npm/range', () => { }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('widens complex bump', () => { const config: RangeConfig = { rangeStrategy: 'bump', @@ -44,6 +49,7 @@ describe('modules/manager/npm/range', () => { }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('defaults to replace', () => { const config: RangeConfig = { rangeStrategy: 'auto', diff --git a/lib/modules/manager/npm/update/dependency/index.spec.ts b/lib/modules/manager/npm/update/dependency/index.spec.ts index 78d19e20de..c5a5187886 100644 --- a/lib/modules/manager/npm/update/dependency/index.spec.ts +++ b/lib/modules/manager/npm/update/dependency/index.spec.ts @@ -22,6 +22,7 @@ describe('modules/manager/npm/update/dependency/index', () => { }); expect(testContent).toEqual(outputContent); }); + it('replaces a github dependency value', () => { const upgrade = { depType: 'dependencies', @@ -44,6 +45,7 @@ describe('modules/manager/npm/update/dependency/index', () => { dependencies: { gulp: 'gulpjs/gulp#v4.0.0' }, }); }); + it('replaces a npm package alias', () => { const upgrade = { depType: 'dependencies', @@ -67,6 +69,7 @@ describe('modules/manager/npm/update/dependency/index', () => { dependencies: { hapi: 'npm:@hapi/hapi@18.3.1' }, }); }); + it('replaces a github short hash', () => { const upgrade = { depType: 'dependencies', @@ -89,6 +92,7 @@ describe('modules/manager/npm/update/dependency/index', () => { dependencies: { gulp: 'gulpjs/gulp#0000000' }, }); }); + it('replaces a github fully specified version', () => { const upgrade = { depType: 'dependencies', @@ -109,6 +113,7 @@ describe('modules/manager/npm/update/dependency/index', () => { expect(res).toMatchSnapshot(); expect(res).toContain('v1.1.0'); }); + it('updates resolutions too', () => { const upgrade = { depType: 'dependencies', @@ -122,6 +127,7 @@ describe('modules/manager/npm/update/dependency/index', () => { expect(JSON.parse(testContent).dependencies.config).toBe('1.22.0'); expect(JSON.parse(testContent).resolutions.config).toBe('1.22.0'); }); + it('updates glob resolutions', () => { const upgrade = { depType: 'dependencies', @@ -135,6 +141,7 @@ describe('modules/manager/npm/update/dependency/index', () => { expect(JSON.parse(testContent).dependencies.config).toBe('1.22.0'); expect(JSON.parse(testContent).resolutions['**/config']).toBe('1.22.0'); }); + it('updates glob resolutions without dep', () => { const upgrade = { depType: 'resolutions', @@ -150,6 +157,7 @@ describe('modules/manager/npm/update/dependency/index', () => { '8.1.0' ); }); + it('replaces only the first instance of a value', () => { const upgrade = { depType: 'devDependencies', @@ -163,6 +171,7 @@ describe('modules/manager/npm/update/dependency/index', () => { }); expect(testContent).toEqual(outputContent); }); + it('replaces only the second instance of a value', () => { const upgrade = { depType: 'devDependencies', @@ -176,6 +185,7 @@ describe('modules/manager/npm/update/dependency/index', () => { }); expect(testContent).toEqual(outputContent); }); + it('handles the case where the desired version is already supported', () => { const upgrade = { depType: 'devDependencies', @@ -188,6 +198,7 @@ describe('modules/manager/npm/update/dependency/index', () => { }); expect(testContent).toEqual(input01Content); }); + it('returns null if throws error', () => { const upgrade = { depType: 'blah', @@ -257,6 +268,7 @@ describe('modules/manager/npm/update/dependency/index', () => { expect(JSON.parse(testContent).resolutions.config).toBeUndefined(); expect(JSON.parse(testContent).resolutions['**/abc']).toBe('2.0.0'); }); + it('pins also the version in patch with npm protocol in resolutions', () => { const upgrade = { depType: 'dependencies', @@ -270,6 +282,7 @@ describe('modules/manager/npm/update/dependency/index', () => { }); expect(testContent).toEqual(outputContent); }); + it('replaces also the version in patch with range in resolutions', () => { const upgrade = { depType: 'dependencies', diff --git a/lib/modules/manager/npm/update/locked-dependency/index.spec.ts b/lib/modules/manager/npm/update/locked-dependency/index.spec.ts index 0887d7ec1d..1141831219 100644 --- a/lib/modules/manager/npm/update/locked-dependency/index.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/index.spec.ts @@ -27,6 +27,7 @@ const bundledPackageLockJson = loadFixture( describe('modules/manager/npm/update/locked-dependency/index', () => { describe('updateLockedDependency()', () => { let config: UpdateLockedConfig; + beforeEach(() => { config = { packageFile: 'package.json', @@ -47,6 +48,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { await updateLockedDependency({ ...config, lockFile: 'yarn.lock2' }) ).toMatchObject({}); }); + it('validates versions', async () => { expect( await updateLockedDependency({ @@ -55,11 +57,13 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { }) ).toMatchObject({}); }); + it('returns null for unparseable files', async () => { expect( await updateLockedDependency({ ...config, lockFileContent: 'not json' }) ).toMatchObject({}); }); + it('rejects lockFileVersion 2', async () => { expect( await updateLockedDependency({ @@ -68,9 +72,11 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { }) ).toMatchObject({}); }); + it('returns null if no locked deps', async () => { expect(await updateLockedDependency(config)).toMatchObject({}); }); + it('rejects null if no constraint found', async () => { expect( await updateLockedDependency({ @@ -82,6 +88,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { }) ).toMatchObject({}); }); + it('remediates in-range', async () => { const res = await updateLockedDependency({ ...config, @@ -93,6 +100,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { JSON.parse(res.files['package-lock.json']).dependencies.mime.version ).toBe('1.2.12'); }); + it('rejects in-range remediation if lockfile v2+', async () => { const res = await updateLockedDependency({ ...config, @@ -103,6 +111,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { }); expect(res.status).toBe('unsupported'); }); + it('fails to remediate if parent dep cannot support', async () => { const acceptsModified = clone(acceptsJson); acceptsModified.versions['2.0.0'] = {}; @@ -122,6 +131,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { }); expect(res).toMatchObject({}); }); + it('remediates express', async () => { config.depName = 'express'; config.currentVersion = '4.0.0'; @@ -131,6 +141,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { const packageLock = JSON.parse(res.files['package-lock.json']); expect(packageLock.dependencies.express.version).toBe('4.1.0'); }); + it('remediates lock file v2 express', async () => { config.depName = 'express'; config.currentVersion = '4.0.0'; @@ -141,6 +152,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { const packageLock = JSON.parse(res.files['package-lock.json']); expect(packageLock.dependencies.express.version).toBe('4.1.0'); }); + it('returns already-updated if already remediated exactly', async () => { config.depName = 'mime'; config.currentVersion = '1.2.10'; @@ -148,6 +160,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { const res = await updateLockedDependency(config); expect(res.status).toBe('already-updated'); }); + it('returns already-updated if already v2 remediated exactly', async () => { config.depName = 'mime'; config.currentVersion = '1.2.10'; @@ -156,6 +169,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { const res = await updateLockedDependency(config); expect(res.status).toBe('already-updated'); }); + it('returns already-updated if already remediated higher', async () => { config.depName = 'mime'; config.currentVersion = '1.2.9'; @@ -164,6 +178,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { const res = await updateLockedDependency(config); expect(res.status).toBe('already-updated'); }); + it('returns already-updated if not found', async () => { config.depName = 'notfound'; config.currentVersion = '1.2.9'; @@ -172,6 +187,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { const res = await updateLockedDependency(config); expect(res.status).toBe('already-updated'); }); + it('returns update-failed if other, lower version found', async () => { config.depName = 'mime'; config.currentVersion = '1.2.5'; @@ -180,6 +196,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { const res = await updateLockedDependency(config); expect(res.status).toBe('update-failed'); }); + it('remediates mime', async () => { config.depName = 'mime'; config.currentVersion = '1.2.11'; @@ -205,6 +222,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { expect(packageLock.dependencies.mime.version).toBe('1.4.1'); expect(packageLock.dependencies.express.version).toBe('4.16.0'); }); + it('fails remediation if cannot update parent', async () => { config.depName = 'mime'; config.currentVersion = '1.2.11'; @@ -213,6 +231,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { const res = await updateLockedDependency(config); expect(res.status).toBe('update-failed'); }); + it('fails remediation if bundled', async () => { config.depName = 'ansi-regex'; config.currentVersion = '3.0.0'; diff --git a/lib/modules/manager/npm/update/locked-dependency/package-lock/dep-constraints.spec.ts b/lib/modules/manager/npm/update/locked-dependency/package-lock/dep-constraints.spec.ts index d9f8626888..71af186f40 100644 --- a/lib/modules/manager/npm/update/locked-dependency/package-lock/dep-constraints.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/package-lock/dep-constraints.spec.ts @@ -25,6 +25,7 @@ describe('modules/manager/npm/update/locked-dependency/package-lock/dep-constrai }, ]); }); + it('finds direct dependency', () => { expect( findDepConstraints( @@ -36,6 +37,7 @@ describe('modules/manager/npm/update/locked-dependency/package-lock/dep-constrai ) ).toEqual([{ constraint: '4.0.0', depType: 'dependencies' }]); }); + it('skips non-matching direct dependency', () => { expect( findDepConstraints( @@ -47,6 +49,7 @@ describe('modules/manager/npm/update/locked-dependency/package-lock/dep-constrai ) ).toHaveLength(0); }); + it('finds direct devDependency', () => { const packageJsonDev = { ...packageJson }; packageJsonDev.devDependencies = packageJsonDev.dependencies; diff --git a/lib/modules/manager/npm/update/locked-dependency/package-lock/get-locked.spec.ts b/lib/modules/manager/npm/update/locked-dependency/package-lock/get-locked.spec.ts index 2f830df87c..22a7665d9a 100644 --- a/lib/modules/manager/npm/update/locked-dependency/package-lock/get-locked.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/package-lock/get-locked.spec.ts @@ -13,9 +13,11 @@ describe('modules/manager/npm/update/locked-dependency/package-lock/get-locked', [] ); }); + it('returns empty if failed to parse', () => { expect(getLockedDependencies({}, 'some-dep', '1.0.0')).toEqual([]); }); + it('finds direct dependency', () => { expect( getLockedDependencies(packageLockJson, 'express', '4.0.0') @@ -26,6 +28,7 @@ describe('modules/manager/npm/update/locked-dependency/package-lock/get-locked', }, ]); }); + it('finds indirect dependency', () => { expect( getLockedDependencies(packageLockJson, 'send', '0.2.0') @@ -36,11 +39,13 @@ describe('modules/manager/npm/update/locked-dependency/package-lock/get-locked', }, ]); }); + it('finds any version', () => { expect(getLockedDependencies(packageLockJson, 'send', null)).toHaveLength( 2 ); }); + it('finds bundled dependency', () => { expect( getLockedDependencies(bundledPackageLockJson, 'ansi-regex', '3.0.0') diff --git a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/get-locked.spec.ts b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/get-locked.spec.ts index c7712181f2..61b7b87a47 100644 --- a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/get-locked.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/get-locked.spec.ts @@ -24,6 +24,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/get-locked', () ] `); }); + it('finds scoped', () => { expect(getLockedDependencies(yarnLock3, '@actions/core', '1.6.0')) .toMatchInlineSnapshot(` diff --git a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.spec.ts b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.spec.ts index 242a6a2771..bc9f29e4d5 100644 --- a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/index.spec.ts @@ -8,17 +8,21 @@ const yarn2Lock = loadFixture('yarn2.lock'); describe('modules/manager/npm/update/locked-dependency/yarn-lock/index', () => { describe('updateLockedDependency()', () => { let config: UpdateLockedConfig; + beforeEach(() => { config = {}; }); + it('returns if cannot parse lock file', () => { config.lockFileContent = 'abc123'; expect(updateLockedDependency(config).status).toBe('update-failed'); }); + it('returns if yarn lock 2', () => { config.lockFileContent = yarn2Lock; expect(updateLockedDependency(config).status).toBe('unsupported'); }); + it('fails if cannot find dep', () => { config.lockFileContent = yarnLock1; config.depName = 'not-found'; @@ -26,6 +30,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/index', () => { config.newVersion = '1.0.1'; expect(updateLockedDependency(config).status).toBe('update-failed'); }); + it('returns already-updated', () => { config.lockFileContent = yarnLock1; config.depName = 'range-parser'; @@ -33,6 +38,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/index', () => { config.newVersion = '1.0.3'; expect(updateLockedDependency(config).status).toBe('already-updated'); }); + it('fails if cannot update dep in-range', () => { config.lockFileContent = yarnLock1; config.depName = 'send'; @@ -40,6 +46,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/index', () => { config.newVersion = '0.2.0'; expect(updateLockedDependency(config).status).toBe('update-failed'); }); + it('succeeds if can update within range', () => { config.lockFileContent = yarnLock1; config.depName = 'negotiator'; diff --git a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/replace.spec.ts b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/replace.spec.ts index 74b80110f3..2145c20d1e 100644 --- a/lib/modules/manager/npm/update/locked-dependency/yarn-lock/replace.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/yarn-lock/replace.spec.ts @@ -17,6 +17,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/replace', () => ); expect(res).toBe(yarn2Lock); }); + it('replaces without dependencies', () => { const res = replaceConstraintVersion( yarnLock1, @@ -41,6 +42,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/replace', () => " `); }); + it('replaces with dependencies', () => { const res = replaceConstraintVersion( yarnLock1, @@ -65,6 +67,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/replace', () => " `); }); + it('replaces constraint too', () => { const res = replaceConstraintVersion( yarnLock1, @@ -92,6 +95,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/replace', () => " `); }); + it('handles escaped constraints', () => { const res = replaceConstraintVersion( yarnLock2, @@ -117,6 +121,7 @@ describe('modules/manager/npm/update/locked-dependency/yarn-lock/replace', () => " `); }); + it('handles quoted', () => { const res = replaceConstraintVersion( yarnLock2, diff --git a/lib/modules/manager/npm/update/package-version/index.spec.ts b/lib/modules/manager/npm/update/package-version/index.spec.ts index 3a83548152..4de57ba062 100644 --- a/lib/modules/manager/npm/update/package-version/index.spec.ts +++ b/lib/modules/manager/npm/update/package-version/index.spec.ts @@ -7,6 +7,7 @@ describe('modules/manager/npm/update/package-version/index', () => { version: '0.0.2', dependencies: { chalk: '2.4.2' }, }); + it('mirrors', () => { const { bumpedContent } = npmUpdater.bumpPackageVersion( content, @@ -16,6 +17,7 @@ describe('modules/manager/npm/update/package-version/index', () => { expect(bumpedContent).toMatchSnapshot(); expect(bumpedContent).not.toEqual(content); }); + it('aborts mirror', () => { const { bumpedContent } = npmUpdater.bumpPackageVersion( content, @@ -24,6 +26,7 @@ describe('modules/manager/npm/update/package-version/index', () => { ); expect(bumpedContent).toEqual(content); }); + it('increments', () => { const { bumpedContent } = npmUpdater.bumpPackageVersion( content, @@ -33,6 +36,7 @@ describe('modules/manager/npm/update/package-version/index', () => { expect(bumpedContent).toMatchSnapshot(); expect(bumpedContent).not.toEqual(content); }); + it('no ops', () => { const { bumpedContent } = npmUpdater.bumpPackageVersion( content, @@ -41,6 +45,7 @@ describe('modules/manager/npm/update/package-version/index', () => { ); expect(bumpedContent).toEqual(content); }); + it('updates', () => { const { bumpedContent } = npmUpdater.bumpPackageVersion( content, @@ -50,6 +55,7 @@ describe('modules/manager/npm/update/package-version/index', () => { expect(bumpedContent).toMatchSnapshot(); expect(bumpedContent).not.toEqual(content); }); + it('returns content if bumping errors', async () => { jest.mock('semver', () => ({ inc: () => { diff --git a/lib/modules/manager/nuget/artifacts.spec.ts b/lib/modules/manager/nuget/artifacts.spec.ts index 3d9d356bf7..ae3896eed2 100644 --- a/lib/modules/manager/nuget/artifacts.spec.ts +++ b/lib/modules/manager/nuget/artifacts.spec.ts @@ -72,6 +72,7 @@ describe('modules/manager/nuget/artifacts', () => { ).toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('aborts if lock file is unchanged', async () => { const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); @@ -87,6 +88,7 @@ describe('modules/manager/nuget/artifacts', () => { ).toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('updates lock file', async () => { const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); @@ -102,6 +104,7 @@ describe('modules/manager/nuget/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('does not update lock file when non-proj file is changed', async () => { const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); @@ -117,6 +120,7 @@ describe('modules/manager/nuget/artifacts', () => { ).toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('does not update lock file when no deps changed', async () => { const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); @@ -132,6 +136,7 @@ describe('modules/manager/nuget/artifacts', () => { ).toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('performs lock file maintenance', async () => { const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); @@ -167,6 +172,7 @@ describe('modules/manager/nuget/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('supports global mode', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'global' }); const execSnapshots = mockExecAll(exec); @@ -183,6 +189,7 @@ describe('modules/manager/nuget/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('catches errors', async () => { fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); fs.readLocalFile.mockResolvedValueOnce('Current packages.lock.json' as any); @@ -205,6 +212,7 @@ describe('modules/manager/nuget/artifacts', () => { }, ]); }); + it('authenticates at registries', async () => { const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); @@ -238,6 +246,7 @@ describe('modules/manager/nuget/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('strips protocol version from feed url', async () => { const execSnapshots = mockExecAll(exec); fs.getSiblingFileName.mockReturnValueOnce('packages.lock.json'); diff --git a/lib/modules/manager/nuget/extract.spec.ts b/lib/modules/manager/nuget/extract.spec.ts index 7b332c82bb..ff321d4518 100644 --- a/lib/modules/manager/nuget/extract.spec.ts +++ b/lib/modules/manager/nuget/extract.spec.ts @@ -16,14 +16,17 @@ describe('modules/manager/nuget/extract', () => { beforeEach(() => { GlobalConfig.set(adminConfig); }); + afterEach(() => { GlobalConfig.reset(); }); + it('returns empty for invalid csproj', async () => { expect(await extractPackageFile('nothing here', 'bogus', config)).toEqual( { deps: [] } ); }); + it('extracts package version dependency', async () => { const packageFile = 'with-centralized-package-versions/Directory.Packages.props'; @@ -32,6 +35,7 @@ describe('modules/manager/nuget/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('extracts all dependencies', async () => { const packageFile = 'sample.csproj'; const sample = loadFixture(packageFile); @@ -39,6 +43,7 @@ describe('modules/manager/nuget/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(17); }); + it('extracts all dependencies from global packages file', async () => { const packageFile = 'packages.props'; const sample = loadFixture(packageFile); @@ -46,6 +51,7 @@ describe('modules/manager/nuget/extract', () => { expect(res.deps).toMatchSnapshot(); expect(res.deps).toHaveLength(17); }); + it('considers NuGet.config', async () => { const packageFile = 'with-config-file/with-config-file.csproj'; const contents = loadFixture(packageFile); @@ -64,6 +70,7 @@ describe('modules/manager/nuget/extract', () => { ], }); }); + it('considers lower-case nuget.config', async () => { const packageFile = 'with-lower-case-config-file/with-lower-case-config-file.csproj'; @@ -83,6 +90,7 @@ describe('modules/manager/nuget/extract', () => { ], }); }); + it('considers pascal-case NuGet.Config', async () => { const packageFile = 'with-pascal-case-config-file/with-pascal-case-config-file.csproj'; @@ -102,6 +110,7 @@ describe('modules/manager/nuget/extract', () => { ], }); }); + it('handles malformed NuGet.config', async () => { const packageFile = 'with-malformed-config-file/with-malformed-config-file.csproj'; @@ -117,6 +126,7 @@ describe('modules/manager/nuget/extract', () => { ], }); }); + it('handles NuGet.config without package sources', async () => { const packageFile = 'without-package-sources/without-package-sources.csproj'; @@ -168,6 +178,7 @@ describe('modules/manager/nuget/extract', () => { ], }); }); + it('extracts registry URLs independently', async () => { const packageFile = 'multiple-package-files/one/one.csproj'; const contents = loadFixture(packageFile); diff --git a/lib/modules/manager/nvm/extract.spec.ts b/lib/modules/manager/nvm/extract.spec.ts index a170e2a528..2f98f18c07 100644 --- a/lib/modules/manager/nvm/extract.spec.ts +++ b/lib/modules/manager/nvm/extract.spec.ts @@ -13,6 +13,7 @@ describe('modules/manager/nvm/extract', () => { }, ]); }); + it('supports ranges', () => { const res = extractPackageFile('8.4\n'); expect(res.deps).toEqual([ @@ -24,6 +25,7 @@ describe('modules/manager/nvm/extract', () => { }, ]); }); + it('skips non ranges', () => { const res = extractPackageFile('latestn'); expect(res.deps).toEqual([ diff --git a/lib/modules/manager/pip_requirements/extract.spec.ts b/lib/modules/manager/pip_requirements/extract.spec.ts index 4f7192a812..044a8ca3d0 100644 --- a/lib/modules/manager/pip_requirements/extract.spec.ts +++ b/lib/modules/manager/pip_requirements/extract.spec.ts @@ -18,32 +18,39 @@ describe('modules/manager/pip_requirements/extract', () => { delete process.env.PIP_TEST_TOKEN; GlobalConfig.reset(); }); + afterEach(() => { delete process.env.PIP_TEST_TOKEN; GlobalConfig.reset(); }); + describe('extractPackageFile()', () => { let config; const OLD_ENV = process.env; + beforeEach(() => { config = { registryUrls: ['AnExistingDefaultUrl'] }; process.env = { ...OLD_ENV }; delete process.env.PIP_INDEX_URL; }); + afterEach(() => { process.env = OLD_ENV; }); + it('returns null for empty', () => { expect( extractPackageFile('nothing here', 'requirements.txt', config) ).toBeNull(); }); + it('extracts dependencies', () => { const res = extractPackageFile(requirements1, 'unused_file_name', config); expect(res).toMatchSnapshot(); expect(res.registryUrls).toEqual(['http://example.com/private-pypi/']); expect(res.deps).toHaveLength(4); }); + it('extracts multiple dependencies', () => { const res = extractPackageFile( requirements2, @@ -53,6 +60,7 @@ describe('modules/manager/pip_requirements/extract', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(5); }); + it('handles comments and commands', () => { const res = extractPackageFile( requirements3, @@ -62,6 +70,7 @@ describe('modules/manager/pip_requirements/extract', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(5); }); + it('handles extras and complex index url', () => { const res = extractPackageFile(requirements4, 'unused_file_name', config); expect(res).toMatchSnapshot(); @@ -70,6 +79,7 @@ describe('modules/manager/pip_requirements/extract', () => { ]); expect(res.deps).toHaveLength(3); }); + it('handles extra index url', () => { const res = extractPackageFile(requirements5, 'unused_file_name', config); expect(res).toMatchSnapshot(); @@ -79,6 +89,7 @@ describe('modules/manager/pip_requirements/extract', () => { ]); expect(res.deps).toHaveLength(6); }); + it('handles extra index url and defaults without index to config', () => { const res = extractPackageFile(requirements6, 'unused_file_name', config); expect(res).toMatchSnapshot(); @@ -88,6 +99,7 @@ describe('modules/manager/pip_requirements/extract', () => { ]); expect(res.deps).toHaveLength(6); }); + it('handles extra index url and defaults without index to pypi', () => { const res = extractPackageFile(requirements6, 'unused_file_name', {}); expect(res).toMatchSnapshot(); @@ -111,6 +123,7 @@ describe('modules/manager/pip_requirements/extract', () => { expect(res.deps).toHaveLength(3); }); + it('should not replace env vars in low trust mode', () => { process.env.PIP_TEST_TOKEN = 'its-a-secret'; const res = extractPackageFile(requirements7, 'unused_file_name', {}); @@ -124,6 +137,7 @@ describe('modules/manager/pip_requirements/extract', () => { 'http://${PIP_TEST_TOKEN}:example.com/private-pypi/', ]); }); + it('should replace env vars in high trust mode', () => { process.env.PIP_TEST_TOKEN = 'its-a-secret'; GlobalConfig.set({ exposeAllEnv: true }); @@ -160,6 +174,7 @@ describe('modules/manager/pip_requirements/extract', () => { ], }); }); + it('should handle git packages', () => { const res = extractPackageFile( requirementsGitPackages, diff --git a/lib/modules/manager/pip_requirements/range.spec.ts b/lib/modules/manager/pip_requirements/range.spec.ts index eca3f7e0da..990d06cfd1 100644 --- a/lib/modules/manager/pip_requirements/range.spec.ts +++ b/lib/modules/manager/pip_requirements/range.spec.ts @@ -6,6 +6,7 @@ describe('modules/manager/pip_requirements/range', () => { const config: RangeConfig = { rangeStrategy: 'widen' }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('pins if auto', () => { const config: RangeConfig = { rangeStrategy: 'auto' }; expect(getRangeStrategy(config)).toBe('pin'); diff --git a/lib/modules/manager/pipenv/artifacts.spec.ts b/lib/modules/manager/pipenv/artifacts.spec.ts index de61e8e635..5b9679d896 100644 --- a/lib/modules/manager/pipenv/artifacts.spec.ts +++ b/lib/modules/manager/pipenv/artifacts.spec.ts @@ -27,6 +27,7 @@ const lockMaintenanceConfig = { ...config, isLockFileMaintenance: true }; describe('modules/manager/pipenv/artifacts', () => { let pipFileLock; + beforeEach(() => { jest.resetAllMocks(); env.getChildProcessEnv.mockReturnValue({ diff --git a/lib/modules/manager/pipenv/extract.spec.ts b/lib/modules/manager/pipenv/extract.spec.ts index e1c9f68274..b8c4efb7b3 100644 --- a/lib/modules/manager/pipenv/extract.spec.ts +++ b/lib/modules/manager/pipenv/extract.spec.ts @@ -14,9 +14,11 @@ describe('modules/manager/pipenv/extract', () => { it('returns null for empty', async () => { expect(await extractPackageFile('[packages]\r\n', 'Pipfile')).toBeNull(); }); + it('returns null for invalid toml file', async () => { expect(await extractPackageFile('nothing here', 'Pipfile')).toBeNull(); }); + it('extracts dependencies', async () => { fsutil.localPathExists.mockResolvedValueOnce(true); const res = await extractPackageFile(pipfile1, 'Pipfile'); @@ -24,40 +26,47 @@ describe('modules/manager/pipenv/extract', () => { expect(res.deps).toHaveLength(6); expect(res.deps.filter((dep) => !dep.skipReason)).toHaveLength(4); }); + it('marks packages with "extras" as skipReason === any-version', async () => { const res = await extractPackageFile(pipfile3, 'Pipfile'); expect(res.deps.filter((r) => !r.skipReason)).toHaveLength(0); expect(res.deps.filter((r) => r.skipReason)).toHaveLength(6); }); + it('extracts multiple dependencies', async () => { fsutil.localPathExists.mockResolvedValueOnce(true); const res = await extractPackageFile(pipfile2, 'Pipfile'); expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(5); }); + it('ignores git dependencies', async () => { const content = '[packages]\r\nflask = {git = "https://github.com/pallets/flask.git"}\r\nwerkzeug = ">=0.14"'; const res = await extractPackageFile(content, 'Pipfile'); expect(res.deps.filter((r) => !r.skipReason)).toHaveLength(1); }); + it('ignores invalid package names', async () => { const content = '[packages]\r\nfoo = "==1.0.0"\r\n_invalid = "==1.0.0"'; const res = await extractPackageFile(content, 'Pipfile'); expect(res.deps).toHaveLength(2); expect(res.deps.filter((dep) => !dep.skipReason)).toHaveLength(1); }); + it('ignores relative path dependencies', async () => { const content = '[packages]\r\nfoo = "==1.0.0"\r\ntest = {path = "."}'; const res = await extractPackageFile(content, 'Pipfile'); expect(res.deps.filter((r) => !r.skipReason)).toHaveLength(1); }); + it('ignores invalid versions', async () => { const content = '[packages]\r\nfoo = "==1.0.0"\r\nsome-package = "==0 0"'; const res = await extractPackageFile(content, 'Pipfile'); expect(res.deps).toHaveLength(2); expect(res.deps.filter((dep) => !dep.skipReason)).toHaveLength(1); }); + it('extracts all sources', async () => { const content = '[[source]]\r\nurl = "source-url"\r\n' + @@ -66,6 +75,7 @@ describe('modules/manager/pipenv/extract', () => { const res = await extractPackageFile(content, 'Pipfile'); expect(res.registryUrls).toEqual(['source-url', 'other-source-url']); }); + it('extracts example pipfile', async () => { fsutil.localPathExists.mockResolvedValueOnce(true); const res = await extractPackageFile(pipfile4, 'Pipfile'); @@ -96,6 +106,7 @@ describe('modules/manager/pipenv/extract', () => { registryUrls: ['https://pypi.python.org/simple'], }); }); + it('supports custom index', async () => { fsutil.localPathExists.mockResolvedValueOnce(true); const res = await extractPackageFile(pipfile5, 'Pipfile'); @@ -105,6 +116,7 @@ describe('modules/manager/pipenv/extract', () => { expect(res.deps[0].registryUrls).toBeDefined(); expect(res.deps[0].registryUrls).toHaveLength(1); }); + it('gets python constraint from python_version', async () => { const content = '[packages]\r\nfoo = "==1.0.0"\r\n' + @@ -112,6 +124,7 @@ describe('modules/manager/pipenv/extract', () => { const res = await extractPackageFile(content, 'Pipfile'); expect(res.constraints.python).toBe('== 3.8.*'); }); + it('gets python constraint from python_full_version', async () => { const content = '[packages]\r\nfoo = "==1.0.0"\r\n' + @@ -119,11 +132,13 @@ describe('modules/manager/pipenv/extract', () => { const res = await extractPackageFile(content, 'Pipfile'); expect(res.constraints.python).toBe('== 3.8.6'); }); + it('gets pipenv constraint from packages', async () => { const content = '[packages]\r\npipenv = "==2020.8.13"'; const res = await extractPackageFile(content, 'Pipfile'); expect(res.constraints.pipenv).toBe('==2020.8.13'); }); + it('gets pipenv constraint from dev-packages', async () => { const content = '[dev-packages]\r\npipenv = "==2020.8.13"'; const res = await extractPackageFile(content, 'Pipfile'); diff --git a/lib/modules/manager/poetry/artifacts.spec.ts b/lib/modules/manager/poetry/artifacts.spec.ts index 2ab5eb3f99..3f29846ba5 100644 --- a/lib/modules/manager/poetry/artifacts.spec.ts +++ b/lib/modules/manager/poetry/artifacts.spec.ts @@ -40,6 +40,7 @@ describe('modules/manager/poetry/artifacts', () => { GlobalConfig.set(adminConfig); docker.resetPrefetchedImages(); }); + it('returns null if no poetry.lock found', async () => { const updatedDeps = [{ depName: 'dep1' }]; expect( @@ -51,6 +52,7 @@ describe('modules/manager/poetry/artifacts', () => { }) ).toBeNull(); }); + it('returns null if updatedDeps is empty', async () => { expect( await updateArtifacts({ @@ -61,6 +63,7 @@ describe('modules/manager/poetry/artifacts', () => { }) ).toBeNull(); }); + it('returns null if unchanged', async () => { fs.readFile.mockReturnValueOnce('Current poetry.lock' as any); const execSnapshots = mockExecAll(exec); @@ -76,6 +79,7 @@ describe('modules/manager/poetry/artifacts', () => { ).toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('returns updated poetry.lock', async () => { fs.readFile.mockResolvedValueOnce('[metadata]\n' as never); const execSnapshots = mockExecAll(exec); @@ -91,6 +95,7 @@ describe('modules/manager/poetry/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('passes private credential environment vars', async () => { fs.readFile.mockResolvedValueOnce(null); fs.readFile.mockResolvedValueOnce('[metadata]\n' as never); @@ -115,6 +120,7 @@ describe('modules/manager/poetry/artifacts', () => { expect(hostRules.find.mock.calls).toHaveLength(4); expect(execSnapshots).toMatchSnapshot(); }); + it('prioritizes pypi-scoped credentials', async () => { fs.readFile.mockResolvedValueOnce(null); fs.readFile.mockResolvedValueOnce(Buffer.from('[metadata]\n')); @@ -139,6 +145,7 @@ describe('modules/manager/poetry/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('returns updated pyproject.lock', async () => { fs.readFile.mockResolvedValueOnce(null); fs.readFile.mockResolvedValueOnce('[metadata]\n' as never); @@ -155,6 +162,7 @@ describe('modules/manager/poetry/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('returns updated poetry.lock using docker', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); // poetry.lock @@ -189,6 +197,7 @@ describe('modules/manager/poetry/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('returns updated poetry.lock using docker (constraints)', async () => { GlobalConfig.set({ ...adminConfig, binarySource: 'docker' }); // poetry.lock @@ -223,6 +232,7 @@ describe('modules/manager/poetry/artifacts', () => { ).not.toBeNull(); expect(execSnapshots).toMatchSnapshot(); }); + it('catches errors', async () => { fs.readFile.mockResolvedValueOnce('Current poetry.lock' as any); fs.outputFile.mockImplementationOnce(() => { @@ -238,6 +248,7 @@ describe('modules/manager/poetry/artifacts', () => { }) ).toMatchSnapshot([{ artifactError: { lockFile: 'poetry.lock' } }]); }); + it('returns updated poetry.lock when doing lockfile maintenance', async () => { fs.readFile.mockResolvedValueOnce('Old poetry.lock' as any); const execSnapshots = mockExecAll(exec); diff --git a/lib/modules/manager/poetry/extract.spec.ts b/lib/modules/manager/poetry/extract.spec.ts index 4a0d4c1343..45b911df62 100644 --- a/lib/modules/manager/poetry/extract.spec.ts +++ b/lib/modules/manager/poetry/extract.spec.ts @@ -21,20 +21,25 @@ describe('modules/manager/poetry/extract', () => { describe('extractPackageFile()', () => { let filename: string; const OLD_ENV = process.env; + beforeEach(() => { filename = ''; process.env = { ...OLD_ENV }; delete process.env.PIP_INDEX_URL; }); + afterEach(() => { process.env = OLD_ENV; }); + it('returns null for empty', async () => { expect(await extractPackageFile('nothing here', filename)).toBeNull(); }); + it('returns null for parsed file without poetry section', async () => { expect(await extractPackageFile(pyproject5toml, filename)).toBeNull(); }); + it('extracts multiple dependencies', async () => { const res = await extractPackageFile(pyproject1toml, filename); expect(res.deps).toMatchSnapshot(); @@ -43,39 +48,47 @@ describe('modules/manager/poetry/extract', () => { python: '~2.7 || ^3.4', }); }); + it('extracts multiple dependencies (with dep = {version = "1.2.3"} case)', async () => { const res = await extractPackageFile(pyproject2toml, filename); expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(7); }); + it('handles case with no dependencies', async () => { const res = await extractPackageFile(pyproject3toml, filename); expect(res).toBeNull(); }); + it('handles multiple constraint dependencies', async () => { const res = await extractPackageFile(pyproject4toml, filename); expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('extracts registries', async () => { const res = await extractPackageFile(pyproject6toml, filename); expect(res.registryUrls).toMatchSnapshot(); expect(res.registryUrls).toHaveLength(3); }); + it('can parse empty registries', async () => { const res = await extractPackageFile(pyproject7toml, filename); expect(res.registryUrls).toBeNull(); }); + it('can parse missing registries', async () => { const res = await extractPackageFile(pyproject1toml, filename); expect(res.registryUrls).toBeNull(); }); + it('dedupes registries', async () => { const res = await extractPackageFile(pyproject8toml, filename); expect(res).toMatchObject({ registryUrls: ['https://pypi.org/pypi/', 'https://bar.baz/+simple/'], }); }); + it('extracts mixed versioning types', async () => { const res = await extractPackageFile(pyproject9toml, filename); expect(res).toMatchSnapshot({ @@ -117,6 +130,7 @@ describe('modules/manager/poetry/extract', () => { ], }); }); + it('resolves lockedVersions from the lockfile', async () => { fs.readLocalFile.mockResolvedValue(pyproject11tomlLock); const res = await extractPackageFile(pyproject11toml, filename); @@ -125,6 +139,7 @@ describe('modules/manager/poetry/extract', () => { deps: [{ lockedVersion: '1.17.5' }], }); }); + it('skips git dependencies', async () => { const content = '[tool.poetry.dependencies]\r\nflask = {git = "https://github.com/pallets/flask.git"}\r\nwerkzeug = ">=0.14"'; @@ -134,6 +149,7 @@ describe('modules/manager/poetry/extract', () => { expect(res[0].skipReason).toBe('git-dependency'); expect(res).toHaveLength(2); }); + it('skips git dependencies with version', async () => { const content = '[tool.poetry.dependencies]\r\nflask = {git = "https://github.com/pallets/flask.git", version="1.2.3"}\r\nwerkzeug = ">=0.14"'; @@ -143,6 +159,7 @@ describe('modules/manager/poetry/extract', () => { expect(res[0].skipReason).toBe('git-dependency'); expect(res).toHaveLength(2); }); + it('skips path dependencies', async () => { const content = '[tool.poetry.dependencies]\r\nflask = {path = "/some/path/"}\r\nwerkzeug = ">=0.14"'; @@ -152,6 +169,7 @@ describe('modules/manager/poetry/extract', () => { expect(res[0].skipReason).toBe('path-dependency'); expect(res).toHaveLength(2); }); + it('skips path dependencies with version', async () => { const content = '[tool.poetry.dependencies]\r\nflask = {path = "/some/path/", version = "1.2.3"}\r\nwerkzeug = ">=0.14"'; diff --git a/lib/modules/manager/poetry/update-locked.spec.ts b/lib/modules/manager/poetry/update-locked.spec.ts index 1fc3bff433..3cc91943b4 100644 --- a/lib/modules/manager/poetry/update-locked.spec.ts +++ b/lib/modules/manager/poetry/update-locked.spec.ts @@ -13,6 +13,7 @@ describe('modules/manager/poetry/update-locked', () => { }; expect(updateLockedDependency(config).status).toBe('already-updated'); }); + it('returns unsupported', () => { const config: UpdateLockedConfig = { lockFileContent, diff --git a/lib/modules/manager/pre-commit/extract.spec.ts b/lib/modules/manager/pre-commit/extract.spec.ts index ccc6e15579..215ad73cc8 100644 --- a/lib/modules/manager/pre-commit/extract.spec.ts +++ b/lib/modules/manager/pre-commit/extract.spec.ts @@ -24,30 +24,37 @@ describe('modules/manager/pre-commit/extract', () => { beforeEach(() => { jest.resetAllMocks(); }); + it('returns null for invalid yaml file content', () => { const result = extractPackageFile('nothing here: [', filename); expect(result).toBeNull(); }); + it('returns null for empty yaml file content', () => { const result = extractPackageFile('', filename); expect(result).toBeNull(); }); + it('returns null for no file content', () => { const result = extractPackageFile(null, filename); expect(result).toBeNull(); }); + it('returns null for no repos', () => { const result = extractPackageFile(noReposPrecommitConfig, filename); expect(result).toBeNull(); }); + it('returns null for empty repos', () => { const result = extractPackageFile(emptyReposPrecommitConfig, filename); expect(result).toBeNull(); }); + it('returns null for invalid repo', () => { const result = extractPackageFile(invalidRepoPrecommitConfig, filename); expect(result).toBeNull(); }); + it('extracts from values.yaml correctly with same structure as "pre-commit sample-config"', () => { const result = extractPackageFile(examplePrecommitConfig, filename); expect(result).toEqual({ @@ -69,6 +76,7 @@ describe('modules/manager/pre-commit/extract', () => { ], }); }); + it('extracts from complex config file correctly', () => { const result = extractPackageFile(complexPrecommitConfig, filename); expect(result).toMatchSnapshot({ @@ -83,6 +91,7 @@ describe('modules/manager/pre-commit/extract', () => { ], }); }); + it('can handle private git repos', () => { hostRules.find.mockReturnValue({ token: 'value' }); const result = extractPackageFile(enterpriseGitPrecommitConfig, filename); @@ -99,6 +108,7 @@ describe('modules/manager/pre-commit/extract', () => { ], }); }); + it('can handle invalid private git repos', () => { hostRules.find.mockReturnValue({}); const result = extractPackageFile(enterpriseGitPrecommitConfig, filename); @@ -115,6 +125,7 @@ describe('modules/manager/pre-commit/extract', () => { ], }); }); + it('can handle unknown private git repos', () => { // First attemp returns a result hostRules.find.mockReturnValueOnce({ token: 'value' }); diff --git a/lib/modules/manager/pub/extract.spec.ts b/lib/modules/manager/pub/extract.spec.ts index 354b12c938..4d0b5eec6b 100644 --- a/lib/modules/manager/pub/extract.spec.ts +++ b/lib/modules/manager/pub/extract.spec.ts @@ -7,6 +7,7 @@ describe('modules/manager/pub/extract', () => { const res = extractPackageFile('foo: bar', 'pubspec.yaml'); expect(res).toBeNull(); }); + it('should return null if package is invalid', () => { const res = extractPackageFile( Fixtures.get('update.yaml'), @@ -14,6 +15,7 @@ describe('modules/manager/pub/extract', () => { ); expect(res).toBeNull(); }); + it('should return valid dependencies', () => { const res = extractPackageFile( Fixtures.get('extract.yaml'), diff --git a/lib/modules/manager/pyenv/extract.spec.ts b/lib/modules/manager/pyenv/extract.spec.ts index cc1f7d92aa..4608069ae7 100644 --- a/lib/modules/manager/pyenv/extract.spec.ts +++ b/lib/modules/manager/pyenv/extract.spec.ts @@ -8,12 +8,14 @@ describe('modules/manager/pyenv/extract', () => { { depName: 'python', currentValue: '3.7.1', datasource: 'docker' }, ]); }); + it('supports ranges', () => { const res = extractPackageFile('3.8\n'); expect(res.deps).toEqual([ { depName: 'python', currentValue: '3.8', datasource: 'docker' }, ]); }); + it('skips non ranges', () => { const res = extractPackageFile('latestn'); expect(res.deps).toEqual([ diff --git a/lib/modules/manager/range.spec.ts b/lib/modules/manager/range.spec.ts index 98dc72f48d..1deed59061 100644 --- a/lib/modules/manager/range.spec.ts +++ b/lib/modules/manager/range.spec.ts @@ -9,6 +9,7 @@ describe('modules/manager/range', () => { }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('returns manager strategy', () => { const config: RangeConfig = { manager: 'npm', @@ -18,6 +19,7 @@ describe('modules/manager/range', () => { }; expect(getRangeStrategy(config)).toBe('pin'); }); + it('defaults to replace', () => { const config: RangeConfig = { manager: 'circleci', @@ -25,6 +27,7 @@ describe('modules/manager/range', () => { }; expect(getRangeStrategy(config)).toBe('replace'); }); + it('returns rangeStrategy if not auto', () => { const config: RangeConfig = { manager: 'circleci', diff --git a/lib/modules/manager/regex/index.spec.ts b/lib/modules/manager/regex/index.spec.ts index 701026fd34..88ab4d4c46 100644 --- a/lib/modules/manager/regex/index.spec.ts +++ b/lib/modules/manager/regex/index.spec.ts @@ -14,6 +14,7 @@ describe('modules/manager/regex/index', () => { pinDigests: false, }); }); + it('extracts multiple dependencies', async () => { const config = { matchStrings: [ @@ -38,6 +39,7 @@ describe('modules/manager/regex/index', () => { ); expect(res.deps.filter((dep) => dep.depType === 'final')).toHaveLength(8); }); + it('returns null if no dependencies found', async () => { const config = { matchStrings: [ @@ -49,6 +51,7 @@ describe('modules/manager/regex/index', () => { const res = await extractPackageFile('', 'Dockerfile', config); expect(res).toBeNull(); }); + it('returns null if invalid template', async () => { const config = { matchStrings: [ @@ -63,6 +66,7 @@ describe('modules/manager/regex/index', () => { ); expect(res).toBeNull(); }); + it('extracts extractVersion', async () => { const config = { matchStrings: [ @@ -82,6 +86,7 @@ describe('modules/manager/regex/index', () => { ).extractVersion ).toBe('^v(?<version>.*)$'); }); + it('extracts registryUrl', async () => { const config = { matchStrings: [ @@ -117,6 +122,7 @@ describe('modules/manager/regex/index', () => { ], }); }); + it('extracts and applies a registryUrlTemplate', async () => { const config = { matchStrings: [ @@ -135,6 +141,7 @@ describe('modules/manager/regex/index', () => { res.deps.find((dep) => dep.depName === 'gradle').registryUrls ).toEqual(['http://registry.gradle.com/']); }); + it('extracts and does not apply a registryUrlTemplate if the result is an invalid url', async () => { jest.mock('../../../logger'); const config = { @@ -163,6 +170,7 @@ describe('modules/manager/regex/index', () => { 'Invalid regex manager registryUrl' ); }); + it('extracts multiple dependencies with multiple matchStrings', async () => { const config = { matchStrings: [ @@ -262,6 +270,7 @@ describe('modules/manager/regex/index', () => { expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('extracts with combination strategy and registry url', async () => { const config: CustomExtractConfig = { matchStringsStrategy: 'combination', @@ -330,6 +339,7 @@ describe('modules/manager/regex/index', () => { expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('extracts with recursive strategy and multiple matches', async () => { const config: CustomExtractConfig = { matchStrings: [ @@ -346,6 +356,7 @@ describe('modules/manager/regex/index', () => { expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(2); }); + it('extracts with recursive strategy and multiple layers ', async () => { const config: CustomExtractConfig = { matchStrings: [ @@ -363,6 +374,7 @@ describe('modules/manager/regex/index', () => { expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(1); }); + it('extracts with recursive strategy and fail because of not sufficient regexes', async () => { const config: CustomExtractConfig = { matchStrings: ['"group.{1}":\\s*\\{[^}]*}'], @@ -375,6 +387,7 @@ describe('modules/manager/regex/index', () => { ); expect(res).toBeNull(); }); + it('extracts with recursive strategy and fail because there is no match', async () => { const config: CustomExtractConfig = { matchStrings: ['"trunk.{1}":\\s*\\{[^}]*}'], @@ -387,6 +400,7 @@ describe('modules/manager/regex/index', () => { ); expect(res).toBeNull(); }); + it('extracts with recursive strategy and merged groups', async () => { const config: CustomExtractConfig = { matchStrings: [ diff --git a/lib/modules/manager/ruby-version/extract.spec.ts b/lib/modules/manager/ruby-version/extract.spec.ts index 11ba9cc962..f75ecece63 100644 --- a/lib/modules/manager/ruby-version/extract.spec.ts +++ b/lib/modules/manager/ruby-version/extract.spec.ts @@ -12,6 +12,7 @@ describe('modules/manager/ruby-version/extract', () => { }, ]); }); + it('supports ranges', () => { const res = extractPackageFile('8.4\n'); expect(res.deps).toEqual([ @@ -22,6 +23,7 @@ describe('modules/manager/ruby-version/extract', () => { }, ]); }); + it('skips non ranges', () => { const res = extractPackageFile('latestn'); expect(res.deps).toEqual([ diff --git a/lib/modules/manager/sbt/extract.spec.ts b/lib/modules/manager/sbt/extract.spec.ts index fbe46dfae8..7b5643d5a0 100644 --- a/lib/modules/manager/sbt/extract.spec.ts +++ b/lib/modules/manager/sbt/extract.spec.ts @@ -36,6 +36,7 @@ describe('modules/manager/sbt/extract', () => { extractPackageFile('libraryDependencies += "foo" % "bar" % "baz" %%') ).toBeNull(); }); + it('extracts deps for generic use-cases', () => { expect(extractPackageFile(sbt)).toMatchSnapshot({ deps: [ @@ -61,6 +62,7 @@ describe('modules/manager/sbt/extract', () => { packageFileVersion: '1.0', }); }); + it('extracts deps when scala version is defined in a variable', () => { expect(extractPackageFile(sbtScalaVersionVariable)).toMatchSnapshot({ deps: [ @@ -78,6 +80,7 @@ describe('modules/manager/sbt/extract', () => { packageFileVersion: '3.2.1', }); }); + it('skips deps when scala version is missing', () => { expect(extractPackageFile(sbtMissingScalaVersion)).toEqual({ deps: [ @@ -104,6 +107,7 @@ describe('modules/manager/sbt/extract', () => { packageFileVersion: '1.0.1', }); }); + it('extract deps from native scala file with variables', () => { expect(extractPackageFile(sbtDependencyFile)).toMatchSnapshot({ deps: [ @@ -122,6 +126,7 @@ describe('modules/manager/sbt/extract', () => { ], }); }); + it('extracts deps when scala version is defined with a trailing comma', () => { const content = ` lazy val commonSettings = Seq( @@ -142,6 +147,7 @@ describe('modules/manager/sbt/extract', () => { ], }); }); + it('extracts deps when scala version is defined in a variable with a trailing comma', () => { const content = ` val ScalaVersion = "2.12.10" @@ -154,6 +160,7 @@ describe('modules/manager/sbt/extract', () => { deps: [{ packageName: 'org.example:bar_2.12', currentValue: '0.0.2' }], }); }); + it('extracts deps when scala version is defined with ThisBuild scope', () => { const content = ` ThisBuild / scalaVersion := "2.12.10" @@ -172,6 +179,7 @@ describe('modules/manager/sbt/extract', () => { ], }); }); + it('extracts deps when scala version is defined in a variable with ThisBuild scope', () => { const content = ` val ScalaVersion = "2.12.10" @@ -187,6 +195,7 @@ describe('modules/manager/sbt/extract', () => { ], }); }); + it('extract deps from native scala file with private variables', () => { expect( extractPackageFile(sbtPrivateVariableDependencyFile) diff --git a/lib/modules/manager/sbt/update.spec.ts b/lib/modules/manager/sbt/update.spec.ts index 0e72c0393e..96423b065e 100644 --- a/lib/modules/manager/sbt/update.spec.ts +++ b/lib/modules/manager/sbt/update.spec.ts @@ -17,6 +17,7 @@ describe('modules/manager/sbt/update', () => { expect(bumpedContent).toEqual(content.replace('0.0.2', '0.0.3')); expect(bumpedContent).not.toEqual(content); }); + it('no ops', () => { const { bumpedContent } = sbtUpdater.bumpPackageVersion( content, @@ -26,6 +27,7 @@ describe('modules/manager/sbt/update', () => { expect(bumpedContent).toEqual(content); }); + it('updates', () => { const { bumpedContent } = sbtUpdater.bumpPackageVersion( content, @@ -35,6 +37,7 @@ describe('modules/manager/sbt/update', () => { expect(bumpedContent).toEqual(content.replace('0.0.2', '0.1.0')); expect(bumpedContent).not.toEqual(content); }); + it('returns content if bumping errors', () => { const { bumpedContent } = sbtUpdater.bumpPackageVersion( content, diff --git a/lib/modules/manager/setup-cfg/extract.spec.ts b/lib/modules/manager/setup-cfg/extract.spec.ts index c5f809e435..3c5fa453b0 100644 --- a/lib/modules/manager/setup-cfg/extract.spec.ts +++ b/lib/modules/manager/setup-cfg/extract.spec.ts @@ -6,6 +6,7 @@ describe('modules/manager/setup-cfg/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts dependencies', () => { const res = extractPackageFile(Fixtures.get('setup-cfg-1.txt')); expect(res).toMatchSnapshot({ diff --git a/lib/modules/manager/setup-cfg/range.spec.ts b/lib/modules/manager/setup-cfg/range.spec.ts index 59052f8a8a..df75c3e17a 100644 --- a/lib/modules/manager/setup-cfg/range.spec.ts +++ b/lib/modules/manager/setup-cfg/range.spec.ts @@ -6,6 +6,7 @@ describe('modules/manager/setup-cfg/range', () => { const config: RangeConfig = { rangeStrategy: 'widen' }; expect(getRangeStrategy(config)).toBe('widen'); }); + it('replaces if auto', () => { const config: RangeConfig = { rangeStrategy: 'auto' }; expect(getRangeStrategy(config)).toBe('replace'); diff --git a/lib/modules/manager/swift/index.spec.ts b/lib/modules/manager/swift/index.spec.ts index c67544079b..0f0e0ce483 100644 --- a/lib/modules/manager/swift/index.spec.ts +++ b/lib/modules/manager/swift/index.spec.ts @@ -9,6 +9,7 @@ describe('modules/manager/swift/index', () => { expect(extractPackageFile(`dependencies:[]`)).toBeNull(); expect(extractPackageFile(`dependencies:["foobar"]`)).toBeNull(); }); + it('returns null for invalid content', () => { expect(extractPackageFile(`dependen`)).toBeNull(); expect(extractPackageFile(`dependencies!: `)).toBeNull(); @@ -67,6 +68,7 @@ describe('modules/manager/swift/index', () => { ) ).toBeNull(); }); + it('parses packages with invalid versions', () => { expect( extractPackageFile( @@ -94,6 +96,7 @@ describe('modules/manager/swift/index', () => { ) ).not.toBeNull(); }); + it('parses package descriptions', () => { expect( extractPackageFile( @@ -126,6 +129,7 @@ describe('modules/manager/swift/index', () => { ) ).toMatchSnapshot({ deps: [{ currentValue: '..<"1.2.3"' }] }); }); + it('parses multiple packages', () => { expect( extractPackageFile(Fixtures.get(`SamplePackage.swift`)) diff --git a/lib/modules/manager/terraform-version/extract.spec.ts b/lib/modules/manager/terraform-version/extract.spec.ts index dc83439a40..f80423d46a 100644 --- a/lib/modules/manager/terraform-version/extract.spec.ts +++ b/lib/modules/manager/terraform-version/extract.spec.ts @@ -14,6 +14,7 @@ describe('modules/manager/terraform-version/extract', () => { ], }); }); + it('skips non ranges', () => { const res = extractPackageFile('latest'); expect(res).toEqual({ diff --git a/lib/modules/manager/terraform/extract.spec.ts b/lib/modules/manager/terraform/extract.spec.ts index 801f82f6a6..9d3acaafa0 100644 --- a/lib/modules/manager/terraform/extract.spec.ts +++ b/lib/modules/manager/terraform/extract.spec.ts @@ -32,6 +32,7 @@ describe('modules/manager/terraform/extract', () => { beforeEach(() => { GlobalConfig.set(adminConfig); }); + describe('extractPackageFile()', () => { it('returns null for empty', async () => { expect(await extractPackageFile('nothing here', '1.tf', {})).toBeNull(); diff --git a/lib/modules/manager/terraform/modules.spec.ts b/lib/modules/manager/terraform/modules.spec.ts index e7a9e2b1c6..984b818929 100644 --- a/lib/modules/manager/terraform/modules.spec.ts +++ b/lib/modules/manager/terraform/modules.spec.ts @@ -21,6 +21,7 @@ describe('modules/manager/terraform/modules', () => { expect(project).toBe('hashicorp/example.repo-123'); }); }); + describe('gitTagsRefMatchRegex', () => { it('should split project and tag from source', () => { const http = gitTagsRefMatchRegex.exec( @@ -64,6 +65,7 @@ describe('modules/manager/terraform/modules', () => { expect(ssh.tag).toBe('v1.0.0'); }); }); + describe('bitbucketRefMatchRegex', () => { it('should split workspace, project and tag from source', () => { const ssh = bitbucketRefMatchRegex.exec( diff --git a/lib/modules/manager/terraform/util.spec.ts b/lib/modules/manager/terraform/util.spec.ts index 173633ba15..3395cf791f 100644 --- a/lib/modules/manager/terraform/util.spec.ts +++ b/lib/modules/manager/terraform/util.spec.ts @@ -8,26 +8,31 @@ describe('modules/manager/terraform/util', () => { TerraformDependencyTypes.module ); }); + it('returns TerraformDependencyTypes.provider', () => { expect(getTerraformDependencyType('provider')).toBe( TerraformDependencyTypes.provider ); }); + it('returns TerraformDependencyTypes.unknown', () => { expect(getTerraformDependencyType('unknown')).toBe( TerraformDependencyTypes.unknown ); }); + it('returns TerraformDependencyTypes.required_providers', () => { expect(getTerraformDependencyType('required_providers')).toBe( TerraformDependencyTypes.required_providers ); }); + it('returns TerraformDependencyTypes.unknown on empty string', () => { expect(getTerraformDependencyType('')).toBe( TerraformDependencyTypes.unknown ); }); + it('returns TerraformDependencyTypes.unknown on string with random chars', () => { expect(getTerraformDependencyType('sdfsgdsfadfhfghfhgdfsdf')).toBe( TerraformDependencyTypes.unknown diff --git a/lib/modules/manager/terragrunt/extract.spec.ts b/lib/modules/manager/terragrunt/extract.spec.ts index 708a98007e..1d81feda46 100644 --- a/lib/modules/manager/terragrunt/extract.spec.ts +++ b/lib/modules/manager/terragrunt/extract.spec.ts @@ -6,12 +6,14 @@ describe('modules/manager/terragrunt/extract', () => { it('returns null for empty', () => { expect(extractPackageFile('nothing here')).toBeNull(); }); + it('extracts terragrunt sources', () => { const res = extractPackageFile(Fixtures.get('2.hcl')); expect(res).toMatchSnapshot(); expect(res.deps).toHaveLength(30); expect(res.deps.filter((dep) => dep.skipReason)).toHaveLength(5); }); + it('returns null if only local terragrunt deps', () => { expect( extractPackageFile(`terragrunt { diff --git a/lib/modules/manager/terragrunt/modules.spec.ts b/lib/modules/manager/terragrunt/modules.spec.ts index d36a95f8ad..ac03f9763d 100644 --- a/lib/modules/manager/terragrunt/modules.spec.ts +++ b/lib/modules/manager/terragrunt/modules.spec.ts @@ -17,6 +17,7 @@ describe('modules/manager/terragrunt/modules', () => { expect(project).toBe('hashicorp/example.repo-123'); }); }); + describe('gitTagsRefMatchRegex', () => { it('should split project and tag from source', () => { const http = gitTagsRefMatchRegex.exec( diff --git a/lib/modules/manager/terragrunt/util.spec.ts b/lib/modules/manager/terragrunt/util.spec.ts index bb1c259038..44f8867661 100644 --- a/lib/modules/manager/terragrunt/util.spec.ts +++ b/lib/modules/manager/terragrunt/util.spec.ts @@ -8,16 +8,19 @@ describe('modules/manager/terragrunt/util', () => { TerragruntDependencyTypes.terragrunt ); }); + it('returns TerragruntDependencyTypes.unknown', () => { expect(getTerragruntDependencyType('unknown')).toBe( TerragruntDependencyTypes.unknown ); }); + it('returns TerragruntDependencyTypes.unknown on empty string', () => { expect(getTerragruntDependencyType('')).toBe( TerragruntDependencyTypes.unknown ); }); + it('returns TerragruntDependencyTypes.unknown on string with random chars', () => { expect(getTerragruntDependencyType('sdfsgdsfadfhfghfhgdfsdf')).toBe( TerragruntDependencyTypes.unknown diff --git a/lib/modules/platform/azure/azure-got-wrapper.spec.ts b/lib/modules/platform/azure/azure-got-wrapper.spec.ts index b906aa9be5..941630320f 100644 --- a/lib/modules/platform/azure/azure-got-wrapper.spec.ts +++ b/lib/modules/platform/azure/azure-got-wrapper.spec.ts @@ -4,6 +4,7 @@ import type * as _hostRules from '../../../util/host-rules'; describe('modules/platform/azure/azure-got-wrapper', () => { let azure: typeof import('./azure-got-wrapper'); let hostRules: typeof _hostRules; + beforeEach(() => { // reset module jest.resetModules(); @@ -17,6 +18,7 @@ describe('modules/platform/azure/azure-got-wrapper', () => { expect(azure.coreApi).toThrow('No config found for azure'); expect(azure.policyApi).toThrow('No config found for azure'); }); + it('should set personal access token and endpoint', () => { hostRules.add({ hostType: PlatformId.Azure, @@ -33,6 +35,7 @@ describe('modules/platform/azure/azure-got-wrapper', () => { // We will track if the lib azure-devops-node-api change expect(res).toMatchSnapshot(); }); + it('should set bearer token and endpoint', () => { hostRules.add({ hostType: PlatformId.Azure, diff --git a/lib/modules/platform/azure/azure-helper.spec.ts b/lib/modules/platform/azure/azure-helper.spec.ts index ba1e22bf95..2a5e9ab990 100644 --- a/lib/modules/platform/azure/azure-helper.spec.ts +++ b/lib/modules/platform/azure/azure-helper.spec.ts @@ -24,6 +24,7 @@ describe('modules/platform/azure/azure-helper', () => { const res = await azureHelper.getRefs('123', 'branch'); expect(res).toMatchSnapshot(); }); + it('should not get ref', async () => { azureApi.gitApi.mockImplementationOnce( () => @@ -34,6 +35,7 @@ describe('modules/platform/azure/azure-helper', () => { const res = await azureHelper.getRefs('123'); expect(res).toHaveLength(0); }); + it('should get the ref with full ref name', async () => { azureApi.gitApi.mockImplementationOnce( () => @@ -61,6 +63,7 @@ describe('modules/platform/azure/azure-helper', () => { ); expect(res).toMatchSnapshot(); }); + it('should get the branch object when ref missing', async () => { azureApi.gitApi.mockImplementationOnce( () => @@ -207,6 +210,7 @@ describe('modules/platform/azure/azure-helper', () => { GitPullRequestMergeStrategy.NoFastForward ); }); + it('should return Squash', async () => { azureApi.policyApi.mockImplementationOnce( () => @@ -232,6 +236,7 @@ describe('modules/platform/azure/azure-helper', () => { GitPullRequestMergeStrategy.Squash ); }); + it('should return default branch policy', async () => { azureApi.policyApi.mockImplementationOnce( () => @@ -270,6 +275,7 @@ describe('modules/platform/azure/azure-helper', () => { GitPullRequestMergeStrategy.Rebase ); }); + it('should return most specific exact branch policy', async () => { const refMock = 'refs/heads/ding'; const defaultBranchMock = 'dong'; @@ -338,6 +344,7 @@ describe('modules/platform/azure/azure-helper', () => { await azureHelper.getMergeMethod('', '', refMock, defaultBranchMock) ).toEqual(GitPullRequestMergeStrategy.Rebase); }); + it('should return most specific prefix branch policy', async () => { const refMock = 'refs/heads/ding-wow'; const defaultBranchMock = 'dong-wow'; diff --git a/lib/modules/platform/azure/index.spec.ts b/lib/modules/platform/azure/index.spec.ts index 6f4f2727b7..555d01016b 100644 --- a/lib/modules/platform/azure/index.spec.ts +++ b/lib/modules/platform/azure/index.spec.ts @@ -23,6 +23,7 @@ describe('modules/platform/azure/index', () => { let azureHelper: jest.Mocked<typeof import('./azure-helper')>; let git: jest.Mocked<typeof _git>; let logger: jest.Mocked<typeof _logger>; + beforeEach(async () => { // reset module jest.resetModules(); @@ -80,6 +81,7 @@ describe('modules/platform/azure/index', () => { expect.assertions(1); expect(() => azure.initPlatform({})).toThrow(); }); + it('should throw if no token nor a username and password', () => { expect.assertions(1); expect(() => @@ -88,6 +90,7 @@ describe('modules/platform/azure/index', () => { }) ).toThrow(); }); + it('should throw if a username but no password', () => { expect.assertions(1); expect(() => @@ -97,6 +100,7 @@ describe('modules/platform/azure/index', () => { }) ).toThrow(); }); + it('should throw if a password but no username', () => { expect.assertions(1); expect(() => @@ -106,6 +110,7 @@ describe('modules/platform/azure/index', () => { }) ).toThrow(); }); + it('should init', async () => { expect( await azure.initPlatform({ @@ -230,6 +235,7 @@ describe('modules/platform/azure/index', () => { }); expect(res).toMatchSnapshot(); }); + it('returns pr if found not open', async () => { azureApi.gitApi.mockImplementationOnce( () => @@ -256,6 +262,7 @@ describe('modules/platform/azure/index', () => { }); expect(res).toMatchSnapshot(); }); + it('returns pr if found it close', async () => { azureApi.gitApi.mockImplementationOnce( () => @@ -282,6 +289,7 @@ describe('modules/platform/azure/index', () => { }); expect(res).toMatchSnapshot(); }); + it('returns pr if found it all state', async () => { azureApi.gitApi.mockImplementationOnce( () => @@ -308,6 +316,7 @@ describe('modules/platform/azure/index', () => { expect(res).toMatchSnapshot(); }); }); + describe('getPrList()', () => { it('returns empty array', async () => { azureApi.gitApi.mockImplementationOnce( @@ -335,6 +344,7 @@ describe('modules/platform/azure/index', () => { const pr = await azure.getBranchPr('somebranch'); expect(pr).toBeNull(); }); + it('should return the pr', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementation( @@ -359,6 +369,7 @@ describe('modules/platform/azure/index', () => { expect(pr).toBeNull(); }); }); + describe('getBranchStatusCheck(branchName, context)', () => { it('should return green if status is succeeded', async () => { await initRepo({ repository: 'some/repo' }); @@ -401,6 +412,7 @@ describe('modules/platform/azure/index', () => { ); expect(res).toBe(BranchStatus.green); }); + it('should return red if status is failed', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -421,6 +433,7 @@ describe('modules/platform/azure/index', () => { ); expect(res).toBe(BranchStatus.red); }); + it('should return red if context status is error', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -441,6 +454,7 @@ describe('modules/platform/azure/index', () => { ); expect(res).toEqual(BranchStatus.red); }); + it('should return yellow if status is pending', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -461,6 +475,7 @@ describe('modules/platform/azure/index', () => { ); expect(res).toBe(BranchStatus.yellow); }); + it('should return yellow if status is not set', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -481,6 +496,7 @@ describe('modules/platform/azure/index', () => { ); expect(res).toBe(BranchStatus.yellow); }); + it('should return null if status not found', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -502,6 +518,7 @@ describe('modules/platform/azure/index', () => { expect(res).toBeNull(); }); }); + describe('getBranchStatus(branchName, ignoreTests)', () => { it('should pass through success', async () => { await initRepo({ repository: 'some/repo' }); @@ -515,6 +532,7 @@ describe('modules/platform/azure/index', () => { const res = await azure.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.green); }); + it('should pass through failed', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -527,6 +545,7 @@ describe('modules/platform/azure/index', () => { const res = await azure.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.red); }); + it('should pass through pending', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -539,6 +558,7 @@ describe('modules/platform/azure/index', () => { const res = await azure.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.yellow); }); + it('should fall back to yellow if no statuses returned', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -558,6 +578,7 @@ describe('modules/platform/azure/index', () => { const pr = await azure.getPr(0); expect(pr).toBeNull(); }); + it('should return null if no PR is returned from azure', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -569,6 +590,7 @@ describe('modules/platform/azure/index', () => { const pr = await azure.getPr(1234); expect(pr).toBeNull(); }); + it('should return a pr in the right format', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementation( @@ -621,6 +643,7 @@ describe('modules/platform/azure/index', () => { }); expect(pr).toMatchSnapshot(); }); + it('should create and return a PR object from base branch', async () => { await initRepo({ repository: 'some/repo' }); azureApi.gitApi.mockImplementationOnce( @@ -642,6 +665,7 @@ describe('modules/platform/azure/index', () => { }); expect(pr).toMatchSnapshot(); }); + it('should create and return a PR object with auto-complete set', async () => { await initRepo({ repository: 'some/repo' }); const prResult = { @@ -685,6 +709,7 @@ describe('modules/platform/azure/index', () => { expect(updateFn).toHaveBeenCalled(); expect(pr).toMatchSnapshot(); }); + it('should create and return an approved PR object', async () => { await initRepo({ repository: 'some/repo' }); const prResult = { @@ -818,6 +843,7 @@ describe('modules/platform/azure/index', () => { expect(gitApiMock.createThread.mock.calls).toMatchSnapshot(); expect(gitApiMock.updateComment.mock.calls).toMatchSnapshot(); }); + it('updates comment if missing', async () => { await initRepo({ repository: 'some/repo' }); const gitApiMock = { @@ -843,6 +869,7 @@ describe('modules/platform/azure/index', () => { expect(gitApiMock.createThread.mock.calls).toMatchSnapshot(); expect(gitApiMock.updateComment.mock.calls).toMatchSnapshot(); }); + it('does nothing if comment exists and is the same', async () => { await initRepo({ repository: 'some/repo' }); const gitApiMock = { @@ -868,6 +895,7 @@ describe('modules/platform/azure/index', () => { expect(gitApiMock.createThread.mock.calls).toMatchSnapshot(); expect(gitApiMock.updateComment.mock.calls).toMatchSnapshot(); }); + it('does nothing if comment exists and is the same when there is no topic', async () => { await initRepo({ repository: 'some/repo' }); const gitApiMock = { @@ -893,6 +921,7 @@ describe('modules/platform/azure/index', () => { describe('ensureCommentRemoval', () => { let gitApiMock; + beforeEach(() => { gitApiMock = { getThreads: jest.fn(() => [ @@ -909,6 +938,7 @@ describe('modules/platform/azure/index', () => { }; azureApi.gitApi.mockImplementation(() => gitApiMock); }); + it('deletes comment by topic if found', async () => { await initRepo({ repository: 'some/repo' }); await azure.ensureCommentRemoval({ @@ -924,6 +954,7 @@ describe('modules/platform/azure/index', () => { 123 ); }); + it('deletes comment by content if found', async () => { await initRepo({ repository: 'some/repo' }); await azure.ensureCommentRemoval({ @@ -939,6 +970,7 @@ describe('modules/platform/azure/index', () => { 124 ); }); + it('comment not found', async () => { await initRepo({ repository: 'some/repo' }); await azure.ensureCommentRemoval({ @@ -1048,6 +1080,7 @@ describe('modules/platform/azure/index', () => { '1' ); }); + it('should build and call the create status api properly with a complex context', async () => { await initRepo({ repository: 'some/repo' }); const createCommitStatusMock = jest.fn(); @@ -1126,6 +1159,7 @@ describe('modules/platform/azure/index', () => { ); expect(res).toBeTrue(); }); + it('should return false if the PR does not update successfully', async () => { await initRepo({ repository: 'some/repo' }); const pullRequestIdMock = 12345; @@ -1271,6 +1305,7 @@ describe('modules/platform/azure/index', () => { expect(azureApi.gitApi.mock.calls).toMatchSnapshot(); }); }); + describe('getJsonFile()', () => { it('returns file content', async () => { const data = { foo: 'bar' }; @@ -1330,6 +1365,7 @@ describe('modules/platform/azure/index', () => { ); await expect(azure.getJsonFile('file.json')).rejects.toThrow(); }); + it('throws on errors', async () => { azureApi.gitApi.mockImplementationOnce( () => @@ -1341,6 +1377,7 @@ describe('modules/platform/azure/index', () => { ); await expect(azure.getJsonFile('file.json')).rejects.toThrow(); }); + it('supports fetch from another repo', async () => { const data = { foo: 'bar' }; const gitApiMock = { diff --git a/lib/modules/platform/azure/util.spec.ts b/lib/modules/platform/azure/util.spec.ts index 5030d638f7..85a7395ae2 100644 --- a/lib/modules/platform/azure/util.spec.ts +++ b/lib/modules/platform/azure/util.spec.ts @@ -18,6 +18,7 @@ describe('modules/platform/azure/util', () => { const res = getNewBranchName('testBB'); expect(res).toBe(`refs/heads/testBB`); }); + it('should be the same', () => { const res = getNewBranchName('refs/heads/testBB'); expect(res).toBe(`refs/heads/testBB`); @@ -29,6 +30,7 @@ describe('modules/platform/azure/util', () => { const contextName = getGitStatusContextCombinedName(null); expect(contextName).toBeUndefined(); }); + it('should combine valid genre and name with slash', () => { const contextName = getGitStatusContextCombinedName({ genre: 'my-genre', @@ -36,6 +38,7 @@ describe('modules/platform/azure/util', () => { }); expect(contextName).toMatch('my-genre/status-name'); }); + it('should combine valid empty genre and name without a slash', () => { const contextName = getGitStatusContextCombinedName({ genre: undefined, @@ -50,6 +53,7 @@ describe('modules/platform/azure/util', () => { const context = getGitStatusContextFromCombinedName(null); expect(context).toBeUndefined(); }); + it('should parse valid genre and name with slash', () => { const context = getGitStatusContextFromCombinedName( 'my-genre/status-name' @@ -59,6 +63,7 @@ describe('modules/platform/azure/util', () => { name: 'status-name', }); }); + it('should parse valid genre and name with multiple slashes', () => { const context = getGitStatusContextFromCombinedName( 'my-genre/sub-genre/status-name' @@ -68,6 +73,7 @@ describe('modules/platform/azure/util', () => { name: 'status-name', }); }); + it('should parse valid empty genre and name without a slash', () => { const context = getGitStatusContextFromCombinedName('status-name'); expect(context).toEqual({ @@ -82,10 +88,12 @@ describe('modules/platform/azure/util', () => { const res = getBranchNameWithoutRefsheadsPrefix('refs/heads/testBB'); expect(res).toBe(`testBB`); }); + it('should log error and return undefined', () => { const res = getBranchNameWithoutRefsheadsPrefix(undefined as any); expect(res).toBeUndefined(); }); + it('should return the input', () => { const res = getBranchNameWithoutRefsheadsPrefix('testBB'); expect(res).toBe('testBB'); @@ -114,6 +122,7 @@ describe('modules/platform/azure/util', () => { const res = await streamToString(Readable.from('foobar')); expect(res).toBe('foobar'); }); + it('handles error', async () => { const stream = Readable.from('foobar'); const res = streamToString(stream); @@ -130,12 +139,14 @@ describe('modules/platform/azure/util', () => { }); expect(res).toMatchSnapshot(); }); + it('should configure personal access token', () => { const res = getStorageExtraCloneOpts({ token: '123456789012345678901234567890123456789012345678test', }); expect(res).toMatchSnapshot(); }); + it('should configure bearer token', () => { const res = getStorageExtraCloneOpts({ token: 'token' }); expect(res).toMatchSnapshot(); @@ -147,6 +158,7 @@ describe('modules/platform/azure/util', () => { const res = max4000Chars('Hello'); expect(res).toMatchSnapshot(); }); + it('should be truncated', () => { let str = ''; for (let i = 0; i < 5000; i += 1) { @@ -162,10 +174,12 @@ describe('modules/platform/azure/util', () => { const res = getProjectAndRepo('myRepoName'); expect(res).toMatchSnapshot(); }); + it('should return the object with project and repo', () => { const res = getProjectAndRepo('prjName/myRepoName'); expect(res).toMatchSnapshot(); }); + it('should return an error', () => { expect(() => getProjectAndRepo('prjName/myRepoName/blalba')).toThrow( Error( @@ -181,11 +195,13 @@ describe('modules/platform/azure/util', () => { expect(getRepoByName('foo/bar', undefined)).toBeNull(); expect(getRepoByName('foo/bar', null)).toBeNull(); }); + it('returns null when repo is not found', () => { expect( getRepoByName('foo/foo', [{ name: 'bar', project: { name: 'bar' } }]) ).toBeNull(); }); + it('finds repo', () => { expect( getRepoByName('foo/bar', [ @@ -198,6 +214,7 @@ describe('modules/platform/azure/util', () => { ]) ).toMatchObject({ id: '3' }); }); + it('supports shorthand names', () => { expect( getRepoByName('foo', [ @@ -206,6 +223,7 @@ describe('modules/platform/azure/util', () => { ]) ).toMatchObject({ id: '2' }); }); + it('is case-independent', () => { const repos = [ { id: '1', name: 'FOO', project: { name: 'FOO' } }, @@ -215,6 +233,7 @@ describe('modules/platform/azure/util', () => { expect(getRepoByName('foo/FOO', repos)).toMatchObject({ id: '1' }); expect(getRepoByName('foo/foo', repos)).toMatchObject({ id: '1' }); }); + it('throws when repo name is invalid', () => { expect(() => getRepoByName(undefined, [])).toThrow(); expect(() => getRepoByName(null, [])).toThrow(); diff --git a/lib/modules/platform/bitbucket-server/index.spec.ts b/lib/modules/platform/bitbucket-server/index.spec.ts index ed3aa90d3a..ee0b9bb1d6 100644 --- a/lib/modules/platform/bitbucket-server/index.spec.ts +++ b/lib/modules/platform/bitbucket-server/index.spec.ts @@ -232,12 +232,14 @@ describe('modules/platform/bitbucket-server/index', () => { expect.assertions(1); expect(() => bitbucket.initPlatform({})).toThrow(); }); + it('should throw if no username/password', () => { expect.assertions(1); expect(() => bitbucket.initPlatform({ endpoint: 'endpoint' }) ).toThrow(); }); + it('should init', async () => { expect( await bitbucket.initPlatform({ @@ -1101,6 +1103,7 @@ describe('modules/platform/bitbucket-server/index', () => { await bitbucket.getBranchPr('userName1/pullRequest5') ).toMatchSnapshot(); }); + it('has no pr', async () => { const scope = await initRepo(); scope @@ -1140,6 +1143,7 @@ describe('modules/platform/bitbucket-server/index', () => { }) ).toMatchSnapshot(); }); + it('has no pr', async () => { const scope = await initRepo(); scope @@ -1221,6 +1225,7 @@ describe('modules/platform/bitbucket-server/index', () => { httpMock.scope(urlHost); expect(await bitbucket.getPr(undefined as any)).toBeNull(); }); + it('gets a PR', async () => { const scope = await initRepo(); scope @@ -1817,6 +1822,7 @@ Followed by some information. }) ).toResolve(); }); + it('should be success 2', async () => { const scope = await initRepo(); scope @@ -1846,6 +1852,7 @@ Followed by some information. }) ).toResolve(); }); + it('should be success 3', async () => { const scope = await initRepo(); scope @@ -1875,6 +1882,7 @@ Followed by some information. }) ).toResolve(); }); + it('should be success 4', async () => { const scope = await initRepo(); scope @@ -1904,6 +1912,7 @@ Followed by some information. }) ).toResolve(); }); + it('should be success 5', async () => { const scope = await initRepo(); scope @@ -1928,6 +1937,7 @@ Followed by some information. }) ).toResolve(); }); + it('should be success 6', async () => { const scope = await initRepo(); scope @@ -2035,6 +2045,7 @@ Followed by some information. }); await expect(bitbucket.getJsonFile('file.json')).rejects.toThrow(); }); + it('throws on long content', async () => { const scope = await initRepo(); scope @@ -2047,6 +2058,7 @@ Followed by some information. }); await expect(bitbucket.getJsonFile('file.json')).rejects.toThrow(); }); + it('throws on errors', async () => { const scope = await initRepo(); scope diff --git a/lib/modules/platform/bitbucket/index.spec.ts b/lib/modules/platform/bitbucket/index.spec.ts index 6a8207a38c..6d8d1f712f 100644 --- a/lib/modules/platform/bitbucket/index.spec.ts +++ b/lib/modules/platform/bitbucket/index.spec.ts @@ -22,6 +22,7 @@ describe('modules/platform/bitbucket/index', () => { let hostRules: jest.Mocked<typeof import('../../../util/host-rules')>; let git: jest.Mocked<typeof _git>; let logger: jest.Mocked<typeof _logger>; + beforeEach(async () => { // reset module jest.resetModules(); @@ -72,6 +73,7 @@ describe('modules/platform/bitbucket/index', () => { expect.assertions(1); await expect(bitbucket.initPlatform({})).rejects.toThrow(); }); + it('should show warning message if custom endpoint', async () => { await bitbucket.initPlatform({ endpoint: 'endpoint', @@ -82,6 +84,7 @@ describe('modules/platform/bitbucket/index', () => { 'Init: Bitbucket Cloud endpoint should generally be https://api.bitbucket.org/ but is being configured to a different value. Did you mean to use Bitbucket Server?' ); }); + it('should init', async () => { httpMock.scope(baseUrl).get('/2.0/user').reply(200); expect( @@ -91,6 +94,7 @@ describe('modules/platform/bitbucket/index', () => { }) ).toMatchSnapshot(); }); + it('should warn for missing "profile" scope', async () => { const scope = httpMock.scope(baseUrl); scope @@ -150,6 +154,7 @@ describe('modules/platform/bitbucket/index', () => { expect(await bitbucket.getBranchPr('branch')).toMatchSnapshot(); }); + it('returns null if no PR for branch', async () => { const scope = await initRepoMock(); scope @@ -185,6 +190,7 @@ describe('modules/platform/bitbucket/index', () => { }); expect(await bitbucket.getBranchStatus('master')).toBe(BranchStatus.red); }); + it('getBranchStatus 4', async () => { const scope = await initRepoMock(); scope @@ -211,6 +217,7 @@ describe('modules/platform/bitbucket/index', () => { BranchStatus.green ); }); + it('getBranchStatus 5', async () => { const scope = await initRepoMock(); scope @@ -237,6 +244,7 @@ describe('modules/platform/bitbucket/index', () => { BranchStatus.yellow ); }); + it('getBranchStatus 6', async () => { const scope = await initRepoMock(); scope @@ -283,14 +291,17 @@ describe('modules/platform/bitbucket/index', () => { ], }); }); + it('getBranchStatusCheck 1', async () => { expect(await bitbucket.getBranchStatusCheck('master', null)).toBeNull(); }); + it('getBranchStatusCheck 2', async () => { expect(await bitbucket.getBranchStatusCheck('master', 'foo')).toBe( BranchStatus.red ); }); + it('getBranchStatusCheck 3', async () => { expect(await bitbucket.getBranchStatusCheck('master', 'bar')).toBeNull(); }); @@ -357,6 +368,7 @@ describe('modules/platform/bitbucket/index', () => { }); expect(await bitbucket.findIssue('title')).toMatchSnapshot(); }); + it('returns null if no issues', async () => { const scope = await initRepoMock( { @@ -374,6 +386,7 @@ describe('modules/platform/bitbucket/index', () => { expect(await bitbucket.findIssue('title')).toBeNull(); }); }); + describe('ensureIssue()', () => { it('updates existing issues', async () => { const scope = await initRepoMock({}, { has_issues: true }); @@ -403,6 +416,7 @@ describe('modules/platform/bitbucket/index', () => { await bitbucket.ensureIssue({ title: 'title', body: 'body' }) ).toBe('updated'); }); + it('creates new issue', async () => { const scope = await initRepoMock( { repository: 'some/empty' }, @@ -427,6 +441,7 @@ describe('modules/platform/bitbucket/index', () => { }) ).toBe('created'); }); + it('noop for existing issue', async () => { const scope = await initRepoMock({}, { has_issues: true }); scope @@ -463,6 +478,7 @@ describe('modules/platform/bitbucket/index', () => { await initRepoMock(); await expect(bitbucket.ensureIssueClosing('title')).toResolve(); }); + it('closes issue', async () => { const scope = await initRepoMock({}, { has_issues: true }); scope @@ -496,6 +512,7 @@ describe('modules/platform/bitbucket/index', () => { await initRepoMock(); expect(await bitbucket.getIssueList()).toEqual([]); }); + it('get issues', async () => { const scope = await initRepoMock({}, { has_issues: true }); scope @@ -522,6 +539,7 @@ describe('modules/platform/bitbucket/index', () => { expect(issues).toHaveLength(2); expect(issues).toMatchSnapshot(); }); + it('does not throw', async () => { const scope = await initRepoMock({}, { has_issues: true }); scope @@ -592,6 +610,7 @@ describe('modules/platform/bitbucket/index', () => { it('exists', () => { expect(bitbucket.getPrList).toBeDefined(); }); + it('filters PR list by author', async () => { const scope = httpMock.scope(baseUrl); scope.get('/2.0/user').reply(200, { uuid: '12345' }); @@ -658,6 +677,7 @@ describe('modules/platform/bitbucket/index', () => { }); expect(number).toBe(5); }); + it('removes inactive reviewers when updating pr', async () => { const inactiveReviewer = { display_name: 'Bob Smith', @@ -706,6 +726,7 @@ describe('modules/platform/bitbucket/index', () => { }); expect(number).toBe(5); }); + it('removes default reviewers no longer member of the workspace when creating pr', async () => { const notMemberReviewer = { display_name: 'Bob Smith', @@ -757,6 +778,7 @@ describe('modules/platform/bitbucket/index', () => { }); expect(number).toBe(5); }); + it('throws exception when unable to check default reviewers workspace membership', async () => { const reviewer = { display_name: 'Bob Smith', @@ -798,6 +820,7 @@ describe('modules/platform/bitbucket/index', () => { }) ).rejects.toThrow(new Error('Response code 401 (Unauthorized)')); }); + it('rethrows exception when PR create error due to unknown reviewers error', async () => { const reviewer = { display_name: 'Jane Smith', @@ -832,6 +855,7 @@ describe('modules/platform/bitbucket/index', () => { }) ).rejects.toThrow(new Error('Response code 400 (Bad Request)')); }); + it('rethrows exception when PR create error not due to reviewers field', async () => { const reviewer = { display_name: 'Jane Smith', @@ -926,6 +950,7 @@ describe('modules/platform/bitbucket/index', () => { bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }) ).toResolve(); }); + it('removes inactive reviewers when updating pr', async () => { const inactiveReviewer = { display_name: 'Bob Smith', @@ -965,6 +990,7 @@ describe('modules/platform/bitbucket/index', () => { bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }) ).toResolve(); }); + it('removes reviewers no longer member of the workspace when updating pr', async () => { const notMemberReviewer = { display_name: 'Bob Smith', @@ -1008,6 +1034,7 @@ describe('modules/platform/bitbucket/index', () => { bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }) ).toResolve(); }); + it('throws exception when unable to check reviewers workspace membership', async () => { const reviewer = { display_name: 'Bob Smith', @@ -1039,6 +1066,7 @@ describe('modules/platform/bitbucket/index', () => { bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }) ).rejects.toThrow(new Error('Response code 401 (Unauthorized)')); }); + it('rethrows exception when PR update error due to unknown reviewers error', async () => { const reviewer = { display_name: 'Jane Smith', @@ -1063,6 +1091,7 @@ describe('modules/platform/bitbucket/index', () => { bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }) ).rejects.toThrowErrorMatchingSnapshot(); }); + it('rethrows exception when PR create error not due to reviewers field', async () => { const reviewer = { display_name: 'Jane Smith', @@ -1087,6 +1116,7 @@ describe('modules/platform/bitbucket/index', () => { bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }) ).rejects.toThrow(new Error('Response code 400 (Bad Request)')); }); + it('throws an error on failure to get current list of reviewers', async () => { const scope = await initRepoMock(); scope @@ -1096,6 +1126,7 @@ describe('modules/platform/bitbucket/index', () => { bitbucket.updatePr({ number: 5, prTitle: 'title', prBody: 'body' }) ).rejects.toThrowErrorMatchingSnapshot(); }); + it('closes PR', async () => { const scope = await initRepoMock(); scope @@ -1262,6 +1293,7 @@ describe('modules/platform/bitbucket/index', () => { .reply(200, '!@#'); await expect(bitbucket.getJsonFile('file.json')).rejects.toThrow(); }); + it('throws on errors', async () => { const scope = await initRepoMock(); scope diff --git a/lib/modules/platform/comment.spec.ts b/lib/modules/platform/comment.spec.ts index 83f7c33995..90ad873e76 100644 --- a/lib/modules/platform/comment.spec.ts +++ b/lib/modules/platform/comment.spec.ts @@ -10,6 +10,7 @@ const cache = mocked(_cache); describe('modules/platform/comment', () => { let repoCache: Cache = {}; + beforeEach(() => { repoCache = {}; jest.resetAllMocks(); diff --git a/lib/modules/platform/gitea/index.spec.ts b/lib/modules/platform/gitea/index.spec.ts index 6174483dc0..197cdec0f4 100644 --- a/lib/modules/platform/gitea/index.spec.ts +++ b/lib/modules/platform/gitea/index.spec.ts @@ -462,6 +462,7 @@ describe('modules/platform/gitea/index', () => { await gitea.getBranchStatusCheck('some-branch', 'some-context') ).toBeNull(); }); + it('should return yellow with unknown status', async () => { helper.getCombinedCommitStatus.mockResolvedValueOnce( partial<ght.CombinedCommitStatus>({ @@ -1497,6 +1498,7 @@ describe('modules/platform/gitea/index', () => { expect(helper.requestPrReviewers).toHaveBeenCalledTimes(1); expect(logger.warn).not.toHaveBeenCalled(); }); + it('should should do nothing if version to old', async () => { expect.assertions(3); const mockPR = mockPRs[0]; @@ -1507,6 +1509,7 @@ describe('modules/platform/gitea/index', () => { expect(helper.requestPrReviewers).not.toHaveBeenCalled(); expect(logger.warn).not.toHaveBeenCalled(); }); + it('catches errors', async () => { expect.assertions(2); const mockPR = mockPRs[0]; @@ -1581,6 +1584,7 @@ describe('modules/platform/gitea/index', () => { const res = await gitea.getJsonFile('file.json5'); expect(res).toEqual({ foo: 'bar' }); }); + it('throws on malformed JSON', async () => { helper.getRepoContents.mockResolvedValueOnce({ contentString: '!@#', @@ -1588,6 +1592,7 @@ describe('modules/platform/gitea/index', () => { await initFakeRepo({ full_name: 'some/repo' }); await expect(gitea.getJsonFile('file.json')).rejects.toThrow(); }); + it('throws on errors', async () => { helper.getRepoContents.mockRejectedValueOnce(new Error('some error')); await initFakeRepo({ full_name: 'some/repo' }); diff --git a/lib/modules/platform/github/index.spec.ts b/lib/modules/platform/github/index.spec.ts index b19b0b4d72..4bba33b7f6 100644 --- a/lib/modules/platform/github/index.spec.ts +++ b/lib/modules/platform/github/index.spec.ts @@ -51,12 +51,14 @@ describe('modules/platform/github/index', () => { 'Init: You must configure a GitHub personal access token' ); }); + it('should throw if user failure', async () => { httpMock.scope(githubApiHost).get('/user').reply(404); await expect( github.initPlatform({ token: '123test' } as any) ).rejects.toThrow(); }); + it('should support default endpoint no email access', async () => { httpMock .scope(githubApiHost) @@ -70,6 +72,7 @@ describe('modules/platform/github/index', () => { await github.initPlatform({ token: '123test' } as any) ).toMatchSnapshot(); }); + it('should support default endpoint no email result', async () => { httpMock .scope(githubApiHost) @@ -83,6 +86,7 @@ describe('modules/platform/github/index', () => { await github.initPlatform({ token: '123test' } as any) ).toMatchSnapshot(); }); + it('should support gitAuthor and username', async () => { expect( await github.initPlatform({ @@ -92,6 +96,7 @@ describe('modules/platform/github/index', () => { } as any) ).toMatchSnapshot(); }); + it('should support default endpoint with email', async () => { httpMock .scope(githubApiHost) @@ -109,6 +114,7 @@ describe('modules/platform/github/index', () => { await github.initPlatform({ token: '123test' } as any) ).toMatchSnapshot(); }); + it('should support custom endpoint', async () => { httpMock .scope('https://ghe.renovatebot.com') @@ -174,6 +180,7 @@ describe('modules/platform/github/index', () => { const repos = await github.getRepos(); expect(repos).toMatchSnapshot(); }); + it('should return an array of repos when using Github App endpoint', async () => { //Use Github App token await github.initPlatform({ @@ -282,6 +289,7 @@ describe('modules/platform/github/index', () => { } as any); expect(config).toMatchSnapshot(); }); + it('should fork when forkMode', async () => { const scope = httpMock.scope(githubApiHost); forkInitRepoMock(scope, 'some/repo', false); @@ -291,6 +299,7 @@ describe('modules/platform/github/index', () => { } as any); expect(config).toMatchSnapshot(); }); + it('should update fork when forkMode', async () => { const scope = httpMock.scope(githubApiHost); forkInitRepoMock(scope, 'some/repo', true); @@ -301,6 +310,7 @@ describe('modules/platform/github/index', () => { } as any); expect(config).toMatchSnapshot(); }); + it('detects fork default branch mismatch', async () => { const scope = httpMock.scope(githubApiHost); forkInitRepoMock(scope, 'some/repo', true, 'not_master'); @@ -313,6 +323,7 @@ describe('modules/platform/github/index', () => { } as any); expect(config).toMatchSnapshot(); }); + it('should squash', async () => { httpMock .scope(githubApiHost) @@ -341,6 +352,7 @@ describe('modules/platform/github/index', () => { } as any); expect(config).toMatchSnapshot(); }); + it('should merge', async () => { httpMock .scope(githubApiHost) @@ -369,6 +381,7 @@ describe('modules/platform/github/index', () => { } as any); expect(config).toMatchSnapshot(); }); + it('should not guess at merge', async () => { httpMock .scope(githubApiHost) @@ -390,6 +403,7 @@ describe('modules/platform/github/index', () => { } as any); expect(config).toMatchSnapshot(); }); + it('should throw error if archived', async () => { httpMock .scope(githubApiHost) @@ -415,6 +429,7 @@ describe('modules/platform/github/index', () => { } as any) ).rejects.toThrow(); }); + it('throws not-found', async () => { httpMock.scope(githubApiHost).post(`/graphql`).reply(404); await expect( @@ -423,6 +438,7 @@ describe('modules/platform/github/index', () => { } as any) ).rejects.toThrow(REPOSITORY_NOT_FOUND); }); + it('should throw error if renamed', async () => { httpMock .scope(githubApiHost) @@ -447,6 +463,7 @@ describe('modules/platform/github/index', () => { } as any) ).rejects.toThrow(REPOSITORY_RENAMED); }); + it('should not be case sensitive', async () => { httpMock .scope(githubApiHost) @@ -472,6 +489,7 @@ describe('modules/platform/github/index', () => { expect(result.isFork).toBeFalse(); }); }); + describe('getRepoForceRebase', () => { it('should detect repoForceRebase', async () => { httpMock @@ -501,6 +519,7 @@ describe('modules/platform/github/index', () => { const res = await github.getRepoForceRebase(); expect(res).toBeTrue(); }); + it('should handle 404', async () => { httpMock .scope(githubApiHost) @@ -509,6 +528,7 @@ describe('modules/platform/github/index', () => { const res = await github.getRepoForceRebase(); expect(res).toBeFalse(); }); + it('should handle 403', async () => { httpMock .scope(githubApiHost) @@ -517,6 +537,7 @@ describe('modules/platform/github/index', () => { const res = await github.getRepoForceRebase(); expect(res).toBeFalse(); }); + it('should throw 401', async () => { httpMock .scope(githubApiHost) @@ -527,6 +548,7 @@ describe('modules/platform/github/index', () => { ).rejects.toThrowErrorMatchingSnapshot(); }); }); + describe('getBranchPr(branchName)', () => { it('should return null if no PR exists', async () => { const scope = httpMock.scope(githubApiHost); @@ -539,6 +561,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getBranchPr('somebranch'); expect(pr).toBeNull(); }); + it('should return the PR object', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -581,6 +604,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getBranchPr('somebranch'); expect(pr).toMatchSnapshot(); }); + it('should reopen an autoclosed PR', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -629,6 +653,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getBranchPr('somebranch'); expect(pr).toMatchSnapshot(); }); + it('aborts reopen if PR is too old', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -653,6 +678,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getBranchPr('somebranch'); expect(pr).toBeNull(); }); + it('aborts reopening if branch recreation fails', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -678,6 +704,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getBranchPr('somebranch'); expect(pr).toBeNull(); }); + it('aborts reopening if PR reopening fails', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -701,6 +728,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getBranchPr('somebranch'); expect(pr).toBeNull(); }); + it('should return the PR object in fork mode', async () => { const scope = httpMock.scope(githubApiHost); forkInitRepoMock(scope, 'some/repo', true); @@ -746,6 +774,7 @@ describe('modules/platform/github/index', () => { expect(pr).toMatchSnapshot(); }); }); + describe('getBranchStatus()', () => { it('returns success if ignoreTests true', async () => { const scope = httpMock.scope(githubApiHost); @@ -757,6 +786,7 @@ describe('modules/platform/github/index', () => { } as any) ).toResolve(); }); + it('should pass through success', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -774,6 +804,7 @@ describe('modules/platform/github/index', () => { const res = await github.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.green); }); + it('should pass through failed', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -791,6 +822,7 @@ describe('modules/platform/github/index', () => { const res = await github.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.red); }); + it('defaults to pending', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -807,6 +839,7 @@ describe('modules/platform/github/index', () => { const res = await github.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.yellow); }); + it('should fail if a check run has failed', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -840,6 +873,7 @@ describe('modules/platform/github/index', () => { const res = await github.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.red); }); + it('should succeed if no status and all passed check runs', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -879,6 +913,7 @@ describe('modules/platform/github/index', () => { const res = await github.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.green); }); + it('should fail if a check run is pending', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -912,6 +947,7 @@ describe('modules/platform/github/index', () => { expect(res).toEqual(BranchStatus.yellow); }); }); + describe('getBranchStatusCheck', () => { it('returns state if found', async () => { const scope = httpMock.scope(githubApiHost); @@ -944,6 +980,7 @@ describe('modules/platform/github/index', () => { ); expect(res).toEqual(BranchStatus.yellow); }); + it('returns null', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -972,6 +1009,7 @@ describe('modules/platform/github/index', () => { expect(res).toBeNull(); }); }); + describe('setBranchStatus', () => { it('returns if already set', async () => { const scope = httpMock.scope(githubApiHost); @@ -999,6 +1037,7 @@ describe('modules/platform/github/index', () => { }) ).toResolve(); }); + it('sets branch status', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1045,6 +1084,7 @@ describe('modules/platform/github/index', () => { ).toResolve(); }); }); + describe('findIssue()', () => { it('returns null if no issue', async () => { httpMock @@ -1078,6 +1118,7 @@ describe('modules/platform/github/index', () => { const res = await github.findIssue('title-3'); expect(res).toBeNull(); }); + it('finds issue', async () => { httpMock .scope(githubApiHost) @@ -1113,6 +1154,7 @@ describe('modules/platform/github/index', () => { expect(res).not.toBeNull(); }); }); + describe('ensureIssue()', () => { it('creates issue', async () => { const scope = httpMock.scope(githubApiHost); @@ -1153,6 +1195,7 @@ describe('modules/platform/github/index', () => { }); expect(res).toBe('created'); }); + it('creates issue if not ensuring only once', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1192,6 +1235,7 @@ describe('modules/platform/github/index', () => { }); expect(res).toBeNull(); }); + it('does not create issue if ensuring only once', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1306,6 +1350,7 @@ describe('modules/platform/github/index', () => { }); expect(res).toBeNull(); }); + it('updates issue', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1432,6 +1477,7 @@ describe('modules/platform/github/index', () => { }); expect(res).toBeNull(); }); + it('deletes if duplicate', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1473,6 +1519,7 @@ describe('modules/platform/github/index', () => { }); expect(res).toBeNull(); }); + it('creates issue if reopen flag false and issue is not open', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1511,6 +1558,7 @@ describe('modules/platform/github/index', () => { }); expect(res).toBe('created'); }); + it('does not create issue if reopen flag false and issue is already open', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1548,6 +1596,7 @@ describe('modules/platform/github/index', () => { expect(res).toBeNull(); }); }); + describe('ensureIssueClosing()', () => { it('closes issue', async () => { httpMock @@ -1583,6 +1632,7 @@ describe('modules/platform/github/index', () => { await expect(github.ensureIssueClosing('title-2')).toResolve(); }); }); + describe('deleteLabel(issueNo, label)', () => { it('should delete the label', async () => { const scope = httpMock.scope(githubApiHost); @@ -1594,6 +1644,7 @@ describe('modules/platform/github/index', () => { await expect(github.deleteLabel(42, 'rebase')).toResolve(); }); }); + describe('addAssignees(issueNo, assignees)', () => { it('should add the given assignees to the issue', async () => { const scope = httpMock.scope(githubApiHost); @@ -1607,6 +1658,7 @@ describe('modules/platform/github/index', () => { ).toResolve(); }); }); + describe('addReviewers(issueNo, reviewers)', () => { it('should add the given reviewers to the PR', async () => { const scope = httpMock.scope(githubApiHost); @@ -1620,6 +1672,7 @@ describe('modules/platform/github/index', () => { ).toResolve(); }); }); + describe('ensureComment', () => { it('add comment if not found', async () => { const scope = httpMock.scope(githubApiHost); @@ -1641,6 +1694,7 @@ describe('modules/platform/github/index', () => { }) ).toResolve(); }); + it('adds comment if found in closed PR list', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1662,6 +1716,7 @@ describe('modules/platform/github/index', () => { }) ).toResolve(); }); + it('add updates comment if necessary', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1682,6 +1737,7 @@ describe('modules/platform/github/index', () => { }) ).toResolve(); }); + it('skips comment', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1700,6 +1756,7 @@ describe('modules/platform/github/index', () => { }) ).toResolve(); }); + it('handles comment with no description', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1719,6 +1776,7 @@ describe('modules/platform/github/index', () => { ).toResolve(); }); }); + describe('ensureCommentRemoval', () => { it('deletes comment by topic if found', async () => { const scope = httpMock.scope(githubApiHost); @@ -1738,6 +1796,7 @@ describe('modules/platform/github/index', () => { }) ).toResolve(); }); + it('deletes comment by content if found', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1757,6 +1816,7 @@ describe('modules/platform/github/index', () => { ).toResolve(); }); }); + describe('findPr(branchName, prTitle, state)', () => { it('returns true if no title and all state', async () => { const scope = httpMock @@ -1796,6 +1856,7 @@ describe('modules/platform/github/index', () => { }); expect(res).toBeDefined(); }); + it('returns true if not open', async () => { httpMock .scope(githubApiHost) @@ -1815,6 +1876,7 @@ describe('modules/platform/github/index', () => { }); expect(res).toBeDefined(); }); + it('caches pr list', async () => { httpMock .scope(githubApiHost) @@ -1844,6 +1906,7 @@ describe('modules/platform/github/index', () => { expect(res).toBeUndefined(); }); }); + describe('createPr()', () => { it('should create and return a PR object', async () => { const scope = httpMock.scope(githubApiHost); @@ -1866,6 +1929,7 @@ describe('modules/platform/github/index', () => { }); expect(pr).toMatchObject({ number: 123 }); }); + it('should use defaultBranch', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1883,6 +1947,7 @@ describe('modules/platform/github/index', () => { }); expect(pr).toMatchObject({ number: 123 }); }); + it('should create a draftPR if set in the settings', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -1901,6 +1966,7 @@ describe('modules/platform/github/index', () => { }); expect(pr).toMatchObject({ number: 123 }); }); + describe('automerge', () => { const createdPrResp = { number: 123, @@ -2049,11 +2115,13 @@ describe('modules/platform/github/index', () => { }); }); }); + describe('getPr(prNo)', () => { it('should return null if no prNo is passed', async () => { const pr = await github.getPr(0); expect(pr).toBeNull(); }); + it('should return PR from graphql result', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2065,6 +2133,7 @@ describe('modules/platform/github/index', () => { expect(pr).toBeDefined(); expect(pr).toMatchSnapshot(); }); + it('should return PR from closed graphql result', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2080,6 +2149,7 @@ describe('modules/platform/github/index', () => { expect(pr).toBeDefined(); expect(pr).toMatchSnapshot(); }); + it('should return null if no PR is returned from GitHub', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2093,6 +2163,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getPr(1234); expect(pr).toBeNull(); }); + it(`should return a PR object - 0`, async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2119,6 +2190,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getPr(1234); expect(pr).toMatchSnapshot({ state: 'merged' }); }); + it(`should return a PR object - 1`, async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2145,6 +2217,7 @@ describe('modules/platform/github/index', () => { const pr = await github.getPr(1234); expect(pr).toMatchSnapshot(); }); + it(`should return a PR object - 2`, async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2169,6 +2242,7 @@ describe('modules/platform/github/index', () => { expect(pr).toMatchSnapshot(); }); }); + describe('updatePr(prNo, title, body)', () => { it('should update the PR', async () => { const scope = httpMock.scope(githubApiHost); @@ -2183,6 +2257,7 @@ describe('modules/platform/github/index', () => { }) ).toResolve(); }); + it('should update and close the PR', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2198,6 +2273,7 @@ describe('modules/platform/github/index', () => { ).toResolve(); }); }); + describe('mergePr(prNo)', () => { it('should merge the PR', async () => { const scope = httpMock.scope(githubApiHost); @@ -2217,6 +2293,7 @@ describe('modules/platform/github/index', () => { }) ).toBeTrue(); }); + it('should handle merge error', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2238,12 +2315,14 @@ describe('modules/platform/github/index', () => { ).toBeFalse(); }); }); + describe('massageMarkdown(input)', () => { it('returns updated pr body', () => { const input = 'https://github.com/foo/bar/issues/5 plus also [a link](https://github.com/foo/bar/issues/5)'; expect(github.massageMarkdown(input)).toMatchSnapshot(); }); + it('returns not-updated pr body for GHE', async () => { const scope = httpMock .scope('https://github.company.com') @@ -2271,6 +2350,7 @@ describe('modules/platform/github/index', () => { expect(github.massageMarkdown(input)).toEqual(input); }); }); + describe('mergePr(prNo) - autodetection', () => { it('should try rebase first', async () => { const scope = httpMock.scope(githubApiHost); @@ -2290,6 +2370,7 @@ describe('modules/platform/github/index', () => { }) ).toBeTrue(); }); + it('should try squash after rebase', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2310,6 +2391,7 @@ describe('modules/platform/github/index', () => { }) ).toBeFalse(); }); + it('should try merge after squash', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2334,6 +2416,7 @@ describe('modules/platform/github/index', () => { }) ).toBeTrue(); }); + it('should give up', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2361,12 +2444,14 @@ describe('modules/platform/github/index', () => { ).toBeFalse(); }); }); + describe('getVulnerabilityAlerts()', () => { it('returns empty if error', async () => { httpMock.scope(githubApiHost).post('/graphql').reply(200, {}); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); }); + it('returns array if found', async () => { httpMock .scope(githubApiHost) @@ -2400,6 +2485,7 @@ describe('modules/platform/github/index', () => { const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(1); }); + it('returns array if found on GHE', async () => { const gheApiHost = 'https://ghe.renovatebot.com'; @@ -2450,18 +2536,21 @@ describe('modules/platform/github/index', () => { const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(1); }); + it('returns empty if disabled', async () => { // prettier-ignore httpMock.scope(githubApiHost).post('/graphql').reply(200, {data: {repository: {}}}); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); }); + it('handles network error', async () => { // prettier-ignore httpMock.scope(githubApiHost).post('/graphql').replyWithError('unknown error'); const res = await github.getVulnerabilityAlerts(); expect(res).toHaveLength(0); }); + it('calls logger.debug with only items that include securityVulnerability', async () => { httpMock .scope(githubApiHost) @@ -2574,6 +2663,7 @@ describe('modules/platform/github/index', () => { }); await expect(github.getJsonFile('file.json')).rejects.toThrow(); }); + it('throws on errors', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2597,6 +2687,7 @@ describe('modules/platform/github/index', () => { ); git.fetchCommit.mockImplementation(() => Promise.resolve('0abcdef')); }); + it('returns null if pre-commit phase has failed', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2616,6 +2707,7 @@ describe('modules/platform/github/index', () => { expect(res).toBeNull(); }); + it('returns null on REST error', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); @@ -2630,6 +2722,7 @@ describe('modules/platform/github/index', () => { expect(res).toBeNull(); }); + it('commits and returns SHA string', async () => { git.pushCommitToRenovateRef.mockResolvedValueOnce(); git.listCommitTree.mockResolvedValueOnce([]); @@ -2656,6 +2749,7 @@ describe('modules/platform/github/index', () => { expect(res).toBe('0abcdef'); }); + it('performs rebase', async () => { git.pushCommitToRenovateRef.mockResolvedValueOnce(); git.listCommitTree.mockResolvedValueOnce([]); diff --git a/lib/modules/platform/gitlab/index.spec.ts b/lib/modules/platform/gitlab/index.spec.ts index f90e11450b..0b5972d900 100644 --- a/lib/modules/platform/gitlab/index.spec.ts +++ b/lib/modules/platform/gitlab/index.spec.ts @@ -69,6 +69,7 @@ describe('modules/platform/gitlab/index', () => { it(`should throw if no token`, async () => { await expect(gitlab.initPlatform({} as any)).rejects.toThrow(); }); + it(`should throw if auth fails`, async () => { // user httpMock.scope(gitlabApiHost).get('/api/v4/user').reply(403); @@ -78,6 +79,7 @@ describe('modules/platform/gitlab/index', () => { }); await expect(res).rejects.toThrow('Init: Authentication failure'); }); + it(`should default to gitlab.com`, async () => { httpMock.scope(gitlabApiHost).get('/api/v4/user').reply(200, { email: 'a@b.com', @@ -93,6 +95,7 @@ describe('modules/platform/gitlab/index', () => { }) ).toMatchSnapshot(); }); + it(`should accept custom endpoint`, async () => { const endpoint = 'https://gitlab.renovatebot.com'; httpMock @@ -138,6 +141,7 @@ describe('modules/platform/gitlab/index', () => { .replyWithError('getRepos error'); await expect(gitlab.getRepos()).rejects.toThrow('getRepos error'); }); + it('should return an array of repos', async () => { httpMock .scope(gitlabApiHost) @@ -187,6 +191,7 @@ describe('modules/platform/gitlab/index', () => { describe('initRepo', () => { const okReturn = { default_branch: 'master', url: 'https://some-url' }; + it(`should escape all forward slashes in project names`, async () => { httpMock .scope(gitlabApiHost) @@ -203,6 +208,7 @@ describe('modules/platform/gitlab/index', () => { } `); }); + it('should throw an error if receiving an error', async () => { httpMock .scope(gitlabApiHost) @@ -214,6 +220,7 @@ describe('modules/platform/gitlab/index', () => { }) ).rejects.toThrow('always error'); }); + it('should throw an error if repository is archived', async () => { httpMock .scope(gitlabApiHost) @@ -225,6 +232,7 @@ describe('modules/platform/gitlab/index', () => { }) ).rejects.toThrow(REPOSITORY_ARCHIVED); }); + it('should throw an error if repository is a mirror', async () => { httpMock .scope(gitlabApiHost) @@ -236,6 +244,7 @@ describe('modules/platform/gitlab/index', () => { }) ).rejects.toThrow(REPOSITORY_MIRRORED); }); + it('should throw an error if repository access is disabled', async () => { httpMock .scope(gitlabApiHost) @@ -247,6 +256,7 @@ describe('modules/platform/gitlab/index', () => { }) ).rejects.toThrow(REPOSITORY_DISABLED); }); + it('should throw an error if MRs are disabled', async () => { httpMock .scope(gitlabApiHost) @@ -258,6 +268,7 @@ describe('modules/platform/gitlab/index', () => { }) ).rejects.toThrow(REPOSITORY_DISABLED); }); + it('should throw an error if repository has empty_repo property', async () => { httpMock .scope(gitlabApiHost) @@ -269,6 +280,7 @@ describe('modules/platform/gitlab/index', () => { }) ).rejects.toThrow(REPOSITORY_EMPTY); }); + it('should throw an error if repository is empty', async () => { httpMock .scope(gitlabApiHost) @@ -280,6 +292,7 @@ describe('modules/platform/gitlab/index', () => { }) ).rejects.toThrow(REPOSITORY_EMPTY); }); + it('should fall back if http_url_to_repo is empty', async () => { httpMock .scope(gitlabApiHost) @@ -364,6 +377,7 @@ describe('modules/platform/gitlab/index', () => { expect(git.initRepo.mock.calls).toMatchSnapshot(); }); }); + describe('getRepoForceRebase', () => { it('should return false', async () => { await initRepo( @@ -405,6 +419,7 @@ describe('modules/platform/gitlab/index', () => { const pr = await gitlab.getBranchPr('some-branch'); expect(pr).toBeNull(); }); + it('should return the PR object', async () => { const scope = await initRepo(); scope @@ -439,6 +454,7 @@ describe('modules/platform/gitlab/index', () => { const pr = await gitlab.getBranchPr('some-branch'); expect(pr).toMatchSnapshot(); }); + it('should strip draft prefix from title', async () => { const scope = await initRepo(); scope @@ -473,6 +489,7 @@ describe('modules/platform/gitlab/index', () => { const pr = await gitlab.getBranchPr('some-branch'); expect(pr).toMatchSnapshot(); }); + it('should strip deprecated draft prefix from title', async () => { const scope = await initRepo(); scope @@ -508,6 +525,7 @@ describe('modules/platform/gitlab/index', () => { expect(pr).toMatchSnapshot(); }); }); + describe('getBranchStatus(branchName, ignoreTests)', () => { it('returns pending if no results', async () => { const scope = await initRepo(); @@ -519,6 +537,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.yellow); }); + it('returns success if all are success', async () => { const scope = await initRepo(); scope @@ -529,6 +548,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.green); }); + it('returns success if optional jobs fail', async () => { const scope = await initRepo(); scope @@ -542,6 +562,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.green); }); + it('returns success if all are optional', async () => { const scope = await initRepo(); scope @@ -552,6 +573,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.green); }); + it('returns success if job is skipped', async () => { const scope = await initRepo(); scope @@ -562,6 +584,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.green); }); + it('returns yellow if there are no jobs expect skipped', async () => { const scope = await initRepo(); scope @@ -572,6 +595,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.yellow); }); + it('returns failure if any mandatory jobs fails and one job is skipped', async () => { const scope = await initRepo(); scope @@ -582,6 +606,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.red); }); + it('returns failure if any mandatory jobs fails', async () => { const scope = await initRepo(); scope @@ -596,6 +621,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.red); }); + it('maps custom statuses to yellow', async () => { const scope = await initRepo(); scope @@ -606,6 +632,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.getBranchStatus('somebranch'); expect(res).toEqual(BranchStatus.yellow); }); + it('throws repository-changed', async () => { expect.assertions(1); git.branchExists.mockReturnValue(false); @@ -615,6 +642,7 @@ describe('modules/platform/gitlab/index', () => { ); }); }); + describe('getBranchStatusCheck', () => { it('returns null if no results', async () => { const scope = await initRepo(); @@ -629,6 +657,7 @@ describe('modules/platform/gitlab/index', () => { ); expect(res).toBeNull(); }); + it('returns null if no matching results', async () => { const scope = await initRepo(); scope @@ -642,6 +671,7 @@ describe('modules/platform/gitlab/index', () => { ); expect(res).toBeNull(); }); + it('returns status if name found', async () => { const scope = await initRepo(); scope @@ -660,6 +690,7 @@ describe('modules/platform/gitlab/index', () => { expect(res).toEqual(BranchStatus.green); }); }); + describe('setBranchStatus', () => { it.each([BranchStatus.green, BranchStatus.yellow, BranchStatus.red])( 'sets branch status %s', @@ -708,6 +739,7 @@ describe('modules/platform/gitlab/index', () => { const res = await gitlab.findIssue('title-3'); expect(res).toBeNull(); }); + it('finds issue', async () => { httpMock .scope(gitlabApiHost) @@ -730,6 +762,7 @@ describe('modules/platform/gitlab/index', () => { expect(res).not.toBeNull(); }); }); + describe('ensureIssue()', () => { it('creates issue', async () => { httpMock @@ -908,6 +941,7 @@ describe('modules/platform/gitlab/index', () => { expect(res).toBe('updated'); }); }); + describe('ensureIssueClosing()', () => { it('closes issue', async () => { httpMock @@ -941,6 +975,7 @@ describe('modules/platform/gitlab/index', () => { .reply(200); await expect(gitlab.addAssignees(42, ['someuser'])).toResolve(); }); + it('should add the given assignees to the issue', async () => { httpMock .scope(gitlabApiHost) @@ -956,6 +991,7 @@ describe('modules/platform/gitlab/index', () => { gitlab.addAssignees(42, ['someuser', 'someotheruser']) ).toResolve(); }); + it('should swallow error', async () => { httpMock .scope(gitlabApiHost) @@ -1093,6 +1129,7 @@ describe('modules/platform/gitlab/index', () => { }) ).toResolve(); }); + it('add updates comment if necessary', async () => { const scope = await initRepo(); scope @@ -1108,6 +1145,7 @@ describe('modules/platform/gitlab/index', () => { }) ).toResolve(); }); + it('skips comment', async () => { const scope = await initRepo(); scope @@ -1121,6 +1159,7 @@ describe('modules/platform/gitlab/index', () => { }) ).toResolve(); }); + it('handles comment with no description', async () => { const scope = await initRepo(); scope @@ -1135,6 +1174,7 @@ describe('modules/platform/gitlab/index', () => { ).toResolve(); }); }); + describe('ensureCommentRemoval', () => { it('deletes comment by topic if found', async () => { const scope = await initRepo(); @@ -1151,6 +1191,7 @@ describe('modules/platform/gitlab/index', () => { }) ).toResolve(); }); + it('deletes comment by content if found', async () => { const scope = await initRepo(); scope @@ -1167,6 +1208,7 @@ describe('modules/platform/gitlab/index', () => { ).toResolve(); }); }); + describe('findPr(branchName, prTitle, state)', () => { it('returns true if no title and all state', async () => { httpMock @@ -1187,6 +1229,7 @@ describe('modules/platform/gitlab/index', () => { }); expect(res).toBeDefined(); }); + it('returns true if not open', async () => { httpMock .scope(gitlabApiHost) @@ -1250,6 +1293,7 @@ describe('modules/platform/gitlab/index', () => { }); expect(res).toBeDefined(); }); + it('returns true with draft prefix title', async () => { httpMock .scope(gitlabApiHost) @@ -1270,6 +1314,7 @@ describe('modules/platform/gitlab/index', () => { }); expect(res).toBeDefined(); }); + it('returns true with deprecated draft prefix title', async () => { httpMock .scope(gitlabApiHost) @@ -1330,6 +1375,7 @@ describe('modules/platform/gitlab/index', () => { }); expect(pr).toMatchSnapshot(); }); + it('uses default branch', async () => { await initPlatform('13.3.6-ee'); httpMock @@ -1349,6 +1395,7 @@ describe('modules/platform/gitlab/index', () => { }); expect(pr).toMatchSnapshot(); }); + it('supports draftPR on < 13.2', async () => { await initPlatform('13.1.0-ee'); httpMock @@ -1368,6 +1415,7 @@ describe('modules/platform/gitlab/index', () => { }); expect(pr).toMatchSnapshot(); }); + it('supports draftPR on >= 13.2', async () => { await initPlatform('13.2.0-ee'); httpMock @@ -1387,6 +1435,7 @@ describe('modules/platform/gitlab/index', () => { }); expect(pr).toMatchSnapshot(); }); + it('auto-accepts the MR when requested', async () => { await initPlatform('13.3.6-ee'); httpMock @@ -1654,6 +1703,7 @@ describe('modules/platform/gitlab/index', () => { `); }); }); + describe('getPr(prNo)', () => { it('returns the PR', async () => { httpMock @@ -1677,6 +1727,7 @@ describe('modules/platform/gitlab/index', () => { expect(pr).toMatchSnapshot(); expect(pr.hasAssignees).toBeFalse(); }); + it('removes draft prefix from returned title', async () => { httpMock .scope(gitlabApiHost) @@ -1699,6 +1750,7 @@ describe('modules/platform/gitlab/index', () => { expect(pr).toMatchSnapshot(); expect(pr.title).toBe('do something'); }); + it('removes deprecated draft prefix from returned title', async () => { httpMock .scope(gitlabApiHost) @@ -1721,6 +1773,7 @@ describe('modules/platform/gitlab/index', () => { expect(pr).toMatchSnapshot(); expect(pr.title).toBe('do something'); }); + it('returns the mergeable PR', async () => { const scope = await initRepo(); scope @@ -1744,6 +1797,7 @@ describe('modules/platform/gitlab/index', () => { expect(pr).toMatchSnapshot(); expect(pr.hasAssignees).toBeTrue(); }); + it('returns the PR with nonexisting branch', async () => { httpMock .scope(gitlabApiHost) @@ -1771,8 +1825,10 @@ describe('modules/platform/gitlab/index', () => { expect(pr.hasAssignees).toBeTrue(); }); }); + describe('updatePr(prNo, title, body)', () => { jest.resetAllMocks(); + it('updates the PR', async () => { await initPlatform('13.3.6-ee'); httpMock @@ -1794,6 +1850,7 @@ describe('modules/platform/gitlab/index', () => { gitlab.updatePr({ number: 1, prTitle: 'title', prBody: 'body' }) ).toResolve(); }); + it('retains draft status when draft uses current prefix', async () => { await initPlatform('13.3.6-ee'); httpMock @@ -1815,6 +1872,7 @@ describe('modules/platform/gitlab/index', () => { gitlab.updatePr({ number: 1, prTitle: 'title', prBody: 'body' }) ).toResolve(); }); + it('retains draft status when draft uses deprecated prefix', async () => { await initPlatform('13.3.6-ee'); httpMock @@ -1836,6 +1894,7 @@ describe('modules/platform/gitlab/index', () => { gitlab.updatePr({ number: 1, prTitle: 'title', prBody: 'body' }) ).toResolve(); }); + it('closes the PR', async () => { await initPlatform('13.3.6-ee'); httpMock @@ -1863,8 +1922,10 @@ describe('modules/platform/gitlab/index', () => { ).toResolve(); }); }); + describe('mergePr(pr)', () => { jest.resetAllMocks(); + it('merges the PR', async () => { httpMock .scope(gitlabApiHost) @@ -1926,6 +1987,7 @@ These updates have all been created already. Click a checkbox below to force a r expect(res).toHaveLength(0); }); }); + describe('deleteLabel(issueNo, label)', () => { it('should delete the label', async () => { httpMock @@ -1949,6 +2011,7 @@ These updates have all been created already. Click a checkbox below to force a r await expect(gitlab.deleteLabel(42, 'rebase')).toResolve(); }); }); + describe('getJsonFile()', () => { it('returns file content', async () => { const data = { foo: 'bar' }; @@ -2026,6 +2089,7 @@ These updates have all been created already. Click a checkbox below to force a r }); await expect(gitlab.getJsonFile('dir/file.json')).rejects.toThrow(); }); + it('throws on errors', async () => { const scope = await initRepo(); scope @@ -2036,6 +2100,7 @@ These updates have all been created already. Click a checkbox below to force a r await expect(gitlab.getJsonFile('dir/file.json')).rejects.toThrow(); }); }); + describe('filterUnavailableUsers(users)', () => { it('filters users that are busy', async () => { httpMock diff --git a/lib/modules/platform/index.spec.ts b/lib/modules/platform/index.spec.ts index 7cdc1acf9b..9ee1daff3a 100644 --- a/lib/modules/platform/index.spec.ts +++ b/lib/modules/platform/index.spec.ts @@ -40,10 +40,12 @@ describe('modules/platform/index', () => { PLATFORM_NOT_FOUND ); }); + it('throws if wrong platform', async () => { const config = { platform: 'wrong', username: 'abc', password: '123' }; await expect(platform.initPlatform(config)).rejects.toThrow(); }); + it('initializes', async () => { httpMock .scope('https://api.bitbucket.org') diff --git a/lib/modules/platform/util.spec.ts b/lib/modules/platform/util.spec.ts index 726a02a77b..463cdabc0a 100644 --- a/lib/modules/platform/util.spec.ts +++ b/lib/modules/platform/util.spec.ts @@ -16,6 +16,7 @@ describe('modules/platform/util', () => { `('("$url") === $hostType', ({ url, hostType }) => { expect(detectPlatform(url)).toBe(hostType); }); + it('uses host rules', () => { hostRules.add({ hostType: 'gitlab-changelog', diff --git a/lib/modules/versioning/aws-machine-image/index.spec.ts b/lib/modules/versioning/aws-machine-image/index.spec.ts index 56a6ed094d..1f25f2b73f 100644 --- a/lib/modules/versioning/aws-machine-image/index.spec.ts +++ b/lib/modules/versioning/aws-machine-image/index.spec.ts @@ -8,40 +8,49 @@ describe('modules/versioning/aws-machine-image/index', () => { expect(aws.getPatch('ami-00e1b2c30011d4e5f')).toBe(0); }); }); + describe('isValid(version)', () => { it('should return true', () => { expect(aws.isValid('ami-00e1b2c30011d4e5f')).toBeTruthy(); }); + it('should return false', () => { expect(aws.isValid('ami-1')).toBeFalsy(); }); }); + describe('isVersion(version)', () => { it('should return true', () => { expect(aws.isVersion('ami-00e1b2c30011d4e5f')).toBeTruthy(); }); + it('should return false', () => { expect(aws.isVersion('ami-1')).toBeFalsy(); }); }); + describe('isCompatible(version)', () => { it('should return true', () => { expect(aws.isCompatible('ami-00e1b2c30011d4e5f')).toBeTruthy(); }); + it('should return false', () => { expect(aws.isCompatible('ami-1')).toBeFalsy(); }); }); + describe('isCompatible(version,range)', () => { it('should return true', () => { expect( aws.isCompatible('ami-00e1b2c30011d4e5f', 'anything') ).toBeTruthy(); }); + it('should return false', () => { expect(aws.isCompatible('ami-1', 'anything')).toBeFalsy(); }); }); + describe('isGreaterThan(version1, version2)', () => { it('should return true', () => { // Since we can't compare AMI IDs directly, we consider any version diff --git a/lib/modules/versioning/generic.spec.ts b/lib/modules/versioning/generic.spec.ts index 52581b164a..b0903e37de 100644 --- a/lib/modules/versioning/generic.spec.ts +++ b/lib/modules/versioning/generic.spec.ts @@ -79,18 +79,22 @@ describe('modules/versioning/generic', () => { expect(api.equals('1.2.3', '1.2.3')).toBeTrue(); expect(api.equals('1.2.3', '3.2.1')).toBeFalse(); }); + it('getMajor', () => { expect(api.getMajor('4.5.6')).toBe(4); expect(api.getMajor('invalid')).toBeNull(); }); + it('getMinor', () => { expect(api.getMinor('4.5.6')).toBe(5); expect(api.getMinor('invalid')).toBeNull(); }); + it('getPatch', () => { expect(api.getPatch('4.5.6')).toBe(6); expect(api.getPatch('invalid')).toBeNull(); }); + it('getNewValue', () => { expect( api.getNewValue({ @@ -101,40 +105,50 @@ describe('modules/versioning/generic', () => { }) ).toBe('3.2.1'); }); + it('isCompatible', () => { expect(api.isCompatible('1.2.3', '')).toBe(true); }); + it('isGreaterThan', () => { expect(api.isGreaterThan('1.2.3', '3.2.1')).toBe(false); expect(api.isGreaterThan('3.2.1', '1.2.3')).toBe(true); }); + it('isSingleVersion', () => { expect(api.isSingleVersion('1.2.3')).toBe(true); }); + it('isStable', () => { expect(api.isStable('1.2.3')).toBe(true); }); + it('isValid', () => { expect(api.isValid('1.2.3')).toBe(true); expect(api.isValid('invalid')).toBe(false); }); + it('isVersion', () => { expect(api.isVersion('1.2.3')).toBe(true); expect(api.isVersion('invalid')).toBe(false); }); + it('matches', () => { expect(api.matches('1.2.3', '1.2.3')).toBe(true); expect(api.matches('1.2.3', '3.2.1')).toBe(false); }); + it('sortVersions', () => { expect(api.sortVersions('1.2.3', '1.2.3')).toBe(0); expect(api.sortVersions('1.2.3', '3.2.1')).toBe(-1); expect(api.sortVersions('3.2.1', '1.2.3')).toBe(1); }); + it('isLessThanRange', () => { expect(api.isLessThanRange('1.2.3', '3.2.1')).toBeTrue(); expect(api.isLessThanRange('3.2.1', '1.2.3')).toBeFalse(); }); + it('minSatisfyingVersion', () => { expect(api.minSatisfyingVersion(['1.2.3'], '1.2.3')).toBe('1.2.3'); expect( @@ -144,6 +158,7 @@ describe('modules/versioning/generic', () => { api.minSatisfyingVersion(['1.1.1', '2.2.2', '3.3.3'], '1.2.3') ).toBeNull(); }); + it('getSatisfyingVersion', () => { expect(api.getSatisfyingVersion(['1.2.3'], '1.2.3')).toBe('1.2.3'); expect( diff --git a/lib/modules/versioning/hashicorp/index.spec.ts b/lib/modules/versioning/hashicorp/index.spec.ts index d3c429f3a0..fc4d18bb2c 100644 --- a/lib/modules/versioning/hashicorp/index.spec.ts +++ b/lib/modules/versioning/hashicorp/index.spec.ts @@ -11,6 +11,7 @@ describe('modules/versioning/hashicorp/index', () => { expect(semver.matches(version, range)).toBe(expected); } ); + test.each` versions | range | expected ${['0.4.0', '0.5.0', '4.0.0', '4.2.0', '5.0.0']} | ${'~> 4.0'} | ${'4.2.0'} diff --git a/lib/modules/versioning/index.spec.ts b/lib/modules/versioning/index.spec.ts index c9cb7ef37f..7a0aa24eb1 100644 --- a/lib/modules/versioning/index.spec.ts +++ b/lib/modules/versioning/index.spec.ts @@ -31,6 +31,7 @@ describe('modules/versioning/index', () => { 'sortVersions', ]); }); + it('validates', () => { function validate( module: VersioningApi | VersioningApiConstructor, diff --git a/lib/modules/versioning/node/index.spec.ts b/lib/modules/versioning/node/index.spec.ts index 73f8572062..45572bf1c2 100644 --- a/lib/modules/versioning/node/index.spec.ts +++ b/lib/modules/versioning/node/index.spec.ts @@ -3,9 +3,11 @@ import { api as nodever } from '.'; describe('modules/versioning/node/index', () => { let dtLocal: any; + beforeEach(() => { dtLocal = DateTime.local; }); + afterEach(() => { DateTime.local = dtLocal; }); diff --git a/lib/modules/versioning/poetry/index.spec.ts b/lib/modules/versioning/poetry/index.spec.ts index 7e8eaa906c..bfd2af34d0 100644 --- a/lib/modules/versioning/poetry/index.spec.ts +++ b/lib/modules/versioning/poetry/index.spec.ts @@ -177,6 +177,7 @@ describe('modules/versioning/poetry/index', () => { expect(versioning.getSatisfyingVersion(versions, range)).toBe(expected); } ); + test.each` currentValue | rangeStrategy | currentVersion | newVersion | expected ${'1.0.0'} | ${'bump'} | ${'1.0.0'} | ${'1.1.0'} | ${'1.1.0'} diff --git a/lib/modules/versioning/versioning-metadata.spec.ts b/lib/modules/versioning/versioning-metadata.spec.ts index aceea9ccbc..01064e0538 100644 --- a/lib/modules/versioning/versioning-metadata.spec.ts +++ b/lib/modules/versioning/versioning-metadata.spec.ts @@ -21,6 +21,7 @@ describe('modules/versioning/versioning-metadata', () => { } } }); + it('contains mandatory fields', async () => { const allVersioning = (await readdir('lib/modules/versioning')).filter( (item) => !item.includes('.') && !item.startsWith('_') diff --git a/lib/proxy.spec.ts b/lib/proxy.spec.ts index 408b9a275d..71f1761c01 100644 --- a/lib/proxy.spec.ts +++ b/lib/proxy.spec.ts @@ -19,6 +19,7 @@ describe('proxy', () => { bootstrap(); expect(hasProxy()).toBeTrue(); }); + it('copies upper case HTTP_PROXY to http_proxy', () => { process.env.HTTP_PROXY = httpProxy; bootstrap(); @@ -31,11 +32,13 @@ describe('proxy', () => { expect(process.env.NO_PROXY).toBeUndefined(); expect(process.env.no_proxy).toBeUndefined(); }); + it('respects HTTPS_PROXY', () => { process.env.HTTPS_PROXY = httpsProxy; bootstrap(); expect(hasProxy()).toBeTrue(); }); + it('copies upper case HTTPS_PROXY to https_proxy', () => { process.env.HTTPS_PROXY = httpsProxy; bootstrap(); @@ -48,6 +51,7 @@ describe('proxy', () => { expect(process.env.NO_PROXY).toBeUndefined(); expect(process.env.no_proxy).toBeUndefined(); }); + it('does nothing', () => { process.env.no_proxy = noProxy; bootstrap(); diff --git a/lib/util/cache/memory/index.spec.ts b/lib/util/cache/memory/index.spec.ts index 09abf21e1f..abaa371b75 100644 --- a/lib/util/cache/memory/index.spec.ts +++ b/lib/util/cache/memory/index.spec.ts @@ -4,11 +4,13 @@ describe('util/cache/memory/index', () => { it('returns undefined if not init', () => { expect(memCache.get('key1')).toBeUndefined(); }); + it('sets and gets repo cache', () => { memCache.init(); memCache.set('key2', 'value'); expect(memCache.get('key2')).toBe('value'); }); + it('resets', () => { memCache.init(); memCache.set('key3', 'value'); diff --git a/lib/util/cache/package/file.spec.ts b/lib/util/cache/package/file.spec.ts index 159283398a..bc15188900 100644 --- a/lib/util/cache/package/file.spec.ts +++ b/lib/util/cache/package/file.spec.ts @@ -6,6 +6,7 @@ describe('util/cache/package/file', () => { await set('test', 'key', 1234); expect(await get('test', 'key')).toBeUndefined(); }); + it('gets null', async () => { init(os.tmpdir()); expect(await get('test', 'missing-key')).toBeUndefined(); diff --git a/lib/util/cache/package/index.spec.ts b/lib/util/cache/package/index.spec.ts index e2ebce71ee..24f1cb5e5f 100644 --- a/lib/util/cache/package/index.spec.ts +++ b/lib/util/cache/package/index.spec.ts @@ -8,6 +8,7 @@ describe('util/cache/package/index', () => { expect(await get('test', 'missing-key')).toBeUndefined(); expect(await set('test', 'some-key', 'some-value', 5)).toBeUndefined(); }); + it('sets and gets file', async () => { await init({ cacheDir: 'some-dir' }); expect( @@ -15,6 +16,7 @@ describe('util/cache/package/index', () => { ).toBeUndefined(); expect(await get('some-namespace', 'unknown-key')).toBeUndefined(); }); + it('sets and gets redis', async () => { await init({ redisUrl: 'some-url' }); expect( diff --git a/lib/util/cache/repository/index.spec.ts b/lib/util/cache/repository/index.spec.ts index af06921428..8e4d01824b 100644 --- a/lib/util/cache/repository/index.spec.ts +++ b/lib/util/cache/repository/index.spec.ts @@ -12,14 +12,17 @@ describe('util/cache/repository/index', () => { jest.resetAllMocks(); GlobalConfig.set({ cacheDir: '/tmp/renovate/cache/' }); }); + const config = { platform: 'github', repository: 'abc/def', }; + it('catches and returns', async () => { await repositoryCache.initialize({}); expect(fs.readFile.mock.calls).toHaveLength(0); }); + it('returns if cache not enabled', async () => { await repositoryCache.initialize({ ...config, @@ -27,6 +30,7 @@ describe('util/cache/repository/index', () => { }); expect(fs.readFile.mock.calls).toHaveLength(0); }); + it('resets if invalid', async () => { fs.readFile.mockResolvedValueOnce('{}' as any); await repositoryCache.initialize({ @@ -38,6 +42,7 @@ describe('util/cache/repository/index', () => { revision: repositoryCache.CACHE_REVISION, }); }); + it('reads from cache and finalizes', async () => { fs.readFile.mockResolvedValueOnce( `{"repository":"abc/def","revision":${repositoryCache.CACHE_REVISION}}` as any @@ -50,6 +55,7 @@ describe('util/cache/repository/index', () => { expect(fs.readFile.mock.calls).toHaveLength(1); expect(fs.outputFile.mock.calls).toHaveLength(1); }); + it('gets', () => { expect(repositoryCache.getCache()).toEqual({}); }); diff --git a/lib/util/exec/buildpack.spec.ts b/lib/util/exec/buildpack.spec.ts index 161856e109..050ac92da4 100644 --- a/lib/util/exec/buildpack.spec.ts +++ b/lib/util/exec/buildpack.spec.ts @@ -18,13 +18,16 @@ describe('util/exec/buildpack', () => { GlobalConfig.reset(); delete process.env.BUILDPACK; }); + it('returns false if binarySource is not install', () => { expect(isDynamicInstall()).toBeFalse(); }); + it('returns false if not buildpack', () => { GlobalConfig.set({ binarySource: 'install' }); expect(isDynamicInstall()).toBeFalse(); }); + it('returns false if any unsupported tools', () => { GlobalConfig.set({ binarySource: 'install' }); process.env.BUILDPACK = 'true'; @@ -34,6 +37,7 @@ describe('util/exec/buildpack', () => { ]; expect(isDynamicInstall(toolConstraints)).toBeFalse(); }); + it('returns false if supported tools', () => { GlobalConfig.set({ binarySource: 'install' }); process.env.BUILDPACK = 'true'; @@ -41,6 +45,7 @@ describe('util/exec/buildpack', () => { expect(isDynamicInstall(toolConstraints)).toBeTrue(); }); }); + describe('resolveConstraint()', () => { beforeEach(() => { datasource.getPkgReleases.mockResolvedValueOnce({ @@ -53,6 +58,7 @@ describe('util/exec/buildpack', () => { ], }); }); + it('returns from config', async () => { expect( await resolveConstraint({ toolName: 'composer', constraint: '1.1.0' }) @@ -103,6 +109,7 @@ describe('util/exec/buildpack', () => { ).toBe('1.2.3'); }); }); + describe('generateInstallCommands()', () => { beforeEach(() => { datasource.getPkgReleases.mockResolvedValueOnce({ @@ -115,6 +122,7 @@ describe('util/exec/buildpack', () => { ], }); }); + it('returns install commands', async () => { const toolConstraints: ToolConstraint[] = [ { @@ -128,6 +136,7 @@ describe('util/exec/buildpack', () => { ] `); }); + it('hashes npm', async () => { const toolConstraints: ToolConstraint[] = [{ toolName: 'npm' }]; const res = await generateInstallCommands(toolConstraints); diff --git a/lib/util/exec/docker/index.spec.ts b/lib/util/exec/docker/index.spec.ts index bc16c7965e..d55501395c 100644 --- a/lib/util/exec/docker/index.spec.ts +++ b/lib/util/exec/docker/index.spec.ts @@ -95,6 +95,7 @@ describe('util/exec/docker/index', () => { getPkgReleases.mockResolvedValueOnce({ releases } as never); expect(await getDockerTag('foo', '^1.2.3', 'npm')).toBe('1.9.9'); }); + it('filters out node unstable', async () => { const releases = [ { version: '12.0.0' }, diff --git a/lib/util/exec/env.spec.ts b/lib/util/exec/env.spec.ts index ac221486e9..5ad5dd978b 100644 --- a/lib/util/exec/env.spec.ts +++ b/lib/util/exec/env.spec.ts @@ -12,14 +12,17 @@ describe('util/exec/env', () => { 'LANG', 'DOCKER_HOST', ]; + beforeEach(() => { envVars.forEach((env) => { process.env[env] = env; }); }); + afterEach(() => { envVars.forEach((env) => delete process.env[env]); }); + it('returns default environment variables', () => { expect(getChildProcessEnv()).toMatchObject({ DOCKER_HOST: 'DOCKER_HOST', @@ -32,10 +35,12 @@ describe('util/exec/env', () => { PATH: 'PATH', }); }); + it('returns environment variable only if defined', () => { delete process.env.PATH; expect(getChildProcessEnv()).not.toHaveProperty('PATH'); }); + it('returns custom environment variables if passed and defined', () => { process.env.FOOBAR = 'FOOBAR'; expect(getChildProcessEnv(['FOOBAR'])).toMatchObject({ diff --git a/lib/util/exec/index.spec.ts b/lib/util/exec/index.spec.ts index 9d1b03b01b..d7c70b9dae 100644 --- a/lib/util/exec/index.spec.ts +++ b/lib/util/exec/index.spec.ts @@ -764,6 +764,7 @@ describe('util/exec/index', () => { `docker run --rm --name=renovate_image --label=renovate_child renovate/image bash -l -c "echo hello"`, ]); }); + it('Supports binarySource=install', async () => { process.env = processEnv; cpExec.mockImplementation(() => { @@ -823,6 +824,7 @@ describe('util/exec/index', () => { ); expect(removeDockerContainerSpy).toHaveBeenCalledTimes(2); }); + it('converts to TEMPORARY_ERROR', async () => { cpExec.mockImplementation(() => { class ErrorSignal extends Error { diff --git a/lib/util/fs/index.spec.ts b/lib/util/fs/index.spec.ts index 3c15ab39c4..b9af543aec 100644 --- a/lib/util/fs/index.spec.ts +++ b/lib/util/fs/index.spec.ts @@ -32,6 +32,7 @@ describe('util/fs/index', () => { it('reads buffer', async () => { expect(await readLocalFile(__filename)).toBeInstanceOf(Buffer); }); + it('reads string', async () => { expect(typeof (await readLocalFile(__filename, 'utf8'))).toBe('string'); }); @@ -46,9 +47,11 @@ describe('util/fs/index', () => { it('returns true for file', async () => { expect(await localPathExists(__filename)).toBeTrue(); }); + it('returns true for directory', async () => { expect(await localPathExists(getSubDirectory(__filename))).toBeTrue(); }); + it('returns false', async () => { expect(await localPathExists(__filename.replace('.ts', '.txt'))).toBe( false diff --git a/lib/util/git/auth.spec.ts b/lib/util/git/auth.spec.ts index 4e0618b625..00b4a8f13a 100644 --- a/lib/util/git/auth.spec.ts +++ b/lib/util/git/auth.spec.ts @@ -5,6 +5,7 @@ describe('util/git/auth', () => { afterEach(() => { delete process.env.GIT_CONFIG_COUNT; }); + describe('getGitAuthenticatedEnvironmentVariables()', () => { it('returns url with token', () => { expect( diff --git a/lib/util/git/author.spec.ts b/lib/util/git/author.spec.ts index 2fa42bdd33..6f6b80fe00 100644 --- a/lib/util/git/author.spec.ts +++ b/lib/util/git/author.spec.ts @@ -5,15 +5,18 @@ describe('util/git/author', () => { it('returns null if empty email given', () => { expect(parseGitAuthor(undefined)).toBeNull(); }); + it('handles a normal address', () => { expect(parseGitAuthor('renovate@whitesourcesoftware.com')).not.toBeNull(); }); + it('parses bot email', () => { expect(parseGitAuthor('renovate[bot]@users.noreply.github.com')).toEqual({ address: 'renovate[bot]@users.noreply.github.com', name: 'renovate[bot]', }); }); + it('parses bot name and email', () => { expect( parseGitAuthor('renovate[bot] <renovate[bot]@users.noreply.github.com>') @@ -22,14 +25,17 @@ describe('util/git/author', () => { name: 'renovate[bot]', }); }); + it('escapes names', () => { expect(parseGitAuthor('name [what] <name@what.com>').name).toBe( `name [what]` ); }); + it('tries again and fails', () => { expect(parseGitAuthor('foo<foo>')).toBeNull(); }); + it('gives up', () => { expect(parseGitAuthor('a.b.c')).toBeNull(); }); diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts index 8302f6af1b..e0991f8cd1 100644 --- a/lib/util/git/index.spec.ts +++ b/lib/util/git/index.spec.ts @@ -169,10 +169,12 @@ describe('util/git/index', () => { it('sets the base branch as master', async () => { await expect(git.checkoutBranch(defaultBranch)).resolves.not.toThrow(); }); + it('sets non-master base branch', async () => { await expect(git.checkoutBranch('develop')).resolves.not.toThrow(); }); }); + describe('getFileList()', () => { it('should return the correct files', async () => { expect(await git.getFileList()).toEqual([ @@ -181,6 +183,7 @@ describe('util/git/index', () => { 'past_file', ]); }); + it('should exclude submodules', async () => { const repo = Git(base.path); await repo.submoduleAdd(base.path, 'submodule'); @@ -200,14 +203,17 @@ describe('util/git/index', () => { await repo.reset(['--hard', 'HEAD^']); }); }); + describe('branchExists(branchName)', () => { it('should return true if found', () => { expect(git.branchExists('renovate/future_branch')).toBeTrue(); }); + it('should return false if not found', () => { expect(git.branchExists('not_found')).toBeFalse(); }); }); + describe('getBranchList()', () => { it('should return all branches', () => { const res = git.getBranchList(); @@ -216,32 +222,39 @@ describe('util/git/index', () => { expect(res).toContain(defaultBranch); }); }); + describe('isBranchStale()', () => { it('should return false if same SHA as master', async () => { expect(await git.isBranchStale('renovate/future_branch')).toBeFalse(); }); + it('should return true if SHA different from master', async () => { expect(await git.isBranchStale('renovate/past_branch')).toBeTrue(); }); + it('should return result even if non-default and not under branchPrefix', async () => { expect(await git.isBranchStale('develop')).toBeTrue(); expect(await git.isBranchStale('develop')).toBeTrue(); // cache }); }); + describe('isBranchModified()', () => { it('should return false when branch is not found', async () => { expect(await git.isBranchModified('renovate/not_found')).toBeFalse(); }); + it('should return false when author matches', async () => { expect(await git.isBranchModified('renovate/future_branch')).toBeFalse(); expect(await git.isBranchModified('renovate/future_branch')).toBeFalse(); }); + it('should return false when author is ignored', async () => { git.setUserRepoConfig({ gitIgnoredAuthors: ['custom@example.com'], }); expect(await git.isBranchModified('renovate/custom_author')).toBeFalse(); }); + it('should return true when custom author is unknown', async () => { expect(await git.isBranchModified('renovate/custom_author')).toBeTrue(); }); @@ -253,20 +266,24 @@ describe('util/git/index', () => { expect(hex).toBe(git.getBranchCommit(defaultBranch)); expect(hex).toHaveLength(40); }); + it('should return null', () => { expect(git.getBranchCommit('not_found')).toBeNull(); }); }); + describe('getBranchParentSha(branchName)', () => { it('should return sha if found', async () => { const parentSha = await git.getBranchParentSha('renovate/future_branch'); expect(parentSha).toHaveLength(40); expect(parentSha).toEqual(git.getBranchCommit(defaultBranch)); }); + it('should return false if not found', async () => { expect(await git.getBranchParentSha('not_found')).toBeNull(); }); }); + describe('getBranchFiles(branchName)', () => { it('detects changed files compared to current base branch', async () => { const file: FileChange = { @@ -296,10 +313,12 @@ describe('util/git/index', () => { ]); expect(merged.all).toContain('renovate/future_branch'); }); + it('should throw if branch merge throws', async () => { await expect(git.mergeBranch('not_found')).rejects.toThrow(); }); }); + describe('deleteBranch(branchName)', () => { it('should send delete', async () => { await git.deleteBranch('renovate/past_branch'); @@ -307,29 +326,35 @@ describe('util/git/index', () => { expect(branches.all).not.toContain('renovate/past_branch'); }); }); + describe('getBranchLastCommitTime', () => { it('should return a Date', async () => { const time = await git.getBranchLastCommitTime(defaultBranch); expect(time).toEqual(masterCommitDate); }); + it('handles error', async () => { const res = await git.getBranchLastCommitTime('some-branch'); expect(res).toBeDefined(); }); }); + describe('getFile(filePath, branchName)', () => { it('gets the file', async () => { const res = await git.getFile('master_file'); expect(res).toBe(defaultBranch); }); + it('short cuts 404', async () => { const res = await git.getFile('some-missing-path'); expect(res).toBeNull(); }); + it('returns null for 404', async () => { expect(await git.getFile('some-path', 'some-branch')).toBeNull(); }); }); + describe('commitFiles({branchName, files, message})', () => { it('creates file', async () => { const file: FileChange = { @@ -344,6 +369,7 @@ describe('util/git/index', () => { }); expect(commit).not.toBeNull(); }); + it('deletes file', async () => { const file: FileChange = { type: 'deletion', @@ -356,6 +382,7 @@ describe('util/git/index', () => { }); expect(commit).not.toBeNull(); }); + it('updates multiple files', async () => { const files: FileChange[] = [ { @@ -376,6 +403,7 @@ describe('util/git/index', () => { }); expect(commit).not.toBeNull(); }); + it('uses right commit SHA', async () => { const files: FileChange[] = [ { @@ -398,6 +426,7 @@ describe('util/git/index', () => { const remoteSha = await git.fetchCommit(commitConfig); expect(commitSha).toEqual(remoteSha); }); + it('updates git submodules', async () => { const files: FileChange[] = [ { @@ -413,6 +442,7 @@ describe('util/git/index', () => { }); expect(commit).toBeNull(); }); + it('does not push when no diff', async () => { const files: FileChange[] = [ { @@ -552,6 +582,7 @@ describe('util/git/index', () => { describe('Storage.getUrl()', () => { const getUrl = git.getUrl; + it('returns https url', () => { expect( getUrl({ @@ -680,6 +711,7 @@ describe('util/git/index', () => { expect(res).toBe('test-extra-config-value'); }); }); + describe('setGitAuthor()', () => { it('throws for invalid', () => { expect(() => git.setGitAuthor('invalid')).toThrow(CONFIG_VALIDATION); @@ -770,6 +802,7 @@ describe('util/git/index', () => { beforeEach(() => { jest.resetAllMocks(); }); + it('returns cached values', async () => { conflictsCache.getCachedConflictResult.mockReturnValue(true); diff --git a/lib/util/git/private-key.spec.ts b/lib/util/git/private-key.spec.ts index 8349f573fd..21faa327a4 100644 --- a/lib/util/git/private-key.spec.ts +++ b/lib/util/git/private-key.spec.ts @@ -17,6 +17,7 @@ describe('util/git/private-key', () => { await expect(writePrivateKey()).resolves.not.toThrow(); await expect(configSigningKey('/tmp/some-repo')).resolves.not.toThrow(); }); + it('throws error if failing', async () => { setPrivateKey('some-key'); exec.exec.mockResolvedValueOnce({ @@ -25,6 +26,7 @@ describe('util/git/private-key', () => { }); await expect(writePrivateKey()).rejects.toThrow(); }); + it('imports the private key', async () => { setPrivateKey('some-key'); exec.exec.mockResolvedValueOnce({ @@ -34,6 +36,7 @@ describe('util/git/private-key', () => { await expect(writePrivateKey()).resolves.not.toThrow(); await expect(configSigningKey('/tmp/some-repo')).resolves.not.toThrow(); }); + it('does not import the key again', async () => { await expect(writePrivateKey()).resolves.not.toThrow(); await expect(configSigningKey('/tmp/some-repo')).resolves.not.toThrow(); diff --git a/lib/util/host-rules.spec.ts b/lib/util/host-rules.spec.ts index cbeaadfb34..c3991fdacd 100644 --- a/lib/util/host-rules.spec.ts +++ b/lib/util/host-rules.spec.ts @@ -15,6 +15,7 @@ describe('util/host-rules', () => { beforeEach(() => { clear(); }); + describe('add()', () => { it('throws if both domainName and hostName', () => { expect(() => @@ -25,6 +26,7 @@ describe('util/host-rules', () => { } as HostRule) ).toThrow(); }); + it('throws if both domainName and baseUrl', () => { expect(() => add({ @@ -34,6 +36,7 @@ describe('util/host-rules', () => { } as HostRule) ).toThrow(); }); + it('throws if both hostName and baseUrl', () => { expect(() => add({ @@ -43,6 +46,7 @@ describe('util/host-rules', () => { } as HostRule) ).toThrow(); }); + it('supports baseUrl-only', () => { add({ matchHost: 'https://some.endpoint', @@ -55,13 +59,16 @@ describe('util/host-rules', () => { }); }); }); + describe('find()', () => { beforeEach(() => { clear(); }); + it('warns and returns empty for bad search', () => { expect(find({ abc: 'def' } as any)).toEqual({}); }); + it('needs exact host matches', () => { add({ hostType: NugetDatasource.id, @@ -81,6 +88,7 @@ describe('util/host-rules', () => { find({ hostType: NugetDatasource.id, url: 'https://not-nuget.org' }) ).toEqual({}); }); + it('matches on empty rules', () => { add({ enabled: true, @@ -89,6 +97,7 @@ describe('util/host-rules', () => { find({ hostType: NugetDatasource.id, url: 'https://api.github.com' }) ).toEqual({ enabled: true }); }); + it('matches on hostType', () => { add({ hostType: NugetDatasource.id, @@ -98,6 +107,7 @@ describe('util/host-rules', () => { find({ hostType: NugetDatasource.id, url: 'https://nuget.local/api' }) ).toEqual({ token: 'abc' }); }); + it('matches on domainName', () => { add({ domainName: 'github.com', @@ -190,6 +200,7 @@ describe('util/host-rules', () => { find({ hostType: NugetDatasource.id, url: 'https://nuget.local/api' }) ).toEqual({ token: 'abc' }); }); + it('matches on matchHost with protocol', () => { add({ matchHost: 'https://domain.com', @@ -204,6 +215,7 @@ describe('util/host-rules', () => { }).token ).toBe('def'); }); + it('matches on matchHost without protocol', () => { add({ matchHost: 'domain.com', @@ -213,6 +225,7 @@ describe('util/host-rules', () => { expect(find({ url: 'https://domain.com' }).token).toBe('def'); expect(find({ url: 'httpsdomain.com' }).token).toBeUndefined(); }); + it('matches on matchHost with dot prefix', () => { add({ matchHost: '.domain.com', @@ -222,6 +235,7 @@ describe('util/host-rules', () => { expect(find({ url: 'https://domain.com' }).token).toBeUndefined(); expect(find({ url: 'httpsdomain.com' }).token).toBeUndefined(); }); + it('matches on hostType and endpoint', () => { add({ hostType: NugetDatasource.id, @@ -233,6 +247,7 @@ describe('util/host-rules', () => { .token ).toBe('abc'); }); + it('matches on endpoint subresource', () => { add({ hostType: NugetDatasource.id, @@ -246,6 +261,7 @@ describe('util/host-rules', () => { }) ).toEqual({ token: 'abc' }); }); + it('matches shortest matchHost first', () => { add({ matchHost: 'https://nuget.local/api', @@ -305,6 +321,7 @@ describe('util/host-rules', () => { it('warns and returns empty for bad search', () => { expect(findAll({ abc: 'def' } as any)).toEqual([]); }); + it('needs exact host matches', () => { const hostRule = { hostType: 'nuget', @@ -324,6 +341,7 @@ describe('util/host-rules', () => { ]); }); }); + describe('getAll()', () => { it('returns all host rules', () => { const hostRule1 = { diff --git a/lib/util/html.spec.ts b/lib/util/html.spec.ts index 09795d27ed..95af2a2d18 100644 --- a/lib/util/html.spec.ts +++ b/lib/util/html.spec.ts @@ -10,6 +10,7 @@ describe('util/html', () => { expect(div.textContent).toBe('Hello, world!'); expect(div instanceof parser.HTMLElement).toBeTrue(); }); + it('returns empty', () => { const body = parse(''); expect(body.childNodes).toHaveLength(0); diff --git a/lib/util/http/bitbucket-server.spec.ts b/lib/util/http/bitbucket-server.spec.ts index 21b91448ac..68b5cefe55 100644 --- a/lib/util/http/bitbucket-server.spec.ts +++ b/lib/util/http/bitbucket-server.spec.ts @@ -7,6 +7,7 @@ const baseUrl = 'https://git.example.com'; describe('util/http/bitbucket-server', () => { let api: BitbucketServerHttp; + beforeEach(() => { api = new BitbucketServerHttp(); diff --git a/lib/util/http/bitbucket.spec.ts b/lib/util/http/bitbucket.spec.ts index 9ba588ad3a..a78b78bced 100644 --- a/lib/util/http/bitbucket.spec.ts +++ b/lib/util/http/bitbucket.spec.ts @@ -7,6 +7,7 @@ const baseUrl = 'https://api.bitbucket.org'; describe('util/http/bitbucket', () => { let api: BitbucketHttp; + beforeEach(() => { api = new BitbucketHttp(); @@ -30,6 +31,7 @@ describe('util/http/bitbucket', () => { const res = await api.postJson('some-url'); expect(res.body).toEqual(body); }); + it('accepts custom baseUrl', async () => { const customBaseUrl = 'https://api-test.bitbucket.org'; httpMock.scope(baseUrl).post('/some-url').reply(200, {}); diff --git a/lib/util/http/gitea.spec.ts b/lib/util/http/gitea.spec.ts index f21fc1816c..cc7426638b 100644 --- a/lib/util/http/gitea.spec.ts +++ b/lib/util/http/gitea.spec.ts @@ -63,6 +63,7 @@ describe('util/http/gitea', () => { expect(res.body.data).toHaveLength(6); expect(res.body.data).toEqual(['abc', 'def', 'ghi', 'jkl', 'mno', 'pqr']); }); + it('handles pagination with empty response', async () => { httpMock .scope(baseUrl) diff --git a/lib/util/http/github.spec.ts b/lib/util/http/github.spec.ts index b1ffb15b3f..7afd84ce9a 100644 --- a/lib/util/http/github.spec.ts +++ b/lib/util/http/github.spec.ts @@ -107,6 +107,7 @@ describe('util/http/github', () => { const res = await githubApi.getJson(url, { paginate: true }); expect(res.body).toEqual(['a', 'b', 'c', 'd', 'e']); }); + it('uses paginationField', async () => { const url = '/some-url'; httpMock @@ -135,6 +136,7 @@ describe('util/http/github', () => { }); expect(res.body.the_field).toEqual(['a', 'b', 'c', 'd']); }); + it('attempts to paginate', async () => { const url = '/some-url'; httpMock @@ -147,6 +149,7 @@ describe('util/http/github', () => { expect(res).toBeDefined(); expect(res.body).toEqual(['a']); }); + describe('handleGotError', () => { async function fail( code: number, @@ -182,6 +185,7 @@ describe('util/http/github', () => { 'Response code 404 (Not Found)' ); }); + it('should throw 410', async () => { await expect( fail(410, { message: 'Issues are disabled for this repo' }) @@ -189,6 +193,7 @@ describe('util/http/github', () => { 'Response code 410 (Issues are disabled for this repo)' ); }); + it('should throw rate limit exceeded', async () => { await expect( fail(403, { @@ -197,6 +202,7 @@ describe('util/http/github', () => { }) ).rejects.toThrow(PLATFORM_RATE_LIMIT_EXCEEDED); }); + it('should throw secondary rate limit exceeded', async () => { await expect( fail(403, { @@ -205,11 +211,13 @@ describe('util/http/github', () => { }) ).rejects.toThrow(PLATFORM_RATE_LIMIT_EXCEEDED); }); + it('should throw Bad credentials', async () => { await expect( fail(401, { message: 'Bad credentials. (401)' }) ).rejects.toThrow(PLATFORM_BAD_CREDENTIALS); }); + it('should throw platform failure', async () => { await expect( fail( @@ -221,6 +229,7 @@ describe('util/http/github', () => { ) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('should throw platform failure for ENOTFOUND, ETIMEDOUT or EAI_AGAIN', async () => { const codes = ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN']; for (let idx = 0; idx < codes.length; idx += 1) { @@ -230,22 +239,27 @@ describe('util/http/github', () => { ); } }); + it('should throw platform failure for 500', async () => { await expect(fail(500)).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('should throw platform failure ParseError', async () => { await expect(fail(200, '{{')).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('should throw for unauthorized integration', async () => { await expect( fail(403, { message: 'Resource not accessible by integration (403)' }) ).rejects.toThrow(PLATFORM_INTEGRATION_UNAUTHORIZED); }); + it('should throw for unauthorized integration2', async () => { await expect( fail(403, { message: 'Upgrade to GitHub Pro' }) ).rejects.toThrow('Upgrade to GitHub Pro'); }); + it('should throw on abuse', async () => { await expect( fail(403, { @@ -253,6 +267,7 @@ describe('util/http/github', () => { }) ).rejects.toThrow(PLATFORM_RATE_LIMIT_EXCEEDED); }); + it('should throw on repository change', async () => { await expect( fail(422, { @@ -261,6 +276,7 @@ describe('util/http/github', () => { }) ).rejects.toThrow(REPOSITORY_CHANGED); }); + it('should throw platform failure on 422 response', async () => { await expect( fail(422, { @@ -268,6 +284,7 @@ describe('util/http/github', () => { }) ).rejects.toThrow(EXTERNAL_HOST_ERROR); }); + it('should throw original error when failed to add reviewers', async () => { await expect( fail(422, { @@ -277,6 +294,7 @@ describe('util/http/github', () => { 'Review cannot be requested from pull request author.' ); }); + it('should throw original error when pull requests aleady existed', async () => { await expect( fail(422, { @@ -372,6 +390,7 @@ describe('util/http/github', () => { expect(req).toBeDefined(); expect(req.url).toBe('https://ghe.mycompany.com/api/graphql'); }); + it('supports app mode', async () => { hostRules.add({ hostType: 'github', token: 'x-access-token:123test' }); httpMock @@ -387,6 +406,7 @@ describe('util/http/github', () => { 'application/vnd.github.machine-man-preview+json' ); }); + it('returns empty array for undefined data', async () => { httpMock .scope(githubApiHost) @@ -402,6 +422,7 @@ describe('util/http/github', () => { }) ).toEqual([]); }); + it('returns empty array for undefined data.', async () => { httpMock .scope(githubApiHost) @@ -415,6 +436,7 @@ describe('util/http/github', () => { }) ).toEqual([]); }); + it('throws errors for invalid responses', async () => { httpMock.scope(githubApiHost).post('/graphql').reply(418); await expect( @@ -423,6 +445,7 @@ describe('util/http/github', () => { }) ).rejects.toThrow("Response code 418 (I'm a Teapot)"); }); + it('halves node count and retries request', async () => { httpMock .scope(githubApiHost) @@ -437,6 +460,7 @@ describe('util/http/github', () => { await githubApi.queryRepoField(graphqlQuery, 'testItem') ).toMatchInlineSnapshot(`Array []`); }); + it('queryRepo', async () => { const repository = { foo: 'foo', @@ -450,6 +474,7 @@ describe('util/http/github', () => { const res = await githubApi.requestGraphql(graphqlQuery); expect(res?.data).toStrictEqual({ repository }); }); + it('queryRepoField', async () => { httpMock .scope(githubApiHost) @@ -463,6 +488,7 @@ describe('util/http/github', () => { const items = await githubApi.queryRepoField(graphqlQuery, 'testItem'); expect(items).toHaveLength(3); }); + it('limit result size', async () => { httpMock .scope(githubApiHost) @@ -476,6 +502,7 @@ describe('util/http/github', () => { }); expect(items).toHaveLength(2); }); + it('shrinks items count on 50x', async () => { repoCache.platform ??= {}; repoCache.platform.github ??= {}; @@ -504,6 +531,7 @@ describe('util/http/github', () => { repoCache?.platform?.github?.graphqlPageCache?.testItem?.pageSize ).toBe(25); }); + it('expands items count on timeout', async () => { repoCache.platform ??= {}; repoCache.platform.github ??= {}; @@ -531,6 +559,7 @@ describe('util/http/github', () => { repoCache?.platform?.github?.graphqlPageCache?.testItem?.pageSize ).toBe(84); }); + it('continues to iterate with a lower page size on error 502', async () => { httpMock .scope(githubApiHost) @@ -546,6 +575,7 @@ describe('util/http/github', () => { const items = await githubApi.queryRepoField(graphqlQuery, 'testItem'); expect(items).toHaveLength(3); }); + it('removes cache record once expanded to the maximum', async () => { repoCache.platform ??= {}; repoCache.platform.github ??= {}; @@ -574,6 +604,7 @@ describe('util/http/github', () => { repoCache?.platform?.github?.graphqlPageCache?.testItem ).toBeUndefined(); }); + it('throws on 50x if count < 10', async () => { httpMock.scope(githubApiHost).post('/graphql').reply(500); await expect( diff --git a/lib/util/http/gitlab.spec.ts b/lib/util/http/gitlab.spec.ts index fa2cb03620..1df2998cb1 100644 --- a/lib/util/http/gitlab.spec.ts +++ b/lib/util/http/gitlab.spec.ts @@ -49,6 +49,7 @@ describe('util/http/gitlab', () => { const res = await gitlabApi.getJson('some-url', { paginate: true }); expect(res.body).toHaveLength(4); }); + it('paginates with GITLAB_IGNORE_REPO_URL set', async () => { process.env.GITLAB_IGNORE_REPO_URL = 'true'; setBaseUrl(`${selfHostedUrl}/api/v4/`); @@ -91,12 +92,14 @@ describe('util/http/gitlab', () => { const res = await gitlabApi.getJson('some-url', { paginate: true }); expect(res.body).toHaveLength(1); }); + it('posts', async () => { const body = ['a', 'b']; httpMock.scope(gitlabApiHost).post('/api/v4/some-url').reply(200, body); const res = await gitlabApi.postJson('some-url'); expect(res.body).toEqual(body); }); + it('sets baseUrl', () => { expect(() => setBaseUrl(`${selfHostedUrl}/api/v4/`)).not.toThrow(); }); diff --git a/lib/util/http/host-rules.spec.ts b/lib/util/http/host-rules.spec.ts index f995038770..66412226d5 100644 --- a/lib/util/http/host-rules.spec.ts +++ b/lib/util/http/host-rules.spec.ts @@ -11,6 +11,7 @@ describe('util/http/host-rules', () => { const options = { hostType: PlatformId.Github, }; + beforeEach(() => { // reset module jest.resetAllMocks(); diff --git a/lib/util/http/index.spec.ts b/lib/util/http/index.spec.ts index 04b1f54f03..d90ff7ac0b 100644 --- a/lib/util/http/index.spec.ts +++ b/lib/util/http/index.spec.ts @@ -17,6 +17,7 @@ describe('util/http/index', () => { hostRules.clear(); queue.clear(); }); + it('get', async () => { httpMock.scope(baseUrl).get('/test').reply(200); expect(await http.get('http://renovate.com/test')).toEqual({ @@ -27,6 +28,7 @@ describe('util/http/index', () => { }); expect(httpMock.allUsed()).toBeTrue(); }); + it('returns 429 error', async () => { httpMock.scope(baseUrl).get('/test').reply(429); await expect(http.get('http://renovate.com/test')).rejects.toThrow( @@ -34,6 +36,7 @@ describe('util/http/index', () => { ); expect(httpMock.allUsed()).toBeTrue(); }); + it('converts 404 error to ExternalHostError', async () => { httpMock.scope(baseUrl).get('/test').reply(404); hostRules.add({ abortOnError: true }); @@ -42,12 +45,14 @@ describe('util/http/index', () => { ); expect(httpMock.allUsed()).toBeTrue(); }); + it('disables hosts', async () => { hostRules.add({ matchHost: 'renovate.com', enabled: false }); await expect(http.get('http://renovate.com/test')).rejects.toThrow( HOST_DISABLED ); }); + it('ignores 404 error and does not throw ExternalHostError', async () => { httpMock.scope(baseUrl).get('/test').reply(404); hostRules.add({ abortOnError: true, abortIgnoreStatusCodes: [404] }); @@ -56,6 +61,7 @@ describe('util/http/index', () => { ); expect(httpMock.allUsed()).toBeTrue(); }); + it('getJson', async () => { httpMock.scope(baseUrl).get('/').reply(200, '{ "test": true }'); expect(await http.getJson('http://renovate.com')).toEqual({ @@ -67,6 +73,7 @@ describe('util/http/index', () => { statusCode: 200, }); }); + it('postJson', async () => { httpMock.scope(baseUrl).post('/').reply(200, {}); expect( @@ -81,6 +88,7 @@ describe('util/http/index', () => { }); expect(httpMock.allUsed()).toBeTrue(); }); + it('putJson', async () => { httpMock.scope(baseUrl).put('/').reply(200, {}); expect( @@ -95,6 +103,7 @@ describe('util/http/index', () => { }); expect(httpMock.allUsed()).toBeTrue(); }); + it('patchJson', async () => { httpMock.scope(baseUrl).patch('/').reply(200, {}); expect( @@ -109,6 +118,7 @@ describe('util/http/index', () => { }); expect(httpMock.allUsed()).toBeTrue(); }); + it('deleteJson', async () => { httpMock.scope(baseUrl).delete('/').reply(200, {}); expect( @@ -123,6 +133,7 @@ describe('util/http/index', () => { }); expect(httpMock.allUsed()).toBeTrue(); }); + it('headJson', async () => { httpMock.scope(baseUrl).head('/').reply(200, {}); expect(await http.headJson('http://renovate.com', { baseUrl })).toEqual({ diff --git a/lib/util/index.spec.ts b/lib/util/index.spec.ts index 80844cd6b8..5501cdea42 100644 --- a/lib/util/index.spec.ts +++ b/lib/util/index.spec.ts @@ -3,24 +3,31 @@ import { sampleSize } from '.'; describe('util/index', () => { describe('sampleSize', () => { const array = ['a', 'b', 'c', 'd']; + it('returns correct sized array', () => { expect(sampleSize(array, 2)).toHaveLength(2); }); + it('returns full array for undefined number', () => { expect(sampleSize(array, undefined as never)).toEqual(array); }); + it('returns full array for null number', () => { expect(sampleSize(array, null)).toBeEmptyArray(); }); + it('returns full array for 0 number', () => { expect(sampleSize(array, 0)).toBeEmptyArray(); }); + it('returns empty array for null array', () => { expect(sampleSize(null, 1)).toBeEmptyArray(); }); + it('returns empty array for undefined array', () => { expect(sampleSize(undefined, 1)).toBeEmptyArray(); }); + it('returns empty array for empty array', () => { expect(sampleSize([], 1)).toBeEmptyArray(); }); diff --git a/lib/util/object.spec.ts b/lib/util/object.spec.ts index bfc925898e..3abbe23f09 100644 --- a/lib/util/object.spec.ts +++ b/lib/util/object.spec.ts @@ -8,9 +8,11 @@ describe('util/object', () => { it('finds key in regular object', () => { expect(hasKey('foo', { foo: true })).toBeTrue(); }); + it('detects missing key in regular object', () => { expect(hasKey('foo', { bar: true })).toBeFalse(); }); + it('returns false for wrong instance type', () => { expect(hasKey('foo', 'i-am-not-an-object')).toBeFalse(); }); diff --git a/lib/util/package-rules.spec.ts b/lib/util/package-rules.spec.ts index ffb02321bf..427c9754c0 100644 --- a/lib/util/package-rules.spec.ts +++ b/lib/util/package-rules.spec.ts @@ -35,6 +35,7 @@ describe('util/package-rules', () => { }, ], }; + it('applies', () => { const config: PackageRuleInputConfig = { depName: 'a', @@ -70,6 +71,7 @@ describe('util/package-rules', () => { matchUpdateTypes: ['bump'], }); }); + it('applies both rules for a', () => { const dep = { depName: 'a', @@ -79,6 +81,7 @@ describe('util/package-rules', () => { expect(res.y).toBe(2); expect(res.groupName).toBeUndefined(); }); + it('applies both rules for b', () => { const dep = { depName: 'b', @@ -88,6 +91,7 @@ describe('util/package-rules', () => { expect(res.y).toBe(2); expect(res.groupName).toBeUndefined(); }); + it('applies the second rule', () => { const dep = { depName: 'abc', @@ -97,6 +101,7 @@ describe('util/package-rules', () => { expect(res.y).toBe(2); expect(res.groupName).toBeUndefined(); }); + it('applies matchPackagePrefixes', () => { const dep = { depName: 'xyz/abc', @@ -123,6 +128,7 @@ describe('util/package-rules', () => { expect(res.x).toBeUndefined(); expect(res.groupName).toBe('xyz'); }); + it('applies the second second rule', () => { const dep = { depName: 'bc', @@ -131,6 +137,7 @@ describe('util/package-rules', () => { expect(res.x).toBeUndefined(); expect(res.y).toBe(2); }); + it('excludes package name', () => { const dep = { depName: 'aa', @@ -139,6 +146,7 @@ describe('util/package-rules', () => { expect(res.x).toBeUndefined(); expect(res.y).toBeUndefined(); }); + it('excludes package pattern', () => { const dep = { depName: 'bcd', @@ -147,6 +155,7 @@ describe('util/package-rules', () => { expect(res.x).toBeUndefined(); expect(res.y).toBeUndefined(); }); + it('ignores patterns if lock file maintenance', () => { const dep = { enabled: true, @@ -164,6 +173,7 @@ describe('util/package-rules', () => { const res2 = applyPackageRules({ ...dep, depName: 'anything' }); expect(res2.enabled).toBeFalse(); }); + it('matches anything if missing inclusive rules', () => { const config: TestConfig = { packageRules: [ @@ -184,6 +194,7 @@ describe('util/package-rules', () => { }); expect(res2.x).toBeDefined(); }); + it('supports inclusive or', () => { const config: TestConfig = { packageRules: [ @@ -202,6 +213,7 @@ describe('util/package-rules', () => { }); expect(res2.x).toBeDefined(); }); + it('filters requested depType', () => { const config: TestConfig = { packageRules: [ @@ -219,6 +231,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('filters from list of requested depTypes', () => { const config: TestConfig = { packageRules: [ @@ -236,6 +249,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('filters managers with matching manager', () => { const config: TestConfig = { packageRules: [ @@ -255,6 +269,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('filters managers with non-matching manager', () => { const config: TestConfig = { packageRules: [ @@ -274,6 +289,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('filters languages with matching language', () => { const config: TestConfig = { packageRules: [ @@ -296,6 +312,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('filters languages with non-matching language', () => { const config: TestConfig = { packageRules: [ @@ -315,6 +332,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('filters datasources with matching datasource', () => { const config: TestConfig = { packageRules: [ @@ -332,6 +350,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('filters branches with matching branch', () => { const config: TestConfig = { packageRules: [ @@ -349,6 +368,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('filters datasources with non-matching datasource', () => { const config: TestConfig = { packageRules: [ @@ -365,6 +385,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('filters branches with non-matching branch', () => { const config: TestConfig = { packageRules: [ @@ -381,6 +402,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('filters branches with matching branch regex', () => { const config: TestConfig = { packageRules: [ @@ -416,6 +438,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('filters updateType', () => { const config: TestConfig = { packageRules: [ @@ -433,6 +456,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('matches matchSourceUrlPrefixes', () => { const config: TestConfig = { packageRules: [ @@ -454,6 +478,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('non-matches matchSourceUrlPrefixes', () => { const config: TestConfig = { packageRules: [ @@ -475,6 +500,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('handles matchSourceUrlPrefixes when missing sourceUrl', () => { const config: TestConfig = { packageRules: [ @@ -495,6 +521,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('matches matchSourceUrls', () => { const config: TestConfig = { packageRules: [ @@ -516,6 +543,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('non-matches matchSourceUrls', () => { const config: TestConfig = { packageRules: [ @@ -537,6 +565,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('handles matchSourceUrls when missing sourceUrl', () => { const config: TestConfig = { packageRules: [ @@ -557,6 +586,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('filters naked depType', () => { const config: TestConfig = { packageRules: [ @@ -573,6 +603,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('filters out unrequested depType', () => { const config: TestConfig = { packageRules: [ @@ -590,6 +621,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBeUndefined(); }); + it('checks if matchCurrentVersion selector is valid and satisfies the condition on range overlap', () => { const config: TestConfig = { packageRules: [ @@ -618,6 +650,7 @@ describe('util/package-rules', () => { }); expect(res2.x).toBeUndefined(); }); + it('checks if matchCurrentVersion selector is valid and satisfies the condition on pinned to range overlap', () => { const config: TestConfig = { packageRules: [ @@ -638,6 +671,7 @@ describe('util/package-rules', () => { }); expect(res1.x).toBeDefined(); }); + it('checks if matchCurrentVersion selector is a version and matches if currentValue is a range', () => { const config: TestConfig = { packageRules: [ @@ -665,6 +699,7 @@ describe('util/package-rules', () => { }); expect(res2.x).toBeUndefined(); }); + it('checks if matchCurrentVersion selector works with static values', () => { const config: TestConfig = { packageRules: [ @@ -685,6 +720,7 @@ describe('util/package-rules', () => { }); expect(res1.x).toBeDefined(); }); + it('checks if matchCurrentVersion selector works with regular expressions', () => { const config: TestConfig = { packageRules: [ @@ -714,6 +750,7 @@ describe('util/package-rules', () => { expect(res1.x).toBeDefined(); expect(res2.x).toBeUndefined(); }); + it('checks if matchCurrentVersion selector works with negated regular expressions', () => { const config: TestConfig = { packageRules: [ @@ -743,6 +780,7 @@ describe('util/package-rules', () => { expect(res1.x).toBeUndefined(); expect(res2.x).toBeDefined(); }); + it('matches packageFiles', () => { const config: TestConfig = { packageFile: 'examples/foo/package.json', @@ -765,6 +803,7 @@ describe('util/package-rules', () => { }); expect(res2.x).toBeDefined(); }); + it('matches lock files', () => { const config: TestConfig = { packageFile: 'examples/foo/package.json', @@ -779,6 +818,7 @@ describe('util/package-rules', () => { const res = applyPackageRules(config); expect(res.x).toBeDefined(); }); + it('matches paths', () => { const config: TestConfig = { packageFile: 'examples/foo/package.json', @@ -807,6 +847,7 @@ describe('util/package-rules', () => { }); expect(res3.x).toBeDefined(); }); + it('empty rules', () => { expect(applyPackageRules({ ...config1, packageRules: null })).toEqual({ foo: 'bar', @@ -832,6 +873,7 @@ describe('util/package-rules', () => { const res = applyPackageRules(config); expect(res.groupSlug).toBe('b'); }); + it('matches matchSourceUrlPrefixes(case-insensitive)', () => { const config: TestConfig = { packageRules: [ @@ -853,6 +895,7 @@ describe('util/package-rules', () => { const res = applyPackageRules({ ...config, ...dep }); expect(res.x).toBe(1); }); + it('matches matchSourceUrls(case-insensitive)', () => { const config: TestConfig = { packageRules: [ diff --git a/lib/util/regex.spec.ts b/lib/util/regex.spec.ts index 49951096c3..89139ddbc0 100644 --- a/lib/util/regex.spec.ts +++ b/lib/util/regex.spec.ts @@ -10,6 +10,7 @@ describe('util/regex', () => { it('uses RE2', () => { expect(regEx('foo')).toBeInstanceOf(RE2); }); + it('throws unsafe 2', () => { expect(() => regEx(`x++`)).toThrow(CONFIG_VALIDATION); }); diff --git a/lib/util/sanitize.spec.ts b/lib/util/sanitize.spec.ts index 3968698227..305eeeeecc 100644 --- a/lib/util/sanitize.spec.ts +++ b/lib/util/sanitize.spec.ts @@ -15,6 +15,7 @@ describe('util/sanitize', () => { expect(sanitize(null as never)).toBeNull(); expect(sanitize('')).toBe(''); }); + it('sanitizes secrets from strings', () => { const token = '123testtoken'; const username = 'userabc'; @@ -33,6 +34,7 @@ describe('util/sanitize', () => { const outputX2 = [output, output].join('\n'); expect(sanitize(inputX2)).toBe(outputX2); }); + it('sanitizes github app tokens', () => { addSecretForSanitizing('x-access-token:abc123'); expect(sanitize(`hello ${toBase64('abc123')} world`)).toBe( diff --git a/lib/util/template/index.spec.ts b/lib/util/template/index.spec.ts index a89b76b0e9..8460d91de6 100644 --- a/lib/util/template/index.spec.ts +++ b/lib/util/template/index.spec.ts @@ -9,6 +9,7 @@ describe('util/template/index', () => { ); expect(missingOptions).toEqual([]); }); + it('filters out disallowed fields', () => { const userTemplate = '{{#if isFoo}}foo{{/if}}{{platform}} token = "{{token}}"'; @@ -18,6 +19,7 @@ describe('util/template/index', () => { expect(output).toContain('github'); expect(output).not.toContain('123test'); }); + it('containsString', () => { const userTemplate = "{{#if (containsString platform 'git')}}True{{else}}False{{/if}}"; @@ -25,6 +27,7 @@ describe('util/template/index', () => { const output = template.compile(userTemplate, input); expect(output).toContain('True'); }); + it('not containsString', () => { const userTemplate = "{{#if (containsString platform 'hub')}}True{{else}}False{{/if}}"; @@ -32,6 +35,7 @@ describe('util/template/index', () => { const output = template.compile(userTemplate, input); expect(output).toContain('False'); }); + it('and returns true when all parameters are true', () => { const userTemplate = '{{#if (and isMajor isSingleVersion isReplacement)}}True{{else}}False{{/if}}'; @@ -39,6 +43,7 @@ describe('util/template/index', () => { const output = template.compile(userTemplate, input); expect(output).toContain('True'); }); + it('and returns false when at least one parameter is false', () => { const userTemplate = '{{#if (and isMajor isPatch isGithub)}}True{{else}}False{{/if}}'; @@ -46,6 +51,7 @@ describe('util/template/index', () => { const output = template.compile(userTemplate, input); expect(output).toContain('False'); }); + it('or returns true when at least one is true', () => { const userTemplate = '{{#if (or isMajor isPatch isReplacement)}}True{{else}}False{{/if}}'; @@ -53,6 +59,7 @@ describe('util/template/index', () => { const output = template.compile(userTemplate, input); expect(output).toContain('True'); }); + it('or returns false when all are false', () => { const userTemplate = '{{#if (or isMajor isPatch isReplacement)}}True{{else}}False{{/if}}'; @@ -60,6 +67,7 @@ describe('util/template/index', () => { const output = template.compile(userTemplate, input); expect(output).toContain('False'); }); + it('string to pretty JSON ', () => { const userTemplate = '{{{ stringToPrettyJSON \'{"some":{"fancy":"json"}}\'}}}'; diff --git a/lib/workers/global/autodiscover.spec.ts b/lib/workers/global/autodiscover.spec.ts index 9e6bde2984..2f083c7e7a 100644 --- a/lib/workers/global/autodiscover.spec.ts +++ b/lib/workers/global/autodiscover.spec.ts @@ -14,6 +14,7 @@ const ghApi: jest.Mocked<typeof _ghApi> = _ghApi as never; describe('workers/global/autodiscover', () => { let config: RenovateConfig; + beforeEach(async () => { jest.resetAllMocks(); config = {}; @@ -23,9 +24,11 @@ describe('workers/global/autodiscover', () => { endpoint: 'endpoint', }); }); + it('returns if not autodiscovering', async () => { expect(await autodiscoverRepositories(config)).toEqual(config); }); + it('autodiscovers github but empty', async () => { config.autodiscover = true; config.platform = PlatformId.Github; @@ -36,6 +39,7 @@ describe('workers/global/autodiscover', () => { const res = await autodiscoverRepositories(config); expect(res).toEqual(config); }); + it('autodiscovers github repos', async () => { config.autodiscover = true; config.platform = PlatformId.Github; @@ -46,6 +50,7 @@ describe('workers/global/autodiscover', () => { const res = await autodiscoverRepositories(config); expect(res.repositories).toHaveLength(2); }); + it('filters autodiscovered github repos', async () => { config.autodiscover = true; config.autodiscoverFilter = 'project/re*'; @@ -59,6 +64,7 @@ describe('workers/global/autodiscover', () => { const res = await autodiscoverRepositories(config); expect(res.repositories).toEqual(['project/repo']); }); + it('filters autodiscovered github repos but nothing matches', async () => { config.autodiscover = true; config.autodiscoverFilter = 'project/re*'; @@ -72,6 +78,7 @@ describe('workers/global/autodiscover', () => { const res = await autodiscoverRepositories(config); expect(res).toEqual(config); }); + it('filters autodiscovered github repos with regex', async () => { config.autodiscover = true; config.autodiscoverFilter = '/project/re*./'; @@ -85,6 +92,7 @@ describe('workers/global/autodiscover', () => { const res = await autodiscoverRepositories(config); expect(res.repositories).toEqual(['project/repo']); }); + it('filters autodiscovered github repos with regex negation', async () => { config.autodiscover = true; config.autodiscoverFilter = '!/project/re*./'; @@ -98,6 +106,7 @@ describe('workers/global/autodiscover', () => { const res = await autodiscoverRepositories(config); expect(res.repositories).toEqual(['project/another-repo']); }); + it('fail if regex pattern is not valid', async () => { config.autodiscover = true; config.autodiscoverFilter = '/project/re**./'; diff --git a/lib/workers/global/config/parse/cli.spec.ts b/lib/workers/global/config/parse/cli.spec.ts index 194f18451a..7cb5103c83 100644 --- a/lib/workers/global/config/parse/cli.spec.ts +++ b/lib/workers/global/config/parse/cli.spec.ts @@ -5,9 +5,11 @@ import type { ParseConfigOptions } from './types'; describe('workers/global/config/parse/cli', () => { let argv: string[]; + beforeEach(() => { argv = getArgv(); }); + describe('.getCliName(definition)', () => { it('generates CLI value', () => { const option: ParseConfigOptions = { @@ -15,6 +17,7 @@ describe('workers/global/config/parse/cli', () => { }; expect(cli.getCliName(option)).toBe('--one-two-three'); }); + it('generates returns empty if CLI false', () => { const option: ParseConfigOptions = { name: 'oneTwoThree', @@ -23,20 +26,24 @@ describe('workers/global/config/parse/cli', () => { expect(cli.getCliName(option)).toBe(''); }); }); + describe('.getConfig(argv)', () => { it('returns empty argv', () => { expect(cli.getConfig(argv)).toEqual({}); }); + it('supports boolean no value', () => { argv.push('--recreate-closed'); expect(cli.getConfig(argv)).toEqual({ recreateClosed: true }); argv = argv.slice(0, -1); }); + it('supports boolean space true', () => { argv.push('--recreate-closed'); argv.push('true'); expect(cli.getConfig(argv)).toEqual({ recreateClosed: true }); }); + it('throws exception for invalid boolean value', () => { argv.push('--recreate-closed'); argv.push('badvalue'); @@ -46,36 +53,44 @@ describe('workers/global/config/parse/cli', () => { ) ); }); + it('supports boolean space false', () => { argv.push('--recreate-closed'); argv.push('false'); expect(cli.getConfig(argv)).toEqual({ recreateClosed: false }); }); + it('supports boolean equals true', () => { argv.push('--recreate-closed=true'); expect(cli.getConfig(argv)).toEqual({ recreateClosed: true }); }); + it('supports boolean equals false', () => { argv.push('--recreate-closed=false'); expect(cli.getConfig(argv)).toEqual({ recreateClosed: false }); }); + it('supports list single', () => { argv.push('--labels=a'); expect(cli.getConfig(argv)).toEqual({ labels: ['a'] }); }); + it('supports list multiple', () => { argv.push('--labels=a,b,c'); expect(cli.getConfig(argv)).toEqual({ labels: ['a', 'b', 'c'] }); }); + it('supports string', () => { argv.push('--token=a'); expect(cli.getConfig(argv)).toEqual({ token: 'a' }); }); + it('supports repositories', () => { argv.push('foo'); argv.push('bar'); expect(cli.getConfig(argv)).toEqual({ repositories: ['foo', 'bar'] }); }); + it('parses json lists correctly', () => { argv.push( `--host-rules=[{"matchHost":"docker.io","hostType":"${DockerDatasource.id}","username":"user","password":"password"}]` @@ -91,18 +106,21 @@ describe('workers/global/config/parse/cli', () => { ], }); }); + it('parses [] correctly as empty list of hostRules', () => { argv.push(`--host-rules=[]`); expect(cli.getConfig(argv)).toEqual({ hostRules: [], }); }); + it('parses an empty string correctly as empty list of hostRules', () => { argv.push(`--host-rules=`); expect(cli.getConfig(argv)).toEqual({ hostRules: [], }); }); + test.each` arg | config ${'--endpoints='} | ${{ hostRules: [] }} @@ -116,18 +134,21 @@ describe('workers/global/config/parse/cli', () => { argv.push(arg); expect(cli.getConfig(argv)).toMatchObject(config); }); + it('parses json object correctly when empty', () => { argv.push(`--onboarding-config=`); expect(cli.getConfig(argv)).toEqual({ onboardingConfig: {}, }); }); + it('parses json {} object correctly', () => { argv.push(`--onboarding-config={}`); expect(cli.getConfig(argv)).toEqual({ onboardingConfig: {}, }); }); + it('parses json object correctly', () => { argv.push(`--onboarding-config={"extends": ["config:base"]}`); expect(cli.getConfig(argv)).toEqual({ @@ -136,24 +157,29 @@ describe('workers/global/config/parse/cli', () => { }, }); }); + it('throws exception for invalid json object', () => { argv.push('--onboarding-config=Hello_World'); expect(() => cli.getConfig(argv)).toThrow( Error("Invalid JSON value: 'Hello_World'") ); }); + it('dryRun boolean true', () => { argv.push('--dry-run=true'); expect(cli.getConfig(argv)).toEqual({ dryRun: 'full' }); }); + it('dryRun no value', () => { argv.push('--dry-run'); expect(cli.getConfig(argv)).toEqual({ dryRun: 'full' }); }); + it('dryRun boolean false', () => { argv.push('--dry-run=false'); expect(cli.getConfig(argv)).toEqual({ dryRun: null }); }); + it('dryRun null', () => { argv.push('--dry-run=null'); expect(cli.getConfig(argv)).toEqual({ dryRun: null }); diff --git a/lib/workers/global/config/parse/env.spec.ts b/lib/workers/global/config/parse/env.spec.ts index a9f88009bb..cea7c4d568 100644 --- a/lib/workers/global/config/parse/env.spec.ts +++ b/lib/workers/global/config/parse/env.spec.ts @@ -8,31 +8,39 @@ describe('workers/global/config/parse/env', () => { it('returns empty env', () => { expect(env.getConfig({})).toEqual({ hostRules: [] }); }); + it('supports boolean true', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_RECREATE_CLOSED: 'true' }; expect(env.getConfig(envParam).recreateClosed).toBeTrue(); }); + it('supports boolean false', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_RECREATE_CLOSED: 'false' }; expect(env.getConfig(envParam).recreateClosed).toBeFalse(); }); + it('supports boolean nonsense as false', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_RECREATE_CLOSED: 'foo' }; expect(env.getConfig(envParam).recreateClosed).toBeFalse(); }); + delete process.env.RENOVATE_RECREATE_CLOSED; + it('supports list single', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_LABELS: 'a' }; expect(env.getConfig(envParam).labels).toEqual(['a']); }); + it('supports list multiple', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_LABELS: 'a,b,c' }; expect(env.getConfig(envParam).labels).toEqual(['a', 'b', 'c']); }); + it('supports string', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_TOKEN: 'a' }; expect(env.getConfig(envParam).token).toBe('a'); }); + it('supports custom prefixes', () => { const envParam: NodeJS.ProcessEnv = { ENV_PREFIX: 'FOOBAR_', @@ -41,12 +49,14 @@ describe('workers/global/config/parse/env', () => { const res = env.getConfig(envParam); expect(res).toMatchObject({ token: 'abc' }); }); + it('supports json', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_LOCK_FILE_MAINTENANCE: '{}', }; expect(env.getConfig(envParam).lockFileMaintenance).toEqual({}); }); + it('supports arrays of objects', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_HOST_RULES: JSON.stringify([{ foo: 'bar' }]), @@ -54,6 +64,7 @@ describe('workers/global/config/parse/env', () => { const res = env.getConfig(envParam); expect(res).toMatchObject({ hostRules: [{ foo: 'bar' }] }); }); + it('skips misconfigured arrays', () => { const envName = 'RENOVATE_HOST_RULES'; const val = JSON.stringify('foobar'); @@ -67,6 +78,7 @@ describe('workers/global/config/parse/env', () => { 'Could not parse object array' ); }); + it('skips garbage array values', () => { const envName = 'RENOVATE_HOST_RULES'; const val = '!@#'; @@ -80,6 +92,7 @@ describe('workers/global/config/parse/env', () => { 'Could not parse environment variable' ); }); + it('supports GitHub token', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_TOKEN: 'github.com token', @@ -88,6 +101,7 @@ describe('workers/global/config/parse/env', () => { token: 'github.com token', }); }); + it('supports GitHub custom endpoint', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_ENDPOINT: 'a ghe endpoint', @@ -96,6 +110,7 @@ describe('workers/global/config/parse/env', () => { endpoint: 'a ghe endpoint', }); }); + it('supports GitHub custom endpoint and github.com', () => { const envParam: NodeJS.ProcessEnv = { GITHUB_COM_TOKEN: 'a github.com token', @@ -114,6 +129,7 @@ describe('workers/global/config/parse/env', () => { token: 'a ghe token', }); }); + it('supports GitHub custom endpoint and gitlab.com', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_ENDPOINT: 'a ghe endpoint', @@ -124,6 +140,7 @@ describe('workers/global/config/parse/env', () => { token: 'a ghe token', }); }); + it('supports GitLab token', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: PlatformId.Gitlab, @@ -134,6 +151,7 @@ describe('workers/global/config/parse/env', () => { token: 'a gitlab.com token', }); }); + it('supports GitLab custom endpoint', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: PlatformId.Gitlab, @@ -146,6 +164,7 @@ describe('workers/global/config/parse/env', () => { token: 'a gitlab token', }); }); + it('supports Azure DevOps', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: 'azure', @@ -158,6 +177,7 @@ describe('workers/global/config/parse/env', () => { token: 'an Azure DevOps token', }); }); + it('supports Bitbucket token', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: PlatformId.Bitbucket, @@ -172,6 +192,7 @@ describe('workers/global/config/parse/env', () => { password: 'app-password', }); }); + it('supports Bitbucket username/password', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_PLATFORM: PlatformId.Bitbucket, @@ -187,6 +208,7 @@ describe('workers/global/config/parse/env', () => { username: 'some-username', }); }); + it('merges full config from env', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_CONFIG: '{"enabled":false,"token":"foo"}', @@ -196,6 +218,7 @@ describe('workers/global/config/parse/env', () => { expect(config.enabled).toBeFalse(); expect(config.token).toBe('a'); }); + describe('malformed RENOVATE_CONFIG', () => { let processExit: jest.SpyInstance<never, [code?: number]>; @@ -216,6 +239,7 @@ describe('workers/global/config/parse/env', () => { expect(processExit).toHaveBeenCalledWith(1); }); }); + describe('migrations', () => { it('renames migrated variables', () => { const envParam: NodeJS.ProcessEnv = { @@ -226,6 +250,7 @@ describe('workers/global/config/parse/env', () => { }); }); }); + describe('.getEnvName(definition)', () => { it('returns empty', () => { const option: ParseConfigOptions = { @@ -234,6 +259,7 @@ describe('workers/global/config/parse/env', () => { }; expect(env.getEnvName(option)).toBe(''); }); + it('returns existing env', () => { const option: ParseConfigOptions = { name: 'foo', @@ -241,12 +267,14 @@ describe('workers/global/config/parse/env', () => { }; expect(env.getEnvName(option)).toBe('FOO'); }); + it('generates RENOVATE_ env', () => { const option: ParseConfigOptions = { name: 'oneTwoThree', }; expect(env.getEnvName(option)).toBe('RENOVATE_ONE_TWO_THREE'); }); + it('dryRun boolean true', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_DRY_RUN: 'true', @@ -254,6 +282,7 @@ describe('workers/global/config/parse/env', () => { const config = env.getConfig(envParam); expect(config.dryRun).toBe('full'); }); + it('dryRun boolean false', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_DRY_RUN: 'false', @@ -261,6 +290,7 @@ describe('workers/global/config/parse/env', () => { const config = env.getConfig(envParam); expect(config.dryRun).toBeNull(); }); + it('dryRun null', () => { const envParam: NodeJS.ProcessEnv = { RENOVATE_DRY_RUN: 'null', diff --git a/lib/workers/global/config/parse/host-rules-from-env.spec.ts b/lib/workers/global/config/parse/host-rules-from-env.spec.ts index f714e4db90..db97c766d6 100644 --- a/lib/workers/global/config/parse/host-rules-from-env.spec.ts +++ b/lib/workers/global/config/parse/host-rules-from-env.spec.ts @@ -14,6 +14,7 @@ describe('workers/global/config/parse/host-rules-from-env', () => { }, ]); }); + it('supports password-only', () => { const envParam: NodeJS.ProcessEnv = { NPM_PASSWORD: 'some-password', @@ -22,6 +23,7 @@ describe('workers/global/config/parse/host-rules-from-env', () => { { hostType: 'npm', password: 'some-password' }, ]); }); + it('supports domain and host names with case insensitivity', () => { const envParam: NodeJS.ProcessEnv = { GITHUB__TAGS_GITHUB_COM_TOKEN: 'some-token', @@ -33,6 +35,7 @@ describe('workers/global/config/parse/host-rules-from-env', () => { { matchHost: 'my.custom.host', password: 'some-password' }, ]); }); + it('regression test for #10937', () => { const envParam: NodeJS.ProcessEnv = { GIT__TAGS_GITLAB_EXAMPLE__DOMAIN_NET_USERNAME: 'some-user', @@ -47,6 +50,7 @@ describe('workers/global/config/parse/host-rules-from-env', () => { }, ]); }); + it('supports datasource env token', () => { const envParam: NodeJS.ProcessEnv = { PYPI_TOKEN: 'some-token', @@ -55,12 +59,14 @@ describe('workers/global/config/parse/host-rules-from-env', () => { { hostType: 'pypi', token: 'some-token' }, ]); }); + it('rejects incomplete datasource env token', () => { const envParam: NodeJS.ProcessEnv = { PYPI_FOO_TOKEN: 'some-token', }; expect(hostRulesFromEnv(envParam)).toHaveLength(0); }); + it('rejects npm env', () => { const envParam: NodeJS.ProcessEnv = { npm_package_devDependencies__types_registry_auth_token: '4.2.0', diff --git a/lib/workers/global/config/parse/index.spec.ts b/lib/workers/global/config/parse/index.spec.ts index 8cd4fdabe2..bcc6e0a310 100644 --- a/lib/workers/global/config/parse/index.spec.ts +++ b/lib/workers/global/config/parse/index.spec.ts @@ -20,6 +20,7 @@ describe('workers/global/config/parse/index', () => { let configParser: typeof import('.'); let defaultArgv: string[]; let defaultEnv: NodeJS.ProcessEnv; + beforeEach(async () => { configParser = await import('./index'); defaultArgv = getArgv(); @@ -31,6 +32,7 @@ describe('workers/global/config/parse/index', () => { }; jest.mock('delay', () => Promise.resolve()); }); + it('supports token in env', async () => { const env: NodeJS.ProcessEnv = { ...defaultEnv, RENOVATE_TOKEN: 'abc' }; const parsedConfig = await configParser.parseConfigs(env, defaultArgv); @@ -67,6 +69,7 @@ describe('workers/global/config/parse/index', () => { ]); expect(parsedConfig).not.toContainKey('configFile'); }); + it('supports config.force', async () => { const configPath = upath.join(__dirname, '__fixtures__/with-force.js'); const env: NodeJS.ProcessEnv = { @@ -84,6 +87,7 @@ describe('workers/global/config/parse/index', () => { ], ]); }); + it('reads private key from file', async () => { const privateKeyPath = upath.join(__dirname, '__fixtures__/private.pem'); const privateKeyPathOld = upath.join( @@ -100,6 +104,7 @@ describe('workers/global/config/parse/index', () => { expect(parsedConfig).toContainEntries([['privateKey', expected]]); }); + it('supports Bitbucket username/password', async () => { defaultArgv = defaultArgv.concat([ '--platform=bitbucket', @@ -116,6 +121,7 @@ describe('workers/global/config/parse/index', () => { ['password', 'pass'], ]); }); + it('massages trailing slash into endpoint', async () => { defaultArgv = defaultArgv.concat([ '--endpoint=https://github.renovatebot.com/api/v3', @@ -123,6 +129,7 @@ describe('workers/global/config/parse/index', () => { const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv); expect(parsed.endpoint).toBe('https://github.renovatebot.com/api/v3/'); }); + it('parses global manager config', async () => { defaultArgv = defaultArgv.concat(['--detect-global-manager-config=true']); const parsed = await configParser.parseConfigs(defaultEnv, defaultArgv); diff --git a/lib/workers/global/index.spec.ts b/lib/workers/global/index.spec.ts index aa5ead9357..348071e1c2 100644 --- a/lib/workers/global/index.spec.ts +++ b/lib/workers/global/index.spec.ts @@ -111,6 +111,7 @@ describe('workers/global/index', () => { expect(configParser.parseConfigs).toHaveBeenCalledTimes(1); expect(repositoryWorker.renovateRepository).toHaveBeenCalledTimes(0); }); + it('exits with non-zero when errors are logged', async () => { configParser.parseConfigs.mockResolvedValueOnce({ baseDir: '/tmp/base', @@ -126,6 +127,7 @@ describe('workers/global/index', () => { ]); await expect(globalWorker.start()).resolves.not.toBe(0); }); + it('exits with zero when warnings are logged', async () => { configParser.parseConfigs.mockResolvedValueOnce({ baseDir: '/tmp/base', @@ -141,6 +143,7 @@ describe('workers/global/index', () => { ]); await expect(globalWorker.start()).resolves.toBe(0); }); + describe('processes platforms', () => { it('github', async () => { configParser.parseConfigs.mockResolvedValueOnce({ @@ -152,6 +155,7 @@ describe('workers/global/index', () => { expect(configParser.parseConfigs).toHaveBeenCalledTimes(1); expect(repositoryWorker.renovateRepository).toHaveBeenCalledTimes(1); }); + it('gitlab', async () => { configParser.parseConfigs.mockResolvedValueOnce({ repositories: [{ repository: 'a' }], diff --git a/lib/workers/repository/configured.spec.ts b/lib/workers/repository/configured.spec.ts index 521af44123..dd658b52a1 100644 --- a/lib/workers/repository/configured.spec.ts +++ b/lib/workers/repository/configured.spec.ts @@ -2,6 +2,7 @@ import { RenovateConfig, getConfig } from '../../../test/util'; import { checkIfConfigured } from './configured'; let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -12,10 +13,12 @@ describe('workers/repository/configured', () => { it('returns', () => { expect(() => checkIfConfigured(config)).not.toThrow(); }); + it('throws if disabled', () => { config.enabled = false; expect(() => checkIfConfigured(config)).toThrow(); }); + it('throws if unconfigured fork', () => { config.enabled = true; config.isFork = true; diff --git a/lib/workers/repository/dependency-dashboard.spec.ts b/lib/workers/repository/dependency-dashboard.spec.ts index c2875f54e6..56338e0279 100644 --- a/lib/workers/repository/dependency-dashboard.spec.ts +++ b/lib/workers/repository/dependency-dashboard.spec.ts @@ -16,6 +16,7 @@ import * as dependencyDashboard from './dependency-dashboard'; type PrUpgrade = BranchUpgradeConfig; let config: RenovateConfig; + beforeEach(() => { jest.clearAllMocks(); config = getConfig(); @@ -69,6 +70,7 @@ describe('workers/repository/dependency-dashboard', () => { beforeEach(() => { GlobalConfig.reset(); }); + it('do nothing if dependencyDashboard is disabled', async () => { const branches: BranchConfig[] = []; await dependencyDashboard.ensureDependencyDashboard(config, branches); @@ -486,6 +488,7 @@ describe('workers/repository/dependency-dashboard', () => { expect(platform.ensureIssue).toHaveBeenCalledTimes(1); expect(platform.ensureIssue.mock.calls[0][0].body).toMatchSnapshot(); }); + it('rechecks branches', async () => { const branches: BranchConfig[] = [ { diff --git a/lib/workers/repository/error-config.spec.ts b/lib/workers/repository/error-config.spec.ts index d662a41fc0..84b3c6c380 100644 --- a/lib/workers/repository/error-config.spec.ts +++ b/lib/workers/repository/error-config.spec.ts @@ -9,6 +9,7 @@ import { raiseConfigWarningIssue } from './error-config'; jest.mock('../../modules/platform'); let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -19,6 +20,7 @@ describe('workers/repository/error-config', () => { beforeEach(() => { GlobalConfig.reset(); }); + it('creates issues', async () => { const error = new Error(CONFIG_VALIDATION); error.validationSource = 'package.json'; @@ -27,6 +29,7 @@ describe('workers/repository/error-config', () => { const res = await raiseConfigWarningIssue(config, error); expect(res).toBeUndefined(); }); + it('creates issues (dryRun)', async () => { const error = new Error(CONFIG_VALIDATION); error.validationSource = 'package.json'; @@ -36,6 +39,7 @@ describe('workers/repository/error-config', () => { const res = await raiseConfigWarningIssue(config, error); expect(res).toBeUndefined(); }); + it('handles onboarding', async () => { const error = new Error(CONFIG_VALIDATION); error.validationSource = 'package.json'; @@ -48,6 +52,7 @@ describe('workers/repository/error-config', () => { const res = await raiseConfigWarningIssue(config, error); expect(res).toBeUndefined(); }); + it('handles onboarding (dryRun)', async () => { const error = new Error(CONFIG_VALIDATION); error.validationSource = 'package.json'; diff --git a/lib/workers/repository/error.spec.ts b/lib/workers/repository/error.spec.ts index 4d79735e3c..891dd33e2b 100644 --- a/lib/workers/repository/error.spec.ts +++ b/lib/workers/repository/error.spec.ts @@ -33,6 +33,7 @@ import handleError from './error'; jest.mock('./error-config'); let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -72,6 +73,7 @@ describe('workers/repository/error', () => { expect(res).toEqual(err); }); }); + it(`handles ExternalHostError`, async () => { const res = await handleError( config, @@ -79,6 +81,7 @@ describe('workers/repository/error', () => { ); 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" @@ -86,6 +89,7 @@ describe('workers/repository/error', () => { 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' @@ -93,6 +97,7 @@ describe('workers/repository/error', () => { 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' @@ -100,6 +105,7 @@ describe('workers/repository/error', () => { 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); diff --git a/lib/workers/repository/extract/file-match.spec.ts b/lib/workers/repository/extract/file-match.spec.ts index 991dd90f51..7137a1233f 100644 --- a/lib/workers/repository/extract/file-match.spec.ts +++ b/lib/workers/repository/extract/file-match.spec.ts @@ -5,17 +5,20 @@ jest.mock('../../../util/git'); describe('workers/repository/extract/file-match', () => { const fileList = ['package.json', 'frontend/package.json']; + describe('getIncludedFiles()', () => { it('returns fileList if no includePaths', () => { const res = fileMatch.getIncludedFiles(fileList, []); expect(res).toEqual(fileList); }); + it('returns exact matches', () => { const includePaths = ['frontend/package.json']; const res = fileMatch.getIncludedFiles(fileList, includePaths); expect(res).toMatchSnapshot(); expect(res).toHaveLength(1); }); + it('returns minimatch matches', () => { const includePaths = ['frontend/**']; const res = fileMatch.getIncludedFiles(fileList, includePaths); @@ -23,17 +26,20 @@ describe('workers/repository/extract/file-match', () => { expect(res).toHaveLength(1); }); }); + describe('filterIgnoredFiles()', () => { it('returns fileList if no ignoredPaths', () => { const res = fileMatch.filterIgnoredFiles(fileList, []); expect(res).toEqual(fileList); }); + it('ignores partial matches', () => { const ignoredPaths = ['frontend']; const res = fileMatch.filterIgnoredFiles(fileList, ignoredPaths); expect(res).toMatchSnapshot(); expect(res).toHaveLength(1); }); + it('returns minimatch matches', () => { const ignoredPaths = ['frontend/**']; const res = fileMatch.filterIgnoredFiles(fileList, ignoredPaths); @@ -41,6 +47,7 @@ describe('workers/repository/extract/file-match', () => { expect(res).toHaveLength(1); }); }); + describe('getMatchingFiles()', () => { const config: RenovateConfig = { includePaths: [], @@ -48,12 +55,14 @@ describe('workers/repository/extract/file-match', () => { manager: 'npm', fileMatch: ['(^|/)package.json$'], }; + it('returns npm files', () => { fileList.push('Dockerfile'); const res = fileMatch.getMatchingFiles(config, fileList); expect(res).toMatchSnapshot(); expect(res).toHaveLength(2); }); + it('deduplicates', () => { config.fileMatch.push('package.json'); const res = fileMatch.getMatchingFiles(config, fileList); diff --git a/lib/workers/repository/extract/index.spec.ts b/lib/workers/repository/extract/index.spec.ts index f7afcf2bc2..5fdae44427 100644 --- a/lib/workers/repository/extract/index.spec.ts +++ b/lib/workers/repository/extract/index.spec.ts @@ -13,16 +13,19 @@ describe('workers/repository/extract/index', () => { describe('extractAllDependencies()', () => { let config: RenovateConfig; const fileList = ['README', 'package.json', 'tasks/ansible.yaml']; + beforeEach(() => { jest.resetAllMocks(); git.getFileList.mockResolvedValue(fileList); config = { ...defaultConfig }; }); + it('runs', async () => { managerFiles.getManagerPackageFiles.mockResolvedValue([{} as never]); const res = await extractAllDependencies(config); expect(Object.keys(res)).toContain('ansible'); }); + it('skips non-enabled managers', async () => { config.enabledManagers = ['npm']; managerFiles.getManagerPackageFiles.mockResolvedValue([{} as never]); diff --git a/lib/workers/repository/extract/manager-files.spec.ts b/lib/workers/repository/extract/manager-files.spec.ts index f643870fc3..66105a6b84 100644 --- a/lib/workers/repository/extract/manager-files.spec.ts +++ b/lib/workers/repository/extract/manager-files.spec.ts @@ -14,27 +14,32 @@ const html = mocked(_html); describe('workers/repository/extract/manager-files', () => { describe('getManagerPackageFiles()', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); }); + it('returns empty of manager is disabled', async () => { const managerConfig = { manager: 'travis', enabled: false }; const res = await getManagerPackageFiles(managerConfig); expect(res).toHaveLength(0); }); + it('returns empty of manager is not enabled', async () => { config.enabledManagers = ['npm']; const managerConfig = { manager: 'docker', enabled: true }; const res = await getManagerPackageFiles(managerConfig); expect(res).toHaveLength(0); }); + it('skips files if null content returned', async () => { const managerConfig = { manager: 'npm', enabled: true }; fileMatch.getMatchingFiles.mockReturnValue(['package.json']); const res = await getManagerPackageFiles(managerConfig); expect(res).toHaveLength(0); }); + it('returns files with extractPackageFile', async () => { const managerConfig = { manager: 'html', @@ -54,6 +59,7 @@ describe('workers/repository/extract/manager-files', () => { }, ]); }); + it('returns files with extractAllPackageFiles', async () => { const managerConfig = { manager: 'npm', diff --git a/lib/workers/repository/finalise/prune.spec.ts b/lib/workers/repository/finalise/prune.spec.ts index 7c73abdc2b..a0c69c45b8 100644 --- a/lib/workers/repository/finalise/prune.spec.ts +++ b/lib/workers/repository/finalise/prune.spec.ts @@ -11,6 +11,7 @@ import * as cleanup from './prune'; jest.mock('../../../util/git'); let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -24,11 +25,13 @@ describe('workers/repository/finalise/prune', () => { beforeEach(() => { GlobalConfig.reset(); }); + it('returns if no branchList', async () => { delete config.branchList; await cleanup.pruneStaleBranches(config, config.branchList); expect(git.getBranchList).toHaveBeenCalledTimes(0); }); + it('returns if no renovate branches', async () => { config.branchList = []; git.getBranchList.mockReturnValueOnce([]); @@ -36,6 +39,7 @@ describe('workers/repository/finalise/prune', () => { cleanup.pruneStaleBranches(config, config.branchList) ).resolves.not.toThrow(); }); + it('returns if no remaining branches', async () => { config.branchList = ['renovate/a', 'renovate/b']; git.getBranchList.mockReturnValueOnce(config.branchList); @@ -43,6 +47,7 @@ describe('workers/repository/finalise/prune', () => { expect(git.getBranchList).toHaveBeenCalledTimes(1); expect(git.deleteBranch).toHaveBeenCalledTimes(0); }); + it('renames deletes remaining branch', async () => { config.branchList = ['renovate/a', 'renovate/b']; git.getBranchList.mockReturnValueOnce( @@ -54,6 +59,7 @@ describe('workers/repository/finalise/prune', () => { expect(git.deleteBranch).toHaveBeenCalledTimes(1); expect(platform.updatePr).toHaveBeenCalledTimes(1); }); + it('skips rename but still deletes branch', async () => { config.branchList = ['renovate/a', 'renovate/b']; git.getBranchList.mockReturnValueOnce( @@ -67,6 +73,7 @@ describe('workers/repository/finalise/prune', () => { expect(git.deleteBranch).toHaveBeenCalledTimes(1); expect(platform.updatePr).toHaveBeenCalledTimes(1); }); + it('does nothing on dryRun', async () => { config.branchList = ['renovate/a', 'renovate/b']; GlobalConfig.set({ dryRun: 'full' }); @@ -79,6 +86,7 @@ describe('workers/repository/finalise/prune', () => { expect(git.deleteBranch).toHaveBeenCalledTimes(0); expect(platform.updatePr).toHaveBeenCalledTimes(0); }); + it('does nothing on prune stale branches disabled', async () => { config.branchList = ['renovate/a', 'renovate/b']; config.pruneStaleBranches = false; @@ -91,6 +99,7 @@ describe('workers/repository/finalise/prune', () => { expect(git.deleteBranch).toHaveBeenCalledTimes(0); expect(platform.updatePr).toHaveBeenCalledTimes(0); }); + it('posts comment if someone pushed to PR', async () => { config.branchList = ['renovate/a', 'renovate/b']; git.getBranchList.mockReturnValueOnce( @@ -105,6 +114,7 @@ describe('workers/repository/finalise/prune', () => { expect(platform.updatePr).toHaveBeenCalledTimes(0); expect(platform.ensureComment).toHaveBeenCalledTimes(1); }); + it('skips comment if dry run', async () => { config.branchList = ['renovate/a', 'renovate/b']; GlobalConfig.set({ dryRun: 'full' }); @@ -120,6 +130,7 @@ describe('workers/repository/finalise/prune', () => { expect(platform.updatePr).toHaveBeenCalledTimes(0); expect(platform.ensureComment).toHaveBeenCalledTimes(0); }); + it('dry run delete branch no PR', async () => { config.branchList = ['renovate/a', 'renovate/b']; GlobalConfig.set({ dryRun: 'full' }); @@ -132,6 +143,7 @@ describe('workers/repository/finalise/prune', () => { expect(git.deleteBranch).toHaveBeenCalledTimes(0); expect(platform.updatePr).toHaveBeenCalledTimes(0); }); + it('delete branch no PR', async () => { config.branchList = ['renovate/a', 'renovate/b']; git.getBranchList.mockReturnValueOnce( diff --git a/lib/workers/repository/index.spec.ts b/lib/workers/repository/index.spec.ts index 75fe87158d..67f5578a27 100644 --- a/lib/workers/repository/index.spec.ts +++ b/lib/workers/repository/index.spec.ts @@ -16,10 +16,12 @@ jest.mock('./error'); describe('workers/repository/index', () => { describe('renovateRepository()', () => { let config: RenovateConfig; + beforeEach(() => { config = getConfig(); GlobalConfig.set({ localDir: '' }); }); + it('runs', async () => { process.extractDependencies.mockResolvedValue(mock<ExtractResult>()); const res = await renovateRepository(config); diff --git a/lib/workers/repository/init/apis.spec.ts b/lib/workers/repository/init/apis.spec.ts index b35f28d6f2..bb541ffdfd 100644 --- a/lib/workers/repository/init/apis.spec.ts +++ b/lib/workers/repository/init/apis.spec.ts @@ -8,6 +8,7 @@ import { initApis } from './apis'; describe('workers/repository/init/apis', () => { describe('initApis', () => { let config: RenovateConfig; + beforeEach(() => { config = { ...getConfig() }; config.errors = []; @@ -16,9 +17,11 @@ describe('workers/repository/init/apis', () => { delete config.optimizeForDisabled; delete config.includeForks; }); + afterEach(() => { jest.resetAllMocks(); }); + it('runs', async () => { platform.initRepo.mockResolvedValueOnce({ defaultBranch: 'master', @@ -27,6 +30,7 @@ describe('workers/repository/init/apis', () => { const workerPlatformConfig = await initApis(config); expect(workerPlatformConfig).toBeTruthy(); }); + it('throws for disabled', async () => { platform.initRepo.mockResolvedValueOnce({ defaultBranch: 'master', @@ -40,6 +44,7 @@ describe('workers/repository/init/apis', () => { }) ).rejects.toThrow(REPOSITORY_DISABLED); }); + it('throws for forked', async () => { platform.initRepo.mockResolvedValueOnce({ defaultBranch: 'master', @@ -53,6 +58,7 @@ describe('workers/repository/init/apis', () => { }) ).rejects.toThrow(REPOSITORY_FORKED); }); + it('ignores platform.getJsonFile() failures', async () => { platform.initRepo.mockResolvedValueOnce({ defaultBranch: 'master', @@ -68,6 +74,7 @@ describe('workers/repository/init/apis', () => { }) ).resolves.not.toThrow(); }); + it('uses the onboardingConfigFileName if set', async () => { platform.initRepo.mockResolvedValueOnce({ defaultBranch: 'master', @@ -88,6 +95,7 @@ describe('workers/repository/init/apis', () => { ); expect(platform.getJsonFile).not.toHaveBeenCalledWith('renovate.json'); }); + it('falls back to "renovate.json" if onboardingConfigFileName is not set', async () => { platform.initRepo.mockResolvedValueOnce({ defaultBranch: 'master', @@ -103,6 +111,7 @@ describe('workers/repository/init/apis', () => { expect(workerPlatformConfig.onboardingConfigFileName).toBeUndefined(); expect(platform.getJsonFile).toHaveBeenCalledWith('renovate.json'); }); + it('falls back to "renovate.json" if onboardingConfigFileName is not valid', async () => { platform.initRepo.mockResolvedValueOnce({ defaultBranch: 'master', diff --git a/lib/workers/repository/init/cache.spec.ts b/lib/workers/repository/init/cache.spec.ts index 9f06b635a1..ea2385dfaa 100644 --- a/lib/workers/repository/init/cache.spec.ts +++ b/lib/workers/repository/init/cache.spec.ts @@ -5,10 +5,12 @@ import { initializeCaches } from './cache'; describe('workers/repository/init/cache', () => { describe('initializeCaches()', () => { let config: RenovateConfig; + beforeEach(() => { config = { ...getConfig() }; GlobalConfig.set({ cacheDir: '' }); }); + it('initializes', async () => { expect(await initializeCaches(config)).toBeUndefined(); }); diff --git a/lib/workers/repository/init/index.spec.ts b/lib/workers/repository/init/index.spec.ts index a166c3d702..c65928f429 100644 --- a/lib/workers/repository/init/index.spec.ts +++ b/lib/workers/repository/init/index.spec.ts @@ -26,6 +26,7 @@ describe('workers/repository/init/index', () => { beforeEach(() => { GlobalConfig.set({ localDir: '', cacheDir: '' }); }); + afterEach(() => { GlobalConfig.reset(); }); @@ -40,6 +41,7 @@ describe('workers/repository/init/index', () => { const renovateConfig = await initRepo({}); expect(renovateConfig).toEqual({}); }); + it('warns on unsupported options', async () => { apis.initApis.mockResolvedValue({} as never); onboarding.checkOnboardingBranch.mockResolvedValueOnce({}); diff --git a/lib/workers/repository/init/merge.spec.ts b/lib/workers/repository/init/merge.spec.ts index 36c7a4076d..6f6f8068cf 100644 --- a/lib/workers/repository/init/merge.spec.ts +++ b/lib/workers/repository/init/merge.spec.ts @@ -22,6 +22,7 @@ const migrate = mocked(_migrate); const migrateAndValidate = mocked(_migrateAndValidate); let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -43,6 +44,7 @@ describe('workers/repository/init/merge', () => { fs.readLocalFile.mockResolvedValue('{}'); expect(await detectRepoFileConfig()).toEqual({}); }); + it('uses package.json config if found', async () => { git.getFileList.mockResolvedValue(['package.json']); const pJson = JSON.stringify({ @@ -62,6 +64,7 @@ describe('workers/repository/init/merge', () => { configFileParsed: undefined, }); }); + it('massages package.json renovate string', async () => { git.getFileList.mockResolvedValue(['package.json']); const pJson = JSON.stringify({ @@ -75,6 +78,7 @@ describe('workers/repository/init/merge', () => { configFileParsed: { extends: ['github>renovatebot/renovate'] }, }); }); + it('returns error if cannot parse', async () => { git.getFileList.mockResolvedValue(['package.json', 'renovate.json']); fs.readLocalFile.mockResolvedValue('cannot parse'); @@ -86,6 +90,7 @@ describe('workers/repository/init/merge', () => { }, }); }); + it('throws error if duplicate keys', async () => { git.getFileList.mockResolvedValue(['package.json', '.renovaterc']); fs.readLocalFile.mockResolvedValue( @@ -100,6 +105,7 @@ describe('workers/repository/init/merge', () => { }, }); }); + it('finds and parse renovate.json5', async () => { git.getFileList.mockResolvedValue(['package.json', 'renovate.json5']); fs.readLocalFile.mockResolvedValue(`{ @@ -110,6 +116,7 @@ describe('workers/repository/init/merge', () => { configFileParsed: {}, }); }); + it('finds .github/renovate.json', async () => { git.getFileList.mockResolvedValue([ 'package.json', @@ -121,6 +128,7 @@ describe('workers/repository/init/merge', () => { configFileParsed: {}, }); }); + it('finds .gitlab/renovate.json', async () => { git.getFileList.mockResolvedValue([ 'package.json', @@ -132,6 +140,7 @@ describe('workers/repository/init/merge', () => { configFileParsed: {}, }); }); + it('finds .renovaterc.json', async () => { git.getFileList.mockResolvedValue(['package.json', '.renovaterc.json']); fs.readLocalFile.mockResolvedValue('{}'); @@ -148,10 +157,12 @@ describe('workers/repository/init/merge', () => { `); }); }); + describe('checkForRepoConfigError', () => { it('returns if no error', () => { expect(checkForRepoConfigError({})).toBeUndefined(); }); + it('throws on error', () => { expect(() => checkForRepoConfigError({ @@ -160,6 +171,7 @@ describe('workers/repository/init/merge', () => { ).toThrow(); }); }); + describe('mergeRenovateConfig()', () => { beforeEach(() => { migrate.migrateConfig.mockReturnValue({ @@ -167,6 +179,7 @@ describe('workers/repository/init/merge', () => { migratedConfig: {}, }); }); + it('throws error if misconfigured', async () => { git.getFileList.mockResolvedValue(['package.json', '.renovaterc.json']); fs.readLocalFile.mockResolvedValue('{}'); @@ -182,6 +195,7 @@ describe('workers/repository/init/merge', () => { expect(e).toBeDefined(); expect(e.toString()).toBe('Error: config-validation'); }); + it('migrates nested config', async () => { git.getFileList.mockResolvedValue(['renovate.json']); fs.readLocalFile.mockResolvedValue('{}'); @@ -196,6 +210,7 @@ describe('workers/repository/init/merge', () => { config.extends = [':automergeDisabled']; expect(await mergeRenovateConfig(config)).toBeDefined(); }); + it('continues if no errors', async () => { git.getFileList.mockResolvedValue(['package.json', '.renovaterc.json']); fs.readLocalFile.mockResolvedValue('{}'); diff --git a/lib/workers/repository/init/semantic.spec.ts b/lib/workers/repository/init/semantic.spec.ts index c043f5b9e7..2df9212205 100644 --- a/lib/workers/repository/init/semantic.spec.ts +++ b/lib/workers/repository/init/semantic.spec.ts @@ -5,6 +5,7 @@ import { detectSemanticCommits } from './semantic'; jest.mock('../../../util/git'); let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -17,6 +18,7 @@ describe('workers/repository/init/semantic', () => { beforeEach(async () => { await initialize({}); }); + it('detects false if unknown', async () => { config.semanticCommits = null; git.getCommitMessages.mockResolvedValueOnce(['foo', 'bar']); @@ -29,6 +31,7 @@ describe('workers/repository/init/semantic', () => { const res2 = await detectSemanticCommits(); expect(res2).toBe('disabled'); }); + it('detects true if known', async () => { config.semanticCommits = null; git.getCommitMessages.mockResolvedValue(['fix: foo', 'refactor: bar']); diff --git a/lib/workers/repository/init/vulnerability.spec.ts b/lib/workers/repository/init/vulnerability.spec.ts index a50d742999..f1f7e627bd 100644 --- a/lib/workers/repository/init/vulnerability.spec.ts +++ b/lib/workers/repository/init/vulnerability.spec.ts @@ -9,6 +9,7 @@ import type { VulnerabilityAlert } from '../../../types'; import { detectVulnerabilityAlerts } from './vulnerability'; let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = JSON.parse(JSON.stringify(defaultConfig)); @@ -20,15 +21,18 @@ describe('workers/repository/init/vulnerability', () => { delete config.vulnerabilityAlerts; expect(await detectVulnerabilityAlerts(config)).toEqual(config); }); + it('returns if alerts are disabled', async () => { config.vulnerabilityAlerts.enabled = false; expect(await detectVulnerabilityAlerts(config)).toEqual(config); }); + it('returns if no alerts', async () => { delete config.vulnerabilityAlerts.enabled; platform.getVulnerabilityAlerts.mockResolvedValue([]); expect(await detectVulnerabilityAlerts(config)).toEqual(config); }); + it('throws if no alerts and vulnerabilityAlertsOnly', async () => { config.vulnerabilityAlertsOnly = true; platform.getVulnerabilityAlerts.mockResolvedValue([]); @@ -36,6 +40,7 @@ describe('workers/repository/init/vulnerability', () => { NO_VULNERABILITY_ALERTS ); }); + it('returns alerts and remediations', async () => { config.transitiveRemediation = true; delete config.vulnerabilityAlerts.enabled; diff --git a/lib/workers/repository/onboarding/branch/config.spec.ts b/lib/workers/repository/onboarding/branch/config.spec.ts index c5e62f9c56..8ce9e6a8bc 100644 --- a/lib/workers/repository/onboarding/branch/config.spec.ts +++ b/lib/workers/repository/onboarding/branch/config.spec.ts @@ -39,6 +39,7 @@ describe('workers/repository/onboarding/branch/config', () => { ); }); }); + describe('getOnboardingConfig', () => { it('handles finding an organization preset', async () => { mockedPresets.getPreset.mockResolvedValueOnce({ enabled: true }); @@ -49,6 +50,7 @@ describe('workers/repository/onboarding/branch/config', () => { extends: ['local>some/renovate-config'], }); }); + it('handles finding an organization dot platform preset', async () => { mockedPresets.getPreset.mockRejectedValueOnce( new Error(PRESET_DEP_NOT_FOUND) @@ -61,6 +63,7 @@ describe('workers/repository/onboarding/branch/config', () => { extends: ['local>some/.github:renovate-config'], }); }); + it('handles not finding an organization preset', async () => { mockedPresets.getPreset.mockRejectedValue( new Error(PRESET_DEP_NOT_FOUND) @@ -69,6 +72,7 @@ describe('workers/repository/onboarding/branch/config', () => { expect(mockedPresets.getPreset).toHaveBeenCalledTimes(2); expect(onboardingConfig).toEqual(config.onboardingConfig); }); + it('ignores an unknown error', async () => { mockedPresets.getPreset.mockRejectedValue( new Error('unknown error for test') @@ -77,6 +81,7 @@ describe('workers/repository/onboarding/branch/config', () => { expect(mockedPresets.getPreset).toHaveBeenCalledTimes(2); expect(onboardingConfig).toEqual(config.onboardingConfig); }); + it('ignores unsupported platform', async () => { mockedPresets.getPreset.mockRejectedValue( new Error(`Unsupported platform 'dummy' for local preset.`) diff --git a/lib/workers/repository/onboarding/branch/create.spec.ts b/lib/workers/repository/onboarding/branch/create.spec.ts index a1467eb1db..70b27386bf 100644 --- a/lib/workers/repository/onboarding/branch/create.spec.ts +++ b/lib/workers/repository/onboarding/branch/create.spec.ts @@ -13,10 +13,12 @@ jest.mock('./config', () => ({ describe('workers/repository/onboarding/branch/create', () => { let config: RenovateConfig; + beforeEach(() => { jest.clearAllMocks(); config = getConfig(); }); + describe('createOnboardingBranch', () => { it('applies the default commit message', async () => { await createOnboardingBranch(config); @@ -33,6 +35,7 @@ describe('workers/repository/onboarding/branch/create', () => { platformCommit: false, }); }); + it('commits via platform', async () => { platform.commitFiles = jest.fn(); @@ -53,6 +56,7 @@ describe('workers/repository/onboarding/branch/create', () => { platformCommit: true, }); }); + it('applies supplied commit message', async () => { const message = 'We can Renovate if we want to, we can leave PRs in decline'; @@ -74,6 +78,7 @@ describe('workers/repository/onboarding/branch/create', () => { platformCommit: false, }); }); + describe('applies the commitMessagePrefix value', () => { it('to the default commit message', async () => { const prefix = 'RENOV-123'; @@ -96,6 +101,7 @@ describe('workers/repository/onboarding/branch/create', () => { platformCommit: false, }); }); + it('to the supplied commit message', async () => { const prefix = 'RENOV-123'; const text = @@ -121,6 +127,7 @@ describe('workers/repository/onboarding/branch/create', () => { }); }); }); + describe('applies semanticCommit prefix', () => { it('to the default commit message', async () => { const prefix = 'chore(deps)'; @@ -143,6 +150,7 @@ describe('workers/repository/onboarding/branch/create', () => { platformCommit: false, }); }); + it('to the supplied commit message', async () => { const prefix = 'chore(deps)'; const text = @@ -168,6 +176,7 @@ describe('workers/repository/onboarding/branch/create', () => { }); }); }); + describe('setting the onboarding configuration file name', () => { it('falls back to the default option if not present', async () => { const prefix = 'chore(deps)'; @@ -191,6 +200,7 @@ describe('workers/repository/onboarding/branch/create', () => { platformCommit: false, }); }); + it('falls back to the default option if in list of allowed names', async () => { const prefix = 'chore(deps)'; const message = `${prefix}${CommitMessage.SEPARATOR} add renovate.json`; @@ -213,6 +223,7 @@ describe('workers/repository/onboarding/branch/create', () => { platformCommit: false, }); }); + it('uses the given name if valid', async () => { const prefix = 'chore(deps)'; const path = '.gitlab/renovate.json'; @@ -236,6 +247,7 @@ describe('workers/repository/onboarding/branch/create', () => { platformCommit: false, }); }); + it('applies to the default commit message', async () => { const prefix = 'chore(deps)'; const path = `.renovaterc`; diff --git a/lib/workers/repository/onboarding/branch/index.spec.ts b/lib/workers/repository/onboarding/branch/index.spec.ts index 09d5363637..cbc9fdfc22 100644 --- a/lib/workers/repository/onboarding/branch/index.spec.ts +++ b/lib/workers/repository/onboarding/branch/index.spec.ts @@ -34,6 +34,7 @@ const cache = mocked(_cache); describe('workers/repository/onboarding/branch/index', () => { describe('checkOnboardingBranch', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -41,6 +42,7 @@ describe('workers/repository/onboarding/branch/index', () => { git.getFileList.mockResolvedValue([]); cache.getCache.mockReturnValue({}); }); + it('throws if no package files', async () => { await expect(checkOnboardingBranch(config)).rejects.toThrow( REPOSITORY_NO_PACKAGE_FILES @@ -60,6 +62,7 @@ describe('workers/repository/onboarding/branch/index', () => { REPOSITORY_FORKED ); }); + it('has default onboarding config', async () => { configModule.getOnboardingConfig.mockResolvedValue( config.onboardingConfig @@ -79,6 +82,7 @@ describe('workers/repository/onboarding/branch/index', () => { $schema: 'https://docs.renovatebot.com/renovate-schema.json', }); }); + it('uses discovered onboarding config', async () => { configModule.getOnboardingConfig.mockResolvedValue({ onboardingBranch: 'test', @@ -109,12 +113,14 @@ describe('workers/repository/onboarding/branch/index', () => { extends: ['some/renovate-config'], }); }); + it('handles skipped onboarding combined with requireConfig = false', async () => { config.requireConfig = false; config.onboarding = false; const res = await checkOnboardingBranch(config); expect(res.repoIsOnboarded).toBeTrue(); }); + it('handles skipped onboarding, requireConfig=true, and a config file', async () => { config.requireConfig = true; config.onboarding = false; @@ -122,6 +128,7 @@ describe('workers/repository/onboarding/branch/index', () => { const res = await checkOnboardingBranch(config); expect(res.repoIsOnboarded).toBeTrue(); }); + it('handles skipped onboarding, requireConfig=true, and no config file', async () => { config.requireConfig = true; config.onboarding = false; @@ -130,6 +137,7 @@ describe('workers/repository/onboarding/branch/index', () => { const onboardingResult = checkOnboardingBranch(config); await expect(onboardingResult).rejects.toThrow('disabled'); }); + it('detects repo is onboarded via file', async () => { git.getFileList.mockResolvedValueOnce(['renovate.json']); const res = await checkOnboardingBranch(config); @@ -164,12 +172,14 @@ describe('workers/repository/onboarding/branch/index', () => { const res = await checkOnboardingBranch(config); expect(res.repoIsOnboarded).toBeTrue(); }); + it('detects repo is onboarded via PR', async () => { config.requireConfig = false; platform.findPr.mockResolvedValueOnce(mock<Pr>()); const res = await checkOnboardingBranch(config); expect(res.repoIsOnboarded).toBeTrue(); }); + it('throws if no required config', async () => { config.requireConfig = true; platform.findPr.mockResolvedValue(mock<Pr>()); @@ -182,6 +192,7 @@ describe('workers/repository/onboarding/branch/index', () => { ]); await expect(checkOnboardingBranch(config)).rejects.toThrow(); }); + it('updates onboarding branch', async () => { git.getFileList.mockResolvedValue(['package.json']); platform.findPr.mockResolvedValue(null); diff --git a/lib/workers/repository/onboarding/branch/rebase.spec.ts b/lib/workers/repository/onboarding/branch/rebase.spec.ts index d28b97185a..8bbb18c349 100644 --- a/lib/workers/repository/onboarding/branch/rebase.spec.ts +++ b/lib/workers/repository/onboarding/branch/rebase.spec.ts @@ -18,6 +18,7 @@ describe('workers/repository/onboarding/branch/rebase', () => { describe('rebaseOnboardingBranch()', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = { @@ -25,11 +26,13 @@ describe('workers/repository/onboarding/branch/rebase', () => { repository: 'some/repo', }; }); + it('does not rebase modified branch', async () => { git.isBranchModified.mockResolvedValueOnce(true); await rebaseOnboardingBranch(config); expect(git.commitFiles).toHaveBeenCalledTimes(0); }); + it('does nothing if branch is up to date', async () => { const contents = JSON.stringify(defaultConfig.onboardingConfig, null, 2) + '\n'; @@ -39,11 +42,13 @@ describe('workers/repository/onboarding/branch/rebase', () => { await rebaseOnboardingBranch(config); expect(git.commitFiles).toHaveBeenCalledTimes(0); }); + it('rebases onboarding branch', async () => { git.isBranchStale.mockResolvedValueOnce(true); await rebaseOnboardingBranch(config); expect(git.commitFiles).toHaveBeenCalledTimes(1); }); + it('rebases via platform', async () => { platform.commitFiles = jest.fn(); config.platformCommit = true; @@ -51,6 +56,7 @@ describe('workers/repository/onboarding/branch/rebase', () => { await rebaseOnboardingBranch(config); expect(platform.commitFiles).toHaveBeenCalledTimes(1); }); + it('uses the onboardingConfigFileName if set', async () => { git.isBranchStale.mockResolvedValueOnce(true); await rebaseOnboardingBranch({ @@ -65,6 +71,7 @@ describe('workers/repository/onboarding/branch/rebase', () => { '.github/renovate.json' ); }); + it('falls back to "renovate.json" if onboardingConfigFileName is not set', async () => { git.isBranchStale.mockResolvedValueOnce(true); await rebaseOnboardingBranch({ diff --git a/lib/workers/repository/onboarding/pr/base-branch.spec.ts b/lib/workers/repository/onboarding/pr/base-branch.spec.ts index 39bcac7a8b..a6cc6670c4 100644 --- a/lib/workers/repository/onboarding/pr/base-branch.spec.ts +++ b/lib/workers/repository/onboarding/pr/base-branch.spec.ts @@ -5,14 +5,17 @@ import { getBaseBranchDesc } from './base-branch'; describe('workers/repository/onboarding/pr/base-branch', () => { describe('getBaseBranchDesc()', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); }); + it('returns empty if no baseBranch', () => { const res = getBaseBranchDesc(config); expect(res).toBeEmptyString(); }); + it('describes baseBranch', () => { config.baseBranches = ['some-branch']; const res = getBaseBranchDesc(config); @@ -20,6 +23,7 @@ describe('workers/repository/onboarding/pr/base-branch', () => { 'You have configured Renovate to use branch `some-branch` as base branch.' ); }); + it('describes baseBranches', () => { config.baseBranches = ['some-branch', 'some-other-branch']; const res = getBaseBranchDesc(config); diff --git a/lib/workers/repository/onboarding/pr/config-description.spec.ts b/lib/workers/repository/onboarding/pr/config-description.spec.ts index f7317943fe..2ec105e20e 100644 --- a/lib/workers/repository/onboarding/pr/config-description.spec.ts +++ b/lib/workers/repository/onboarding/pr/config-description.spec.ts @@ -5,15 +5,18 @@ import { getConfigDesc } from './config-description'; describe('workers/repository/onboarding/pr/config-description', () => { describe('getConfigDesc()', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); }); + it('returns empty', () => { delete config.description; const res = getConfigDesc(config); expect(res).toBe(''); }); + it('returns a full list', () => { const packageFiles: Record<string, PackageFile[]> = { npm: [], @@ -29,6 +32,7 @@ describe('workers/repository/onboarding/pr/config-description', () => { expect(res).toMatchSnapshot(); expect(res.indexOf('Docker-only')).not.toBe(-1); }); + it('assignees, labels and schedule', () => { delete config.description; config.packageFiles = []; @@ -51,6 +55,7 @@ describe('workers/repository/onboarding/pr/config-description', () => { " `); }); + it('contains the onboardingConfigFileName if set', () => { delete config.description; config.schedule = ['before 5am']; @@ -60,6 +65,7 @@ describe('workers/repository/onboarding/pr/config-description', () => { expect(res.indexOf('`.github/renovate.json`')).not.toBe(-1); expect(res.indexOf('`renovate.json`')).toBe(-1); }); + it('falls back to "renovate.json" if onboardingConfigFileName is not set', () => { delete config.description; config.schedule = ['before 5am']; @@ -68,6 +74,7 @@ describe('workers/repository/onboarding/pr/config-description', () => { expect(res).toMatchSnapshot(); expect(res.indexOf('`renovate.json`')).not.toBe(-1); }); + it('falls back to "renovate.json" if onboardingConfigFileName is not valid', () => { delete config.description; config.schedule = ['before 5am']; diff --git a/lib/workers/repository/onboarding/pr/errors-warnings.spec.ts b/lib/workers/repository/onboarding/pr/errors-warnings.spec.ts index 3fe71fa35c..00fa9a8924 100644 --- a/lib/workers/repository/onboarding/pr/errors-warnings.spec.ts +++ b/lib/workers/repository/onboarding/pr/errors-warnings.spec.ts @@ -5,10 +5,12 @@ import { getDepWarnings, getErrors, getWarnings } from './errors-warnings'; describe('workers/repository/onboarding/pr/errors-warnings', () => { describe('getWarnings()', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); }); + it('returns warning text', () => { config.warnings = [ { @@ -30,10 +32,12 @@ describe('workers/repository/onboarding/pr/errors-warnings', () => { `); }); }); + describe('getDepWarnings()', () => { beforeEach(() => { jest.resetAllMocks(); }); + it('returns warning text', () => { const packageFiles: Record<string, PackageFile[]> = { npm: [ @@ -84,12 +88,15 @@ describe('workers/repository/onboarding/pr/errors-warnings', () => { `); }); }); + describe('getErrors()', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); }); + it('returns error text', () => { config.errors = [ { diff --git a/lib/workers/repository/onboarding/pr/index.spec.ts b/lib/workers/repository/onboarding/pr/index.spec.ts index ba164bea0e..efcd1e96c8 100644 --- a/lib/workers/repository/onboarding/pr/index.spec.ts +++ b/lib/workers/repository/onboarding/pr/index.spec.ts @@ -19,6 +19,7 @@ describe('workers/repository/onboarding/pr/index', () => { let config: RenovateConfig; let packageFiles: Record<string, PackageFile[]>; let branches: BranchConfig[]; + beforeEach(() => { jest.resetAllMocks(); config = { @@ -33,13 +34,16 @@ describe('workers/repository/onboarding/pr/index', () => { platform.createPr.mockResolvedValueOnce(partial<Pr>({})); GlobalConfig.reset(); }); + let createPrBody: string; + it('returns if onboarded', async () => { config.repoIsOnboarded = true; await expect( ensureOnboardingPr(config, packageFiles, branches) ).resolves.not.toThrow(); }); + it('creates PR', async () => { await ensureOnboardingPr(config, packageFiles, branches); expect(platform.createPr).toHaveBeenCalledTimes(1); @@ -126,6 +130,7 @@ describe('workers/repository/onboarding/pr/index', () => { expect(platform.createPr).toHaveBeenCalledTimes(0); expect(platform.updatePr).toHaveBeenCalledTimes(0); }); + it('updates PR when conflicted', async () => { config.baseBranch = 'some-branch'; platform.getBranchPr.mockResolvedValueOnce( @@ -140,6 +145,7 @@ describe('workers/repository/onboarding/pr/index', () => { expect(platform.createPr).toHaveBeenCalledTimes(0); expect(platform.updatePr).toHaveBeenCalledTimes(1); }); + it('updates PR when modified', async () => { config.baseBranch = 'some-branch'; platform.getBranchPr.mockResolvedValueOnce( @@ -153,11 +159,13 @@ describe('workers/repository/onboarding/pr/index', () => { expect(platform.createPr).toHaveBeenCalledTimes(0); expect(platform.updatePr).toHaveBeenCalledTimes(1); }); + it('creates PR (no require config)', async () => { config.requireConfig = false; await ensureOnboardingPr(config, packageFiles, branches); expect(platform.createPr).toHaveBeenCalledTimes(1); }); + it('dryrun of updates PR when modified', async () => { GlobalConfig.set({ dryRun: 'full' }); config.baseBranch = 'some-branch'; @@ -177,6 +185,7 @@ describe('workers/repository/onboarding/pr/index', () => { 'DRY-RUN: Would update onboarding PR' ); }); + it('dryrun of creates PR', async () => { GlobalConfig.set({ dryRun: 'full' }); await ensureOnboardingPr(config, packageFiles, branches); diff --git a/lib/workers/repository/onboarding/pr/pr-list.spec.ts b/lib/workers/repository/onboarding/pr/pr-list.spec.ts index c0bef2c4d8..2300c3cfae 100644 --- a/lib/workers/repository/onboarding/pr/pr-list.spec.ts +++ b/lib/workers/repository/onboarding/pr/pr-list.spec.ts @@ -5,10 +5,12 @@ import { getPrList } from './pr-list'; describe('workers/repository/onboarding/pr/pr-list', () => { describe('getPrList()', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); }); + it('handles empty', () => { const branches: BranchConfig[] = []; const res = getPrList(config, branches); @@ -20,6 +22,7 @@ describe('workers/repository/onboarding/pr/pr-list', () => { " `); }); + it('has special lock file maintenance description', () => { const branches = [ { @@ -52,6 +55,7 @@ describe('workers/repository/onboarding/pr/pr-list', () => { " `); }); + it('handles multiple', () => { const branches = [ { diff --git a/lib/workers/repository/process/deprecated.spec.ts b/lib/workers/repository/process/deprecated.spec.ts index c9ca70c715..a0981c7177 100644 --- a/lib/workers/repository/process/deprecated.spec.ts +++ b/lib/workers/repository/process/deprecated.spec.ts @@ -7,6 +7,7 @@ describe('workers/repository/process/deprecated', () => { const config = {}; await expect(raiseDeprecationWarnings(config, {})).resolves.not.toThrow(); }); + it('returns if disabled', async () => { const config: RenovateConfig = { repoIsOnboarded: true, @@ -14,6 +15,7 @@ describe('workers/repository/process/deprecated', () => { }; await expect(raiseDeprecationWarnings(config, {})).resolves.not.toThrow(); }); + it('raises deprecation warnings', async () => { const config: RenovateConfig = { repoIsOnboarded: true, diff --git a/lib/workers/repository/process/extract-update.spec.ts b/lib/workers/repository/process/extract-update.spec.ts index 51bb5d6acd..a76f3cf003 100644 --- a/lib/workers/repository/process/extract-update.spec.ts +++ b/lib/workers/repository/process/extract-update.spec.ts @@ -44,6 +44,7 @@ describe('workers/repository/process/extract-update', () => { }); await expect(update(config, res.branches)).resolves.not.toThrow(); }); + it('runs with baseBranches', async () => { const config = { baseBranches: ['master', 'dev'], @@ -55,6 +56,7 @@ describe('workers/repository/process/extract-update', () => { const packageFiles = await extract(config); expect(packageFiles).toBeUndefined(); }); + it('uses repository cache', async () => { const packageFiles: Record<string, PackageFile[]> = {}; const config = { diff --git a/lib/workers/repository/process/fetch.spec.ts b/lib/workers/repository/process/fetch.spec.ts index 15c188bbff..cde26cb0f3 100644 --- a/lib/workers/repository/process/fetch.spec.ts +++ b/lib/workers/repository/process/fetch.spec.ts @@ -11,10 +11,12 @@ jest.mock('./lookup'); describe('workers/repository/process/fetch', () => { describe('fetchUpdates()', () => { let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); }); + it('handles empty deps', async () => { const packageFiles: Record<string, PackageFile[]> = { npm: [{ packageFile: 'package.json', deps: [] }], @@ -24,6 +26,7 @@ describe('workers/repository/process/fetch', () => { npm: [{ deps: [], packageFile: 'package.json' }], }); }); + it('handles ignored, skipped and disabled', async () => { config.ignoreDeps = ['abcd']; config.packageRules = [ @@ -51,6 +54,7 @@ describe('workers/repository/process/fetch', () => { expect(packageFiles.npm[0].deps[1].skipReason).toBe('disabled'); expect(packageFiles.npm[0].deps[1].updates).toHaveLength(0); }); + it('fetches updates', async () => { config.rangeStrategy = 'auto'; const packageFiles: any = { @@ -65,6 +69,7 @@ describe('workers/repository/process/fetch', () => { await fetchUpdates(config, packageFiles); expect(packageFiles).toMatchSnapshot(); }); + it('skips deps with empty names', async () => { const packageFiles: Record<string, PackageFile[]> = { docker: [ diff --git a/lib/workers/repository/process/index.spec.ts b/lib/workers/repository/process/index.spec.ts index e9334fa48b..143837f27f 100644 --- a/lib/workers/repository/process/index.spec.ts +++ b/lib/workers/repository/process/index.spec.ts @@ -18,6 +18,7 @@ jest.mock('./extract-update'); const extract = mocked(_extractUpdate).extract; let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -29,6 +30,7 @@ describe('workers/repository/process/index', () => { const res = await extractDependencies(config); expect(res).toBeUndefined(); }); + it('processes baseBranches', async () => { extract.mockResolvedValue({} as never); config.baseBranches = ['branch1', 'branch2']; @@ -100,6 +102,7 @@ describe('workers/repository/process/index', () => { CONFIG_VALIDATION ); }); + it('processes baseBranches dryRun extract', async () => { extract.mockResolvedValue({} as never); GlobalConfig.set({ dryRun: 'extract' }); diff --git a/lib/workers/repository/process/limits.spec.ts b/lib/workers/repository/process/limits.spec.ts index eb16171a05..dfccd38352 100644 --- a/lib/workers/repository/process/limits.spec.ts +++ b/lib/workers/repository/process/limits.spec.ts @@ -12,6 +12,7 @@ import * as limits from './limits'; jest.mock('../../../util/git'); let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -41,17 +42,20 @@ describe('workers/repository/process/limits', () => { }); expect(res).toBe(7); }); + it('returns prHourlyLimit if errored', async () => { config.prHourlyLimit = 2; platform.getPrList.mockRejectedValue('Unknown error'); const res = await limits.getPrHourlyRemaining(config); expect(res).toBe(2); }); + it('returns 99 if no hourly limit', async () => { const res = await limits.getPrHourlyRemaining(config); expect(res).toBe(99); }); }); + describe('getConcurrentPrsRemaining()', () => { it('calculates concurrent limit remaining', async () => { config.prConcurrentLimit = 20; @@ -70,6 +74,7 @@ describe('workers/repository/process/limits', () => { const res = await limits.getConcurrentPrsRemaining(config, branches); expect(res).toBe(19); }); + it('returns 99 if no concurrent limit', async () => { const res = await limits.getConcurrentPrsRemaining(config, []); expect(res).toBe(99); @@ -83,6 +88,7 @@ describe('workers/repository/process/limits', () => { const res = await limits.getPrsRemaining(config, []); expect(res).toBe(5); }); + it('returns concurrent limit', async () => { config.prConcurrentLimit = 5; const res = await limits.getPrsRemaining(config, []); @@ -99,6 +105,7 @@ describe('workers/repository/process/limits', () => { ] as never); expect(res).toBe(19); }); + it('defaults to prConcurrentLimit', () => { config.branchConcurrentLimit = null; config.prConcurrentLimit = 20; @@ -108,16 +115,19 @@ describe('workers/repository/process/limits', () => { ] as never); expect(res).toBe(19); }); + it('does not use prConcurrentLimit for explicit branchConcurrentLimit=0', () => { config.branchConcurrentLimit = 0; config.prConcurrentLimit = 20; const res = limits.getConcurrentBranchesRemaining(config, []); expect(res).toBe(99); }); + it('returns 99 if no limits are set', () => { const res = limits.getConcurrentBranchesRemaining(config, []); expect(res).toBe(99); }); + it('returns prConcurrentLimit if errored', () => { config.branchConcurrentLimit = 2; const res = limits.getConcurrentBranchesRemaining(config, null); diff --git a/lib/workers/repository/process/lookup/filter-checks.spec.ts b/lib/workers/repository/process/lookup/filter-checks.spec.ts index cd38804563..3fab3d1c75 100644 --- a/lib/workers/repository/process/lookup/filter-checks.spec.ts +++ b/lib/workers/repository/process/lookup/filter-checks.spec.ts @@ -38,6 +38,7 @@ const releases: Release[] = [ describe('workers/repository/process/lookup/filter-checks', () => { let sortedReleases: Release[]; + beforeEach(() => { config = getConfig(); config.currentVersion = '1.0.0'; diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts index 58c4c8134c..99531f7d0c 100644 --- a/lib/workers/repository/process/lookup/index.spec.ts +++ b/lib/workers/repository/process/lookup/index.spec.ts @@ -60,6 +60,7 @@ describe('workers/repository/process/lookup/index', () => { config.datasource = 'does not exist'; expect((await lookup.lookupUpdates(config)).updates).toEqual([]); }); + it('returns rollback for pinned version', async () => { config.currentValue = '0.9.99'; config.depName = 'q'; @@ -71,6 +72,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'major' }, ]); }); + it('returns rollback for ranged version', async () => { config.currentValue = '^0.9.99'; config.depName = 'q'; @@ -81,6 +83,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '^0.9.7', updateType: 'rollback' }, ]); }); + it('supports minor and major upgrades for tilde ranges', async () => { config.currentValue = '^0.4.0'; config.rangeStrategy = 'pin'; @@ -93,6 +96,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'major' }, ]); }); + it('supports lock file updates mixed with regular updates', async () => { config.currentValue = '^0.4.0'; config.rangeStrategy = 'update-lockfile'; @@ -107,6 +111,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '^1.0.0', updateType: 'major' }, ]); }); + it('returns multiple updates if grouping but separateMajorMinor=true', async () => { config.groupName = 'somegroup'; config.currentValue = '0.4.0'; @@ -118,6 +123,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(2); }); + it('returns additional update if grouping but separateMinorPatch=true', async () => { config.groupName = 'somegroup'; config.currentValue = '0.4.0'; @@ -130,6 +136,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(3); }); + it('returns one update if grouping and separateMajorMinor=false', async () => { config.groupName = 'somegroup'; config.currentValue = '0.4.0'; @@ -142,6 +149,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates).toHaveLength(1); }); + it('returns both updates if automerging minor', async () => { config.minor = { automerge: true }; config.currentValue = '^0.4.0'; @@ -155,6 +163,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'major' }, ]); }); + it('enforces allowedVersions', async () => { config.currentValue = '0.4.0'; config.allowedVersions = '<1'; @@ -163,6 +172,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(1); }); + it('enforces allowedVersions with regex', async () => { config.currentValue = '0.4.0'; config.allowedVersions = '/^0/'; @@ -171,6 +181,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(1); }); + it('enforces allowedVersions with negative regex', async () => { config.currentValue = '0.4.0'; config.allowedVersions = '!/^1/'; @@ -179,6 +190,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(1); }); + it('falls back to semver syntax allowedVersions', async () => { config.currentValue = '0.4.0'; config.allowedVersions = '<1'; @@ -188,6 +200,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(1); }); + it('falls back to pep440 syntax allowedVersions', async () => { config.currentValue = '0.4.0'; config.allowedVersions = '==0.9.4'; @@ -197,6 +210,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(1); }); + it('skips invalid allowedVersions', async () => { config.currentValue = '0.4.0'; config.allowedVersions = 'less than 1'; @@ -207,6 +221,7 @@ describe('workers/repository/process/lookup/index', () => { Error(CONFIG_VALIDATION) ); }); + it('returns patch update even if separate patches not configured', async () => { config.currentValue = '0.9.0'; config.rangeStrategy = 'pin'; @@ -219,6 +234,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates[0].updateType).toBe('patch'); expect(res.updates[1].updateType).toBe('major'); }); + it('returns minor update if automerging both patch and minor', async () => { config.patch = { automerge: true, @@ -235,6 +251,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates[0].updateType).toBe('patch'); }); + it('returns patch update if separateMinorPatch', async () => { config.separateMinorPatch = true; config.currentValue = '0.9.0'; @@ -247,6 +264,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'major' }, ]); }); + it('returns patch minor and major', async () => { config.separateMinorPatch = true; config.currentValue = '0.8.0'; @@ -258,6 +276,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toHaveLength(3); expect(res.updates).toMatchSnapshot(); }); + it('disables major release separation (major)', async () => { config.separateMajorMinor = false; config.currentValue = '^0.4.0'; @@ -270,6 +289,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'major' }, ]); }); + it('disables major release separation (minor)', async () => { config.separateMajorMinor = false; config.currentValue = '1.0.0'; @@ -281,6 +301,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'minor' }, ]); }); + it('uses minimum version for vulnerabilityAlerts', async () => { config.currentValue = '1.0.0'; config.isVulnerabilityAlert = true; @@ -291,6 +312,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(1); }); + it('supports minor and major upgrades for ranged versions', async () => { config.currentValue = '~0.4.0'; config.rangeStrategy = 'pin'; @@ -303,6 +325,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'major' }, ]); }); + it('ignores pinning for ranges when other upgrade exists', async () => { config.currentValue = '~0.9.0'; config.rangeStrategy = 'pin'; @@ -314,6 +337,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'major' }, ]); }); + it('upgrades minor ranged versions', async () => { config.currentValue = '~1.0.0'; config.rangeStrategy = 'pin'; @@ -325,6 +349,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'minor' }, ]); }); + it('handles update-lockfile', async () => { config.currentValue = '^1.2.1'; config.lockedVersion = '1.2.1'; @@ -385,6 +410,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates[0].newValue).toBeUndefined(); expect(res.updates[0].updateType).toBe('minor'); }); + it('widens minor ranged versions if configured', async () => { config.currentValue = '~1.3.0'; config.rangeStrategy = 'widen'; @@ -395,6 +421,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '~1.3.0 || ~1.4.0', updateType: 'minor' }, ]); }); + it('replaces minor complex ranged versions if configured', async () => { config.currentValue = '~1.2.0 || ~1.3.0'; config.rangeStrategy = 'replace'; @@ -405,6 +432,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '~1.4.0', updateType: 'minor' }, ]); }); + it('widens major ranged versions if configured', async () => { config.currentValue = '^2.0.0'; config.rangeStrategy = 'widen'; @@ -418,6 +446,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '^2.0.0 || ^3.0.0', updateType: 'major' }, ]); }); + it('replaces major complex ranged versions if configured', async () => { config.currentValue = '^1.0.0 || ^2.0.0'; config.rangeStrategy = 'replace'; @@ -431,6 +460,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '^3.0.0', updateType: 'major' }, ]); }); + it('pins minor ranged versions', async () => { config.currentValue = '^1.0.0'; config.rangeStrategy = 'pin'; @@ -441,6 +471,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'pin' }, ]); }); + it('uses the locked version for pinning', async () => { config.currentValue = '^1.0.0'; config.lockedVersion = '1.0.0'; @@ -453,6 +484,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'minor' }, ]); }); + it('ignores minor ranged versions when not pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '^1.0.0'; @@ -461,6 +493,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(0); }); + it('ignores minor ranged versions when locked', async () => { config.rangeStrategy = 'replace'; config.currentValue = '^1.0.0'; @@ -470,6 +503,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(0); }); + it('upgrades tilde ranges', async () => { config.rangeStrategy = 'pin'; config.currentValue = '~1.3.0'; @@ -481,6 +515,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'minor' }, ]); }); + it('upgrades .x minor ranges', async () => { config.currentValue = '1.3.x'; config.rangeStrategy = 'pin'; @@ -492,6 +527,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'minor' }, ]); }); + it('upgrades tilde ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '~1.3.0'; @@ -502,6 +538,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '~1.4.0', updateType: 'minor' }, ]); }); + it('upgrades .x major ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '0.x'; @@ -512,6 +549,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.x', updateType: 'major' }, ]); }); + it('upgrades .x minor ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '1.3.x'; @@ -522,6 +560,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.x', updateType: 'minor' }, ]); }); + it('upgrades .x complex minor ranges without pinning', async () => { config.rangeStrategy = 'widen'; config.currentValue = '1.2.x - 1.3.x'; @@ -532,6 +571,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.2.x - 1.4.x', updateType: 'minor' }, ]); }); + it('upgrades shorthand major ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '0'; @@ -542,6 +582,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1', updateType: 'major' }, ]); }); + it('upgrades shorthand minor ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '1.3'; @@ -552,6 +593,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4', updateType: 'minor' }, ]); }); + it('upgrades multiple tilde ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '~0.7.0'; @@ -563,6 +605,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '~1.4.0', updateType: 'major' }, ]); }); + it('upgrades multiple caret ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '^0.7.0'; @@ -574,6 +617,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '^1.0.0', updateType: 'major' }, ]); }); + it('supports complex ranges', async () => { config.rangeStrategy = 'widen'; config.currentValue = '^0.7.0 || ^0.8.0'; @@ -587,6 +631,7 @@ describe('workers/repository/process/lookup/index', () => { updateType: 'minor', }); }); + it('supports complex major ranges', async () => { config.rangeStrategy = 'widen'; config.currentValue = '^1.0.0 || ^2.0.0'; @@ -603,6 +648,7 @@ describe('workers/repository/process/lookup/index', () => { }, ]); }); + it('supports complex major hyphen ranges', async () => { config.rangeStrategy = 'widen'; config.currentValue = '1.x - 2.x'; @@ -616,6 +662,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.x - 3.x', updateType: 'major' }, ]); }); + it('widens .x OR ranges', async () => { config.rangeStrategy = 'widen'; config.currentValue = '1.x || 2.x'; @@ -629,6 +676,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.x || 2.x || 3.x', updateType: 'major' }, ]); }); + it('widens stanndalone major OR ranges', async () => { config.rangeStrategy = 'widen'; config.currentValue = '1 || 2'; @@ -642,6 +690,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1 || 2 || 3', updateType: 'major' }, ]); }); + it('supports complex tilde ranges', async () => { config.rangeStrategy = 'widen'; config.currentValue = '~1.2.0 || ~1.3.0'; @@ -652,6 +701,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '~1.2.0 || ~1.3.0 || ~1.4.0', updateType: 'minor' }, ]); }); + it('returns nothing for greater than ranges', async () => { config.rangeStrategy = 'replace'; config.currentValue = '>= 0.7.0'; @@ -660,6 +710,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(0); }); + it('upgrades less than equal ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '<= 0.7.2'; @@ -671,6 +722,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '<= 1.4.1', updateType: 'major' }, ]); }); + it('upgrades less than ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '< 0.7.2'; @@ -682,6 +734,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '< 1.4.2', updateType: 'major' }, ]); }); + it('upgrades less than major ranges', async () => { config.rangeStrategy = 'replace'; config.currentValue = '< 1'; @@ -692,6 +745,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '< 2', updateType: 'major' }, ]); }); + it('upgrades less than equal minor ranges', async () => { config.rangeStrategy = 'replace'; config.currentValue = '<= 1.3'; @@ -702,6 +756,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '<= 1.4', updateType: 'minor' }, ]); }); + it('upgrades equal minor ranges', async () => { config.rangeStrategy = 'replace'; config.currentValue = '=1.3.1'; @@ -712,6 +767,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '=1.4.1', updateType: 'minor' }, ]); }); + it('upgrades less than equal major ranges', async () => { config.rangeStrategy = 'replace'; config.respectLatest = false; @@ -723,6 +779,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '<= 2', updateType: 'major' }, ]); }); + it('upgrades major less than equal ranges', async () => { config.rangeStrategy = 'replace'; config.currentValue = '<= 1.0.0'; @@ -733,6 +790,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates[0].newValue).toBe('<= 1.4.1'); }); + it('upgrades major less than ranges without pinning', async () => { config.rangeStrategy = 'replace'; config.currentValue = '< 1.0.0'; @@ -743,6 +801,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates[0].newValue).toBe('< 2.0.0'); }); + it('upgrades major greater than less than ranges without pinning', async () => { config.rangeStrategy = 'widen'; config.currentValue = '>= 0.5.0 < 1.0.0'; @@ -753,6 +812,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toMatchSnapshot(); expect(res.updates[0].newValue).toBe('>= 0.5.0 < 2.0.0'); }); + it('upgrades minor greater than less than ranges without pinning', async () => { config.rangeStrategy = 'widen'; config.currentValue = '>= 0.5.0 <0.8'; @@ -764,6 +824,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates[0].newValue).toBe('>= 0.5.0 <0.10'); expect(res.updates[1].newValue).toBe('>= 0.5.0 <1.5'); }); + it('upgrades minor greater than less than equals ranges without pinning', async () => { config.rangeStrategy = 'widen'; config.currentValue = '>= 0.5.0 <= 0.8.0'; @@ -775,6 +836,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates[0].newValue).toBe('>= 0.5.0 <= 0.9.7'); expect(res.updates[1].newValue).toBe('>= 0.5.0 <= 1.4.1'); }); + it('rejects reverse ordered less than greater than', async () => { config.rangeStrategy = 'widen'; config.currentValue = '<= 0.8.0 >= 0.5.0'; @@ -784,6 +846,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toMatchSnapshot([]); }); + it('supports > latest versions if configured', async () => { config.respectLatest = false; config.currentValue = '1.4.1'; @@ -794,6 +857,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '2.0.3', updateType: 'major' }, ]); }); + it('should ignore unstable versions if the current version is stable', async () => { config.currentValue = '2.5.16'; config.depName = 'vue'; @@ -804,6 +868,7 @@ describe('workers/repository/process/lookup/index', () => { .reply(200, vueJson); expect((await lookup.lookupUpdates(config)).updates).toHaveLength(0); }); + it('should ignore unstable versions from datasource', async () => { config.currentValue = '1.4.4'; config.depName = 'some/action'; @@ -884,6 +949,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toHaveLength(1); expect(res.updates[0].newValue).toBe('2.5.17-beta.0'); }); + it('should allow unstable versions if the current version is unstable', async () => { config.currentValue = '3.1.0-dev.20180731'; config.depName = 'typescript'; @@ -897,6 +963,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toHaveLength(1); expect(res.updates[0].newValue).toBe('3.1.0-dev.20180813'); }); + it('should not jump unstable versions', async () => { config.currentValue = '3.0.1-insiders.20180726'; config.depName = 'typescript'; @@ -953,6 +1020,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toHaveLength(1); expect(res.updates[0].newValue).toBe('3.0.1-insiders.20180726'); }); + it('should roll back to dist-tag if current version is higher', async () => { config.currentValue = '3.1.0-dev.20180813'; config.depName = 'typescript'; @@ -968,6 +1036,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toHaveLength(1); expect(res.updates[0].newValue).toBe('3.0.1-insiders.20180726'); }); + it('should jump unstable versions if followTag', async () => { config.currentValue = '3.0.0-insiders.20180706'; config.depName = 'typescript'; @@ -982,6 +1051,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toHaveLength(1); expect(res.updates[0].newValue).toBe('3.0.1-insiders.20180726'); }); + it('should update nothing if current version is dist-tag', async () => { config.currentValue = '3.0.1-insiders.20180726'; config.depName = 'typescript'; @@ -994,6 +1064,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toHaveLength(0); }); + it('should warn if no version matches dist-tag', async () => { config.currentValue = '3.0.1-dev.20180726'; config.depName = 'typescript'; @@ -1011,6 +1082,7 @@ describe('workers/repository/process/lookup/index', () => { "Can't find version with tag foo for typescript" ); }); + it('should treat zero zero tilde ranges as 0.0.x', async () => { config.rangeStrategy = 'replace'; config.currentValue = '~0.0.34'; @@ -1022,6 +1094,7 @@ describe('workers/repository/process/lookup/index', () => { .reply(200, helmetJson); expect((await lookup.lookupUpdates(config)).updates).toEqual([]); }); + it('should treat zero zero caret ranges as pinned', async () => { config.rangeStrategy = 'replace'; config.currentValue = '^0.0.34'; @@ -1035,6 +1108,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '^0.0.35', updateType: 'patch' }, ]); }); + it('should downgrade from missing versions', async () => { config.currentValue = '1.16.1'; config.depName = 'coffeelint'; @@ -1048,6 +1122,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res.updates).toHaveLength(1); expect(res.updates[0]).toMatchSnapshot(); }); + it('should upgrade to only one major', async () => { config.currentValue = '1.0.0'; config.depName = 'webpack'; @@ -1059,6 +1134,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toHaveLength(2); }); + it('should upgrade to two majors', async () => { config.currentValue = '1.0.0'; config.separateMultipleMajor = true; @@ -1071,6 +1147,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toHaveLength(3); }); + it('does not jump major unstable', async () => { config.currentValue = '^4.4.0-canary.3'; config.rangeStrategy = 'replace'; @@ -1083,6 +1160,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toHaveLength(0); }); + it('supports in-range caret updates', async () => { config.rangeStrategy = 'bump'; config.currentValue = '^1.0.0'; @@ -1093,6 +1171,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '^1.4.1', updateType: 'minor' }, ]); }); + it('supports in-range tilde updates', async () => { config.rangeStrategy = 'bump'; config.currentValue = '~1.0.0'; @@ -1105,6 +1184,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '~1.4.1', updateType: 'minor' }, ]); }); + it('supports in-range tilde patch updates', async () => { config.rangeStrategy = 'bump'; config.currentValue = '~1.0.0'; @@ -1117,6 +1197,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '~1.4.1', updateType: 'minor' }, ]); }); + it('supports in-range gte updates', async () => { config.rangeStrategy = 'bump'; config.currentValue = '>=1.0.0'; @@ -1127,6 +1208,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '>=1.4.1', updateType: 'minor' }, ]); }); + it('supports majorgte updates', async () => { config.rangeStrategy = 'bump'; config.currentValue = '>=0.9.0'; @@ -1138,6 +1220,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '>=1.4.1', updateType: 'major' }, ]); }); + it('rejects in-range unsupported operator', async () => { config.rangeStrategy = 'bump'; config.currentValue = '>1.0.0'; @@ -1146,6 +1229,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); + it('rejects non-fully specified in-range updates', async () => { config.rangeStrategy = 'bump'; config.currentValue = '1.x'; @@ -1154,6 +1238,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); + it('rejects complex range in-range updates', async () => { config.rangeStrategy = 'bump'; config.currentValue = '^0.9.0 || ^1.0.0'; @@ -1162,6 +1247,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://registry.npmjs.org').get('/q').reply(200, qJson); expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); + it('replaces non-range in-range updates', async () => { config.depName = 'q'; config.datasource = NpmDatasource.id; @@ -1173,6 +1259,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '1.4.1', updateType: 'minor' }, ]); }); + it('handles github 404', async () => { config.depName = 'foo'; config.datasource = GithubTagsDatasource.id; @@ -1181,6 +1268,7 @@ describe('workers/repository/process/lookup/index', () => { httpMock.scope('https://pypi.org').get('/pypi/foo/json').reply(404); expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); + it('handles pypi 404', async () => { config.depName = 'foo'; config.datasource = PypiDatasource.id; @@ -1192,6 +1280,7 @@ describe('workers/repository/process/lookup/index', () => { .reply(404); expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); + it('handles packagist', async () => { config.depName = 'foo/bar'; config.datasource = PackagistDatasource.id; @@ -1204,6 +1293,7 @@ describe('workers/repository/process/lookup/index', () => { .reply(404); expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); + it('handles unknown datasource', async () => { config.depName = 'foo'; config.datasource = 'typo'; @@ -1211,6 +1301,7 @@ describe('workers/repository/process/lookup/index', () => { config.currentValue = '1.0.0'; expect((await lookup.lookupUpdates(config)).updates).toMatchSnapshot([]); }); + it('handles PEP440', async () => { config.manager = 'pip_requirements'; config.versioning = pep440VersioningId; @@ -1230,6 +1321,7 @@ describe('workers/repository/process/lookup/index', () => { { newValue: '==1.4.1', updateType: 'major' }, ]); }); + it('returns complex object', async () => { config.currentValue = '1.3.0'; config.depName = 'q'; @@ -1239,6 +1331,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toMatchSnapshot(); expect(res.sourceUrl).toBeDefined(); }); + it('ignores deprecated', async () => { config.currentValue = '1.3.0'; config.depName = 'q2'; @@ -1254,6 +1347,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toMatchSnapshot(); expect(res.updates[0].newVersion).toBe('1.4.0'); }); + it('is deprecated', async () => { config.currentValue = '1.3.0'; config.depName = 'q3'; @@ -1273,6 +1367,7 @@ describe('workers/repository/process/lookup/index', () => { expect(res).toMatchSnapshot(); expect(res.updates[0].newVersion).toBe('1.4.1'); }); + it('skips unsupported values', async () => { config.currentValue = 'alpine'; config.depName = 'node'; @@ -1280,12 +1375,14 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res).toMatchSnapshot({ skipReason: 'invalid-value' }); }); + it('skips undefined values', async () => { config.depName = 'node'; config.datasource = DockerDatasource.id; const res = await lookup.lookupUpdates(config); expect(res).toMatchSnapshot({ skipReason: 'invalid-value' }); }); + it('handles digest pin', async () => { config.currentValue = '8.0.0'; config.depName = 'node'; @@ -1321,6 +1418,7 @@ describe('workers/repository/process/lookup/index', () => { ], }); }); + it('skips uncompatible versions for 8.1.0', async () => { config.currentValue = '8.1.0'; config.depName = 'node'; @@ -1344,6 +1442,7 @@ describe('workers/repository/process/lookup/index', () => { updates: [{ newValue: '8.2.5', updateType: 'minor' }], }); }); + it('skips uncompatible versions for 8.1', async () => { config.currentValue = '8.1'; config.depName = 'node'; @@ -1370,6 +1469,7 @@ describe('workers/repository/process/lookup/index', () => { ], }); }); + it('skips uncompatible versions for 8', async () => { config.currentValue = '8'; config.depName = 'node'; @@ -1393,6 +1493,7 @@ describe('workers/repository/process/lookup/index', () => { updates: [{ newValue: '9', updateType: 'major' }], }); }); + it('handles digest pin for up to date version', async () => { config.currentValue = '8.1.0'; config.depName = 'node'; @@ -1420,6 +1521,7 @@ describe('workers/repository/process/lookup/index', () => { ], }); }); + it('handles digest pin for non-version', async () => { config.currentValue = 'alpine'; config.depName = 'node'; @@ -1450,6 +1552,7 @@ describe('workers/repository/process/lookup/index', () => { ], }); }); + it('handles digest lookup failure', async () => { config.currentValue = 'alpine'; config.depName = 'node'; @@ -1472,6 +1575,7 @@ describe('workers/repository/process/lookup/index', () => { const res = await lookup.lookupUpdates(config); expect(res.updates).toHaveLength(0); }); + it('handles digest update', async () => { config.currentValue = '8.0.0'; config.depName = 'node'; @@ -1506,6 +1610,7 @@ describe('workers/repository/process/lookup/index', () => { ], }); }); + it('handles digest update for non-version', async () => { config.currentValue = 'alpine'; config.depName = 'node'; @@ -1537,6 +1642,7 @@ describe('workers/repository/process/lookup/index', () => { ], }); }); + it('handles git submodule update', async () => { jest.mock('../../../../modules/datasource/git-refs', () => ({ GitRefsDatasource: jest.fn(() => ({ @@ -1577,6 +1683,7 @@ describe('workers/repository/process/lookup/index', () => { versioning: 'git', }); }); + it('handles sourceUrl packageRules with version restrictions', async () => { config.currentValue = '0.9.99'; config.depName = 'q'; diff --git a/lib/workers/repository/process/sort.spec.ts b/lib/workers/repository/process/sort.spec.ts index 4267c68587..f22d57a4ba 100644 --- a/lib/workers/repository/process/sort.spec.ts +++ b/lib/workers/repository/process/sort.spec.ts @@ -30,6 +30,7 @@ describe('workers/repository/process/sort', () => { { prTitle: 'some major update', updateType: 'major' }, ]); }); + it('sorts based on prPriority', () => { const branches = [ { @@ -61,6 +62,7 @@ describe('workers/repository/process/sort', () => { { prPriority: -1, prTitle: 'a minor update', updateType: 'minor' }, ]); }); + it('sorts based on isVulnerabilityAlert', () => { const branches = [ { diff --git a/lib/workers/repository/process/write.spec.ts b/lib/workers/repository/process/write.spec.ts index f6056976f6..bf02f04a41 100644 --- a/lib/workers/repository/process/write.spec.ts +++ b/lib/workers/repository/process/write.spec.ts @@ -17,6 +17,7 @@ limits.getPrsRemaining = jest.fn().mockResolvedValue(99); limits.getBranchesRemaining = jest.fn().mockResolvedValue(99); let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -54,6 +55,7 @@ describe('workers/repository/process/write', () => { expect(res).toBe('automerged'); expect(branchWorker.processBranch).toHaveBeenCalledTimes(4); }); + it('increments branch counter', async () => { const branches: BranchConfig[] = [{}] as never; branchWorker.processBranch.mockResolvedValueOnce({ diff --git a/lib/workers/repository/result.spec.ts b/lib/workers/repository/result.spec.ts index 1966de1cef..888778c78f 100644 --- a/lib/workers/repository/result.spec.ts +++ b/lib/workers/repository/result.spec.ts @@ -2,6 +2,7 @@ import { RenovateConfig, getConfig } from '../../../test/util'; import { processResult } from './result'; let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); diff --git a/lib/workers/repository/update/branch/artifacts.spec.ts b/lib/workers/repository/update/branch/artifacts.spec.ts index fa02e8210d..17d4103e50 100644 --- a/lib/workers/repository/update/branch/artifacts.spec.ts +++ b/lib/workers/repository/update/branch/artifacts.spec.ts @@ -6,6 +6,7 @@ import { setArtifactErrorStatus } from './artifacts'; describe('workers/repository/update/branch/artifacts', () => { let config: BranchConfig; + beforeEach(() => { GlobalConfig.set({}); jest.resetAllMocks(); diff --git a/lib/workers/repository/update/branch/auto-replace.spec.ts b/lib/workers/repository/update/branch/auto-replace.spec.ts index fd00314ce9..b09d9b73dc 100644 --- a/lib/workers/repository/update/branch/auto-replace.spec.ts +++ b/lib/workers/repository/update/branch/auto-replace.spec.ts @@ -17,11 +17,13 @@ describe('workers/repository/update/branch/auto-replace', () => { describe('doAutoReplace', () => { let reuseExistingBranch: boolean; let upgrade: BranchUpgradeConfig; + beforeAll(() => { GlobalConfig.set({ localDir: '/temp', }); }); + beforeEach(() => { upgrade = { ...JSON.parse(JSON.stringify(defaultConfig)), @@ -41,6 +43,7 @@ describe('workers/repository/update/branch/auto-replace', () => { ); expect(res).toBeNull(); }); + it('rebases if the deps to update has changed', async () => { upgrade.baseDeps = extractPackageFile(sampleHtml).deps; upgrade.baseDeps[0].currentValue = '1.0.0'; @@ -48,6 +51,7 @@ describe('workers/repository/update/branch/auto-replace', () => { const res = await doAutoReplace(upgrade, sampleHtml, reuseExistingBranch); expect(res).toBeNull(); }); + it('updates version only', async () => { const script = '<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/7.1.0/reactstrap.min.js">'; @@ -62,6 +66,7 @@ describe('workers/repository/update/branch/auto-replace', () => { const res = await doAutoReplace(upgrade, src, reuseExistingBranch); expect(res).toEqual(src.replace('7.1.0', '7.1.1')); }); + it('handles a double attempt', async () => { const script = '<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/7.1.0/reactstrap.min.js">'; @@ -75,6 +80,7 @@ describe('workers/repository/update/branch/auto-replace', () => { const res = await doAutoReplace(upgrade, src, reuseExistingBranch); expect(res).toBe(` ${script} ${script.replace('7.1.0', '7.1.1')} `); }); + it('handles already updated', async () => { const script = '<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/7.1.0/reactstrap.min.js">'; @@ -95,6 +101,7 @@ describe('workers/repository/update/branch/auto-replace', () => { ); expect(res).toEqual(srcAlreadyUpdated); }); + it('returns existing content if replaceString mismatch', async () => { const script = '<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/7.1.0/reactstrap.min.js">'; @@ -113,6 +120,7 @@ describe('workers/repository/update/branch/auto-replace', () => { ); expect(res).toBe('wrong source'); }); + it('updates version and integrity', async () => { const script = '<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.10.0/katex.min.js" integrity="sha384-K3vbOmF2BtaVai+Qk37uypf7VrgBubhQreNQe9aGsz9lB63dIFiQVlJbr92dw2Lx" crossorigin="anonymous">'; @@ -131,6 +139,7 @@ describe('workers/repository/update/branch/auto-replace', () => { `<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.11.1/katex.min.js" integrity="sha256-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" crossorigin="anonymous">` ); }); + it('updates with autoReplaceNewString', async () => { const dockerfile = 'FROM node:8.11.3-alpine@sha256:d743b4141b02fcfb8beb68f92b4cd164f60ee457bf2d053f36785bf86de16b0d AS node'; @@ -152,6 +161,7 @@ describe('workers/repository/update/branch/auto-replace', () => { `FROM node:8.11.4-alpine@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa AS node` ); }); + it('fails with oldversion in depname', async () => { const yml = 'image: "1111111111.dkr.ecr.us-east-1.amazonaws.com/my-repository:1"\n\n'; diff --git a/lib/workers/repository/update/branch/automerge.spec.ts b/lib/workers/repository/update/branch/automerge.spec.ts index 12960dd87c..d2fde69aae 100644 --- a/lib/workers/repository/update/branch/automerge.spec.ts +++ b/lib/workers/repository/update/branch/automerge.spec.ts @@ -9,33 +9,39 @@ jest.mock('../../../../util/git'); describe('workers/repository/update/branch/automerge', () => { describe('tryBranchAutomerge', () => { let config: RenovateConfig; + beforeEach(() => { config = { ...defaultConfig, }; GlobalConfig.reset(); }); + it('returns false if not configured for automerge', async () => { config.automerge = false; expect(await tryBranchAutomerge(config)).toBe('no automerge'); }); + it('returns false if automergeType is pr', async () => { config.automerge = true; config.automergeType = 'pr'; expect(await tryBranchAutomerge(config)).toBe('no automerge'); }); + it('returns false if branch status is not success', async () => { config.automerge = true; config.automergeType = 'branch'; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); expect(await tryBranchAutomerge(config)).toBe('no automerge'); }); + it('returns branch status error if branch status is failure', async () => { config.automerge = true; config.automergeType = 'branch'; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.red); expect(await tryBranchAutomerge(config)).toBe('branch status error'); }); + it('returns false if PR exists', async () => { platform.getBranchPr.mockResolvedValueOnce({} as never); config.automerge = true; @@ -45,6 +51,7 @@ describe('workers/repository/update/branch/automerge', () => { 'automerge aborted - PR exists' ); }); + it('returns false if automerge fails', async () => { config.automerge = true; config.automergeType = 'branch'; @@ -54,12 +61,14 @@ describe('workers/repository/update/branch/automerge', () => { }); expect(await tryBranchAutomerge(config)).toBe('failed'); }); + it('returns true if automerge succeeds', async () => { config.automerge = true; config.automergeType = 'branch'; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); expect(await tryBranchAutomerge(config)).toBe('automerged'); }); + it('returns true if automerge succeeds (dry-run)', async () => { config.automerge = true; config.automergeType = 'branch'; diff --git a/lib/workers/repository/update/branch/check-existing.spec.ts b/lib/workers/repository/update/branch/check-existing.spec.ts index 1ca936d573..09d3506ded 100644 --- a/lib/workers/repository/update/branch/check-existing.spec.ts +++ b/lib/workers/repository/update/branch/check-existing.spec.ts @@ -6,6 +6,7 @@ import { prAlreadyExisted } from './check-existing'; describe('workers/repository/update/branch/check-existing', () => { describe('prAlreadyExisted', () => { let config: BranchConfig; + beforeEach(() => { config = partial<BranchConfig>({ ...defaultConfig, @@ -14,16 +15,19 @@ describe('workers/repository/update/branch/check-existing', () => { }); jest.resetAllMocks(); }); + it('returns false if recreating closed PRs', async () => { config.recreateClosed = true; expect(await prAlreadyExisted(config)).toBeNull(); expect(platform.findPr).toHaveBeenCalledTimes(0); }); + it('returns false if check misses', async () => { config.recreatedClosed = true; expect(await prAlreadyExisted(config)).toBeNull(); expect(platform.findPr).toHaveBeenCalledTimes(1); }); + it('returns true if first check hits', async () => { platform.findPr.mockResolvedValueOnce({ number: 12 } as never); platform.getPr.mockResolvedValueOnce({ diff --git a/lib/workers/repository/update/branch/commit.spec.ts b/lib/workers/repository/update/branch/commit.spec.ts index 84d86767b8..e3cf9fff0c 100644 --- a/lib/workers/repository/update/branch/commit.spec.ts +++ b/lib/workers/repository/update/branch/commit.spec.ts @@ -13,6 +13,7 @@ jest.mock('../../../../util/git'); describe('workers/repository/update/branch/commit', () => { describe('commitFilesToBranch', () => { let config: BranchConfig; + beforeEach(() => { config = partial<BranchConfig>({ ...defaultConfig, @@ -29,10 +30,12 @@ describe('workers/repository/update/branch/commit', () => { platform.commitFiles = jest.fn(); GlobalConfig.reset(); }); + it('handles empty files', async () => { await commitFilesToBranch(config); expect(git.commitFiles).toHaveBeenCalledTimes(0); }); + it('commits files', async () => { config.updatedPackageFiles.push({ type: 'addition', @@ -43,6 +46,7 @@ describe('workers/repository/update/branch/commit', () => { expect(git.commitFiles).toHaveBeenCalledTimes(1); expect(git.commitFiles.mock.calls).toMatchSnapshot(); }); + it('commits via platform', async () => { config.updatedPackageFiles.push({ type: 'addition', @@ -54,6 +58,7 @@ describe('workers/repository/update/branch/commit', () => { expect(platform.commitFiles).toHaveBeenCalledTimes(1); expect(platform.commitFiles.mock.calls).toMatchSnapshot(); }); + it('dry runs', async () => { GlobalConfig.set({ dryRun: 'full' }); config.updatedPackageFiles.push({ diff --git a/lib/workers/repository/update/branch/get-updated.spec.ts b/lib/workers/repository/update/branch/get-updated.spec.ts index 97917fe84f..509f510004 100644 --- a/lib/workers/repository/update/branch/get-updated.spec.ts +++ b/lib/workers/repository/update/branch/get-updated.spec.ts @@ -27,6 +27,7 @@ jest.mock('./auto-replace'); describe('workers/repository/update/branch/get-updated', () => { describe('getUpdatedPackageFiles()', () => { let config: BranchConfig; + beforeEach(() => { config = { ...defaultConfig, @@ -35,6 +36,7 @@ describe('workers/repository/update/branch/get-updated', () => { npm.updateDependency = jest.fn(); git.getFile.mockResolvedValueOnce('existing content'); }); + it('handles autoreplace base updated', async () => { config.upgrades.push({ packageFile: 'index.html', @@ -49,6 +51,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('handles autoreplace branch no update', async () => { config.upgrades.push({ packageFile: 'index.html', @@ -64,11 +67,13 @@ describe('workers/repository/update/branch/get-updated', () => { updatedPackageFiles: [], }); }); + it('handles autoreplace failure', async () => { config.upgrades.push({ manager: 'html', branchName: undefined }); autoReplace.doAutoReplace.mockResolvedValueOnce(null); await expect(getUpdatedPackageFiles(config)).rejects.toThrow(); }); + it('handles autoreplace branch needs update', async () => { config.reuseExistingBranch = true; config.upgrades.push({ @@ -85,6 +90,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('handles empty', async () => { const res = await getUpdatedPackageFiles(config); expect(res).toEqual({ @@ -94,6 +100,7 @@ describe('workers/repository/update/branch/get-updated', () => { updatedPackageFiles: [], }); }); + it('handles null content', async () => { config.reuseExistingBranch = true; config.upgrades.push({ @@ -101,6 +108,7 @@ describe('workers/repository/update/branch/get-updated', () => { } as never); await expect(getUpdatedPackageFiles(config)).rejects.toThrow(); }); + it('handles content change', async () => { config.reuseExistingBranch = true; config.upgrades.push({ @@ -119,6 +127,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('handles lock files', async () => { config.reuseExistingBranch = true; config.upgrades.push({ @@ -154,6 +163,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('handles lockFileMaintenance', async () => { config.upgrades.push({ manager: 'composer', @@ -179,6 +189,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('handles isRemediation success', async () => { config.upgrades.push({ manager: 'npm', @@ -200,6 +211,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('handles unsupported isRemediation', async () => { config.upgrades.push({ manager: 'npm', @@ -219,6 +231,7 @@ describe('workers/repository/update/branch/get-updated', () => { } `); }); + it('handles isRemediation rebase', async () => { config.upgrades.push({ manager: 'npm', @@ -241,6 +254,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('handles lockFileMaintenance error', async () => { config.upgrades.push({ manager: 'composer', @@ -259,6 +273,7 @@ describe('workers/repository/update/branch/get-updated', () => { artifactErrors: [{ lockFile: 'composer.lock', stderr: 'some error' }], }); }); + it('handles lock file errors', async () => { config.reuseExistingBranch = true; config.upgrades.push({ @@ -279,6 +294,7 @@ describe('workers/repository/update/branch/get-updated', () => { artifactErrors: [{ lockFile: 'composer.lock', stderr: 'some error' }], }); }); + it('handles git submodules', async () => { config.upgrades.push({ packageFile: '.gitmodules', @@ -297,6 +313,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('update artifacts on update-lockfile strategy', async () => { config.upgrades.push({ packageFile: 'composer.json', @@ -334,6 +351,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('update artifacts on update-lockfile strategy with no updateLockedDependency', async () => { config.upgrades.push({ packageFile: 'abc.tf', @@ -368,6 +386,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('attempts updateLockedDependency and handles unsupported', async () => { config.upgrades.push({ packageFile: 'package.json', @@ -389,6 +408,7 @@ describe('workers/repository/update/branch/get-updated', () => { } `); }); + it('attempts updateLockedDependency and handles already-updated', async () => { config.reuseExistingBranch = true; config.upgrades.push({ @@ -411,6 +431,7 @@ describe('workers/repository/update/branch/get-updated', () => { } `); }); + it('attempts updateLockedDependency and handles updated files with reuse branch', async () => { config.reuseExistingBranch = true; config.upgrades.push({ @@ -435,6 +456,7 @@ describe('workers/repository/update/branch/get-updated', () => { } `); }); + it('bumps versions in updateDependency managers', async () => { config.upgrades.push({ packageFile: 'package.json', @@ -455,6 +477,7 @@ describe('workers/repository/update/branch/get-updated', () => { ], }); }); + it('bumps versions in autoReplace managers', async () => { config.upgrades.push({ packageFile: 'Chart.yaml', diff --git a/lib/workers/repository/update/branch/index.spec.ts b/lib/workers/repository/update/branch/index.spec.ts index eda3f79654..84bc24a01d 100644 --- a/lib/workers/repository/update/branch/index.spec.ts +++ b/lib/workers/repository/update/branch/index.spec.ts @@ -83,6 +83,7 @@ describe('workers/repository/update/branch/index', () => { updatedArtifacts: [], }; let config: BranchConfig; + beforeEach(() => { git.branchExists.mockReturnValue(false); prWorker.ensurePr = jest.fn(); @@ -110,6 +111,7 @@ describe('workers/repository/update/branch/index', () => { GlobalConfig.set(adminConfig); sanitize.sanitize.mockImplementation((input) => input); }); + afterEach(() => { platform.ensureComment.mockClear(); platform.ensureCommentRemoval.mockClear(); @@ -117,6 +119,7 @@ describe('workers/repository/update/branch/index', () => { jest.resetAllMocks(); GlobalConfig.reset(); }); + it('skips branch if not scheduled and branch does not exist', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); const res = await branchWorker.processBranch(config); @@ -126,6 +129,7 @@ describe('workers/repository/update/branch/index', () => { result: 'not-scheduled', }); }); + it('skips branch if not scheduled and not updating out of schedule', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); config.updateNotScheduled = false; @@ -137,6 +141,7 @@ describe('workers/repository/update/branch/index', () => { result: 'update-not-scheduled', }); }); + it('skips branch for fresh release with stabilityDays', async () => { schedule.isScheduledNow.mockReturnValueOnce(true); config.prCreation = 'not-pending'; @@ -159,6 +164,7 @@ describe('workers/repository/update/branch/index', () => { result: 'pending', }); }); + it('skips branch if not stabilityDays not met', async () => { schedule.isScheduledNow.mockReturnValueOnce(true); config.prCreation = 'not-pending'; @@ -223,6 +229,7 @@ describe('workers/repository/update/branch/index', () => { await branchWorker.processBranch(config); expect(reuse.shouldReuseExistingBranch).toHaveBeenCalled(); }); + it('skips branch if closed major PR found', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); @@ -234,6 +241,7 @@ describe('workers/repository/update/branch/index', () => { await branchWorker.processBranch(config); expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0); }); + it('skips branch if closed digest PR found', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); @@ -245,6 +253,7 @@ describe('workers/repository/update/branch/index', () => { await branchWorker.processBranch(config); expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0); }); + it('skips branch if closed minor PR found', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); @@ -255,6 +264,7 @@ describe('workers/repository/update/branch/index', () => { await branchWorker.processBranch(config); expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0); }); + it('skips branch if merged PR found', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); @@ -265,6 +275,7 @@ describe('workers/repository/update/branch/index', () => { await branchWorker.processBranch(config); expect(reuse.shouldReuseExistingBranch).toHaveBeenCalledTimes(0); }); + it('throws error if closed PR found', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); @@ -276,6 +287,7 @@ describe('workers/repository/update/branch/index', () => { REPOSITORY_CHANGED ); }); + it('does not skip branch if edited PR found with rebaseLabel', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); @@ -291,6 +303,7 @@ describe('workers/repository/update/branch/index', () => { result: 'error', }); }); + it('skips branch if edited PR found', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); @@ -306,6 +319,7 @@ describe('workers/repository/update/branch/index', () => { result: 'pr-edited', }); }); + it('skips branch if target branch changed', async () => { schedule.isScheduledNow.mockReturnValueOnce(false); git.branchExists.mockReturnValue(true); @@ -322,6 +336,7 @@ describe('workers/repository/update/branch/index', () => { result: 'pr-edited', }); }); + it('skips branch if branch edited and no PR found', async () => { git.branchExists.mockReturnValue(true); git.isBranchModified.mockResolvedValueOnce(true); @@ -332,6 +347,7 @@ describe('workers/repository/update/branch/index', () => { result: 'pr-edited', }); }); + it('continues branch if branch edited and but PR found', async () => { git.branchExists.mockReturnValue(true); git.isBranchModified.mockResolvedValueOnce(true); @@ -344,6 +360,7 @@ describe('workers/repository/update/branch/index', () => { result: 'error', }); }); + it('skips branch if branch edited and and PR found with sha mismatch', async () => { git.branchExists.mockReturnValue(true); git.isBranchModified.mockResolvedValueOnce(true); @@ -356,6 +373,7 @@ describe('workers/repository/update/branch/index', () => { result: 'pr-edited', }); }); + it('returns if branch creation limit exceeded', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ ...updatedPackageFiles, @@ -372,6 +390,7 @@ describe('workers/repository/update/branch/index', () => { result: 'branch-limit-reached', }); }); + it('returns if pr creation limit exceeded and branch exists', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ ...updatedPackageFiles, @@ -392,6 +411,7 @@ describe('workers/repository/update/branch/index', () => { result: 'pr-limit-reached', }); }); + it('returns if commit limit exceeded', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ ...updatedPackageFiles, @@ -409,6 +429,7 @@ describe('workers/repository/update/branch/index', () => { result: 'commit-limit-reached', }); }); + it('returns if no work', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ ...updatedPackageFiles, @@ -493,6 +514,7 @@ describe('workers/repository/update/branch/index', () => { expect(prWorker.ensurePr).toHaveBeenCalledTimes(0); expect(git.deleteBranch).toHaveBeenCalledTimes(0); }); + it('returns if branch exists and prCreation set to approval', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -514,6 +536,7 @@ describe('workers/repository/update/branch/index', () => { result: 'needs-pr-approval', }); }); + it('returns if branch exists but pending', async () => { expect.assertions(1); getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ @@ -536,6 +559,7 @@ describe('workers/repository/update/branch/index', () => { result: 'pending', }); }); + it('returns if branch automerge is pending', async () => { expect.assertions(1); getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ @@ -558,6 +582,7 @@ describe('workers/repository/update/branch/index', () => { result: 'done', }); }); + it('returns if PR creation failed', async () => { expect.assertions(1); getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ @@ -580,6 +605,7 @@ describe('workers/repository/update/branch/index', () => { result: 'error', }); }); + it('handles unknown PrBlockedBy', async () => { expect.assertions(1); getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ @@ -602,6 +628,7 @@ describe('workers/repository/update/branch/index', () => { result: 'error', }); }); + it('returns if branch exists but updated', async () => { expect.assertions(3); getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ @@ -626,6 +653,7 @@ describe('workers/repository/update/branch/index', () => { expect(automerge.tryBranchAutomerge).toHaveBeenCalledTimes(0); expect(prWorker.ensurePr).toHaveBeenCalledTimes(0); }); + it('ensures PR and tries automerge', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -647,6 +675,7 @@ describe('workers/repository/update/branch/index', () => { expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(0); expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(1); }); + it('ensures PR when impossible to automerge', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -672,6 +701,7 @@ describe('workers/repository/update/branch/index', () => { expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(0); expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(1); }); + it('ensures PR and adds lock file error comment if no releaseTimestamp', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -693,6 +723,7 @@ describe('workers/repository/update/branch/index', () => { expect(prWorker.ensurePr).toHaveBeenCalledTimes(1); expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(0); }); + it('ensures PR and adds lock file error comment if old releaseTimestamp', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -715,6 +746,7 @@ describe('workers/repository/update/branch/index', () => { expect(prWorker.ensurePr).toHaveBeenCalledTimes(1); expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(0); }); + it('ensures PR and adds lock file error comment if new releaseTimestamp and branch exists', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -737,6 +769,7 @@ describe('workers/repository/update/branch/index', () => { expect(prWorker.ensurePr).toHaveBeenCalledTimes(1); expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(0); }); + it('throws error if lock file errors and new releaseTimestamp', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -757,6 +790,7 @@ describe('workers/repository/update/branch/index', () => { Error(MANAGER_LOCKFILE_ERROR) ); }); + it('ensures PR and adds lock file error comment recreate closed', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -779,6 +813,7 @@ describe('workers/repository/update/branch/index', () => { expect(prWorker.ensurePr).toHaveBeenCalledTimes(1); expect(prAutomerge.checkAutoMerge).toHaveBeenCalledTimes(0); }); + it('swallows branch errors', async () => { getUpdated.getUpdatedPackageFiles.mockImplementationOnce(() => { throw new Error('some error'); @@ -790,6 +825,7 @@ describe('workers/repository/update/branch/index', () => { result: 'error', }); }); + it('throws and swallows branch errors', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], @@ -805,6 +841,7 @@ describe('workers/repository/update/branch/index', () => { result: 'pr-created', }); }); + it('swallows pr errors', async () => { getUpdated.getUpdatedPackageFiles.mockResolvedValueOnce({ updatedPackageFiles: [{}], diff --git a/lib/workers/repository/update/branch/lock-files/index.spec.ts b/lib/workers/repository/update/branch/lock-files/index.spec.ts index ab52397486..2fe27648a2 100644 --- a/lib/workers/repository/update/branch/lock-files/index.spec.ts +++ b/lib/workers/repository/update/branch/lock-files/index.spec.ts @@ -36,11 +36,13 @@ describe('workers/repository/update/branch/lock-files/index', () => { }); fs.outputFile = jest.fn(); }); + it('returns if no updated packageFiles', async () => { delete config.updatedPackageFiles; await writeUpdatedPackageFiles(config); expect(fs.outputFile).toHaveBeenCalledTimes(0); }); + it('returns if no updated packageFiles are package.json', async () => { config.updatedPackageFiles = [ { @@ -52,6 +54,7 @@ describe('workers/repository/update/branch/lock-files/index', () => { await writeUpdatedPackageFiles(config); expect(fs.outputFile).toHaveBeenCalledTimes(0); }); + it('writes updated packageFiles', async () => { config.updatedPackageFiles = [ { @@ -75,6 +78,7 @@ describe('workers/repository/update/branch/lock-files/index', () => { expect(fs.outputFile).toHaveBeenCalledTimes(2); }); }); + describe('getAdditionalFiles', () => { beforeEach(() => { GlobalConfig.set({ @@ -96,9 +100,11 @@ describe('workers/repository/update/branch/lock-files/index', () => { lerna.generateLockFiles = jest.fn(); lockFiles.determineLockFileDirs = jest.fn(); }); + afterEach(() => { jest.resetAllMocks(); }); + it('returns no error and empty lockfiles if updateLockFiles false', async () => { config.updateLockFiles = false; const res = await getAdditionalFiles(config, { npm: [{}] }); @@ -106,6 +112,7 @@ describe('workers/repository/update/branch/lock-files/index', () => { expect(res.artifactErrors).toHaveLength(0); expect(res.updatedArtifacts).toHaveLength(0); }); + it('returns no error and empty lockfiles if lock file maintenance exists', async () => { config.updateType = 'lockFileMaintenance'; config.reuseExistingBranch = true; diff --git a/lib/workers/repository/update/branch/reuse.spec.ts b/lib/workers/repository/update/branch/reuse.spec.ts index 1cf45f73c7..02e4f9edaf 100644 --- a/lib/workers/repository/update/branch/reuse.spec.ts +++ b/lib/workers/repository/update/branch/reuse.spec.ts @@ -15,6 +15,7 @@ describe('workers/repository/update/branch/reuse', () => { title: 'any', }; let config: BranchConfig; + beforeEach(() => { config = { branchName: 'renovate/some-branch', @@ -24,17 +25,20 @@ describe('workers/repository/update/branch/reuse', () => { }; jest.resetAllMocks(); }); + it('returns false if branch does not exist', async () => { git.branchExists.mockReturnValueOnce(false); const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeFalse(); }); + it('returns true if no PR', async () => { git.branchExists.mockReturnValueOnce(true); platform.getBranchPr.mockReturnValue(null); const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeTrue(); }); + it('returns true if does not need rebasing', async () => { git.branchExists.mockReturnValueOnce(true); git.isBranchConflicted.mockResolvedValueOnce(false); @@ -96,6 +100,7 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeTrue(); }); + it('returns true if unmergeable and can rebase, but rebaseWhen is never', async () => { config.rebaseWhen = 'never'; git.branchExists.mockReturnValueOnce(true); @@ -105,6 +110,7 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeTrue(); }); + it('returns false if PR title rebase!', async () => { git.branchExists.mockReturnValueOnce(true); platform.getBranchPr.mockResolvedValueOnce({ @@ -114,6 +120,7 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeFalse(); }); + it('returns false if PR body check rebase', async () => { git.branchExists.mockReturnValueOnce(true); platform.getBranchPr.mockResolvedValueOnce({ @@ -124,6 +131,7 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeFalse(); }); + it('returns false if manual rebase by label', async () => { git.branchExists.mockReturnValueOnce(true); platform.getBranchPr.mockResolvedValueOnce({ @@ -133,6 +141,7 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeFalse(); }); + it('returns false if unmergeable and can rebase', async () => { git.branchExists.mockReturnValueOnce(true); git.isBranchConflicted.mockResolvedValueOnce(true); @@ -141,6 +150,7 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeFalse(); }); + it('returns true if automerge branch and not stale', async () => { config.automerge = true; config.automergeType = 'branch'; @@ -148,6 +158,7 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeTrue(); }); + it('returns false if automerge branch and stale', async () => { config.rebaseWhen = 'auto'; config.automerge = true; @@ -157,6 +168,7 @@ describe('workers/repository/update/branch/reuse', () => { const res = await shouldReuseExistingBranch(config); expect(res.reuseExistingBranch).toBeFalse(); }); + it('returns true if rebaseWhen=behind-base-branch but cannot rebase', async () => { config.rebaseWhen = 'behind-base-branch'; git.branchExists.mockReturnValueOnce(true); diff --git a/lib/workers/repository/update/branch/schedule.spec.ts b/lib/workers/repository/update/branch/schedule.spec.ts index f34ec39da6..3c7f819a4d 100644 --- a/lib/workers/repository/update/branch/schedule.spec.ts +++ b/lib/workers/repository/update/branch/schedule.spec.ts @@ -7,73 +7,91 @@ describe('workers/repository/update/branch/schedule', () => { it('returns false for invalid timezone', () => { expect(schedule.hasValidTimezone('Asia')[0]).toBeFalse(); }); + it('returns true for valid timezone', () => { expect(schedule.hasValidTimezone('Asia/Singapore')[0]).toBeTrue(); }); }); + describe('hasValidSchedule(schedule)', () => { beforeEach(() => { jest.resetAllMocks(); }); + it('returns true for null', () => { expect(schedule.hasValidSchedule(null)[0]).toBeTrue(); }); + it('returns true for at any time', () => { expect(schedule.hasValidSchedule('at any time')[0]).toBeTrue(); }); + it('returns false for invalid schedule', () => { expect(schedule.hasValidSchedule(['foo'])[0]).toBeFalse(); }); + it('returns false if any schedule fails to parse', () => { expect(schedule.hasValidSchedule(['after 5:00pm', 'foo'])[0]).toBeFalse(); }); + it('returns false if using minutes', () => { expect( schedule.hasValidSchedule(['every 15 mins every weekday'])[0] ).toBeFalse(); }); + it('returns false if schedules have no days or time range', () => { expect(schedule.hasValidSchedule(['at 5:00pm'])[0]).toBeFalse(); }); + it('returns false if any schedule has no days or time range', () => { expect(schedule.hasValidSchedule(['at 5:00pm', 'on saturday'])[0]).toBe( false ); }); + it('returns false for every xday', () => { expect(schedule.hasValidSchedule(['every friday'])[0]).toBeFalse(); }); + it('returns true if schedule has days of week', () => { expect(schedule.hasValidSchedule(['on friday and saturday'])[0]).toBe( true ); }); + it('returns true for multi day schedules', () => { expect( schedule.hasValidSchedule(['after 5:00pm on wednesday and thursday'])[0] ).toBeTrue(); }); + it('returns true if schedule has a start time', () => { expect(schedule.hasValidSchedule(['after 8:00pm'])[0]).toBeTrue(); }); + it('returns true for first day of the month', () => { expect( schedule.hasValidSchedule(['on the first day of the month'])[0] ).toBeTrue(); }); + it('returns true for schedules longer than 1 month', () => { expect(schedule.hasValidSchedule(['every 3 months'])[0]).toBeTrue(); expect(schedule.hasValidSchedule(['every 6 months'])[0]).toBeTrue(); expect(schedule.hasValidSchedule(['every 12 months'])[0]).toBeTrue(); }); + it('returns true if schedule has an end time', () => { expect(schedule.hasValidSchedule(['before 6:00am'])[0]).toBeTrue(); }); + it('returns true if schedule has a start and end time', () => { expect( schedule.hasValidSchedule(['after 11:00pm and before 6:00am'])[0] ).toBeTrue(); }); + it('returns true if schedule has days and a start and end time', () => { expect( schedule.hasValidSchedule([ @@ -94,6 +112,7 @@ describe('workers/repository/update/branch/schedule', () => { ).toBeTrue(); expect(schedule.hasValidSchedule(['every month'])[0]).toBeTrue(); }); + it('supports hours shorthand', () => { const [res] = schedule.hasValidSchedule([ 'after 11pm and before 6am every weekend', @@ -108,48 +127,58 @@ describe('workers/repository/update/branch/schedule', () => { expect(res).toBeTrue(); }); }); + describe('isScheduledNow(config)', () => { let config: RenovateConfig; + beforeEach(() => { mockDate.set('2017-06-30T10:50:00.000'); // Locally 2017-06-30 10:50am jest.resetAllMocks(); config = {}; }); + it('returns true if no schedule', () => { const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('returns true if at any time', () => { config.schedule = 'at any time' as never; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('returns true if at any time array', () => { config.schedule = ['at any time']; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('returns true if invalid schedule', () => { config.schedule = ['every 15 minutes']; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('returns true if invalid timezone', () => { config.schedule = ['after 4:00pm']; config.timezone = 'Asia'; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('supports before hours true', () => { config.schedule = ['before 4:00pm']; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('supports before hours false', () => { config.schedule = ['before 4:00am']; const res = schedule.isScheduledNow(config); expect(res).toBeFalse(); }); + it('massages string', () => { config.schedule = 'before 4:00am' as never; const res = schedule.isScheduledNow(config); @@ -227,100 +256,118 @@ describe('workers/repository/update/branch/schedule', () => { expect(schedule.isScheduledNow(config)).toBe(expected); }); }); + it('supports multiple schedules', () => { config.schedule = ['after 4:00pm', 'before 11:00am']; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('supports day match', () => { config.schedule = ['on friday and saturday']; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('supports day mismatch', () => { config.schedule = ['on monday and tuesday']; const res = schedule.isScheduledNow(config); expect(res).toBeFalse(); }); + it('supports every weekday', () => { config.schedule = ['every weekday']; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('supports every weekend', () => { config.schedule = ['every weekend']; const res = schedule.isScheduledNow(config); expect(res).toBeFalse(); }); + it('supports every weekday with time', () => { config.schedule = ['before 11:00am every weekday']; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('supports o every weekday', () => { config.schedule = ['before 11:00am on inevery weekday']; const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('rejects first day of the month', () => { config.schedule = ['before 11am on the first day of the month']; const res = schedule.isScheduledNow(config); expect(res).toBeFalse(); }); + it('approves first day of the month', () => { config.schedule = ['before 11am on the first day of the month']; mockDate.set('2017-10-01T05:26:06.000'); // Locally Sunday, 1 October 2017 05:26:06 const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('approves valid weeks of year', () => { config.schedule = ['every 2 weeks of the year before 08:00 on Monday']; mockDate.set('2017-01-02T06:00:00.000'); // Locally Monday, 2 January 2017 6am (first Monday of the year) const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('rejects on weeks of year', () => { config.schedule = ['every 2 weeks of the year before 08:00 on Monday']; mockDate.set('2017-01-09T06:00:00.000'); // Locally Monday, 2 January 2017 6am (second Monday of the year) const res = schedule.isScheduledNow(config); expect(res).toBeFalse(); }); + it('approves on months of year', () => { config.schedule = ['of January']; mockDate.set('2017-01-02T06:00:00.000'); // Locally Monday, 2 January 2017 6am const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('rejects on months of year', () => { config.schedule = ['of January']; mockDate.set('2017-02-02T06:00:00.000'); // Locally Thursday, 2 February 2017 6am const res = schedule.isScheduledNow(config); expect(res).toBeFalse(); }); + it('approves schedule longer than 1 month', () => { config.schedule = ['every 3 months']; mockDate.set('2017-07-01T06:00:00.000'); // Locally Saturday, 1 July 2017 6am const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('rejects schedule longer than 1 month', () => { config.schedule = ['every 6 months']; mockDate.set('2017-02-01T06:00:00.000'); // Locally Thursday, 2 February 2017 6am const res = schedule.isScheduledNow(config); expect(res).toBeFalse(); }); + it('approves schedule longer than 1 month with day of month', () => { config.schedule = ['every 3 months on the first day of the month']; mockDate.set('2017-07-01T06:00:00.000'); // Locally Saturday, 1 July 2017 6am const res = schedule.isScheduledNow(config); expect(res).toBeTrue(); }); + it('rejects schedule longer than 1 month with day of month', () => { config.schedule = ['every 3 months on the first day of the month']; mockDate.set('2017-02-01T06:00:00.000'); // Locally Thursday, 2 February 2017 6am const res = schedule.isScheduledNow(config); expect(res).toBeFalse(); }); + it('supports weekday instances', () => { config.schedule = ['on Monday on the first day instance']; diff --git a/lib/workers/repository/update/branch/status-checks.spec.ts b/lib/workers/repository/update/branch/status-checks.spec.ts index 275215065f..f0366b4dc1 100644 --- a/lib/workers/repository/update/branch/status-checks.spec.ts +++ b/lib/workers/repository/update/branch/status-checks.spec.ts @@ -11,31 +11,37 @@ import { describe('workers/repository/update/branch/status-checks', () => { describe('setStability', () => { let config: StabilityConfig; + beforeEach(() => { config = { ...defaultConfig, branchName: 'renovate/some-branch', }; }); + afterEach(() => { jest.resetAllMocks(); }); + it('returns if not configured', async () => { await setStability(config); expect(platform.getBranchStatusCheck).toHaveBeenCalledTimes(0); }); + it('sets status yellow', async () => { config.stabilityStatus = BranchStatus.yellow; await setStability(config); expect(platform.getBranchStatusCheck).toHaveBeenCalledTimes(1); expect(platform.setBranchStatus).toHaveBeenCalledTimes(1); }); + it('sets status green', async () => { config.stabilityStatus = BranchStatus.green; await setStability(config); expect(platform.getBranchStatusCheck).toHaveBeenCalledTimes(1); expect(platform.setBranchStatus).toHaveBeenCalledTimes(1); }); + it('skips status if already set', async () => { config.stabilityStatus = BranchStatus.green; platform.getBranchStatusCheck.mockResolvedValueOnce(BranchStatus.green); @@ -47,12 +53,14 @@ describe('workers/repository/update/branch/status-checks', () => { describe('setConfidence', () => { let config: ConfidenceConfig; + beforeEach(() => { config = { ...defaultConfig, branchName: 'renovate/some-branch', }; }); + afterEach(() => { jest.resetAllMocks(); }); diff --git a/lib/workers/repository/update/pr/automerge.spec.ts b/lib/workers/repository/update/pr/automerge.spec.ts index dcbace1174..b5160535a3 100644 --- a/lib/workers/repository/update/pr/automerge.spec.ts +++ b/lib/workers/repository/update/pr/automerge.spec.ts @@ -14,15 +14,18 @@ describe('workers/repository/update/pr/automerge', () => { describe('checkAutoMerge(pr, config)', () => { let config: BranchConfig; let pr: Pr; + beforeEach(() => { config = partial<BranchConfig>({ ...defaultConfig, }); pr = partial<Pr>({}); }); + afterEach(() => { jest.clearAllMocks(); }); + it('should automerge if enabled and pr is mergeable', async () => { config.automerge = true; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); @@ -31,6 +34,7 @@ describe('workers/repository/update/pr/automerge', () => { expect(res).toEqual({ automerged: true, branchRemoved: true }); expect(platform.mergePr).toHaveBeenCalledTimes(1); }); + it('should indicate if automerge failed', async () => { config.automerge = true; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); @@ -42,6 +46,7 @@ describe('workers/repository/update/pr/automerge', () => { }); expect(platform.mergePr).toHaveBeenCalledTimes(1); }); + it('should automerge comment', async () => { config.automerge = true; config.automergeType = 'pr-comment'; @@ -53,6 +58,7 @@ describe('workers/repository/update/pr/automerge', () => { expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(0); expect(platform.ensureComment).toHaveBeenCalledTimes(1); }); + it('should remove previous automerge comment when rebasing', async () => { config.automerge = true; config.automergeType = 'pr-comment'; @@ -65,6 +71,7 @@ describe('workers/repository/update/pr/automerge', () => { expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(1); expect(platform.ensureComment).toHaveBeenCalledTimes(1); }); + it('should not automerge if enabled and pr is mergeable but cannot rebase', async () => { config.automerge = true; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); @@ -76,6 +83,7 @@ describe('workers/repository/update/pr/automerge', () => { }); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('should not automerge if enabled and pr is mergeable but branch status is not success', async () => { config.automerge = true; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); @@ -86,6 +94,7 @@ describe('workers/repository/update/pr/automerge', () => { }); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('should not automerge if enabled and pr is mergeable but unstable', async () => { config.automerge = true; pr.cannotMergeReason = 'some reason'; @@ -96,6 +105,7 @@ describe('workers/repository/update/pr/automerge', () => { }); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('should not automerge if enabled and pr is unmergeable', async () => { config.automerge = true; git.isBranchConflicted.mockResolvedValueOnce(true); @@ -106,6 +116,7 @@ describe('workers/repository/update/pr/automerge', () => { }); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('dryRun full should not automerge', async () => { config.automerge = true; GlobalConfig.set({ dryRun: 'full' }); @@ -117,6 +128,7 @@ describe('workers/repository/update/pr/automerge', () => { }); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('dryRun full pr-comment', async () => { config.automergeType = 'pr-comment'; const expectedResult = { diff --git a/lib/workers/repository/update/pr/body/config-description.spec.ts b/lib/workers/repository/update/pr/body/config-description.spec.ts index 0b3d393e84..2ef8337261 100644 --- a/lib/workers/repository/update/pr/body/config-description.spec.ts +++ b/lib/workers/repository/update/pr/body/config-description.spec.ts @@ -7,6 +7,7 @@ jest.mock('../../../../../util/git'); describe('workers/repository/update/pr/body/config-description', () => { describe('getPrConfigDescription', () => { let branchConfig: BranchConfig; + beforeEach(() => { jest.resetAllMocks(); branchConfig = mock<BranchConfig>(); diff --git a/lib/workers/repository/update/pr/body/controls.spec.ts b/lib/workers/repository/update/pr/body/controls.spec.ts index 5d3a86e072..bfed66aef4 100644 --- a/lib/workers/repository/update/pr/body/controls.spec.ts +++ b/lib/workers/repository/update/pr/body/controls.spec.ts @@ -8,15 +8,18 @@ jest.mock('../../../../../util/git'); describe('workers/repository/update/pr/body/controls', () => { describe('getControls', () => { let branchConfig: BranchConfig; + beforeEach(() => { jest.resetAllMocks(); branchConfig = mock<BranchConfig>(); branchConfig.branchName = 'branchName'; }); + describe(`when the branch is modified`, () => { beforeEach(() => { git.isBranchModified.mockResolvedValue(true); }); + it('has the correct contents', async () => { expect(await getControls(branchConfig)).toContain( `- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox. ⚠ **Warning**: custom changes will be lost.` @@ -32,6 +35,7 @@ describe('workers/repository/update/pr/body/controls', () => { beforeEach(() => { git.isBranchModified.mockResolvedValue(false); }); + it('has the correct contents', async () => { expect(await getControls(branchConfig)).toContain( `- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, click this checkbox.` diff --git a/lib/workers/repository/update/pr/body/updates-table.spec.ts b/lib/workers/repository/update/pr/body/updates-table.spec.ts index 9e33bab633..8e6089a987 100644 --- a/lib/workers/repository/update/pr/body/updates-table.spec.ts +++ b/lib/workers/repository/update/pr/body/updates-table.spec.ts @@ -11,6 +11,7 @@ describe('workers/repository/update/pr/body/updates-table', () => { const result = getPrUpdatesTable(configObj); expect(result).toBe(''); }); + it('checks results for getPrUpdatesTable', () => { const upgrade0: BranchUpgradeConfig = { branchName: 'some-branch', diff --git a/lib/workers/repository/update/pr/changelog/gitlab.spec.ts b/lib/workers/repository/update/pr/changelog/gitlab.spec.ts index 3493c3801c..556fe39a85 100644 --- a/lib/workers/repository/update/pr/changelog/gitlab.spec.ts +++ b/lib/workers/repository/update/pr/changelog/gitlab.spec.ts @@ -54,6 +54,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { }) ).toBeNull(); }); + it('returns null if currentVersion equals newVersion', async () => { expect( await getChangeLogJSON({ @@ -63,6 +64,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { }) ).toBeNull(); }); + it('skips invalid repos', async () => { expect( await getChangeLogJSON({ @@ -71,6 +73,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { }) ).toBeNull(); }); + it('works without GitLab', async () => { expect( await getChangeLogJSON({ @@ -95,6 +98,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { ], }); }); + it('uses GitLab tags', async () => { httpMock .scope(matchHost) @@ -136,6 +140,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { ], }); }); + it('handles empty GitLab tags response', async () => { httpMock .scope(matchHost) @@ -170,6 +175,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { ], }); }); + it('uses GitLab tags with error', async () => { httpMock .scope(matchHost) @@ -204,6 +210,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { ], }); }); + it('handles no sourceUrl', async () => { expect( await getChangeLogJSON({ @@ -212,6 +219,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { }) ).toBeNull(); }); + it('handles invalid sourceUrl', async () => { expect( await getChangeLogJSON({ @@ -220,6 +228,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { }) ).toBeNull(); }); + it('handles no releases', async () => { expect( await getChangeLogJSON({ @@ -228,6 +237,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { }) ).toBeNull(); }); + it('handles not enough releases', async () => { expect( await getChangeLogJSON({ @@ -236,6 +246,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { }) ).toBeNull(); }); + it('supports gitlab enterprise and gitlab enterprise changelog', async () => { hostRules.add({ hostType: PlatformId.Gitlab, @@ -268,6 +279,7 @@ describe('workers/repository/update/pr/changelog/gitlab', () => { ], }); }); + it('supports self-hosted gitlab changelog', async () => { httpMock.scope('https://git.test.com').persist().get(/.*/).reply(200, []); hostRules.add({ diff --git a/lib/workers/repository/update/pr/changelog/index.spec.ts b/lib/workers/repository/update/pr/changelog/index.spec.ts index 9aa99bf715..e18721f0c2 100644 --- a/lib/workers/repository/update/pr/changelog/index.spec.ts +++ b/lib/workers/repository/update/pr/changelog/index.spec.ts @@ -50,6 +50,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toBeNull(); }); + it('returns null if no currentVersion', async () => { expect( await getChangeLogJSON({ @@ -58,6 +59,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toBeNull(); }); + it('returns null if currentVersion equals newVersion', async () => { expect( await getChangeLogJSON({ @@ -67,6 +69,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toBeNull(); }); + it('skips invalid repos', async () => { expect( await getChangeLogJSON({ @@ -75,6 +78,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toBeNull(); }); + it('works without Github', async () => { httpMock .scope(githubApiHost) @@ -109,6 +113,7 @@ describe('workers/repository/update/pr/changelog/index', () => { ], }); }); + it('uses GitHub tags', async () => { httpMock .scope(githubApiHost) @@ -147,6 +152,7 @@ describe('workers/repository/update/pr/changelog/index', () => { ], }); }); + it('filters unnecessary warns', async () => { httpMock .scope(githubApiHost) @@ -176,6 +182,7 @@ describe('workers/repository/update/pr/changelog/index', () => { ], }); }); + it('supports node engines', async () => { expect( await getChangeLogJSON({ @@ -203,6 +210,7 @@ describe('workers/repository/update/pr/changelog/index', () => { // FIXME: missing mocks httpMock.clear(false); }); + it('handles no sourceUrl', async () => { expect( await getChangeLogJSON({ @@ -211,6 +219,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toBeNull(); }); + it('handles invalid sourceUrl', async () => { expect( await getChangeLogJSON({ @@ -219,6 +228,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toBeNull(); }); + it('handles missing Github token', async () => { expect( await getChangeLogJSON({ @@ -227,6 +237,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toEqual({ error: ChangeLogError.MissingGithubToken }); }); + it('handles no releases', async () => { expect( await getChangeLogJSON({ @@ -235,6 +246,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toBeNull(); }); + it('handles not enough releases', async () => { expect( await getChangeLogJSON({ @@ -243,6 +255,7 @@ describe('workers/repository/update/pr/changelog/index', () => { }) ).toBeNull(); }); + it('supports github enterprise and github.com changelog', async () => { httpMock.scope(githubApiHost).persist().get(/.*/).reply(200, []); hostRules.add({ @@ -274,6 +287,7 @@ describe('workers/repository/update/pr/changelog/index', () => { ], }); }); + it('supports github enterprise and github enterprise changelog', async () => { httpMock .scope('https://github-enterprise.example.com') diff --git a/lib/workers/repository/update/pr/changelog/release-notes.spec.ts b/lib/workers/repository/update/pr/changelog/release-notes.spec.ts index 0d321b8d28..032b3a1882 100644 --- a/lib/workers/repository/update/pr/changelog/release-notes.spec.ts +++ b/lib/workers/repository/update/pr/changelog/release-notes.spec.ts @@ -67,6 +67,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { describe('releaseNotesCacheMinutes', () => { const now = DateTime.local(); + it.each([ [now, 55], [now.minus({ weeks: 2 }), 1435], @@ -307,6 +308,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { url: 'https://github.com/some/other-repository/releases/1.0.1', }); }); + it('gets release notes with body "v"', async () => { const prefix = 'v'; httpMock @@ -337,6 +339,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { url: 'https://github.com/some/other-repository/releases/v1.0.1', }); }); + it('gets release notes with body "other-"', async () => { const prefix = 'other-'; httpMock @@ -367,6 +370,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { url: 'https://github.com/some/other-repository/releases/other-1.0.1', }); }); + it('gets release notes with body "other_v"', async () => { const prefix = 'other_v'; httpMock @@ -397,6 +401,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { url: 'https://github.com/some/other-repository/releases/other_v1.0.1', }); }); + it('gets release notes with body "other@"', async () => { const prefix = 'other@'; httpMock @@ -460,6 +465,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { url: 'https://gitlab.com/some/other-repository/tags/1.0.1', }); }); + it('gets release notes with body from gitlab repo "v"', async () => { const prefix = 'v'; httpMock @@ -492,6 +498,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { url: 'https://gitlab.com/some/other-repository/tags/v1.0.1', }); }); + it('gets release notes with body from gitlab repo "other-"', async () => { const prefix = 'other-'; httpMock @@ -792,6 +799,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { describe('ReleaseNotes Correctness', () => { let versionOneNotes: ChangeLogNotes; let versionTwoNotes: ChangeLogNotes; + it('parses yargs 15.3.0', async () => { httpMock .scope('https://api.github.com') diff --git a/lib/workers/repository/update/pr/changelog/releases.spec.ts b/lib/workers/repository/update/pr/changelog/releases.spec.ts index a255cf0034..70fbddf173 100644 --- a/lib/workers/repository/update/pr/changelog/releases.spec.ts +++ b/lib/workers/repository/update/pr/changelog/releases.spec.ts @@ -37,6 +37,7 @@ describe('workers/repository/update/pr/changelog/releases', () => { ], }); }); + it('should contain only stable', async () => { const config = partial<BranchUpgradeConfig>({ datasource: 'some-datasource', @@ -49,6 +50,7 @@ describe('workers/repository/update/pr/changelog/releases', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(3); }); + it('should contain currentVersion unstable', async () => { const config = partial<BranchUpgradeConfig>({ datasource: 'some-datasource', @@ -61,6 +63,7 @@ describe('workers/repository/update/pr/changelog/releases', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(4); }); + it('should contain newVersion unstable', async () => { const config = partial<BranchUpgradeConfig>({ datasource: 'some-datasource', @@ -73,6 +76,7 @@ describe('workers/repository/update/pr/changelog/releases', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(4); }); + it('should contain both currentVersion newVersion unstable', async () => { const config = partial<BranchUpgradeConfig>({ datasource: 'some-datasource', @@ -85,6 +89,7 @@ describe('workers/repository/update/pr/changelog/releases', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(6); }); + it('should valueToVersion', async () => { const config = partial<BranchUpgradeConfig>({ datasource: 'some-datasource', diff --git a/lib/workers/repository/update/pr/code-owners.spec.ts b/lib/workers/repository/update/pr/code-owners.spec.ts index 886475d898..fdf8644b7f 100644 --- a/lib/workers/repository/update/pr/code-owners.spec.ts +++ b/lib/workers/repository/update/pr/code-owners.spec.ts @@ -9,16 +9,19 @@ jest.mock('../../../../util/git'); describe('workers/repository/update/pr/code-owners', () => { describe('codeOwnersForPr', () => { let pr: Pr; + beforeEach(() => { jest.resetAllMocks(); pr = mock<Pr>(); }); + it('returns global code owner', async () => { fs.readLocalFile.mockResolvedValueOnce(['* @jimmy'].join('\n')); git.getBranchFiles.mockResolvedValueOnce(['README.md']); const codeOwners = await codeOwnersForPr(pr); expect(codeOwners).toEqual(['@jimmy']); }); + it('returns more specific code owners', async () => { fs.readLocalFile.mockResolvedValueOnce( ['* @jimmy', 'package.json @john @maria'].join('\n') @@ -27,6 +30,7 @@ describe('workers/repository/update/pr/code-owners', () => { const codeOwners = await codeOwnersForPr(pr); expect(codeOwners).toEqual(['@john', '@maria']); }); + it('ignores comments and leading/trailing whitespace', async () => { fs.readLocalFile.mockResolvedValueOnce( [ @@ -41,12 +45,14 @@ describe('workers/repository/update/pr/code-owners', () => { const codeOwners = await codeOwnersForPr(pr); expect(codeOwners).toEqual(['@john', '@maria']); }); + it('returns empty array when no code owners set', async () => { fs.readLocalFile.mockResolvedValueOnce(null); git.getBranchFiles.mockResolvedValueOnce(['package.json']); const codeOwners = await codeOwnersForPr(pr); expect(codeOwners).toBeEmptyArray(); }); + it('returns empty array when no code owners match', async () => { fs.readLocalFile.mockResolvedValueOnce( ['package-lock.json @mike'].join('\n') @@ -55,6 +61,7 @@ describe('workers/repository/update/pr/code-owners', () => { const codeOwners = await codeOwnersForPr(pr); expect(codeOwners).toEqual([]); }); + it('returns empty array when error occurs', async () => { fs.readLocalFile.mockImplementationOnce((_, __) => { throw new Error(); @@ -62,6 +69,7 @@ describe('workers/repository/update/pr/code-owners', () => { const codeOwners = await codeOwnersForPr(pr); expect(codeOwners).toBeEmptyArray(); }); + const codeOwnerFilePaths = [ 'CODEOWNERS', '.github/CODEOWNERS', diff --git a/lib/workers/repository/update/pr/index.spec.ts b/lib/workers/repository/update/pr/index.spec.ts index 5af672a84e..ab25a757b1 100644 --- a/lib/workers/repository/update/pr/index.spec.ts +++ b/lib/workers/repository/update/pr/index.spec.ts @@ -119,19 +119,23 @@ describe('workers/repository/update/pr/index', () => { describe('checkAutoMerge(pr, config)', () => { let config: BranchConfig; let pr: Pr; + beforeEach(() => { config = partial<BranchConfig>({ ...getConfig(), }); pr = partial<Pr>({}); }); + afterEach(() => { jest.clearAllMocks(); }); + it('should not automerge if not configured', async () => { await prAutomerge.checkAutoMerge(pr, config); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('should automerge if enabled and pr is mergeable', async () => { config.automerge = true; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); @@ -139,6 +143,7 @@ describe('workers/repository/update/pr/index', () => { await prAutomerge.checkAutoMerge(pr, config); expect(platform.mergePr).toHaveBeenCalledTimes(1); }); + it('should automerge comment', async () => { config.automerge = true; config.automergeType = 'pr-comment'; @@ -148,6 +153,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(0); expect(platform.ensureComment).toHaveBeenCalledTimes(1); }); + it('should remove previous automerge comment when rebasing', async () => { config.automerge = true; config.automergeType = 'pr-comment'; @@ -158,6 +164,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.ensureCommentRemoval).toHaveBeenCalledTimes(1); expect(platform.ensureComment).toHaveBeenCalledTimes(1); }); + it('should not automerge if enabled and pr is mergeable but cannot rebase', async () => { config.automerge = true; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); @@ -165,18 +172,21 @@ describe('workers/repository/update/pr/index', () => { await prAutomerge.checkAutoMerge(pr, config); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('should not automerge if enabled and pr is mergeable but branch status is not success', async () => { config.automerge = true; platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); await prAutomerge.checkAutoMerge(pr, config); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('should not automerge if enabled and pr is mergeable but unstable', async () => { config.automerge = true; pr.cannotMergeReason = 'some reason'; await prAutomerge.checkAutoMerge(pr, config); expect(platform.mergePr).toHaveBeenCalledTimes(0); }); + it('should not automerge if enabled and pr is unmergeable', async () => { config.automerge = true; git.isBranchConflicted.mockResolvedValueOnce(true); @@ -184,6 +194,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.mergePr).toHaveBeenCalledTimes(0); }); }); + describe('ensurePr', () => { let config: BranchConfig; // TODO fix type @@ -192,6 +203,7 @@ describe('workers/repository/update/pr/index', () => { title: 'Update dependency dummy to v1.1.0', body: 'Some body<!-- Reviewable:start -->something<!-- Reviewable:end -->\n\n', } as never; + beforeEach(() => { jest.resetAllMocks(); setupChangelogMock(); @@ -217,9 +229,11 @@ describe('workers/repository/update/pr/index', () => { config.upgrades = [config]; platform.massageMarkdown.mockImplementation((input) => input); }); + afterEach(() => { jest.clearAllMocks(); }); + it('should return PR if update fails', async () => { platform.updatePr.mockImplementationOnce(() => { throw new Error('oops'); @@ -230,6 +244,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithPr(result); expect(result.pr).toBeDefined(); }); + it('should return null if waiting for success', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.red); config.prCreation = 'status-success'; @@ -237,6 +252,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithoutPr(result); expect(result.prBlockedBy).toBe('AwaitingTests'); }); + it('should return needs-approval if prCreation set to approval', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); config.prCreation = 'approval'; @@ -244,6 +260,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithoutPr(result); expect(result.prBlockedBy).toBe('NeedsApproval'); }); + it('should create PR if success for gitlab deps', async () => { setupGitlabChangelogMock(); config.branchName = 'renovate/gitlabdummy-1.x'; @@ -270,6 +287,7 @@ describe('workers/repository/update/pr/index', () => { config.sourceUrl = 'https://github.com/renovateapp/dummy'; config.changelogUrl = 'https://github.com/renovateapp/dummy/changelog.md'; }); + it('should create PR if success', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); config.logJSON = await changelogHelper.getChangeLogJSON(config); @@ -287,6 +305,7 @@ describe('workers/repository/update/pr/index', () => { ]); existingPr.body = platform.createPr.mock.calls[0][0].prBody; }); + it('should not create PR if limit is reached', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); config.logJSON = await changelogHelper.getChangeLogJSON(config); @@ -299,6 +318,7 @@ describe('workers/repository/update/pr/index', () => { expect(result.prBlockedBy).toBe('RateLimited'); expect(platform.createPr.mock.calls).toBeEmpty(); }); + it('should create PR if limit is reached but dashboard checked', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); config.logJSON = await changelogHelper.getChangeLogJSON(config); @@ -312,6 +332,7 @@ describe('workers/repository/update/pr/index', () => { }); expect(platform.createPr).toHaveBeenCalled(); }); + it('should create group PR', async () => { const depsWithSameNotesSourceUrl = ['e', 'f']; const depsWithSameSourceUrl = ['g', 'h']; @@ -455,6 +476,7 @@ describe('workers/repository/update/pr/index', () => { }, ]); }); + it('should add note about Pin', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); config.prCreation = 'status-success'; @@ -488,6 +510,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithoutPr(result); expect(result.prBlockedBy).toBe('Error'); }); + it('should return null if waiting for not pending', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); git.getBranchLastCommitTime.mockImplementationOnce(() => @@ -498,6 +521,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithoutPr(result); expect(result.prBlockedBy).toBe('AwaitingTests'); }); + it('should not create PR if waiting for not pending with stabilityStatus yellow', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); git.getBranchLastCommitTime.mockImplementationOnce(() => @@ -509,6 +533,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithoutPr(result); expect(result.prBlockedBy).toBe('AwaitingTests'); }); + it('should create PR if pending timeout hit', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); git.getBranchLastCommitTime.mockImplementationOnce(() => @@ -520,6 +545,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithPr(result); expect(result.pr).toMatchObject({ displayNumber: 'New Pull Request' }); }); + it('should create PR if no longer pending', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.red); config.prCreation = 'not-pending'; @@ -527,11 +553,13 @@ describe('workers/repository/update/pr/index', () => { isResultWithPr(result); expect(result.pr).toMatchObject({ displayNumber: 'New Pull Request' }); }); + it('should create new branch if none exists', async () => { const result = await prWorker.ensurePr(config); isResultWithPr(result); expect(result.pr).toMatchObject({ displayNumber: 'New Pull Request' }); }); + it('should add assignees and reviewers to new PR', async () => { config.assignees = ['@foo', 'bar']; config.reviewers = ['baz', '@boo']; @@ -543,6 +571,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addReviewers).toHaveBeenCalledTimes(1); expect(platform.addReviewers.mock.calls).toMatchSnapshot(); }); + it('should filter assignees and reviewers based on their availability', async () => { config.assignees = ['@foo', 'bar']; config.reviewers = ['foo', '@bar', 'foo@bar.com']; @@ -555,6 +584,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addReviewers.mock.calls).toMatchSnapshot(); expect(platform.filterUnavailableUsers.mock.calls).toMatchSnapshot(); }); + it('should determine assignees from code owners', async () => { config.assigneesFromCodeOwners = true; codeOwnersMock.codeOwnersForPr.mockResolvedValueOnce(['@john', '@maria']); @@ -562,6 +592,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addAssignees).toHaveBeenCalledTimes(1); expect(platform.addAssignees.mock.calls).toMatchSnapshot(); }); + it('should determine reviewers from code owners', async () => { config.reviewersFromCodeOwners = true; codeOwnersMock.codeOwnersForPr.mockResolvedValueOnce(['@john', '@maria']); @@ -569,6 +600,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addReviewers).toHaveBeenCalledTimes(1); expect(platform.addReviewers.mock.calls).toMatchSnapshot(); }); + it('should combine assignees from code owners and config', async () => { codeOwnersMock.codeOwnersForPr.mockResolvedValueOnce(['@jimmy']); config.assignees = ['@mike', '@julie']; @@ -577,6 +609,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addAssignees).toHaveBeenCalledTimes(1); expect(platform.addAssignees.mock.calls).toMatchSnapshot(); }); + it('should add reviewers even if assignees fails', async () => { platform.addAssignees.mockImplementationOnce(() => { throw new Error('some error'); @@ -589,6 +622,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addAssignees).toHaveBeenCalledTimes(1); expect(platform.addReviewers).toHaveBeenCalledTimes(1); }); + it('should handled failed reviewers add', async () => { platform.addReviewers.mockImplementationOnce(() => { throw new Error('some error'); @@ -601,6 +635,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addAssignees).toHaveBeenCalledTimes(1); expect(platform.addReviewers).toHaveBeenCalledTimes(1); }); + it('should not add assignees and reviewers to new PR if automerging enabled regularly', async () => { config.assignees = ['bar']; config.reviewers = ['baz']; @@ -611,6 +646,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addAssignees).toHaveBeenCalledTimes(0); expect(platform.addReviewers).toHaveBeenCalledTimes(0); }); + it('should add assignees and reviewers to new PR if automerging enabled but configured to always assign', async () => { config.assignees = ['bar']; config.reviewers = ['baz']; @@ -622,6 +658,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addAssignees).toHaveBeenCalledTimes(1); expect(platform.addReviewers).toHaveBeenCalledTimes(1); }); + it('should add random sample of assignees and reviewers to new PR', async () => { config.assignees = ['foo', 'bar', 'baz']; config.assigneesSampleSize = 2; @@ -640,6 +677,7 @@ describe('workers/repository/update/pr/index', () => { expect(reviewers).toHaveLength(2); expect(config.reviewers).toEqual(expect.arrayContaining(reviewers)); }); + it('should not add any assignees or reviewers to new PR', async () => { config.assignees = ['foo', 'bar', 'baz']; config.assigneesSampleSize = 0; @@ -651,6 +689,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addAssignees).toHaveBeenCalledTimes(0); expect(platform.addReviewers).toHaveBeenCalledTimes(0); }); + it('should add and deduplicate additionalReviewers on new PR', async () => { config.reviewers = ['@foo', 'bar']; config.additionalReviewers = ['bar', 'baz', '@boo']; @@ -660,6 +699,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addReviewers).toHaveBeenCalledTimes(1); expect(platform.addReviewers.mock.calls).toMatchSnapshot(); }); + it('should add and deduplicate additionalReviewers to empty reviewers on new PR', async () => { config.reviewers = []; config.additionalReviewers = ['bar', 'baz', '@boo', '@foo', 'bar']; @@ -669,6 +709,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.addReviewers).toHaveBeenCalledTimes(1); expect(platform.addReviewers.mock.calls).toMatchSnapshot(); }); + it('should return unmodified existing PR', async () => { platform.getBranchPr.mockResolvedValueOnce(existingPr); config.semanticCommitScope = null; @@ -681,6 +722,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.updatePr).toHaveBeenCalledTimes(0); expect(result.pr).toMatchObject(existingPr); }); + it('should return unmodified existing PR if only whitespace changes', async () => { const modifiedPr = JSON.parse( JSON.stringify(existingPr).replace(' ', ' ').replace('\n', '\r\n') @@ -695,6 +737,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.updatePr).toHaveBeenCalledTimes(0); expect(result.pr).toMatchObject(modifiedPr); }); + it('should return modified existing PR', async () => { config.newValue = '1.2.0'; config.automerge = true; @@ -708,6 +751,7 @@ describe('workers/repository/update/pr/index', () => { title: 'Update dependency dummy to v1.1.0', }); }); + it('should return modified existing PR title', async () => { config.newValue = '1.2.0'; platform.getBranchPr.mockResolvedValueOnce({ @@ -721,6 +765,7 @@ describe('workers/repository/update/pr/index', () => { title: 'wrong', }); }); + it('should create PR if branch tests failed', async () => { config.automerge = true; config.automergeType = 'branch'; @@ -730,6 +775,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithPr(result); expect(result.pr).toMatchObject({ displayNumber: 'New Pull Request' }); }); + it('should create PR if branch automerging failed', async () => { config.automerge = true; config.automergeType = 'branch'; @@ -739,6 +785,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithPr(result); expect(result.pr).toMatchObject({ displayNumber: 'New Pull Request' }); }); + it('should return no PR if branch automerging not failed', async () => { config.automerge = true; config.automergeType = 'branch'; @@ -748,6 +795,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithoutPr(result); expect(result.prBlockedBy).toBe('BranchAutomerge'); }); + it('should return PR if branch automerging taking too long', async () => { config.automerge = true; config.automergeType = 'branch'; @@ -757,6 +805,7 @@ describe('workers/repository/update/pr/index', () => { isResultWithPr(result); expect(result.pr).toBeDefined(); }); + it('should return no PR if stabilityStatus yellow', async () => { config.automerge = true; config.automergeType = 'branch'; @@ -767,12 +816,14 @@ describe('workers/repository/update/pr/index', () => { isResultWithoutPr(result); expect(result.prBlockedBy).toBe('BranchAutomerge'); }); + it('handles duplicate upgrades', async () => { config.upgrades.push(config.upgrades[0]); const result = await prWorker.ensurePr(config); isResultWithPr(result); expect(result.pr).toMatchObject({ displayNumber: 'New Pull Request' }); }); + it('should create privateRepo PR if success', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.green); config.prCreation = 'status-success'; @@ -785,6 +836,7 @@ describe('workers/repository/update/pr/index', () => { expect(platform.createPr.mock.calls[0]).toMatchSnapshot(); existingPr.body = platform.createPr.mock.calls[0][0].prBody; }); + it('should create PR if waiting for not pending but artifactErrors', async () => { platform.getBranchStatus.mockResolvedValueOnce(BranchStatus.yellow); git.getBranchLastCommitTime.mockResolvedValueOnce(new Date()); diff --git a/lib/workers/repository/updates/branch-name.spec.ts b/lib/workers/repository/updates/branch-name.spec.ts index a1436ee7c3..88d91f8651 100644 --- a/lib/workers/repository/updates/branch-name.spec.ts +++ b/lib/workers/repository/updates/branch-name.spec.ts @@ -14,6 +14,7 @@ describe('workers/repository/updates/branch-name', () => { generateBranchName(upgrade); expect(upgrade.branchName).toBe('some-group-name-grouptopic'); }); + it('uses groupSlug if defined', () => { const upgrade: RenovateConfig = { groupName: 'some group name', @@ -26,6 +27,7 @@ describe('workers/repository/updates/branch-name', () => { generateBranchName(upgrade); expect(upgrade.branchName).toBe('some-group-slug-grouptopic'); }); + it('separates major with groups', () => { const upgrade: RenovateConfig = { groupName: 'some group name', @@ -42,6 +44,7 @@ describe('workers/repository/updates/branch-name', () => { generateBranchName(upgrade); expect(upgrade.branchName).toBe('major-2-some-group-slug-grouptopic'); }); + it('uses single major with groups', () => { const upgrade: RenovateConfig = { groupName: 'some group name', @@ -58,6 +61,7 @@ describe('workers/repository/updates/branch-name', () => { generateBranchName(upgrade); expect(upgrade.branchName).toBe('major-some-group-slug-grouptopic'); }); + it('separates patch groups and uses update topic', () => { const upgrade: RenovateConfig = { branchName: 'update-branch-{{groupSlug}}-{{branchTopic}}', @@ -75,6 +79,7 @@ describe('workers/repository/updates/branch-name', () => { 'update-branch-patch-some-group-slug-update-topic' ); }); + it('compiles multiple times', () => { const upgrade: RenovateConfig = { branchName: '{{branchTopic}}', @@ -85,6 +90,7 @@ describe('workers/repository/updates/branch-name', () => { generateBranchName(upgrade); expect(upgrade.branchName).toBe('dep'); }); + it('separates patches when separateMinorPatch=true', () => { const upgrade: RenovateConfig = { branchName: @@ -104,6 +110,7 @@ describe('workers/repository/updates/branch-name', () => { generateBranchName(upgrade); expect(upgrade.branchName).toBe('renovate/lodash-4.17.x'); }); + it('does not separate patches when separateMinorPatch=false', () => { const upgrade: RenovateConfig = { branchName: diff --git a/lib/workers/repository/updates/branchify.spec.ts b/lib/workers/repository/updates/branchify.spec.ts index fff5fe18cd..6886657acb 100644 --- a/lib/workers/repository/updates/branchify.spec.ts +++ b/lib/workers/repository/updates/branchify.spec.ts @@ -11,6 +11,7 @@ jest.mock('./flatten'); jest.mock('../changelog'); let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); @@ -25,6 +26,7 @@ describe('workers/repository/updates/branchify', () => { const res = await branchifyUpgrades(config, {}); expect(res.branches).toBeEmptyArray(); }); + it('returns one branch if one input', async () => { flattenUpdates.mockResolvedValueOnce([ { @@ -40,6 +42,7 @@ describe('workers/repository/updates/branchify', () => { const res = await branchifyUpgrades(config, {}); expect(Object.keys(res.branches)).toHaveLength(1); }); + it('deduplicates', async () => { flattenUpdates.mockResolvedValueOnce([ { @@ -65,6 +68,7 @@ describe('workers/repository/updates/branchify', () => { const res = await branchifyUpgrades(config, {}); expect(Object.keys(res.branches)).toHaveLength(1); }); + it('groups if same compiled branch names', async () => { flattenUpdates.mockResolvedValueOnce([ { @@ -89,6 +93,7 @@ describe('workers/repository/updates/branchify', () => { const res = await branchifyUpgrades(config, {}); expect(Object.keys(res.branches)).toHaveLength(2); }); + it('groups if same compiled group name', async () => { flattenUpdates.mockResolvedValueOnce([ { @@ -117,6 +122,7 @@ describe('workers/repository/updates/branchify', () => { const res = await branchifyUpgrades(config, {}); expect(Object.keys(res.branches)).toHaveLength(2); }); + it('no fetch changelogs', async () => { config.fetchReleaseNotes = false; flattenUpdates.mockResolvedValueOnce([ diff --git a/lib/workers/repository/updates/flatten.spec.ts b/lib/workers/repository/updates/flatten.spec.ts index 18ca0e56a1..ce5df4f5a7 100644 --- a/lib/workers/repository/updates/flatten.spec.ts +++ b/lib/workers/repository/updates/flatten.spec.ts @@ -4,6 +4,7 @@ import { ProgrammingLanguage } from '../../../constants'; import { flattenUpdates } from './flatten'; let config: RenovateConfig; + beforeEach(() => { jest.resetAllMocks(); config = getConfig(); diff --git a/lib/workers/repository/updates/generate.spec.ts b/lib/workers/repository/updates/generate.spec.ts index c1b9eb4a89..9d67b5fe66 100644 --- a/lib/workers/repository/updates/generate.spec.ts +++ b/lib/workers/repository/updates/generate.spec.ts @@ -30,6 +30,7 @@ describe('workers/repository/updates/generate', () => { expect(res.groupName).toBeUndefined(); expect(res.releaseTimestamp).toBeDefined(); }); + it('handles lockFileMaintenance', () => { const branch = [ { @@ -52,6 +53,7 @@ describe('workers/repository/updates/generate', () => { ], }); }); + it('handles lockFileUpdate', () => { const branch = [ { @@ -90,6 +92,7 @@ describe('workers/repository/updates/generate', () => { ], }); }); + it('does not group same upgrades', () => { const branch = [ { @@ -117,6 +120,7 @@ describe('workers/repository/updates/generate', () => { expect(res.foo).toBe(1); expect(res.groupName).toBeUndefined(); }); + it('groups multiple upgrades same version', () => { const branch = [ { @@ -185,6 +189,7 @@ describe('workers/repository/updates/generate', () => { bar: '2.0.0', }); }); + it('groups multiple upgrades different version', () => { const branch = [ { @@ -225,6 +230,7 @@ describe('workers/repository/updates/generate', () => { expect(res.groupName).toBeDefined(); expect(res.releaseTimestamp).toBe('2017-02-08T20:01:41+00:00'); }); + it('groups multiple upgrades different version but same value', () => { const branch = [ { @@ -265,6 +271,7 @@ describe('workers/repository/updates/generate', () => { expect(res.groupName).toBeDefined(); expect(res.releaseTimestamp).toBe('2017-02-08T20:01:41+00:00'); }); + it('groups multiple upgrades different value but same version', () => { const branch = [ { @@ -305,6 +312,7 @@ describe('workers/repository/updates/generate', () => { expect(res.groupName).toBeDefined(); expect(res.releaseTimestamp).toBe('2017-02-08T20:01:41+00:00'); }); + it('groups multiple digest updates', () => { const branch = [ { @@ -342,6 +350,7 @@ describe('workers/repository/updates/generate', () => { expect(res.recreateClosed).toBeTrue(); expect(res.groupName).toBeDefined(); }); + it('pins digest to table', () => { const branch = [ partial<LookupUpdate & BranchUpgradeConfig>({ @@ -356,6 +365,7 @@ describe('workers/repository/updates/generate', () => { expect(res.upgrades[0].displayFrom).toBe(''); expect(res.upgrades[0].displayTo).toBe('abcdefg'); }); + it('fixes different messages', () => { const branch = [ { @@ -393,6 +403,7 @@ describe('workers/repository/updates/generate', () => { expect(res.foo).toBe(1); expect(res.groupName).toBeUndefined(); }); + it('uses semantic commits', () => { const branch = [ partial<BranchUpgradeConfig>({ @@ -415,6 +426,7 @@ describe('workers/repository/updates/generate', () => { 'chore(package): update dependency some-dep to v1.2.0' ); }); + it('scopes monorepo commits', () => { const branch = [ partial<BranchUpgradeConfig>({ @@ -437,6 +449,7 @@ describe('workers/repository/updates/generate', () => { const res = generateBranchConfig(branch); expect(res.prTitle).toBe('chore(): update dependency some-dep to v1.2.0'); }); + it('scopes monorepo commits with nested package files using parent directory', () => { const branch = [ partial<BranchUpgradeConfig>({ @@ -462,6 +475,7 @@ describe('workers/repository/updates/generate', () => { 'chore(bar): update dependency some-dep to v1.2.0' ); }); + it('scopes monorepo commits with nested package files using base directory', () => { const branch = [ partial<BranchUpgradeConfig>({ @@ -486,6 +500,7 @@ describe('workers/repository/updates/generate', () => { 'chore(foo/bar): update dependency some-dep to v1.2.0' ); }); + it('adds commit message body', () => { const branch = [ partial<BranchUpgradeConfig>({ @@ -501,6 +516,7 @@ describe('workers/repository/updates/generate', () => { expect(res.commitMessage).toMatchSnapshot(); expect(res.commitMessage).toContain('\n'); }); + it('supports manual prTitle', () => { const branch = [ partial<BranchUpgradeConfig>({ @@ -513,6 +529,7 @@ describe('workers/repository/updates/generate', () => { const res = generateBranchConfig(branch); expect(res.prTitle).toBe('upgrade some-dep'); }); + it('handles @types specially', () => { const branch: BranchUpgradeConfig[] = [ { @@ -572,6 +589,7 @@ describe('workers/repository/updates/generate', () => { ], }); }); + it('handles @types specially (reversed)', () => { const branch: BranchUpgradeConfig[] = [ { @@ -627,6 +645,7 @@ describe('workers/repository/updates/generate', () => { ], }); }); + it('handles upgrades', () => { const branch: BranchUpgradeConfig[] = [ { @@ -675,6 +694,7 @@ describe('workers/repository/updates/generate', () => { const res = generateBranchConfig(branch); expect(res.prTitle).toMatchSnapshot('some-title (patch)'); }); + it('combines prBodyColumns', () => { const branch: BranchUpgradeConfig[] = [ { @@ -689,6 +709,7 @@ describe('workers/repository/updates/generate', () => { const res = generateBranchConfig(branch); expect(res.prBodyColumns).toEqual(['column-a', 'column-b', 'column-c']); }); + it('sorts upgrades, without position first', () => { const branch: BranchUpgradeConfig[] = [ { @@ -725,6 +746,7 @@ describe('workers/repository/updates/generate', () => { res.upgrades.map((upgrade) => upgrade.fileReplacePosition) ).toStrictEqual([undefined, undefined, 4, 1]); }); + it('passes through pendingChecks', () => { const branch = [ { @@ -746,6 +768,7 @@ describe('workers/repository/updates/generate', () => { expect(res.pendingChecks).toBeTrue(); expect(res.upgrades).toHaveLength(2); }); + it('filters pendingChecks', () => { const branch = [ { @@ -766,6 +789,7 @@ describe('workers/repository/updates/generate', () => { expect(res.pendingChecks).toBeUndefined(); expect(res.upgrades).toHaveLength(1); }); + it('displays pending versions', () => { const branch = [ { diff --git a/package.json b/package.json index 8240e11c40..d192e09131 100644 --- a/package.json +++ b/package.json @@ -268,6 +268,7 @@ "eslint-import-resolver-typescript": "2.7.1", "eslint-plugin-import": "2.25.4", "eslint-plugin-jest": "26.1.3", + "eslint-plugin-jest-formatting": "3.1.0", "eslint-plugin-promise": "6.0.0", "eslint-plugin-typescript-enum": "2.1.0", "expect-more-jest": "5.4.0", diff --git a/test/website-docs.spec.ts b/test/website-docs.spec.ts index 8b8f542cf3..436c0836bf 100644 --- a/test/website-docs.spec.ts +++ b/test/website-docs.spec.ts @@ -47,15 +47,19 @@ describe('website-docs', () => { it('has doc headers sorted alphabetically', () => { expect(headers).toEqual([...headers].sort()); }); + it('has headers for every required option', () => { expect(headers).toEqual(expectedOptions); }); + it('has self hosted doc headers sorted alphabetically', () => { expect(selfHostHeaders).toEqual([...selfHostHeaders].sort()); }); + it('has headers (self hosted) for every required option', () => { expect(selfHostHeaders).toEqual(selfHostExpectedOptions); }); + const headers3 = doc .match(/\n### (.*?)\n/g) .map((match) => match.substring(5, match.length - 1)); @@ -67,6 +71,7 @@ describe('website-docs', () => { .map((option) => option.name) .sort(); expectedOptions3.sort(); + it('has headers for every required sub-option', () => { expect(headers3).toEqual(expectedOptions3); }); diff --git a/yarn.lock b/yarn.lock index 4ef6dcea9b..5b0a54c49e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1960,7 +1960,6 @@ "@renovate/eslint-plugin@https://github.com/renovatebot/eslint-plugin#v0.0.4": version "0.0.4" - uid "0c444386e79d6145901212507521b8a0a48af000" resolved "https://github.com/renovatebot/eslint-plugin#0c444386e79d6145901212507521b8a0a48af000" "@renovatebot/pep440@2.1.1": @@ -4391,6 +4390,11 @@ eslint-plugin-import@2.25.4: resolve "^1.20.0" tsconfig-paths "^3.12.0" +eslint-plugin-jest-formatting@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest-formatting/-/eslint-plugin-jest-formatting-3.1.0.tgz#b26dd5a40f432b642dcc880021a771bb1c93dcd2" + integrity sha512-XyysraZ1JSgGbLSDxjj5HzKKh0glgWf+7CkqxbTqb7zEhW7X2WHo5SBQ8cGhnszKN+2Lj3/oevBlHNbHezoc/A== + eslint-plugin-jest@26.1.3: version "26.1.3" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-26.1.3.tgz#e722e5efeea18aa9dec7c7349987b641db19feb7" -- GitLab