Coverage for tests/test_node/test_node.py: 100%
73 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-08-27 18:25 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2024-08-27 18:25 +0000
1import os
2import sys
3import shutil
4import subprocess
5from typing import cast
6from pathlib import Path
8import pytest
9from pytest_subprocess import FakeProcess
11from prisma.cli import _node as node
12from prisma._compat import nodejs
13from prisma._config import Config
14from prisma.cli._node import Target
16from ..utils import set_config
18THIS_DIR = Path(__file__).parent
20parametrize_target = pytest.mark.parametrize('target', ['node', 'npm'])
23def _assert_can_run_js(strategy: node.Node) -> None:
24 proc = strategy.run(
25 str(THIS_DIR.joinpath('test.js')),
26 stdout=subprocess.PIPE,
27 )
28 output = proc.stdout.decode('utf-8')
29 assert output == 'Hello world!\n'
32def _assert_can_run_npm(strategy: node.Node) -> None:
33 assert strategy.target == 'npm'
35 proc = strategy.run('help', stdout=subprocess.PIPE)
36 output = proc.stdout.decode('utf-8')
38 assert 'npm' in output
41def assert_strategy(strategy: node.Node) -> None:
42 if strategy.target == 'node':
43 _assert_can_run_js(strategy)
44 elif strategy.target == 'npm':
45 _assert_can_run_npm(strategy)
46 else: # pragma: no cover
47 raise ValueError(f'No tests implemented for strategy target: {strategy.target}')
50def test_resolve_bad_target() -> None:
51 """resolve() raises a helpful error message when given an unknown target"""
52 with pytest.raises(
53 node.UnknownTargetError,
54 match='Unknown target: foo; Valid choices are: node, npm',
55 ):
56 node.resolve(cast(node.Target, 'foo'))
59@parametrize_target
60@pytest.mark.skipif(nodejs is None, reason='nodejs-bin is not installed')
61def test_nodejs_bin(target: Target) -> None:
62 """When `nodejs-bin` is installed, it is resolved to and can be successfully used"""
63 with set_config(
64 Config.parse(
65 use_nodejs_bin=True,
66 use_global_node=False,
67 )
68 ):
69 strategy = node.resolve(target)
70 assert strategy.resolver == 'nodejs-bin'
71 assert_strategy(strategy)
74@parametrize_target
75@pytest.mark.skipif(
76 shutil.which('node') is None,
77 reason='Node is not installed globally',
78)
79def test_resolves_binary_node(target: Target) -> None:
80 """When `node` is installed globally, it is resolved to and can be successfully used"""
81 with set_config(
82 Config.parse(
83 use_nodejs_bin=False,
84 use_global_node=True,
85 )
86 ):
87 strategy = node.resolve(target)
88 assert strategy.resolver == 'global'
89 assert_strategy(strategy)
91 with set_config(
92 Config.parse(
93 use_nodejs_bin=False,
94 use_global_node=False,
95 )
96 ):
97 strategy = node.resolve(target)
98 assert strategy.resolver == 'nodeenv'
99 assert_strategy(strategy)
102@parametrize_target
103def test_nodeenv(target: Target) -> None:
104 """When `nodejs-bin` and global `node` is not installed / configured to use, `nodeenv` is resolved to and can be successfully used"""
105 with set_config(
106 Config.parse(
107 use_nodejs_bin=False,
108 use_global_node=False,
109 )
110 ):
111 strategy = node.resolve(target)
112 assert strategy.resolver == 'nodeenv'
113 assert_strategy(strategy)
116@parametrize_target
117def test_nodeenv_extra_args(
118 target: Target,
119 tmp_path: Path,
120 fake_process: FakeProcess,
121) -> None:
122 """The config option `nodeenv_extra_args` is respected"""
123 cache_dir = tmp_path / 'nodeenv'
125 fake_process.register_subprocess(
126 [sys.executable, '-m', 'nodeenv', str(cache_dir), '--my-extra-flag'],
127 returncode=403,
128 )
130 with set_config(
131 Config.parse(
132 use_nodejs_bin=False,
133 use_global_node=False,
134 nodeenv_extra_args=['--my-extra-flag'],
135 nodeenv_cache_dir=cache_dir,
136 )
137 ):
138 with pytest.raises(subprocess.CalledProcessError) as exc:
139 node.resolve(target)
141 assert exc.value.returncode == 403
144def test_update_path_env() -> None:
145 """The _update_path_env() function correctly appends the target binary path to the PATH environment variable"""
146 target = THIS_DIR / 'bin'
147 if not target.exists(): # pragma: no branch
148 target.mkdir()
150 sep = os.pathsep
152 # known PATH separators - please update if need be
153 assert sep in {':', ';'}
155 # no env
156 env = node._update_path_env(env=None, target_bin=target)
157 assert env['PATH'].startswith(f'{target.absolute()}{sep}')
159 # env without PATH
160 env = node._update_path_env(
161 env={'FOO': 'bar'},
162 target_bin=target,
163 )
164 assert env['PATH'].startswith(f'{target.absolute()}{sep}')
166 # env with empty PATH
167 env = node._update_path_env(
168 env={'PATH': ''},
169 target_bin=target,
170 )
171 assert env['PATH'].startswith(f'{target.absolute()}{sep}')
173 # env with set PATH without the separator postfix
174 env = node._update_path_env(
175 env={'PATH': '/foo'},
176 target_bin=target,
177 )
178 assert env['PATH'] == f'{target.absolute()}{sep}/foo'
180 # env with set PATH with the separator as a prefix
181 env = node._update_path_env(
182 env={'PATH': f'{sep}/foo'},
183 target_bin=target,
184 )
185 assert env['PATH'] == f'{target.absolute()}{sep}/foo'
187 # returned env included non PATH environment variables
188 env = node._update_path_env(
189 env={'PATH': '/foo', 'FOO': 'bar'},
190 target_bin=target,
191 )
192 assert env['FOO'] == 'bar'
193 assert env['PATH'] == f'{target.absolute()}{sep}/foo'
195 # accepts a custom path separator
196 env = node._update_path_env(
197 env={'PATH': '/foo'},
198 target_bin=target,
199 sep='---',
200 )
201 assert env['PATH'] == f'{target.absolute()}---/foo'
204@parametrize_target
205@pytest.mark.skipif(
206 shutil.which('node') is None,
207 reason='Node is not installed globally',
208)
209def test_node_version(target: Target, fake_process: FakeProcess) -> None:
210 """The node version can be detected properly and correctly constrained to our minimum required version"""
212 def _register_process(stdout: str) -> None:
213 # register the same process twice as we will call it twice
214 # in our assertions and fake_process consumes called processes
215 for _ in range(2):
216 fake_process.register_subprocess([str(path), '--version'], stdout=stdout)
218 which = shutil.which(target)
219 assert which is not None
221 path = Path(which)
223 _register_process('v1.3.4')
224 version = node._get_binary_version(target, path)
225 assert version == (1, 3)
226 assert node._should_use_binary(target, path) is False
228 _register_process('v1.32433')
229 version = node._get_binary_version(target, path)
230 assert version == (1, 32433)
231 assert node._should_use_binary(target, path) is False
233 _register_process('v16.15.a4')
234 version = node._get_binary_version(target, path)
235 assert version == (16, 15)
236 assert node._should_use_binary(target, path) is True
238 _register_process('v16.13.1')
239 version = node._get_binary_version(target, path)
240 assert version == (16, 13)
241 assert node._should_use_binary(target, path) is True
244def test_should_use_binary_unknown_target() -> None:
245 """The UnknownTargetError() is raised by the _should_use_binary() function given an invalid target"""
246 with pytest.raises(node.UnknownTargetError):
247 node._should_use_binary(
248 target='foo', # type: ignore
249 path=Path.cwd(),
250 )