conftest.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. import pytest
  2. import factory
  3. from collections import namedtuple
  4. from wtforms import Form, fields
  5. from werkzeug.datastructures import ImmutableMultiDict
  6. from pydantic_form import PydanticForm
  7. from pydantic import BaseModel, ValidationError
  8. from pydantic.types import StrictStr
  9. from typing import List
  10. DataFactories = namedtuple(
  11. "DataFactories",
  12. ['valid', 'bad', 'missing', 'mixed']
  13. )
  14. ExpectedErrors = namedtuple(
  15. "ExpectedErrors",
  16. ['valid', 'bad', 'missing', 'mixed']
  17. )
  18. Scenario = namedtuple(
  19. "ScenarioClasses",
  20. ['form', 'schema', 'keys', 'data_factory', 'errors']
  21. )
  22. Keys = namedtuple(
  23. "Keys",
  24. ["class_", "instance"]
  25. )
  26. @pytest.fixture
  27. def scenario(request):
  28. return request.getfixturevalue(request.param)
  29. class MissingDataFactory(factory.Factory):
  30. class Meta:
  31. model = dict
  32. # Simplest case
  33. simple_keys = Keys(
  34. [('integer',), ('string',), ('float',)],
  35. [('integer',), ('string',), ('float',)]
  36. )
  37. class SimpleSchema(BaseModel):
  38. integer: int
  39. string: StrictStr
  40. float: float
  41. class SimpleWTForm(Form):
  42. _schema = SimpleSchema
  43. integer = fields.IntegerField()
  44. string = fields.StringField()
  45. float = fields.FloatField()
  46. class SimpleForm(SimpleWTForm, PydanticForm):
  47. _schema = SimpleSchema
  48. class SimpleDataFactory(factory.Factory):
  49. class Meta:
  50. model = dict
  51. integer = factory.Faker('pyint')
  52. string = factory.Faker('pystr')
  53. float = factory.Faker('pyfloat')
  54. class SimpleBadDataFactory(factory.Factory):
  55. class Meta:
  56. model = dict
  57. integer = factory.Faker('pystr')
  58. string = factory.Faker('pyint')
  59. float = factory.Faker('pystr')
  60. class SimpleMixedDataFactory(factory.Factory):
  61. class Meta:
  62. model = dict
  63. integer = factory.Faker('pyint')
  64. float = factory.Faker('pystr')
  65. simple_data_factories = DataFactories(
  66. SimpleDataFactory,
  67. SimpleBadDataFactory,
  68. MissingDataFactory,
  69. SimpleMixedDataFactory
  70. )
  71. simple_expected_errors = ExpectedErrors(
  72. {},
  73. {
  74. 'integer': 'type_error.integer',
  75. 'string': 'type_error.str',
  76. 'float': 'type_error.float'
  77. },
  78. {
  79. 'integer': 'value_error.missing',
  80. 'string': 'value_error.missing',
  81. 'float': 'value_error.missing',
  82. },
  83. {
  84. 'string': 'value_error.missing',
  85. 'float': 'type_error.float'
  86. }
  87. )
  88. @pytest.fixture
  89. def scenario_simple():
  90. return Scenario(
  91. SimpleForm,
  92. SimpleSchema,
  93. simple_keys,
  94. simple_data_factories,
  95. simple_expected_errors
  96. )
  97. # Simple list case
  98. simple_list_keys = Keys(
  99. [('integers',), ('strings',), ('floats',)],
  100. [
  101. ('integers', 0), ('integers', 1),
  102. ('strings', 0), ('strings', 1),
  103. ('floats', 0), ('floats', 1)
  104. ],
  105. )
  106. class SimpleListSchema(BaseModel):
  107. integers: List[int]
  108. strings: List[StrictStr]
  109. floats: List[float]
  110. class SimpleListWTForm(Form):
  111. _schema = SimpleSchema
  112. integers = fields.FieldList(fields.IntegerField(), min_entries=2)
  113. strings = fields.FieldList(fields.StringField(), min_entries=2)
  114. floats = fields.FieldList(fields.FloatField(), min_entries=2)
  115. class SimpleListForm(SimpleListWTForm, PydanticForm):
  116. _schema = SimpleListSchema
  117. class SimpleListDataFactory(factory.Factory):
  118. class Meta:
  119. model = dict
  120. integers = factory.List([factory.Faker('pyint'), factory.Faker('pyint')])
  121. strings = factory.List([factory.Faker('pystr'), factory.Faker('pystr')])
  122. floats = factory.List([factory.Faker('pyfloat'), factory.Faker('pyfloat')])
  123. class SimpleListBadDataFactory(factory.Factory):
  124. class Meta:
  125. model = dict
  126. integers = factory.List([factory.Faker('pystr'), factory.Faker('pystr')])
  127. strings = factory.List([factory.Faker('pyint'), factory.Faker('pyint')])
  128. floats = factory.List([factory.Faker('pystr'), factory.Faker('pystr')])
  129. class SimpleListMixedDataFactory(factory.Factory):
  130. class Meta:
  131. model = dict
  132. integers = factory.List([factory.Faker('pyint'), factory.Faker('pyint')])
  133. floats = factory.List([factory.Faker('pystr'), factory.Faker('pystr')])
  134. simple_list_data_factories = DataFactories(
  135. SimpleListDataFactory,
  136. SimpleListBadDataFactory,
  137. MissingDataFactory,
  138. SimpleListMixedDataFactory
  139. )
  140. simple_list_expected_errors = ExpectedErrors(
  141. {},
  142. {
  143. 'integers': ['type_error.integer', 'type_error.integer'],
  144. 'strings': ['type_error.str', 'type_error.str'],
  145. 'floats': ['type_error.float', 'type_error.float']
  146. },
  147. {
  148. 'integers': 'value_error.missing',
  149. 'strings': 'value_error.missing',
  150. 'floats': 'value_error.missing'
  151. },
  152. {
  153. 'strings': 'value_error.missing',
  154. 'floats': ['type_error.float', 'type_error.float']
  155. }
  156. )
  157. @pytest.fixture
  158. def scenario_simple_list():
  159. return Scenario(
  160. SimpleListForm,
  161. SimpleListSchema,
  162. simple_list_keys,
  163. simple_list_data_factories,
  164. simple_list_expected_errors
  165. )
  166. # Case with one level of nesting
  167. nested_keys = Keys(
  168. [('integer',), ('nested', 'integer'), ('nested', 'string'), ('nested', 'float',)],
  169. [('integer',), ('nested', 'integer'), ('nested', 'string'), ('nested', 'float',)]
  170. )
  171. class NestedSchema(BaseModel):
  172. integer: int
  173. nested: SimpleSchema
  174. class NestedWTForm(Form):
  175. _schema = NestedSchema
  176. integer = fields.IntegerField()
  177. nested = fields.FormField(form_class=SimpleForm)
  178. class NestedForm(NestedWTForm, PydanticForm):
  179. _schema = NestedSchema
  180. class NestedDataFactory(factory.Factory):
  181. class Meta:
  182. model = dict
  183. integer = factory.Faker('pyint')
  184. nested = factory.SubFactory(SimpleDataFactory)
  185. class NestedBadDataFactory(factory.Factory):
  186. class Meta:
  187. model = dict
  188. integer = factory.Faker('pystr')
  189. nested = factory.SubFactory(SimpleBadDataFactory)
  190. class NestedMixedDataFactory(factory.Factory):
  191. class Meta:
  192. model = dict
  193. integer = factory.Faker('pyint')
  194. nested = factory.SubFactory(SimpleMixedDataFactory)
  195. nested_data_factories = DataFactories(
  196. NestedDataFactory,
  197. NestedBadDataFactory,
  198. MissingDataFactory,
  199. NestedMixedDataFactory
  200. )
  201. nested_expected_errors = ExpectedErrors(
  202. {},
  203. {
  204. 'integer': 'type_error.integer',
  205. 'nested': simple_expected_errors.bad
  206. },
  207. {
  208. 'integer': 'value_error.missing',
  209. 'nested': 'value_error.missing',
  210. },
  211. {
  212. 'nested': simple_expected_errors.mixed
  213. },
  214. )
  215. @pytest.fixture
  216. def scenario_nested():
  217. return Scenario(
  218. NestedForm,
  219. NestedSchema,
  220. nested_keys,
  221. nested_data_factories,
  222. nested_expected_errors
  223. )
  224. # Case with one level of nesting inside a list
  225. nested_list_keys = Keys(
  226. [('integer',), ('nested_list', 'integer'), ('nested_list', 'string'), ('nested_list', 'float')],
  227. [
  228. ('integer',),
  229. ('nested_list', 0, 'integer'),
  230. ('nested_list', 1, 'integer'),
  231. ('nested_list', 0, 'string'),
  232. ('nested_list', 1, 'string'),
  233. ('nested_list', 0, 'float'),
  234. ('nested_list', 1, 'float')
  235. ]
  236. )
  237. class NestedListSchema(BaseModel):
  238. integer: int
  239. nested_list: List[SimpleSchema]
  240. class NestedListWTForm(Form):
  241. _schema = NestedListSchema
  242. integer = fields.IntegerField()
  243. nested_list = fields.FieldList(fields.FormField(form_class=SimpleForm), min_entries=2)
  244. class NestedListForm(NestedListWTForm, PydanticForm):
  245. _schema = NestedListSchema
  246. class NestedListDataFactory(factory.Factory):
  247. class Meta:
  248. model = dict
  249. integer = factory.Faker('pyint')
  250. nested_list = factory.List([
  251. factory.SubFactory(SimpleDataFactory),
  252. factory.SubFactory(SimpleDataFactory)
  253. ])
  254. class NestedListBadDataFactory(factory.Factory):
  255. class Meta:
  256. model = dict
  257. integer = factory.Faker('pystr')
  258. nested_list = factory.List([
  259. factory.SubFactory(SimpleBadDataFactory),
  260. factory.SubFactory(SimpleBadDataFactory)
  261. ])
  262. class NestedListMixedDataFactory(factory.Factory):
  263. class Meta:
  264. model = dict
  265. integer = factory.Faker('pyint')
  266. nested_list = factory.List([
  267. factory.SubFactory(SimpleMixedDataFactory),
  268. factory.SubFactory(SimpleMixedDataFactory)
  269. ])
  270. nested_list_data_factories = DataFactories(
  271. NestedListDataFactory,
  272. NestedListBadDataFactory,
  273. MissingDataFactory,
  274. NestedListMixedDataFactory
  275. )
  276. nested_list_expected_errors = ExpectedErrors(
  277. {},
  278. {
  279. 'integer': 'type_error.integer',
  280. 'nested_list': [
  281. {'integer': 'type_error.integer'},
  282. {'string': 'type_error.str'}
  283. ],
  284. },
  285. {
  286. 'integer': 'value_error.missing',
  287. 'nested_list': 'value_error.missing',
  288. },
  289. {
  290. 'nested_list': [
  291. simple_expected_errors.mixed
  292. ]
  293. },
  294. )
  295. @pytest.fixture
  296. def scenario_nested_list():
  297. return Scenario(
  298. NestedListForm,
  299. NestedListSchema,
  300. nested_list_keys,
  301. nested_list_data_factories,
  302. nested_list_expected_errors
  303. )
  304. # Case with two levels of nesting
  305. double_nested_keys = Keys([
  306. ('integer',),
  307. ('double_nested', 'integer'),
  308. ('double_nested', 'nested', 'integer'),
  309. ('double_nested', 'nested', 'string'),
  310. ('double_nested', 'nested', 'float')
  311. ],
  312. [
  313. ('integer',),
  314. ('double_nested', 'integer'),
  315. ('double_nested', 'nested', 'integer'),
  316. ('double_nested', 'nested', 'string'),
  317. ('double_nested', 'nested', 'float')
  318. ]
  319. )
  320. class DoubleNestedSchema(BaseModel):
  321. integer: int
  322. double_nested: NestedSchema
  323. class DoubleNestedWTForm(Form):
  324. _schema = DoubleNestedSchema
  325. integer = fields.IntegerField()
  326. double_nested = fields.FormField(form_class=NestedWTForm)
  327. class DoubleNestedForm(DoubleNestedWTForm, PydanticForm):
  328. _schema = DoubleNestedSchema
  329. double_nested = fields.FormField(form_class=NestedForm)
  330. class DoubleNestedDataFactory(factory.Factory):
  331. class Meta:
  332. model = dict
  333. integer = factory.Faker('pyint')
  334. double_nested = factory.SubFactory(NestedDataFactory)
  335. class DoubleNestedBadDataFactory(factory.Factory):
  336. class Meta:
  337. model = dict
  338. integer = factory.Faker('pystr')
  339. double_nested = factory.SubFactory(NestedBadDataFactory)
  340. class DoubleNestedMixedDataFactory(factory.Factory):
  341. class Meta:
  342. model = dict
  343. integer = factory.Faker('pyint')
  344. double_nested = factory.SubFactory(NestedMixedDataFactory)
  345. double_nested_data_factories = DataFactories(
  346. DoubleNestedDataFactory,
  347. DoubleNestedBadDataFactory,
  348. MissingDataFactory,
  349. DoubleNestedMixedDataFactory
  350. )
  351. double_nested_expected_errors = ExpectedErrors(
  352. {},
  353. {
  354. 'integer': 'type_error.integer',
  355. 'double_nested': {
  356. 'integer': 'type_error.integer',
  357. 'nested': simple_expected_errors.bad
  358. }
  359. },
  360. {
  361. 'integer': 'value_error.missing',
  362. 'double_nested': 'value_error.missing',
  363. },
  364. {
  365. 'double_nested': {
  366. 'nested': simple_expected_errors.mixed
  367. }
  368. },
  369. )
  370. @pytest.fixture
  371. def scenario_double_nested():
  372. return Scenario(
  373. DoubleNestedForm,
  374. DoubleNestedSchema,
  375. double_nested_keys,
  376. double_nested_data_factories,
  377. double_nested_expected_errors
  378. )