conftest.py 11 KB

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