Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add npm test command during npm build #681

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
24 changes: 24 additions & 0 deletions aws_lambda_builders/workflows/nodejs_npm/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,27 @@ def execute(self):

except OSError as ex:
raise ActionFailedError(str(ex))


class NodejsNpmTestAction(NodejsNpmInstallOrUpdateBaseAction):
"""
A Lambda Builder Action that runs tests in NPM project
"""

NAME = "NpmTest"
DESCRIPTION = "Running tests from NPM"

def execute(self):
"""
Runs the action.

:raises lambda_builders.actions.ActionFailedError: when NPM execution fails
"""
try:
LOG.debug("NODEJS running tests in: %s", self.install_dir)

command = ["test", "--if-present"]
self.subprocess_npm.run(command, cwd=self.install_dir)

except NpmExecutionError as ex:
raise ActionFailedError(str(ex))
8 changes: 8 additions & 0 deletions aws_lambda_builders/workflows/nodejs_npm/workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
NodejsNpmPackAction,
NodejsNpmrcAndLockfileCopyAction,
NodejsNpmrcCleanUpAction,
NodejsNpmTestAction,
NodejsNpmUpdateAction,
)
from aws_lambda_builders.workflows.nodejs_npm.npm import SubprocessNpm
Expand Down Expand Up @@ -123,6 +124,13 @@ def __init__(self, source_dir, artifacts_dir, scratch_dir, manifest_path, runtim
)
)

self.actions.append(
NodejsNpmTestAction(
install_dir=self.manifest_dir if is_building_in_source and is_external_manifest else self.build_dir,
subprocess_npm=subprocess_npm,
)
)

if is_building_in_source and is_external_manifest:
# Since we run `npm install` in the manifest directory, so we need to link the node_modules directory in
# the source directory.
Expand Down
32 changes: 32 additions & 0 deletions tests/integration/workflows/nodejs_npm/test_nodejs_npm.py
Original file line number Diff line number Diff line change
Expand Up @@ -779,3 +779,35 @@ def test_builds_project_with_manifest_outside_root_and_local_dependencies_with_d
# expected dependencies in source directory
source_modules = set(os.listdir(os.path.join(source_dir, "node_modules")))
self.assertTrue(all(expected_module in source_modules for expected_module in expected_modules))

@parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",)])
def test_runs_test_script_if_specified(self, runtime):
source_dir = os.path.join(self.TEST_DATA_FOLDER, "test-script-to-create-file")

self.builder.build(
source_dir,
self.artifacts_dir,
self.scratch_dir,
os.path.join(source_dir, "package.json"),
runtime=runtime,
)

expected_files = {"package.json", "created.js"}
output_files = set(os.listdir(self.artifacts_dir))
self.assertEqual(expected_files, output_files)

@parameterized.expand([("nodejs16.x",), ("nodejs18.x",), ("nodejs20.x",)])
def test_does_not_raise_error_if_empty_test_script(self, runtime):
source_dir = os.path.join(self.TEST_DATA_FOLDER, "empty-test-script")

self.builder.build(
source_dir,
self.artifacts_dir,
self.scratch_dir,
os.path.join(source_dir, "package.json"),
runtime=runtime,
)

expected_files = {"package.json"}
output_files = set(os.listdir(self.artifacts_dir))
self.assertEqual(expected_files, output_files)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "testscript",
"version": "1.0.0",
"description": "",
"scripts": {
"test": ""
},
"keywords": [],
"author": "",
"license": "APACHE2.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "testscript",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "touch created.js"
},
"keywords": [],
"author": "",
"license": "APACHE2.0"
}
29 changes: 29 additions & 0 deletions tests/unit/workflows/nodejs_npm/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
NodejsNpmrcCleanUpAction,
NodejsNpmLockFileCleanUpAction,
NodejsNpmCIAction,
NodejsNpmTestAction,
)
from aws_lambda_builders.workflows.nodejs_npm.npm import NpmExecutionError

Expand Down Expand Up @@ -219,3 +220,31 @@ def test_raises_action_failed_when_removing_fails(self, OSUtilMock):

with self.assertRaises(ActionFailedError):
action.execute()


class TestNodejsNpmTestAction(TestCase):
@patch("aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm")
def test_runs_npm_test_for_npm_project(self, SubprocessNpmMock):
subprocess_npm = SubprocessNpmMock.return_value

