From bae02b6ae73dda47dc86590b73c21a85bb7273a5 Mon Sep 17 00:00:00 2001 From: mo khan Date: Tue, 14 Apr 2020 12:50:13 -0600 Subject: Migrate specs from gitlab-org/security-products/license-management --- spec/integration/dotnet/examples_spec.rb | 12 ++ spec/integration/dotnet/nuget_spec.rb | 102 ++++++++++++++ spec/integration/go/modules_spec.rb | 53 ++++++++ spec/integration/java/gradle_spec.rb | 77 +++++++++++ spec/integration/java/maven_spec.rb | 81 +++++++++++ spec/integration/js/npm_spec.rb | 5 + spec/integration/js/yarn_spec.rb | 5 + spec/integration/php/composer_spec.rb | 121 +++++++++++++++++ spec/integration/python/pip_spec.rb | 160 ++++++++++++++++++++++ spec/integration/python/pipenv_spec.rb | 224 +++++++++++++++++++++++++++++++ spec/integration/ruby/bundler_spec.rb | 142 ++++++++++++++++++++ 11 files changed, 982 insertions(+) create mode 100644 spec/integration/dotnet/examples_spec.rb create mode 100644 spec/integration/dotnet/nuget_spec.rb create mode 100644 spec/integration/go/modules_spec.rb create mode 100644 spec/integration/java/gradle_spec.rb create mode 100644 spec/integration/java/maven_spec.rb create mode 100644 spec/integration/js/npm_spec.rb create mode 100644 spec/integration/js/yarn_spec.rb create mode 100644 spec/integration/php/composer_spec.rb create mode 100644 spec/integration/python/pip_spec.rb create mode 100644 spec/integration/python/pipenv_spec.rb create mode 100644 spec/integration/ruby/bundler_spec.rb (limited to 'spec/integration') diff --git a/spec/integration/dotnet/examples_spec.rb b/spec/integration/dotnet/examples_spec.rb new file mode 100644 index 0000000..13f4712 --- /dev/null +++ b/spec/integration/dotnet/examples_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +RSpec.describe ".NET Core" do + it 'scans https://github.com/microsoft/RockPaperScissorsLizardSpock.git' do + runner.clone('https://github.com/microsoft/RockPaperScissorsLizardSpock.git') + report = runner.scan(env: { 'LICENSE_FINDER_CLI_OPTS' => '--recursive' }) + + expect(report).to match_schema(version: '2.0') + expect(report[:licenses].count).not_to be_zero + expect(report[:dependencies].count).not_to be_zero + end +end diff --git a/spec/integration/dotnet/nuget_spec.rb b/spec/integration/dotnet/nuget_spec.rb new file mode 100644 index 0000000..6eeb261 --- /dev/null +++ b/spec/integration/dotnet/nuget_spec.rb @@ -0,0 +1,102 @@ +require 'spec_helper' + +RSpec.describe "nuget" do + include_examples "each report version", "csharp", "nuget-dotnetcore" + + context "when a project has a nuget packages.config file in the root" do + let(:packages_config) do + <<-XML + + + + + + + + + + + + + + XML + end + + it 'produces a valid report' do + runner.add_file('packages.config', packages_config) + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:licenses].count).not_to be_zero + expect(report.dependency_names).to match_array([ + "Microsoft.CodeDom.Providers.DotNetCompilerPlatform", + "Microsoft.Net.Compilers", + "Microsoft.Web.Infrastructure", + "Microsoft.Web.Xdt", + "Newtonsoft.Json", + "NuGet.Core", + "NuGet.Server", + "RouteMagic", + "WebActivatorEx", + "jive" + ]) + end + end + + context "when a project has multiple nuget packages.config in different sub directories" do + let(:console_packages) do + <<-XML + + + + + XML + end + + let(:winforms_packages) do + <<-XML + + + + + XML + end + + it 'produces a report including dependencies from each sub directory' do + runner.add_file('console/packages.config', console_packages) + runner.add_file('winforms/packages.config', winforms_packages) + report = runner.scan(env: { 'LICENSE_FINDER_CLI_OPTS' => '--recursive' }) + + expect(report).to match_schema(version: '2.0') + expect(report.licenses_for('jive')).to match_array(['MIT']) + expect(report.licenses_for('MvcMailer')).to match_array(['MIT']) + end + end + + context "when a project has a dependency that has it's own dependencies" do + let(:packages) do + <<-XML + + + + + XML + end + + pending 'produces a report that includes the dependencies of each dependency' do + runner.add_file('packages.config', packages) + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:licenses].count).not_to be_zero + expect(report[:licenses].map { |x| x[:id] }.uniq).to match_array(['LGPL-2.1', 'Apache-2.0', 'BSD-3-Clause']) + expect(report.dependency_names).to match_array([ + 'Iesi.Collections', + 'Remotion.Linq', + 'Remotion.Linq.EagerFetching', + "Antlr3.Runtime", + "NHibernate" + ]) + end + end +end diff --git a/spec/integration/go/modules_spec.rb b/spec/integration/go/modules_spec.rb new file mode 100644 index 0000000..143d8ea --- /dev/null +++ b/spec/integration/go/modules_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +RSpec.describe "modules" do + include_examples "each report version", "go", "modules" + + context "when scanning a customers go.mod and go.sum files" do + let(:report) { runner.scan } + + before do + runner.add_file('main.go', fixture_file_content('go/main.go')) + runner.add_file('go.mod', fixture_file_content('go/go.mod')) + runner.add_file('go.sum', fixture_file_content('go/go.sum')) + end + + specify { expect(report).to match_schema(version: '2.0') } + specify { expect(report[:licenses]).not_to be_empty } + + specify do + expect(report.dependency_names).to match_array([ + "github.com/davecgh/go-spew", + "github.com/dimfeld/httptreemux/v5", + "github.com/go-logfmt/logfmt", + "github.com/golang/protobuf", + "github.com/google/uuid", + "github.com/pmezard/go-difflib", + "github.com/stretchr/objx", + "golang.org/x/net", + "golang.org/x/oauth2", + "google.golang.org/appengine", + "gopkg.in/yaml.v2", + 'github.com/stretchr/testify' + ]) + end + + specify { expect(report.licenses_for('github.com/dimfeld/httptreemux/v5')).to match_array(['MIT']) } + specify { expect(report.licenses_for('github.com/go-logfmt/logfmt')).to match_array(['MIT']) } + specify { expect(report.licenses_for('github.com/google/uuid')).to match_array(['BSD-3-Clause']) } + specify { expect(report.licenses_for('github.com/stretchr/testify')).to match_array(['MIT']) } + specify { expect(report.licenses_for('golang.org/x/oauth2')).to match_array(['BSD-3-Clause']) } + end + + context "when scanning the `gitaly` project" do + let(:report) { runner.scan } + + before do + runner.clone('https://gitlab.com/gitlab-org/gitaly.git') + end + + specify { expect(report).to match_schema(version: '2.0') } + specify { expect(report[:licenses]).not_to be_empty } + specify { expect(report[:dependencies]).not_to be_empty } + end +end diff --git a/spec/integration/java/gradle_spec.rb b/spec/integration/java/gradle_spec.rb new file mode 100644 index 0000000..1b566bb --- /dev/null +++ b/spec/integration/java/gradle_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +RSpec.describe "gradle" do + include_examples "each report version", "java", "gradle" + + context "when running a default gradle build" do + it 'scans a gradle project' do + content = <<~GRADLE +/* + * This file was generated by the Gradle 'init' task. + * + * This is a general purpose Gradle build. + * Learn how to create Gradle builds at https://guides.gradle.org/creating-new-gradle-builds + */ +plugins { + id "com.github.hierynomus.license" version "0.15.0" +} + GRADLE + runner.add_file('build.gradle', content) + + report = runner.scan + expect(report).to match_schema(version: '2.0') + expect(report[:licenses]).to be_empty + expect(report[:dependencies]).to be_empty + end + end + + context 'when scanning a gradle project that does not include the `com.github.hierynomus.license` plugin' do + let(:project_url) { 'https://gitlab.com/one-touch-pipeline/otp.git' } + let(:result) { runner.scan } + + before do + runner.clone(project_url) + end + + it 'is able to detect licenses' do + expect(result).to match_schema(version: '2.0') + expect(result[:licenses]).not_to be_empty + + [ + { name: 'ant', licenses: ['Apache-2.0'] }, + { name: 'activation', licenses: ['CDDL-1.0'] }, + { name: 'xml-apis', licenses: ['Apache-2.0', 'SAX-PD', 'W3C-20150513'] }, + { name: 'sitemesh', licenses: ['Apache-1.1'] }, + { name: 'hibernate-jpa-2.1-api', licenses: ['BSD-3-Clause', 'EPL-1.0'] } + ].each do |dependency| + expect(result.licenses_for(dependency[:name])).to match_array(dependency[:licenses]) + end + end + end + + [ + '6.2', + '5.6', + '4.9', + '3.5', + '2.9', + '1.9' + ].each do |gradle_version| + %w[8 11].each do |java_version| + context "when scanning a gradle (v#{gradle_version}) project that uses a kotlin build script" do + let(:build_file_content) { fixture_file_content("java/build.gradle.kts") } + + it 'scans a gradle project' do + runner.add_file('build.gradle.kts', build_file_content) + runner.add_file('settings.gradle.kts', 'rootProject.name = "example"') + runner.add_file('.tool-versions', "gradle #{gradle_version}") + + report = runner.scan(env: { 'LM_JAVA_VERSION' => java_version }) + expect(report).to match_schema(version: '2.0') + expect(report[:licenses]).to be_empty + expect(report[:dependencies]).to be_empty + end + end + end + end +end diff --git a/spec/integration/java/maven_spec.rb b/spec/integration/java/maven_spec.rb new file mode 100644 index 0000000..92444e8 --- /dev/null +++ b/spec/integration/java/maven_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +RSpec.describe "maven" do + include_examples "each report version", "java", "maven" + include_examples "each report version", "java", "maven-multimodules" + + describe "When the maven dependencies come from a custom public maven repository" do + it 'is able to detect some of the licenses' do + runner.add_file('pom.xml', fixture_file_content('java/pom-public-gitlab-repository.xml')) + + report = runner.scan(env: { + 'CI_PROJECT_ID' => '17523603' + }) + + expect(report).to match_schema(version: '2.0') + expect(report[:dependencies]).to match_array([{ name: 'mvn-spike', url: '', description: '', paths: ['.'], licenses: ['MIT'] }]) + end + + it 'downloads packages from by using a custom `settings.xml`' do + runner.add_file('pom.xml', fixture_file_content('java/pom-public-gitlab-repository.xml')) + runner.add_file('my_settings.xml', fixture_file_content('java/custom-maven-settings.xml')) + + report = runner.scan(env: { + 'CI_PROJECT_ID' => 'invalid', + 'MAVEN_CLI_OPTS' => "--settings my_settings.xml" + }) + + expect(report).to match_schema(version: '2.0') + expect(report[:dependencies]).to match_array([{ name: 'mvn-spike', url: '', description: '', paths: ['.'], licenses: ['MIT'] }]) + end + end + + describe "When using the `SETUP_CMD`" do + it 'executes the custom script' do + runner.add_file('custom.sh') do + <<~SCRIPT + #!/bin/bash -l + + echo 'hello' + SCRIPT + end + + report = runner.scan(env: { + 'SETUP_CMD' => 'bash custom.sh' + }) + + expect(report).to match_schema(version: '2.0') + end + end + + describe "When scanning a project with multiple modules" do + before do + runner.mount(dir: fixture_file('java/maven-multimodule')) + end + + it 'detects dependences from each module' do + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:dependencies]).not_to be_empty + + [ + { name: "asm", licenses: ["BSD-4-Clause"] }, + { name: "asm-commons", licenses: ["BSD-4-Clause"] }, + { name: "jackson-annotations", licenses: ["Apache-2.0"] }, + { name: "jackson-core", licenses: ["Apache-2.0"] }, + { name: "jackson-databind", licenses: ["Apache-2.0"] }, + { name: "jackson-dataformat-xml", licenses: ["Apache-2.0"] }, + { name: "jackson-module-jaxb-annotations", licenses: ["Apache-2.0"] }, + { name: "log4j-api", licenses: ["Apache-2.0"] }, + { name: "log4j-core", licenses: ["Apache-2.0"] }, + { name: "netty-all", licenses: ["Apache-2.0"] }, + { name: "stax2-api", licenses: ["BSD-4-Clause"] } + ].each do |dependency| + expect(report.licenses_for(dependency[:name])).to match_array(dependency[:licenses]) + end + + expect(report.dependency_names).not_to include('junit') + end + end +end diff --git a/spec/integration/js/npm_spec.rb b/spec/integration/js/npm_spec.rb new file mode 100644 index 0000000..1c272bf --- /dev/null +++ b/spec/integration/js/npm_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe "npm" do + include_examples "each report version", "js", "npm" +end diff --git a/spec/integration/js/yarn_spec.rb b/spec/integration/js/yarn_spec.rb new file mode 100644 index 0000000..0756f9d --- /dev/null +++ b/spec/integration/js/yarn_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +RSpec.describe "yarn" do + include_examples "each report version", "js", "yarn" +end diff --git a/spec/integration/php/composer_spec.rb b/spec/integration/php/composer_spec.rb new file mode 100644 index 0000000..62ff598 --- /dev/null +++ b/spec/integration/php/composer_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +RSpec.describe "composer" do + include_examples "each report version", "php", "composer" + + context "when the project's dependencies require php-gd e.g. in the case of Drupal" do + it 'installs the required dependencies and produces a valid report' do + # composer.json from https://git.drupalcode.org/project/drupal/raw/8.7.x/core/composer.json + runner.add_file('composer.json', fixture_file_content('php/drupal_composer.json')) + + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:version]).not_to be_empty + expect(report[:licenses]).not_to be_empty + expect(report.dependency_names).to match_array(%w[ + asm89/stack-cors + behat/mink + behat/mink-browserkit-driver + behat/mink-goutte-driver + behat/mink-selenium2-driver + brumann/polyfill-unserialize + composer/semver + doctrine/annotations + doctrine/cache + doctrine/collections + doctrine/common + doctrine/event-manager + doctrine/inflector + doctrine/instantiator + doctrine/lexer + doctrine/persistence + doctrine/reflection + drupal/coder + easyrdf/easyrdf + egulias/email-validator + fabpot/goutte + guzzlehttp/guzzle + guzzlehttp/promises + guzzlehttp/psr7 + instaclick/php-webdriver + jcalderonzumba/gastonjs + jcalderonzumba/mink-phantomjs-driver + justinrainbow/json-schema + masterminds/html5 + mikey179/vfsstream + myclabs/deep-copy + paragonie/random_compat + pear/archive_tar + pear/console_getopt + pear/pear-core-minimal + pear/pear_exception + phar-io/manifest + phar-io/version + phpdocumentor/reflection-common + phpdocumentor/reflection-docblock + phpdocumentor/type-resolver + phpspec/prophecy + phpunit/php-code-coverage + phpunit/php-file-iterator + phpunit/php-text-template + phpunit/php-timer + phpunit/php-token-stream + phpunit/phpunit + phpunit/phpunit-mock-objects + psr/container + psr/http-message + psr/log + ralouphie/getallheaders + sebastian/code-unit-reverse-lookup + sebastian/comparator + sebastian/diff + sebastian/environment + sebastian/exporter + sebastian/global-state + sebastian/object-enumerator + sebastian/object-reflector + sebastian/recursion-context + sebastian/resource-operations + sebastian/version + squizlabs/php_codesniffer + stack/builder + symfony-cmf/routing + symfony/browser-kit + symfony/class-loader + symfony/console + symfony/css-selector + symfony/debug + symfony/dependency-injection + symfony/dom-crawler + symfony/event-dispatcher + symfony/http-foundation + symfony/http-kernel + symfony/phpunit-bridge + symfony/polyfill-ctype + symfony/polyfill-iconv + symfony/polyfill-intl-idn + symfony/polyfill-mbstring + symfony/polyfill-php56 + symfony/polyfill-php70 + symfony/polyfill-php72 + symfony/polyfill-util + symfony/process + symfony/psr-http-message-bridge + symfony/routing + symfony/serializer + symfony/translation + symfony/validator + symfony/yaml + theseer/tokenizer + twig/twig + typo3/phar-stream-wrapper + webmozart/assert + zendframework/zend-diactoros + zendframework/zend-escaper + zendframework/zend-feed + zendframework/zend-stdlib + ]) + end + end +end diff --git a/spec/integration/python/pip_spec.rb b/spec/integration/python/pip_spec.rb new file mode 100644 index 0000000..8e3ec3d --- /dev/null +++ b/spec/integration/python/pip_spec.rb @@ -0,0 +1,160 @@ +require 'spec_helper' + +RSpec.describe "pip" do + context "when a project depends on the latest version of pip" do + let(:requirements) { "sentry-sdk>=0.7.7" } + + it 'produces a valid report' do + runner.add_file('requirements.txt', requirements) + + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:version]).to start_with('2') + expect(report.dependency_names).to include("sentry-sdk") + expect(report.licenses_for('sentry-sdk')).to match_array(["BSD-4-Clause"]) + end + end + + context "when the project has a dependency that depends on a minimum of python 3.6" do + let(:requirements) do + [ + 'boto3', + 'aws-lambda-context>=1.0.0', + 'jsonschema>=3.0.0', + 'python-json-logger>=0.1.10', + 'sentry-sdk>=0.7.7', + 'ptvsd', + 'pylint', + 'flake8', + 'bandit', + 'pydocstyle' + ].join("\n") + end + + it 'produces a valid report' do + runner.add_file('requirements.txt', requirements) + + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:version]).to start_with('2') + expect(report[:licenses]).not_to be_empty + expect(report[:dependencies]).not_to be_empty + end + end + + [{ version: '2', commit: '04dce91b' }, { version: '3', commit: '48e250a1' }].each do |python| + ['1.0', '1.1', '2.0'].each do |report_version| + context "when generating a `#{report_version}` report using Python `#{python[:version]}`" do + let(:url) { "https://gitlab.com/gitlab-org/security-products/tests/#{language}-#{package_manager}.git" } + let(:language) { 'python' } + let(:package_manager) { 'pip' } + let(:environment) { { 'LM_REPORT_VERSION' => report_version, 'LM_PYTHON_VERSION' => python[:version] } } + let(:expected_content) { fixture_file_content("expected/#{language}/#{python[:version]}/#{package_manager}/v#{report_version}.json").chomp } + + it 'matches the expected report' do + runner.clone(url, branch: python[:commit]) + report = runner.scan(env: environment) + + expect(JSON.pretty_generate(report.to_h)).to eq(expected_content) + expect(report).to match_schema(version: report_version) + end + end + end + end + + context "when scanning projects with a `setup.py` and does not have a `requirements.txt` file" do + it 'detects licenses in a simple `setup.py`' do + runner.add_file('setup.py', fixture_file_content('python/simple-setup.py')) + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:dependencies]).not_to be_empty + expect(report.licenses_for('boto3')).to match_array(['Apache-2.0']) + end + + it 'detects licenses in a more complicated `setup.py`' do + runner.clone('https://github.com/pypa/sampleproject.git', branch: 'd09af3dbd851d385e56f0aed29875bfa3d3df230') + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:dependencies]).not_to be_empty + expect(report.licenses_for('peppercorn')).to match_array(['BSD-2-Clause']) + end + end + + context "when scanning projects that have a custom index-url" do + before do + runner.add_file('requirements.txt', 'six') + end + + it 'detects the licenses from the custom index' do + report = runner.scan(env: { 'PIP_INDEX_URL' => 'https://test.pypi.org/simple/' }) + + expect(report).to match_schema(version: '2.0') + expect(report.licenses_for('six')).to match_array(["MIT"]) + end + end + + context "when a project uses a custom `SETUP_CMD`" do + before do + runner.add_file('requirements.txt', 'six==1.14.0') + end + + it 'detects the software licenses' do + report = runner.scan(env: { 'SETUP_CMD' => 'pip install -r requirements.txt' }) + + expect(report).to match_schema(version: '2.0') + expect(report.licenses_for('six')).to match_array(["MIT"]) + expect(report.dependency_names).to contain_exactly('six') + end + end + + context "when a projects is running in airgap mode" do + before do + runner.add_file('requirements.txt', '') + end + + it 'is able to scan the project' do + report = runner.scan(env: { + 'PIP_INDEX_URL' => 'https://localhost/simple/' + }) + + expect(report).to match_schema(version: '2.0') + expect(report[:licenses]).to be_empty + expect(report[:dependencies]).to be_empty + end + end + + context "when connecting to a private package repository with self signed certificate" do + let(:index_url) { "https://#{private_pypi_host}/simple" } + let(:bundle) { fixture_file_content('python/pypi.crt') } + + before do + runner.add_file('setup.py') do + <<~RAW +from setuptools import setup, find_packages + +setup( + name='gitlab-sp-test-python-pip', + version='1.2.0', + packages=find_packages(), + install_requires=['requests'], +) + RAW + end + end + + it 'downloads the packages and trusts the certificate' do + report = runner.scan(env: { + 'ADDITIONAL_CA_CERT_BUNDLE' => bundle, + 'PIP_INDEX_URL' => index_url + }) + + expect(report).to match_schema(version: '2.0') + expect(report.dependency_names).to include('requests') + expect(report.licenses_for('requests')).to match_array(['Apache-2.0']) + end + end +end diff --git a/spec/integration/python/pipenv_spec.rb b/spec/integration/python/pipenv_spec.rb new file mode 100644 index 0000000..b54ee99 --- /dev/null +++ b/spec/integration/python/pipenv_spec.rb @@ -0,0 +1,224 @@ +require 'spec_helper' + +RSpec.describe "pipenv" do + include_examples "each report version", "python", "pipenv", "pip-file-lock" + + context "when a project depends on a version 6 Pipfile.lock" do + let(:pipfile_lock_content) do + JSON.pretty_generate({ + "_meta": { + "hash": { "sha256": "" }, + "pipfile-spec": 6, + "requires": { "python_version": "3.8" }, + "sources": [{ "name": "pypi", "url": "https://pypi.org/simple", "verify_ssl": true }] + }, + "default": { + "six": { "hashes": [], "index": "pypi", "version": "==1.13.0" } + }, + "develop": {} + }) + end + + it 'produces a valid report' do + runner.add_file('Pipfile.lock', pipfile_lock_content) + + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:version]).not_to be_empty + expect(report[:licenses]).not_to be_empty + expect(report.dependency_names).to contain_exactly("six") + end + end + + context "when a project depends on a version 3.2.1 Pipfile.lock" do + let(:pipfile_lock_content) do + JSON.pretty_generate({ + "default": { + "crayons": { "version": "==0.1.2", "hash": "" }, + "requirements-parser": { "version": "==0.1.0", "hash": "" }, + "pexpect": { "version": "==4.2.1", "hash": "" }, + "delegator.py": { "version": "==0.0.8", "hash": "" }, + "backports.shutil_get_terminal_size": { "version": "==1.0.0", "hash": "" }, + "ptyprocess": { "version": "==0.5.1", "hash": "" }, + "parse": { "version": "==1.6.6", "hash": "" }, + "toml": { "version": "==0.9.2", "hash": "" }, + "colorama": { "version": "==0.3.7", "hash": "" }, + "requests": { "version": "==2.13.0", "hash": "" }, + "click": { "version": "==6.7", "hash": "" } + }, + "develop": { + "packaging": { "version": "==16.8", "hash": "" }, + "pytest": { "version": "==3.0.6", "hash": "" }, + "setuptools": { "version": "==34.0.2", "hash": "" }, + "pyparsing": { "version": "==2.1.10", "hash": "" }, + "py": { "version": "==1.4.32", "hash": "" }, + "six": { "version": "==1.10.0", "hash": "" }, + "appdirs": { "version": "==1.4.0", "hash": "" } + }, + "_meta": { + "sources": [{ "url": "https://pypi.python.org/simple", "verify_ssl": true }], + "requires": {}, + "Pipfile-sha256": "24f12b631b7c40b8c5eff934a1aef263ed04f5eaffb4acf4706442f3d23cba36" + } + }) + end + + it 'produces a valid report' do + runner.add_file('Pipfile.lock', pipfile_lock_content) + + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:version]).not_to be_empty + expect(report[:licenses]).not_to be_empty + expect(report.dependency_names).to match_array([ + "backports.shutil_get_terminal_size", + "click", + "colorama", + "crayons", + "delegator.py", + "parse", + "pexpect", + "ptyprocess", + "requests", + "requirements-parser", + "toml" + ]) + end + end + + context "when a project depends on a version 5 Pipfile.lock" do + let(:pipfile_lock_content) do + JSON.pretty_generate({ + "_meta": { + "hash": { "sha256": "" }, + "host-environment-markers": { + "implementation_name": "cpython", + "implementation_version": "3.6.1", + "os_name": "posix", + "platform_machine": "x86_64", + "platform_python_implementation": "CPython", + "platform_release": "16.7.0", + "platform_system": "Darwin", + "platform_version": "Darwin Kernel Version 16.7.0: Thu Jun 15 17:36:27 PDT 2017; root:xnu-3789.70.16~2/RELEASE_X86_64", + "python_full_version": "3.6.1", + "python_version": "3.6", + "sys_platform": "darwin" + }, + "pipfile-spec": 5, + "requires": {}, + "sources": [{ "name": "pypi", "url": "https://pypi.python.org/simple", "verify_ssl": true }] + }, + "default": { + "certifi": { "hashes": ["", ""], "version": "==2017.7.27.1" }, + "chardet": { "hashes": ["", ""], "version": "==3.0.4" }, + "idna": { "hashes": ["", ""], "version": "==2.6" }, + "requests": { "hashes": ["", ""], "version": "==2.18.4" }, + "urllib3": { "hashes": ["", ""], "version": "==1.22" } + }, + "develop": { + "py": { "hashes": ["", ""], "version": "==1.4.34" }, + "pytest": { "hashes": ["", ""], "version": "==3.2.2" } + } + }) + end + + it 'produces a valid report' do + runner.add_file('Pipfile.lock', pipfile_lock_content) + + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:version]).not_to be_empty + expect(report[:licenses]).not_to be_empty + expect(report.dependency_names).to match_array(%w[ + certifi + chardet + idna + requests + urllib3 + ]) + end + end + + context "when fetching metadata from a custom source" do + let(:pipfile_lock_content) do + JSON.pretty_generate({ + "_meta": { + "hash": { "sha256": "" }, + "pipfile-spec": 6, + "requires": { "python_version": "3.8" }, + "sources": [{ "name": "pypi", "url": "https://test.pypi.org/simple", "verify_ssl": true }] + }, + "default": { + "six": { "hashes": [], "index": "pypi", "version": "==1.13.0" } + }, + "develop": {} + }) + end + + before do + runner.add_file('Pipfile.lock', pipfile_lock_content) + end + + it 'produces a valid report' do + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:licenses]).not_to be_empty + expect(report[:dependencies].count).to be(1) + expect(report.find('six')).not_to be_nil + end + end + + context "when scanning a simple Pipfile project" do + let(:lockfile_content) { fixture_file_content('python/simple-Pipfile.lock') } + let(:lockfile_hash) { JSON.parse(lockfile_content) } + + before do + runner.add_file('Pipfile', fixture_file_content('python/simple-Pipfile')) + runner.add_file('Pipfile.lock', lockfile_content) + end + + [2, 3].each do |version| + context "when scanning a Python #{version} project" do + let(:report) { runner.scan(env: { 'LM_PYTHON_VERSION' => version.to_s }) } + + specify { expect(report).to match_schema(version: '2.0') } + + it 'includes dependencies in the default group' do + lockfile_hash['default'].keys.each do |key| + expect(report.find(key)).not_to be_nil + end + end + + it 'excludes dependencies in the development group' do + lockfile_hash['develop'].keys.each do |key| + expect(report.find(key)).to be_nil + end + end + end + end + end + + context "when connecting to a private package repository with self signed certificate" do + let(:index_url) { "https://#{private_pypi_host}/simple" } + let(:bundle) { fixture_file_content('python/pypi.crt') } + + before do + runner.add_file('Pipfile', fixture_file_content('python/airgap-Pipfile.erb', index_url: index_url)) + runner.add_file('Pipfile.lock', fixture_file_content('python/airgap-Pipfile.lock.erb', index_url: index_url)) + end + + it 'downloads the packages and trusts the certificate' do + report = runner.scan(env: { + 'ADDITIONAL_CA_CERT_BUNDLE' => bundle, + 'PIP_INDEX_URL' => index_url + }) + + expect(report).to match_schema(version: '2.0') + expect(report.dependency_names).to include('requests') + end + end +end diff --git a/spec/integration/ruby/bundler_spec.rb b/spec/integration/ruby/bundler_spec.rb new file mode 100644 index 0000000..5236adf --- /dev/null +++ b/spec/integration/ruby/bundler_spec.rb @@ -0,0 +1,142 @@ +require 'spec_helper' + +RSpec.describe "bundler" do + include_examples "each report version", "ruby", "bundler" + + context "when the project depends on an older version of ruby specified in a `.ruby-version` file" do + it 'installs the required ruby and produces a valid report' do + runner.add_file('.ruby-version', 'ruby-2.4.9') + runner.add_file('Gemfile') do + <<~RAW +source 'https://rubygems.org' + +gem 'saml-kit' + RAW + end + + report = runner.scan + expect(report).to match_schema(version: '2.0') + expect(report[:licenses]).not_to be_empty + expect(report[:dependencies].map { |x| x[:name] }).to include("saml-kit") + end + end + + context "when a project depends on an older version of bundler" do + it 'produces a valid report' do + runner.add_file('Gemfile') do + <<~RAW +source 'https://rubygems.org' + +gem 'saml-kit' + RAW + end + runner.add_file('Gemfile.lock') do + <<~RAW +GEM + remote: https://rubygems.org/ + specs: + activemodel (6.0.2.1) + activesupport (= 6.0.2.1) + activesupport (6.0.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2) + builder (3.2.4) + concurrent-ruby (1.1.5) + i18n (1.7.1) + concurrent-ruby (~> 1.0) + mini_portile2 (2.4.0) + minitest (5.13.0) + net-hippie (0.2.7) + nokogiri (1.10.7) + mini_portile2 (~> 2.4.0) + saml-kit (1.1.0) + activemodel (>= 4.2.0) + net-hippie (~> 0.1) + xml-kit (>= 0.3.0, < 1.0.0) + thread_safe (0.3.6) + tilt (2.0.10) + tzinfo (1.2.6) + thread_safe (~> 0.1) + xml-kit (0.4.0) + activemodel (>= 4.2.0) + builder (~> 3.2) + nokogiri (~> 1.10) + tilt (>= 1.4.1) + xmldsig (~> 0.6) + xmldsig (0.6.6) + nokogiri (>= 1.6.8, < 2.0.0) + zeitwerk (2.2.2) + +PLATFORMS + ruby + +DEPENDENCIES + saml-kit + +BUNDLED WITH + 1.17.3 + RAW + end + + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:licenses]).not_to be_empty + expect(report.dependency_names).to include("saml-kit") + end + end + + context "when a project depends on bundler `~> 2.0`" do + it 'produces a valid report' do + runner.add_file('Gemfile') do + <<~RAW +source 'https://rubygems.org' + +gem 'net-hippie' + RAW + end + runner.add_file('Gemfile.lock') do + <<~RAW +GEM + remote: https://rubygems.org/ + specs: + net-hippie (0.3.2) + +PLATFORMS + ruby + +DEPENDENCIES + net-hippie + +BUNDLED WITH + 2.1.4 + RAW + end + + report = runner.scan + + expect(report).to match_schema(version: '2.0') + expect(report[:licenses]).not_to be_empty + expect(report.find('net-hippie')).to eql({ + name: 'net-hippie', + description: "net/http for hippies. ☮️", + url: "https://github.com/mokhan/net-hippie/", + paths: ['.'], + licenses: ['MIT'] + }) + end + end + + context "when passing custom options to license finder" do + it 'forwards the options to license finder' do + report = runner.scan(env: { + 'LICENSE_FINDER_CLI_OPTS' => "--debug --aggregate-paths=. ruby" + }) + + expect(report).to match_schema(version: '2.0') + end + end +end -- cgit v1.2.3