import { codeBlock } from 'common-tags'; import upath from 'upath'; import { Fixtures } from '~test/fixtures.ts'; import { fs } from '~test/util.ts'; import { GlobalConfig } from '../../../config/global.ts'; import type { InternalGlobalConfigOptions, RepoGlobalConfig, } from '../../../config/types.ts'; import * as hashicorp from '../../versioning/hashicorp/index.ts'; import { extractPackageFile } from './index.ts'; const modules = Fixtures.get('modules.tf'); const bitbucketModules = Fixtures.get('bitbucketModules.tf'); const azureDevOpsModules = Fixtures.get('azureDevOpsModules.tf'); const providers = Fixtures.get('providers.tf'); const docker = Fixtures.get('docker.tf'); const kubernetes = Fixtures.get('kubernetes.tf'); const helm = Fixtures.get('helm.tf'); const lockedVersion = Fixtures.get('lockedVersion.tf'); const lockedVersionLockfile = Fixtures.get('rangeStrategy.hcl'); const terraformBlock = Fixtures.get('terraformBlock.tf'); const tfeWorkspaceBlock = Fixtures.get('tfeWorkspace.tf'); const adminConfig: RepoGlobalConfig & InternalGlobalConfigOptions = { // `join` fixes Windows CI localDir: upath.join('/tmp/github/some/repo'), cacheDir: upath.join('/tmp/cache'), containerbaseDir: upath.join('/tmp/cache/containerbase'), }; // auto-mock fs vi.mock('../../../util/fs/index.ts'); describe('modules/manager/terraform/extract', () => { beforeEach(() => { GlobalConfig.set(adminConfig); }); describe('extractPackageFile()', () => { it('returns null for empty', async () => { await expect( extractPackageFile('nothing here', '1.tf', {}), ).resolves.toBeNull(); }); it('returns null for no deps', async () => { // ModuleExtractor matches `module` at any position. const src = codeBlock` data "sops_file" "secrets" { source_file = "\${path.module}/secrets.enc.json" } `; await expect(extractPackageFile(src, '1.tf', {})).resolves.toBeNull(); }); it('extracts modules', async () => { const res = await extractPackageFile(modules, 'modules.tf', {}); expect(res?.deps).toHaveLength(23); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(3); expect(res?.deps).toIncludeAllPartialMembers([ { packageName: 'hashicorp/example', depType: 'module', depName: 'github.com/hashicorp/example', currentValue: 'next', datasource: 'github-tags', }, { packageName: 'hashicorp/example', depType: 'module', depName: 'github.com/hashicorp/example', currentValue: 'v1.0.0', datasource: 'github-tags', }, { packageName: 'hashicorp/example', depType: 'module', depName: 'github.com/hashicorp/example', currentValue: 'next', datasource: 'github-tags', }, { packageName: 'githubuser/myrepo', depType: 'module', depName: 'github.com/githubuser/myrepo', currentValue: 'tfmodule_one-v0.0.9', datasource: 'github-tags', }, { packageName: 'hashicorp/example.2.3', depType: 'module', depName: 'github.com/hashicorp/example.2.3', currentValue: 'v1.0.0', datasource: 'github-tags', }, { packageName: 'hashicorp/example.2.3', depType: 'module', depName: 'github.com/hashicorp/example.2.3', currentValue: 'v1.0.0', datasource: 'github-tags', }, { currentValue: '0.1.0', depType: 'module', depName: 'hashicorp/consul/aws', datasource: 'terraform-module', }, { packageName: 'hashicorp/example', depType: 'module', depName: 'github.com/hashicorp/example', currentValue: 'v2.0.0', datasource: 'github-tags', }, { currentValue: '~> 1.1.0', registryUrls: ['https://app.terraform.io'], depType: 'module', depName: 'app.terraform.io/example-corp/k8s-cluster/azurerm', datasource: 'terraform-module', }, { currentValue: '~> 1.1', registryUrls: ['https://app.terraform.io'], depType: 'module', depName: 'app.terraform.io/example-corp/k8s-cluster/azurerm', datasource: 'terraform-module', }, { currentValue: '~~ 1.1', registryUrls: ['https://app.terraform.io'], depType: 'module', depName: 'app.terraform.io/example-corp/k8s-cluster/azurerm', datasource: 'terraform-module', }, { currentValue: '>= 1.0.0, <= 2.0.0', depType: 'module', depName: 'hashicorp/consul/aws', datasource: 'terraform-module', }, { packageName: 'tieto-cem/terraform-aws-ecs-task-definition', depType: 'module', depName: 'github.com/tieto-cem/terraform-aws-ecs-task-definition', currentValue: 'v0.1.0', datasource: 'github-tags', }, { packageName: 'tieto-cem/terraform-aws-ecs-task-definition', depType: 'module', depName: 'github.com/tieto-cem/terraform-aws-ecs-task-definition', currentValue: 'v0.1.0', datasource: 'github-tags', }, { packageName: 'git@git.example.com:group/repo/foo', currentValue: 'v1.0.0', depType: 'module', depName: 'git.example.com:group/repo/foo', datasource: 'git-tags', }, { packageName: 'git@git.example.com:group/repo/foo', currentValue: 'v1.0.0', depType: 'module', depName: 'git.example.com:group/repo/foo', datasource: 'git-tags', }, { packageName: 'git@git.example.com:group/repo/foo', currentValue: 'bar-v1.0.0', depType: 'module', depName: 'git.example.com:group/repo/foo', datasource: 'git-tags', }, { packageName: 'git@git.example.com:group/repo/foo', currentValue: 'bar-v1.0.0', depType: 'module', depName: 'git.example.com:group/repo/foo', datasource: 'git-tags', }, { depType: 'module', depName: 'terraform-aws-modules/security-group/aws', datasource: 'terraform-module', }, { currentValue: '<= 2.4.0', depType: 'module', depName: 'terraform-aws-modules/security-group/aws', datasource: 'terraform-module', }, { currentValue: '1.28.3', depType: 'module', depName: 'particuleio/addons/kubernetes', datasource: 'terraform-module', }, { depName: 'relative', depType: 'module', currentValue: undefined, skipReason: 'local', }, { depName: 'relative', depType: 'module', currentValue: undefined, skipReason: 'local', }, { depName: 'nosauce', depType: 'module', currentValue: undefined, skipReason: 'no-source', }, ]); }); it('extracts bitbucket modules', async () => { const res = await extractPackageFile(bitbucketModules, 'modules.tf', {}); expect(res?.deps).toHaveLength(11); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(0); expect(res?.deps).toIncludeAllPartialMembers([ { currentValue: 'v1.0.0', datasource: 'git-tags', depName: 'bitbucket.com/hashicorp/example', depType: 'module', packageName: 'https://bitbucket.com/hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'git-tags', depName: 'bitbucket.com/hashicorp/example', depType: 'module', packageName: 'https://bitbucket.com/hashicorp/example', }, { currentValue: 'next', datasource: 'git-tags', depName: 'bitbucket.com/hashicorp/example', depType: 'module', packageName: 'https://bitbucket.com/hashicorp/example', }, { currentValue: 'v1.0.1', datasource: 'git-tags', depName: 'bitbucket.com/hashicorp/example', depType: 'module', packageName: 'https://bitbucket.com/hashicorp/example', }, { currentValue: 'v1.0.2', datasource: 'git-tags', depName: 'bitbucket.com/hashicorp/example', depType: 'module', packageName: 'http://bitbucket.com/hashicorp/example', }, { currentValue: 'v1.0.3', datasource: 'git-tags', depName: 'bitbucket.com/hashicorp/example', depType: 'module', packageName: 'ssh://git@bitbucket.com/hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'bitbucket-tags', depName: 'hashicorp/example', depType: 'module', packageName: 'hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'bitbucket-tags', depName: 'hashicorp/example', depType: 'module', packageName: 'hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'bitbucket-tags', depName: 'hashicorp/example', depType: 'module', packageName: 'hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'bitbucket-tags', depName: 'hashicorp/example', depType: 'module', packageName: 'hashicorp/example', }, { currentValue: 'v1.0.0', datasource: 'bitbucket-tags', depName: 'hashicorp/example', depType: 'module', packageName: 'hashicorp/example', }, ]); }); it('extracts azureDevOps modules', async () => { const res = await extractPackageFile( azureDevOpsModules, 'modules.tf', {}, ); expect(res?.deps).toHaveLength(3); expect(res?.deps).toIncludeAllPartialMembers([ { currentValue: 'v1.0.0', datasource: 'git-tags', depName: 'MyOrg/MyProject/MyRepository', depType: 'module', packageName: 'git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepository', }, { currentValue: 'v1.0.0', datasource: 'git-tags', depName: 'MyOrg/MyProject/MyRepository', depType: 'module', packageName: 'git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepository', }, { currentValue: 'v1.0.0', datasource: 'git-tags', depName: 'MyOrg/MyProject/MyRepository//some-module/path', depType: 'module', packageName: 'git@ssh.dev.azure.com:v3/MyOrg/MyProject/MyRepository', }, ]); }); it('resolves OCI registry aliases', async () => { const src = codeBlock` module "aliased_oci" { source = "oci://hub.proxy.test/terraform-modules/vpc?tag=1.0.0" } `; const res = await extractPackageFile(src, 'oci.tf', { registryAliases: { 'hub.proxy.test': 'index.docker.io' }, }); expect(res?.deps).toIncludeAllPartialMembers([ { currentValue: '1.0.0', datasource: 'docker', depName: 'aliased_oci', depType: 'module', packageName: 'index.docker.io/terraform-modules/vpc', }, ]); }); it('handles invalid OCI source URL', async () => { const src = codeBlock` module "bad_oci" { source = "oci://not a valid url" } `; const res = await extractPackageFile(src, 'oci.tf', {}); expect(res?.deps).toIncludeAllPartialMembers([ { depName: 'bad_oci', depType: 'module', skipReason: 'invalid-url', }, ]); }); it('extracts OCI modules and providers', async () => { const src = codeBlock` module "vpc_oci" { source = "oci://registry.example.com/terraform-modules/vpc?tag=1.2.3" } module "storage_oci_tagged" { source = "oci://ghcr.io/terraform-modules/storage?tag=3.1.0" } module "digest_oci" { source = "oci://ghcr.io/terraform-modules/pinned?digest=sha256:abc123" } module "no_version_oci" { source = "oci://registry.example.com/terraform-modules/noversion" } terraform { required_providers { custom_oci = { source = "oci://registry.example.com/providers/custom?tag=1.0.0" } tagged_oci = { source = "oci://ghcr.io/providers/tagged?tag=4.2.0" } no_version_oci = { source = "oci://registry.example.com/providers/noversion" } } } `; const res = await extractPackageFile(src, 'oci.tf', {}); expect(res?.deps).toHaveLength(7); expect(res?.deps).toIncludeAllPartialMembers([ { currentValue: '1.2.3', datasource: 'docker', depName: 'vpc_oci', depType: 'module', packageName: 'registry.example.com/terraform-modules/vpc', }, { currentValue: '3.1.0', datasource: 'docker', depName: 'storage_oci_tagged', depType: 'module', packageName: 'ghcr.io/terraform-modules/storage', }, { currentDigest: 'sha256:abc123', datasource: 'docker', depName: 'digest_oci', depType: 'module', packageName: 'ghcr.io/terraform-modules/pinned', }, { datasource: 'docker', depName: 'no_version_oci', depType: 'module', packageName: 'registry.example.com/terraform-modules/noversion', skipReason: 'unspecified-version', }, { currentValue: '1.0.0', datasource: 'docker', depName: 'custom_oci', depType: 'required_provider', packageName: 'registry.example.com/providers/custom', }, { currentValue: '4.2.0', datasource: 'docker', depName: 'tagged_oci', depType: 'required_provider', packageName: 'ghcr.io/providers/tagged', }, { datasource: 'docker', depName: 'no_version_oci', depType: 'required_provider', packageName: 'registry.example.com/providers/noversion', skipReason: 'unspecified-version', }, ]); }); it('extracts providers', async () => { const res = await extractPackageFile(providers, 'providers.tf', {}); expect(res?.deps).toHaveLength(15); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(2); expect(res?.deps).toIncludeAllPartialMembers([ { currentValue: '1.36.1', datasource: 'terraform-provider', depName: 'azurerm', depType: 'provider', packageName: 'hashicorp/azurerm', }, { currentValue: '=2.4', datasource: 'terraform-provider', depName: 'gitlab', depType: 'provider', packageName: 'hashicorp/gitlab', }, { currentValue: '=1.3', datasource: 'terraform-provider', depName: 'gitlab1', depType: 'provider', packageName: 'hashicorp/gitlab1', }, { datasource: 'terraform-provider', depName: 'helm', depType: 'provider', packageName: 'hashicorp/helm', skipReason: 'unspecified-version', }, { currentValue: 'V1.9', datasource: 'terraform-provider', depName: 'newrelic', depType: 'provider', packageName: 'hashicorp/newrelic', }, { currentValue: '>= 2.7.0', datasource: 'terraform-provider', depName: 'aws', depType: 'required_provider', packageName: 'hashicorp/aws', }, { currentValue: '>= 2.0.0', datasource: 'terraform-provider', depName: 'azurerm', depType: 'required_provider', packageName: 'hashicorp/azurerm', }, { currentValue: '>= 0.13', datasource: 'github-releases', depName: 'hashicorp/terraform', depType: 'required_version', extractVersion: 'v(?.*)$', }, { currentValue: '2.7.2', datasource: 'terraform-provider', depName: 'docker', depType: 'required_provider', packageName: 'hashicorp/docker', registryUrls: ['https://releases.hashicorp.com'], }, { currentValue: '2.7.0', datasource: 'terraform-provider', depName: 'aws', depType: 'required_provider', packageName: 'hashicorp/aws', }, { currentValue: '=2.27.0', datasource: 'terraform-provider', depName: 'azurerm', depType: 'required_provider', packageName: 'hashicorp/azurerm', }, { currentValue: '1.2.4', datasource: 'terraform-provider', depName: 'invalid', depType: 'required_provider', skipReason: 'unsupported-url', }, { currentValue: '1.2.4', datasource: 'terraform-provider', depName: 'helm', depType: 'required_provider', packageName: 'hashicorp/helm', }, { currentValue: '>= 1.0', datasource: 'terraform-provider', depName: 'kubernetes', depType: 'required_provider', packageName: 'hashicorp/kubernetes', registryUrls: ['https://terraform.example.com'], }, { currentValue: '>= 4.0', datasource: 'terraform-provider', depName: 'oci', depType: 'required_provider', packageName: 'oracle/oci', registryUrls: ['https://terraform-company_special.example.com'], }, ]); }); it('extracts docker resources', async () => { const res = await extractPackageFile(docker, 'docker.tf', { registryAliases: { 'hub.proxy.test': 'index.docker.io' }, }); expect(res?.deps).toHaveLength(8); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(3); expect(res?.deps).toMatchObject([ { autoReplaceStringTemplate: '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', datasource: 'docker', depType: 'docker_image', replaceString: '${data.docker_registry_image.ubuntu.name}', skipReason: 'contains-variable', }, { depType: 'docker_image', skipReason: 'invalid-dependency-specification', }, { autoReplaceStringTemplate: '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentValue: '1.7.8', datasource: 'docker', depName: 'nginx', depType: 'docker_image', replaceString: 'nginx:1.7.8', }, { autoReplaceStringTemplate: 'hub.proxy.test/bitnami/nginx:{{#if newValue}}{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentValue: '1.24.0', datasource: 'docker', depName: 'hub.proxy.test/bitnami/nginx', packageName: 'index.docker.io/bitnami/nginx', depType: 'docker_image', replaceString: 'hub.proxy.test/bitnami/nginx:1.24.0', }, { autoReplaceStringTemplate: '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentValue: '1.7.8', datasource: 'docker', depName: 'nginx', depType: 'docker_container', replaceString: 'nginx:1.7.8', }, { depType: 'docker_container', skipReason: 'invalid-dependency-specification', }, { autoReplaceStringTemplate: '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentDigest: undefined, currentValue: 'v1', datasource: 'docker', depName: 'repo.mycompany.com:8080/foo-service', depType: 'docker_service', replaceString: 'repo.mycompany.com:8080/foo-service:v1', }, { autoReplaceStringTemplate: '{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}', currentDigest: undefined, currentValue: 'precise', datasource: 'docker', depName: 'ubuntu', depType: 'docker_registry_image', packageName: 'ubuntu', replaceString: 'ubuntu:precise', versioning: 'ubuntu', }, ]); }); it('extracts kubernetes resources', async () => { const res = await extractPackageFile(kubernetes, 'kubernetes.tf', {}); expect(res?.deps).toHaveLength(18); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(1); expect(res?.deps).toIncludeAllPartialMembers([ { depName: 'gcr.io/kaniko-project/executor', currentValue: 'v1.7.0', currentDigest: 'sha256:8504bde9a9a8c9c4e9a4fe659703d265697a36ff13607b7669a4caa4407baa52', depType: 'kubernetes_cron_job_v1', }, { depName: 'node', currentValue: '14', depType: 'kubernetes_cron_job_v1', }, { depName: 'gcr.io/kaniko-project/executor', currentValue: 'v1.8.0', currentDigest: 'sha256:8504bde9a9a8c9c4e9a4fe659703d265697a36ff13607b7669a4caa4407baa52', depType: 'kubernetes_cron_job', }, { depName: 'nginx', currentValue: '1.21.1', depType: 'kubernetes_daemon_set_v1', }, { depName: 'nginx', currentValue: '1.21.2', depType: 'kubernetes_daemonset', }, { depName: 'nginx', currentValue: '1.21.3', depType: 'kubernetes_deployment', }, { depName: 'nginx', currentValue: '1.21.4', depType: 'kubernetes_deployment_v1', }, { depName: 'nginx', currentValue: '1.21.5', depType: 'kubernetes_job', }, { depType: 'kubernetes_job', skipReason: 'invalid-dependency-specification', }, { depName: 'nginx', currentValue: '1.21.6', depType: 'kubernetes_job_v1', }, { depName: 'nginx', currentValue: '1.21.7', depType: 'kubernetes_pod', }, { depName: 'nginx', currentValue: '1.21.8', depType: 'kubernetes_pod_v1', }, { depName: 'nginx', currentValue: '1.21.9', depType: 'kubernetes_replication_controller', }, { depName: 'nginx', currentValue: '1.21.10', depType: 'kubernetes_replication_controller_v1', }, { depName: 'nginx', currentValue: '1.21.11', depType: 'kubernetes_stateful_set', }, { depName: 'prom/prometheus', currentValue: 'v2.2.1', depType: 'kubernetes_stateful_set', }, { depName: 'nginx', currentValue: '1.21.12', depType: 'kubernetes_stateful_set_v1', }, { depName: 'prom/prometheus', currentValue: 'v2.2.2', depType: 'kubernetes_stateful_set_v1', }, ]); }); it('returns dep with skipReason local', async () => { const src = codeBlock` module "relative" { source = "../fe" } `; await expect(extractPackageFile(src, '2.tf', {})).resolves.toMatchObject({ deps: [{ skipReason: 'local' }], }); }); it('returns null with only not added resources', async () => { const src = codeBlock` resource "test_resource" "relative" { source = "../fe" } `; await expect(extractPackageFile(src, '2.tf', {})).resolves.toBeNull(); }); it('extract helm releases', async () => { const res = await extractPackageFile(helm, 'helm.tf', { registryAliases: { 'hub.proxy.test': 'index.docker.io' }, }); expect(res?.deps).toHaveLength(9); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(2); expect(res?.deps).toMatchObject([ { currentValue: '4.0.1', datasource: 'helm', depName: undefined, depType: 'helm_release', skipReason: 'invalid-name', }, { currentValue: '5.0.1', datasource: 'helm', depName: 'redis', depType: 'helm_release', registryUrls: ['https://charts.helm.sh/stable'], }, { currentValue: '6.0.1', datasource: 'helm', depName: 'redis', depType: 'helm_release', }, { currentValue: 'v0.22.1', datasource: 'docker', depName: 'public.ecr.aws/karpenter/karpenter', depType: 'helm_release', pinDigests: false, }, { currentValue: 'v0.22.1', datasource: 'docker', depName: 'karpenter', depType: 'helm_release', packageName: 'public.ecr.aws/karpenter/karpenter', pinDigests: false, }, { datasource: 'helm', depName: './charts/example', depType: 'helm_release', skipReason: 'local-chart', }, { currentValue: '8.9.1', datasource: 'docker', depName: 'kube-prometheus', depType: 'helm_release', packageName: 'index.docker.io/bitnamicharts/kube-prometheus', pinDigests: false, }, { currentValue: '1.0.1', datasource: 'helm', depName: 'redis', depType: 'helm_release', registryUrls: ['https://charts.helm.sh/stable'], }, { datasource: 'helm', depName: 'redis', depType: 'helm_release', registryUrls: ['https://charts.helm.sh/stable'], }, ]); }); it('extracts helm releases from OCI registries with a port', async () => { const src = codeBlock` resource "helm_release" "redis" { name = "redis" repository = "oci://registry.example.com:5000/charts" chart = "redis" version = "1.0.1" } `; const res = await extractPackageFile(src, 'helm.tf', {}); expect(res?.deps).toEqual([ { currentValue: '1.0.1', datasource: 'docker', depName: 'redis', depType: 'helm_release', packageName: 'registry.example.com:5000/charts/redis', pinDigests: false, }, ]); }); it('update lockfile constraints with range strategy update-lockfile', async () => { fs.readLocalFile.mockResolvedValueOnce(lockedVersionLockfile); fs.findLocalSiblingOrParent.mockResolvedValueOnce('aLockFile.hcl'); const res = await extractPackageFile( lockedVersion, 'lockedVersion.tf', {}, ); expect(res?.deps).toHaveLength(3); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(0); expect(res?.deps).toIncludeAllPartialMembers([ { currentValue: '~> 3.0', datasource: 'terraform-provider', depName: 'aws', depType: 'required_provider', lockedVersion: '3.1.0', packageName: 'hashicorp/aws', }, { currentValue: '~> 2.50.0', datasource: 'terraform-provider', depName: 'azurerm', depType: 'required_provider', lockedVersion: '2.50.0', packageName: 'hashicorp/azurerm', }, { currentValue: '>= 1.0', datasource: 'terraform-provider', depName: 'kubernetes', depType: 'required_provider', packageName: 'example/kubernetes', registryUrls: ['https://terraform.example.com'], }, ]); }); it('test terraform block with only requirement_terraform_version', async () => { const res = await extractPackageFile( terraformBlock, 'terraformBlock.tf', {}, ); expect(res?.deps).toHaveLength(1); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(0); expect(res?.deps).toIncludeAllPartialMembers([ { currentValue: '1.0.0', datasource: 'github-releases', depName: 'hashicorp/terraform', depType: 'required_version', extractVersion: 'v(?.*)$', versioning: hashicorp.id, }, ]); }); it('extracts terraform_version for tfe_workspace and ignores missing terraform_version keys', async () => { const res = await extractPackageFile( tfeWorkspaceBlock, 'tfeWorkspace.tf', {}, ); expect(res?.deps).toHaveLength(3); expect(res?.deps.filter((dep) => dep.skipReason)).toHaveLength(1); expect(res?.deps).toIncludeAllPartialMembers([ { currentValue: '1.1.6', datasource: 'github-releases', depName: 'hashicorp/terraform', depType: 'tfe_workspace', extractVersion: 'v(?.*)$', }, { skipReason: 'unspecified-version', }, { currentValue: '1.1.9', datasource: 'github-releases', depName: 'hashicorp/terraform', depType: 'tfe_workspace', extractVersion: 'v(?.*)$', }, ]); }); it('return null if invalid HCL file', async () => { const res = await extractPackageFile( ` resource my provider `, 'tfeWorkspace.tf', {}, ); expect(res).toBeNull(); }); }); });