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

1import os 

2import sys 

3import shutil 

4import subprocess 

5from typing import cast 

6from pathlib import Path 

7 

8import pytest 

9from pytest_subprocess import FakeProcess 

10 

11from prisma.cli import _node as node 

12from prisma._compat import nodejs 

13from prisma._config import Config 

14from prisma.cli._node import Target 

15 

16from ..utils import set_config 

17 

18THIS_DIR = Path(__file__).parent 

19 

20parametrize_target = pytest.mark.parametrize('target', ['node', 'npm']) 

21 

22 

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' 

30 

31 

32def _assert_can_run_npm(strategy: node.Node) -> None: 

33 assert strategy.target == 'npm' 

34 

35 proc = strategy.run('help', stdout=subprocess.PIPE) 

36 output = proc.stdout.decode('utf-8') 

37 

38 assert 'npm' in output 

39 

40 

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}') 

48 

49 

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')) 

57 

58 

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) 

72 

73 

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) 

90 

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) 

100 

101 

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) 

114 

115 

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' 

124 

125 fake_process.register_subprocess( 

126 [sys.executable, '-m', 'nodeenv', str(cache_dir), '--my-extra-flag'], 

127 returncode=403, 

128 ) 

129 

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) 

140 

141 assert exc.value.returncode == 403 

142 

143 

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() 

149 

150 sep = os.pathsep 

151 

152 # known PATH separators - please update if need be 

153 assert sep in {':', ';'} 

154 

155 # no env 

156 env = node._update_path_env(env=None, target_bin=target) 

157 assert env['PATH'].startswith(f'{target.absolute()}{sep}') 

158 

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}') 

165 

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}') 

172 

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' 

179 

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' 

186 

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' 

194 

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' 

202 

203 

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""" 

211 

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) 

217 

218 which = shutil.which(target) 

219 assert which is not None 

220 

221 path = Path(which) 

222 

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 

227 

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 

232 

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 

237 

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 

242 

243 

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 )