import itertools from unittest import TestCase from unittest.mock import patch, call from parameterized import parameterized from aws_lambda_builders.actions import ActionFailedError from aws_lambda_builders.workflows.nodejs_npm.actions import ( NodejsNpmPackAction, NodejsNpmInstallAction, NodejsNpmrcAndLockfileCopyAction, NodejsNpmrcCleanUpAction, NodejsNpmLockFileCleanUpAction, NodejsNpmCIAction, ) from aws_lambda_builders.workflows.nodejs_npm.npm import NpmExecutionError class TestNodejsNpmPackAction(TestCase): @patch("aws_lambda_builders.workflows.nodejs_npm.actions.extract_tarfile") @patch("aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm") @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_tars_and_unpacks_npm_project(self, OSUtilMock, SubprocessNpmMock, extract_tarfile_mock): osutils = OSUtilMock.return_value subprocess_npm = SubprocessNpmMock.return_value action = NodejsNpmPackAction( "artifacts", "scratch_dir", "manifest", osutils=osutils, subprocess_npm=subprocess_npm ) osutils.dirname.side_effect = lambda value: "/dir:{}".format(value) osutils.abspath.side_effect = lambda value: "/abs:{}".format(value) osutils.joinpath.side_effect = lambda a, b: "{}/{}".format(a, b) subprocess_npm.run.return_value = "package.tar" action.execute() subprocess_npm.run.assert_called_with(["pack", "-q", "file:/abs:/dir:manifest"], cwd="scratch_dir") extract_tarfile_mock.assert_called_with("scratch_dir/package.tar", "artifacts") @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") @patch("aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm") def test_raises_action_failed_when_npm_fails(self, OSUtilMock, SubprocessNpmMock): osutils = OSUtilMock.return_value subprocess_npm = SubprocessNpmMock.return_value builder_instance = SubprocessNpmMock.return_value builder_instance.run.side_effect = NpmExecutionError(message="boom!") action = NodejsNpmPackAction( "artifacts", "scratch_dir", "manifest", osutils=osutils, subprocess_npm=subprocess_npm ) with self.assertRaises(ActionFailedError) as raised: action.execute() self.assertEqual(raised.exception.args[0], "NPM Failed: boom!") class TestNodejsNpmInstallAction(TestCase): @patch("aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm") def test_installs_npm_production_dependencies_for_npm_project(self, SubprocessNpmMock): subprocess_npm = SubprocessNpmMock.return_value action = NodejsNpmInstallAction("artifacts", subprocess_npm=subprocess_npm) action.execute() expected_args = ["install", "-q", "--no-audit", "--no-save", "--unsafe-perm", "--production"] subprocess_npm.run.assert_called_with(expected_args, cwd="artifacts") @patch("aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm") def test_raises_action_failed_when_npm_fails(self, SubprocessNpmMock): subprocess_npm = SubprocessNpmMock.return_value builder_instance = SubprocessNpmMock.return_value builder_instance.run.side_effect = NpmExecutionError(message="boom!") action = NodejsNpmInstallAction("artifacts", subprocess_npm=subprocess_npm) with self.assertRaises(ActionFailedError) as raised: action.execute() self.assertEqual(raised.exception.args[0], "NPM Failed: boom!") class TestNodejsNpmCIAction(TestCase): @patch("aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm") def test_tars_and_unpacks_npm_project(self, SubprocessNpmMock): subprocess_npm = SubprocessNpmMock.return_value action = NodejsNpmCIAction("sources", subprocess_npm=subprocess_npm) action.execute() subprocess_npm.run.assert_called_with(["ci"], cwd="sources") @patch("aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm") def test_raises_action_failed_when_npm_fails(self, SubprocessNpmMock): subprocess_npm = SubprocessNpmMock.return_value builder_instance = SubprocessNpmMock.return_value builder_instance.run.side_effect = NpmExecutionError(message="boom!") action = NodejsNpmCIAction("sources", subprocess_npm=subprocess_npm) with self.assertRaises(ActionFailedError) as raised: action.execute() self.assertEqual(raised.exception.args[0], "NPM Failed: boom!") class TestNodejsNpmrcAndLockfileCopyAction(TestCase): @parameterized.expand(itertools.product([True, False], [True, False], [True, False])) @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_copies_into_a_project_if_file_exists( self, npmrc_exists, package_lock_exists, shrinkwrap_exists, OSUtilMock ): osutils = OSUtilMock.return_value osutils.joinpath.side_effect = lambda a, b: "{}/{}".format(a, b) action = NodejsNpmrcAndLockfileCopyAction("artifacts", "source", osutils=osutils) osutils.file_exists.side_effect = [npmrc_exists, package_lock_exists, shrinkwrap_exists] action.execute() filename_exists = { ".npmrc": npmrc_exists, "package-lock.json": package_lock_exists, "npm-shrinkwrap.json": shrinkwrap_exists, } file_exists_calls = [call("source/{}".format(filename)) for filename in filename_exists] copy_file_calls = [ call("source/{}".format(filename), "artifacts") for filename, exists in filename_exists.items() if exists ] osutils.file_exists.assert_has_calls(file_exists_calls) osutils.copy_file.assert_has_calls(copy_file_calls) @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_raises_action_failed_when_copying_fails(self, OSUtilMock): osutils = OSUtilMock.return_value osutils.joinpath.side_effect = lambda a, b: "{}/{}".format(a, b) osutils.copy_file.side_effect = OSError() action = NodejsNpmrcAndLockfileCopyAction("artifacts", "source", osutils=osutils) with self.assertRaises(ActionFailedError): action.execute() class TestNodejsNpmrcCleanUpAction(TestCase): @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_removes_npmrc_if_npmrc_exists(self, OSUtilMock): osutils = OSUtilMock.return_value osutils.joinpath.side_effect = lambda a, b: "{}/{}".format(a, b) action = NodejsNpmrcCleanUpAction("artifacts", osutils=osutils) osutils.file_exists.side_effect = [True] action.execute() osutils.remove_file.assert_called_with("artifacts/.npmrc") @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_skips_npmrc_removal_if_npmrc_doesnt_exist(self, OSUtilMock): osutils = OSUtilMock.return_value osutils.joinpath.side_effect = lambda a, b: "{}/{}".format(a, b) action = NodejsNpmrcCleanUpAction("artifacts", osutils=osutils) osutils.file_exists.side_effect = [False] action.execute() osutils.remove_file.assert_not_called() @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_raises_action_failed_when_removing_fails(self, OSUtilMock): osutils = OSUtilMock.return_value osutils.joinpath.side_effect = lambda a, b: "{}/{}".format(a, b) osutils.remove_file.side_effect = OSError() action = NodejsNpmrcCleanUpAction("artifacts", osutils=osutils) with self.assertRaises(ActionFailedError): action.execute() class TestNodejsNpmLockFileCleanUpAction(TestCase): @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_removes_dot_package_lock_if_exists(self, OSUtilMock): osutils = OSUtilMock.return_value osutils.joinpath.side_effect = lambda a, b, c: "{}/{}/{}".format(a, b, c) action = NodejsNpmLockFileCleanUpAction("artifacts", osutils=osutils) osutils.file_exists.side_effect = [True] action.execute() osutils.remove_file.assert_called_with("artifacts/node_modules/.package-lock.json") @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_skips_lockfile_removal_if_it_doesnt_exist(self, OSUtilMock): osutils = OSUtilMock.return_value osutils.joinpath.side_effect = lambda a, b, c: "{}/{}/{}".format(a, b, c) action = NodejsNpmLockFileCleanUpAction("artifacts", osutils=osutils) osutils.file_exists.side_effect = [False] action.execute() osutils.remove_file.assert_not_called() @patch("aws_lambda_builders.workflows.nodejs_npm.utils.OSUtils") def test_raises_action_failed_when_removing_fails(self, OSUtilMock): osutils = OSUtilMock.return_value osutils.joinpath.side_effect = lambda a, b, c: "{}/{}/{}".format(a, b, c) osutils.remove_file.side_effect = OSError() action = NodejsNpmLockFileCleanUpAction("artifacts", osutils=osutils) with self.assertRaises(ActionFailedError): action.execute()