action = NodejsNpmTestAction(install_dir="tests", subprocess_npm=subprocess_npm)

action.execute()

expected_args = ["test", "--if-present"]

subprocess_npm.run.assert_called_with(expected_args, cwd="tests")

@patch("aws_lambda_builders.workflows.nodejs_npm.npm.SubprocessNpm")
def test_raises_action_failed_when_npm_test_fails(self, SubprocessNpmMock):
subprocess_npm = SubprocessNpmMock.return_value

builder_instance = SubprocessNpmMock.return_value
builder_instance.run.side_effect = NpmExecutionError(message="boom!")

action = NodejsNpmTestAction("artifacts", subprocess_npm=subprocess_npm)

with self.assertRaises(ActionFailedError) as raised:
action.execute()

self.assertEqual(raised.exception.args[0], "NPM Failed: boom!")
103 changes: 59 additions & 44 deletions tests/unit/workflows/nodejs_npm/test_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
NodejsNpmLockFileCleanUpAction,
NodejsNpmCIAction,
NodejsNpmUpdateAction,
NodejsNpmTestAction,
)


Expand Down Expand Up @@ -57,13 +58,14 @@ def test_workflow_sets_up_npm_actions_with_download_dependencies_without_depende

workflow = NodejsNpmWorkflow("source", "artifacts", "scratch_dir", "source/manifest", osutils=self.osutils)

self.assertEqual(len(workflow.actions), 6)
self.assertEqual(len(workflow.actions), 7)
self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
self.assertIsInstance(workflow.actions[3], NodejsNpmInstallAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmTestAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[6], NodejsNpmLockFileCleanUpAction)

def test_workflow_sets_up_npm_actions_with_download_dependencies_without_dependencies_dir_external_manifest(self):
self.osutils.dirname.return_value = "not_source"
Expand All @@ -73,7 +75,7 @@ def test_workflow_sets_up_npm_actions_with_download_dependencies_without_depende

workflow = NodejsNpmWorkflow("source", "artifacts", "scratch_dir", "not_source/manifest", osutils=self.osutils)

self.assertEqual(len(workflow.actions), 7)
self.assertEqual(len(workflow.actions), 8)
self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
Expand All @@ -82,8 +84,10 @@ def test_workflow_sets_up_npm_actions_with_download_dependencies_without_depende
self.assertEqual(workflow.actions[3].dest_dir, "artifacts")
self.assertIsInstance(workflow.actions[4], NodejsNpmInstallAction)
self.assertEqual(workflow.actions[4].install_dir, "artifacts")
self.assertIsInstance(workflow.actions[5], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[6], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmTestAction)
self.assertEqual(workflow.actions[5].install_dir, "artifacts")
self.assertIsInstance(workflow.actions[6], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[7], NodejsNpmLockFileCleanUpAction)

@patch("aws_lambda_builders.workflows.nodejs_npm.workflow.NodejsNpmWorkflow.can_use_install_links")
def test_workflow_sets_up_npm_actions_with_download_dependencies_without_dependencies_dir_external_manifest_and_build_in_source(
Expand All @@ -100,7 +104,7 @@ def test_workflow_sets_up_npm_actions_with_download_dependencies_without_depende
"source", "artifacts", "scratch_dir", "not_source/manifest", osutils=self.osutils, build_in_source=True
)

self.assertEqual(len(workflow.actions), 8)
self.assertEqual(len(workflow.actions), 9)
self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
Expand All @@ -109,13 +113,15 @@ def test_workflow_sets_up_npm_actions_with_download_dependencies_without_depende
self.assertEqual(workflow.actions[3].dest_dir, "artifacts")
self.assertIsInstance(workflow.actions[4], NodejsNpmUpdateAction)
self.assertEqual(workflow.actions[4].install_dir, "not_source")
self.assertIsInstance(workflow.actions[5], LinkSinglePathAction)
self.assertEqual(workflow.actions[5]._source, os.path.join("not_source", "node_modules"))
self.assertEqual(workflow.actions[5]._dest, os.path.join("source", "node_modules"))
self.assertIsInstance(workflow.actions[5], NodejsNpmTestAction)
self.assertEqual(workflow.actions[5].install_dir, "not_source")
self.assertIsInstance(workflow.actions[6], LinkSinglePathAction)
self.assertEqual(workflow.actions[6]._source, os.path.join("source", "node_modules"))
self.assertEqual(workflow.actions[6]._dest, os.path.join("artifacts", "node_modules"))
self.assertIsInstance(workflow.actions[7], NodejsNpmrcCleanUpAction)
self.assertEqual(workflow.actions[6]._source, os.path.join("not_source", "node_modules"))
self.assertEqual(workflow.actions[6]._dest, os.path.join("source", "node_modules"))
self.assertIsInstance(workflow.actions[7], LinkSinglePathAction)
self.assertEqual(workflow.actions[7]._source, os.path.join("source", "node_modules"))
self.assertEqual(workflow.actions[7]._dest, os.path.join("artifacts", "node_modules"))
self.assertIsInstance(workflow.actions[8], NodejsNpmrcCleanUpAction)

