def test_resolve_npm_invalid_lock(mock_dd, mock_gpad, mock_exists): mock_exists.return_value = True mock_gpad.side_effect = KeyError() expected = "The lock file npm-shrinkwrap.json has an unexpected format" with pytest.raises(CachitoError, match=expected): npm.resolve_npm("/tmp/cachito-bundles/temp/1/app", {"id": 1})
def test_resolve_npm_no_lock(mock_dd, mock_exists): mock_exists.return_value = False expected = ( "The npm-shrinkwrap.json or package-lock.json file must be present for the npm " "package manager") with pytest.raises(CachitoError, match=expected): npm.resolve_npm("/tmp/cachito-bundles/temp/1/app", {"id": 1})
def test_resolve_npm( get_worker_config, mock_dd, mock_gpad, mock_exists, shrink_wrap, package_lock, package_and_deps, tmpdir, ): get_worker_config.return_value = mock.Mock(cachito_bundles_dir=str(tmpdir)) package_json = True mock_dd.return_value = { "@angular-devkit/[email protected]", "@angular/[email protected]", "[email protected]", "[email protected]", "[email protected]", } # Note that the dictionary returned by the get_package_and_deps function is modified as part of # the resolve_npm function. This is why a deep copy is necessary. expected_deps_info = copy.deepcopy(package_and_deps) expected_deps_info["downloaded_deps"] = { "@angular-devkit/[email protected]", "@angular/[email protected]", "[email protected]", "[email protected]", "[email protected]", } if shrink_wrap: expected_deps_info["lock_file_name"] = "npm-shrinkwrap.json" mock_exists.side_effect = [shrink_wrap, package_json] else: expected_deps_info["lock_file_name"] = "package-lock.json" mock_exists.side_effect = [shrink_wrap, package_lock, package_json] mock_gpad.return_value = package_and_deps # Remove the "bundled" key as does the resolve_npm function to get expected returned # dependencies for dep in expected_deps_info["deps"]: dep.pop("bundled") dep.pop("version_in_nexus") src_path = "/tmp/cachito-bundles/temp/1/app" deps_info = npm.resolve_npm(src_path, {"id": 1}) assert deps_info == expected_deps_info package_json_path = os.path.join(src_path, "package.json") if shrink_wrap: lock_file_path = os.path.join(src_path, "npm-shrinkwrap.json") elif package_lock: lock_file_path = os.path.join(src_path, "package-lock.json") mock_gpad.assert_called_once_with(package_json_path, lock_file_path) # We can't verify the actual correct deps value was passed in since the deps that were passed # in were mutated and mock does not keep a deepcopy of the function arguments. mock_dd.assert_called_once_with(RequestBundleDir(1).npm_deps_dir, mock.ANY, mock.ANY, mock.ANY)
def fetch_npm_source(request_id, package_configs=None): """ Resolve and fetch npm dependencies for a given request. This function uses the Python ``os.path`` library to manipulate paths, so the path to the configuration files may differ in format based on the system the Cachito worker is deployed on (i.e. Linux vs Windows). :param int request_id: the Cachito request ID this is for :param list package_configs: the list of optional package configurations submitted by the user :raise CachitoError: if the task fails """ if package_configs is None: package_configs = [] validate_npm_config() bundle_dir = RequestBundleDir(request_id) log.debug("Checking if the application source uses npm") subpaths = [ os.path.normpath(c["path"]) for c in package_configs if c.get("path") ] if not subpaths: # Default to the root of the application source subpaths = [os.curdir] _verify_npm_files(bundle_dir, subpaths) log.info("Configuring Nexus for npm for the request %d", request_id) set_request_state(request_id, "in_progress", "Configuring Nexus for npm") repo_name = get_npm_proxy_repo_name(request_id) prepare_nexus_for_js_request(repo_name) npm_config_files = [] downloaded_deps = set() for i, subpath in enumerate(subpaths): log.info("Fetching the npm dependencies for request %d in subpath %s", request_id, subpath) set_request_state( request_id, "in_progress", f'Fetching the npm dependencies at the "{subpath}" directory"', ) request = get_request(request_id) package_source_path = str(bundle_dir.app_subpath(subpath).source_dir) try: package_and_deps_info = resolve_npm(package_source_path, request, skip_deps=downloaded_deps) except CachitoError: log.exception("Failed to fetch npm dependencies for request %d", request_id) raise downloaded_deps = downloaded_deps | package_and_deps_info[ "downloaded_deps"] log.info( "Generating the npm configuration files for request %d in subpath %s", request_id, subpath, ) remote_package_source_path = os.path.normpath( os.path.join("app", subpath)) if package_and_deps_info["package.json"]: package_json_str = json.dumps( package_and_deps_info["package.json"], indent=2) package_json_path = os.path.join(remote_package_source_path, "package.json") npm_config_files.append( make_base64_config_file(package_json_str, package_json_path)) if package_and_deps_info["lock_file"]: package_lock_str = json.dumps(package_and_deps_info["lock_file"], indent=2) lock_file_name = package_and_deps_info["lock_file_name"] lock_file_path = os.path.join(remote_package_source_path, lock_file_name) npm_config_files.append( make_base64_config_file(package_lock_str, lock_file_path)) if i == 0: env_vars = get_worker_config( ).cachito_default_environment_variables.get("npm", {}) else: env_vars = None package = package_and_deps_info["package"] update_request_with_package(request_id, package, env_vars, package_subpath=subpath) update_request_with_deps(request_id, package, package_and_deps_info["deps"]) log.info("Finalizing the Nexus configuration for npm for the request %d", request_id) set_request_state(request_id, "in_progress", "Finalizing the Nexus configuration for npm") username = get_npm_proxy_username(request_id) password = finalize_nexus_for_js_request(username, repo_name) log.info("Generating the .npmrc file(s)") proxy_repo_url = get_npm_proxy_repo_url(request_id) npm_config_files.extend( generate_npmrc_config_files(proxy_repo_url, username, password, subpaths)) update_request_with_config_files(request_id, npm_config_files)