Coverage for tests/test_generation/test_generator.py: 100%

84 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-28 15:17 +0000

1import sys 

2import subprocess 

3from typing import cast 

4from pathlib import Path 

5from typing_extensions import override 

6 

7import pytest 

8from jinja2 import Environment, FileSystemLoader 

9 

10from prisma import __version__ 

11from prisma._compat import PYDANTIC_V2 

12from prisma.generator import ( 

13 BASE_PACKAGE_DIR, 

14 Manifest, 

15 Generator, 

16 GenericGenerator, 

17 render_template, 

18 cleanup_templates, 

19) 

20from prisma.generator.utils import Faker, copy_tree 

21 

22from .utils import assert_module_is_clean, assert_module_not_clean 

23from ..utils import Testdir 

24 

25 

26def test_repeated_rstrip_bug(tmp_path: Path) -> None: 

27 """Previously, rendering schema.prisma.jinja would have rendered the file 

28 to schema.prism instead of schema.prisma 

29 """ 

30 env = Environment(loader=FileSystemLoader(str(tmp_path))) 

31 

32 template = 'schema.prisma.jinja' 

33 tmp_path.joinpath(template).write_text('foo') 

34 render_template(tmp_path, template, dict(), env=env) 

35 

36 assert tmp_path.joinpath('schema.prisma').read_text() == 'foo' 

37 

38 

39def test_template_cleanup(testdir: Testdir) -> None: 

40 """Cleaning up templates removes all rendered files""" 

41 path = testdir.path / 'prisma' 

42 assert not path.exists() 

43 copy_tree(BASE_PACKAGE_DIR, path) 

44 

45 assert_module_not_clean(path) 

46 cleanup_templates(path) 

47 assert_module_is_clean(path) 

48 

49 # ensure cleaning an already clean module doesn't change anything 

50 cleanup_templates(path) 

51 assert_module_is_clean(path) 

52 

53 

54def test_erroneous_template_cleanup(testdir: Testdir) -> None: 

55 """Template runtime errors do not result in a partially generated module""" 

56 path = testdir.path / 'prisma' 

57 copy_tree(BASE_PACKAGE_DIR, path) 

58 

59 assert_module_not_clean(path) 

60 

61 template = '{{ undefined.foo }}' 

62 template_path = testdir.path / 'prisma' / 'generator' / 'templates' / 'template.py.jinja' 

63 template_path.write_text(template) 

64 

65 with pytest.raises(subprocess.CalledProcessError) as exc: 

66 testdir.generate() 

67 

68 output = str(exc.value.output, sys.getdefaultencoding()) 

69 assert template in output 

70 

71 assert_module_is_clean(path) 

72 

73 

74def test_generation_version_number(testdir: Testdir) -> None: 

75 """Ensure the version number is shown when the client is generated""" 

76 stdout = testdir.generate().stdout.decode('utf-8') 

77 assert f'Generated Prisma Client Python (v{__version__})' in stdout 

78 

79 

80def test_faker() -> None: 

81 """Ensure Faker is re-playable""" 

82 iter1 = iter(Faker()) 

83 iter2 = iter(Faker()) 

84 first = [next(iter1) for _ in range(10)] 

85 second = [next(iter2) for _ in range(10)] 

86 assert first == second 

87 

88 

89def test_invoke_outside_generation() -> None: 

90 """Attempting to invoke a generator outside of Prisma generation errors""" 

91 with pytest.raises(RuntimeError) as exc: 

92 Generator.invoke() 

93 

94 assert exc.value.args[0] == 'Attempted to invoke a generator outside of Prisma generation' 

95 

96 

97def test_invalid_type_argument() -> None: 

98 """Non-BaseModel argument to GenericGenerator raises an error""" 

99 

100 class MyGenerator(GenericGenerator[Path]): # type: ignore 

101 @override 

102 def get_manifest(self) -> Manifest: # pragma: no cover 

103 return super().get_manifest() # type: ignore 

104 

105 @override 

106 def generate(self, data: Path) -> None: # pragma: no cover 

107 raise NotImplementedError() 

108 

109 with pytest.raises(TypeError) as exc: 

110 MyGenerator().data_class # noqa: B018 

111 

112 assert 'pathlib.Path' in exc.value.args[0] 

113 assert 'pydantic.main.BaseModel' in exc.value.args[0] 

114 

115 class MyGenerator2(GenericGenerator[Manifest]): 

116 @override 

117 def get_manifest(self) -> Manifest: # pragma: no cover 

118 return super().get_manifest() # type: ignore 

119 

120 @override 

121 def generate(self, data: Manifest) -> None: # pragma: no cover 

122 raise NotImplementedError() 

123 

124 data_class = MyGenerator2().data_class 

125 assert data_class == Manifest 

126 

127 

128def test_generator_subclass_mismatch() -> None: 

129 """Attempting to subclass Generator instead of BaseGenerator raises an error""" 

130 with pytest.raises(TypeError) as exc: 

131 

132 class MyGenerator(Generator): # pyright: ignore[reportUnusedClass] 

133 ... 

134 

135 message = exc.value.args[0] 

136 assert 'cannot be subclassed, maybe you meant' in message 

137 assert 'BaseGenerator' in message 

138 

139 

140def test_error_handling(testdir: Testdir) -> None: 

141 """Config validation errors are returned through JSONRPC without a stack trace""" 

142 with pytest.raises(subprocess.CalledProcessError) as exc: 

143 testdir.generate(options='partial_type_generator = "foo"') 

144 

145 output = cast(bytes, exc.value.output).decode('utf-8').strip() 

146 if PYDANTIC_V2: 

147 line = output.splitlines()[-2] 

148 assert ( 

149 line 

150 == " Value error, Could not find a python file or module at foo [type=value_error, input_value='foo', input_type=str]" 

151 ) 

152 else: 

153 assert output.endswith( 

154 '\nError: \n' 

155 '1 validation error for PythonData\n' 

156 'generator -> config -> partial_type_generator -> spec\n' 

157 ' Could not find a python file or module at foo (type=value_error)' 

158 ) 

159 

160 

161def test_schema_path_same_path(testdir: Testdir) -> None: 

162 """Generating to the same directory does not cause any errors due to schema copying 

163 

164 https://github.com/RobertCraigie/prisma-client-py/issues/513 

165 """ 

166 proc = testdir.generate(output='.') 

167 assert proc.returncode == 0 

168 assert 'Generated Prisma Client Python' in proc.stdout.decode('utf-8')