def test_workflow_sets_up_npm_actions_without_download_dependencies_with_dependencies_dir(self):
self.osutils.file_exists.return_value = True
Expand Down Expand Up @@ -145,14 +151,15 @@ def test_workflow_sets_up_npm_actions_without_bundler_if_manifest_doesnt_request

workflow = NodejsNpmWorkflow("source", "artifacts", "scratch_dir", "source/manifest", osutils=self.osutils)

self.assertEqual(len(workflow.actions), 6)
self.assertEqual(len(workflow.actions), 7)

self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
self.assertIsInstance(workflow.actions[3], NodejsNpmInstallAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmTestAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[6], NodejsNpmLockFileCleanUpAction)

def test_workflow_sets_up_npm_actions_with_download_dependencies_and_dependencies_dir(self):
self.osutils.file_exists.side_effect = [True, False, False]
Expand All @@ -167,17 +174,18 @@ def test_workflow_sets_up_npm_actions_with_download_dependencies_and_dependencie
osutils=self.osutils,
)

self.assertEqual(len(workflow.actions), 9)
self.assertEqual(len(workflow.actions), 10)

self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
self.assertIsInstance(workflow.actions[3], NodejsNpmInstallAction)
self.assertIsInstance(workflow.actions[4], CleanUpAction)
self.assertIsInstance(workflow.actions[5], CopyDependenciesAction)
self.assertIsInstance(workflow.actions[6], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[7], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmTestAction)
self.assertIsInstance(workflow.actions[5], CleanUpAction)
self.assertIsInstance(workflow.actions[6], CopyDependenciesAction)
self.assertIsInstance(workflow.actions[7], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[8], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[9], NodejsNpmLockFileCleanUpAction)

def test_workflow_sets_up_npm_actions_without_download_dependencies_and_without_dependencies_dir(self):
workflow = NodejsNpmWorkflow(
Expand Down Expand Up @@ -212,17 +220,18 @@ def test_workflow_sets_up_npm_actions_without_combine_dependencies(self):
osutils=self.osutils,
)

self.assertEqual(len(workflow.actions), 9)
self.assertEqual(len(workflow.actions), 10)

self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
self.assertIsInstance(workflow.actions[3], NodejsNpmInstallAction)
self.assertIsInstance(workflow.actions[4], CleanUpAction)
self.assertIsInstance(workflow.actions[5], MoveDependenciesAction)
self.assertIsInstance(workflow.actions[6], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[7], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmTestAction)
self.assertIsInstance(workflow.actions[5], CleanUpAction)
self.assertIsInstance(workflow.actions[6], MoveDependenciesAction)
self.assertIsInstance(workflow.actions[7], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[8], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[9], NodejsNpmLockFileCleanUpAction)

def test_must_validate_architecture(self):
self.osutils.is_windows.side_effect = [False, False]
Expand Down Expand Up @@ -258,13 +267,14 @@ def test_workflow_uses_npm_ci_if_shrinkwrap_exists_and_npm_ci_enabled(self):
options={"use_npm_ci": True},
)

self.assertEqual(len(workflow.actions), 6)
self.assertEqual(len(workflow.actions), 7)
self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
self.assertIsInstance(workflow.actions[3], NodejsNpmCIAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmTestAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[6], NodejsNpmLockFileCleanUpAction)
self.osutils.file_exists.assert_has_calls(
[call("source/package-lock.json"), call("source/npm-shrinkwrap.json")]
)
Expand All @@ -281,13 +291,14 @@ def test_workflow_uses_npm_ci_if_lockfile_exists_and_npm_ci_enabled(self):
options={"use_npm_ci": True},
)

self.assertEqual(len(workflow.actions), 6)
self.assertEqual(len(workflow.actions), 7)
self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
self.assertIsInstance(workflow.actions[3], NodejsNpmCIAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmLockFileCleanUpAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmTestAction)
self.assertIsInstance(workflow.actions[5], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[6], NodejsNpmLockFileCleanUpAction)
self.osutils.file_exists.assert_has_calls([call("source/package-lock.json")])

@patch("aws_lambda_builders.workflows.nodejs_npm.workflow.NodejsNpmWorkflow.can_use_install_links")
Expand Down Expand Up @@ -327,16 +338,18 @@ def test_build_in_source_with_download_dependencies(self, can_use_links_mock):
build_in_source=True,
)

self.assertEqual(len(workflow.actions), 6)
self.assertEqual(len(workflow.actions), 7)
self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
self.assertIsInstance(workflow.actions[3], NodejsNpmUpdateAction)
self.assertEqual(workflow.actions[3].install_dir, source_dir)
self.assertIsInstance(workflow.actions[4], LinkSinglePathAction)
self.assertEqual(workflow.actions[4]._source, os.path.join(source_dir, "node_modules"))
self.assertEqual(workflow.actions[4]._dest, os.path.join(artifacts_dir, "node_modules"))
self.assertIsInstance(workflow.actions[5], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmTestAction)
self.assertEqual(workflow.actions[4].install_dir, source_dir)
self.assertIsInstance(workflow.actions[5], LinkSinglePathAction)
self.assertEqual(workflow.actions[5]._source, os.path.join(source_dir, "node_modules"))
self.assertEqual(workflow.actions[5]._dest, os.path.join(artifacts_dir, "node_modules"))
self.assertIsInstance(workflow.actions[6], NodejsNpmrcCleanUpAction)

@patch("aws_lambda_builders.workflows.nodejs_npm.workflow.NodejsNpmWorkflow.can_use_install_links")
def test_build_in_source_with_download_dependencies_and_dependencies_dir(self, can_use_links_mock):
Expand All @@ -354,18 +367,20 @@ def test_build_in_source_with_download_dependencies_and_dependencies_dir(self, c
dependencies_dir="dep",
)

self.assertEqual(len(workflow.actions), 8)
self.assertEqual(len(workflow.actions), 9)
self.assertIsInstance(workflow.actions[0], NodejsNpmPackAction)
self.assertIsInstance(workflow.actions[1], NodejsNpmrcAndLockfileCopyAction)
self.assertIsInstance(workflow.actions[2], CopySourceAction)
self.assertIsInstance(workflow.actions[3], NodejsNpmUpdateAction)
self.assertEqual(workflow.actions[3].install_dir, source_dir)
self.assertIsInstance(workflow.actions[4], LinkSinglePathAction)
self.assertEqual(workflow.actions[4]._source, os.path.join(source_dir, "node_modules"))
self.assertEqual(workflow.actions[4]._dest, os.path.join(artifacts_dir, "node_modules"))
self.assertIsInstance(workflow.actions[5], CleanUpAction)
self.assertIsInstance(workflow.actions[6], CopyDependenciesAction)
self.assertIsInstance(workflow.actions[7], NodejsNpmrcCleanUpAction)
self.assertIsInstance(workflow.actions[4], NodejsNpmTestAction)
self.assertEqual(workflow.actions[4].install_dir, source_dir)
self.assertIsInstance(workflow.actions[5], LinkSinglePathAction)
self.assertEqual(workflow.actions[5]._source, os.path.join(source_dir, "node_modules"))
self.assertEqual(workflow.actions[5]._dest, os.path.join(artifacts_dir, "node_modules"))
self.assertIsInstance(workflow.actions[6], CleanUpAction)
self.assertIsInstance(workflow.actions[7], CopyDependenciesAction)
self.assertIsInstance(workflow.actions[8], NodejsNpmrcCleanUpAction)

@patch("aws_lambda_builders.workflows.nodejs_npm.workflow.NodejsNpmWorkflow.can_use_install_links")
def test_build_in_source_with_dependencies_dir(self, can_use_links_mock):
Expand Down