test_slot.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. from datetime import datetime, timedelta, timezone
  2. import uuid
  3. from httpx import AsyncClient
  4. from sqlalchemy import select
  5. from sqlalchemy.orm import Session
  6. from app.main import app
  7. from app.models import Project, Slot, SlotTemplate, Volunteer
  8. from app.tests.conftest import (
  9. default_project_id,
  10. default_slot_id,
  11. default_volunteer_id,
  12. default_template_id,
  13. )
  14. async def test_read_list_project_slots(
  15. client: AsyncClient, default_user_headers: dict, default_public_project: Project
  16. ):
  17. response = await client.get(
  18. app.url_path_for("list_project_slots", project_id=default_project_id),
  19. )
  20. assert response.status_code == 401
  21. response = await client.get(
  22. app.url_path_for("list_project_slots", project_id=uuid.uuid4()),
  23. headers=default_user_headers,
  24. )
  25. assert response.status_code == 404
  26. response = await client.get(
  27. app.url_path_for("list_project_slots", project_id="pas un uuid valid"),
  28. headers=default_user_headers,
  29. )
  30. assert response.status_code == 422
  31. response = await client.get(
  32. app.url_path_for("list_project_slots", project_id=default_project_id),
  33. headers=default_user_headers,
  34. )
  35. assert response.status_code == 200
  36. data = response.json()
  37. assert len(data) == 1
  38. slot_response = data[0]
  39. assert slot_response["title"] == "être roi"
  40. assert slot_response["id"] == default_slot_id
  41. assert "created_at" in slot_response
  42. async def test_create_slot(
  43. client: AsyncClient,
  44. default_public_project: Project,
  45. default_user_headers: dict,
  46. session: Session,
  47. ):
  48. # Test without autentication
  49. response = await client.post(app.url_path_for("create_slot", project_id=default_project_id))
  50. assert response.status_code == 401
  51. starting_time = datetime(1900, 1, 1)
  52. payload = {
  53. "title": "être mort",
  54. "starting_time": starting_time.isoformat(),
  55. "ending_time": (starting_time + timedelta(minutes=60)).isoformat(),
  56. }
  57. # Test invalid project_id
  58. response = await client.post(
  59. app.url_path_for("create_slot", project_id=uuid.uuid4()),
  60. json=payload,
  61. headers=default_user_headers,
  62. )
  63. assert response.status_code == 404
  64. # Test normal payload
  65. response = await client.post(
  66. app.url_path_for("create_slot", project_id=default_project_id),
  67. json=payload,
  68. headers=default_user_headers,
  69. )
  70. assert response.status_code == 200
  71. assert response.json()["id"] != default_slot_id
  72. assert response.json()["title"] == "être mort"
  73. assert response.json()["required_volunteers"] == 0
  74. result = session.execute(select(Slot).where(Slot.project_id == default_project_id))
  75. slots = result.scalars().all()
  76. assert len(slots) > 1
  77. slot = [s for s in slots if s.id != default_slot_id][0]
  78. assert slot.title == "être mort"
  79. assert slot.required_volunteers == 0
  80. assert abs(slot.starting_time - starting_time.replace(tzinfo=timezone.utc)) < timedelta(
  81. minutes=30
  82. )
  83. # Test invalid payload
  84. del payload["title"]
  85. response = await client.post(
  86. app.url_path_for("create_slot", project_id=default_project_id),
  87. json=payload,
  88. headers=default_user_headers,
  89. )
  90. assert response.status_code == 422
  91. async def test_create_slot_min_volunteer(
  92. client: AsyncClient,
  93. default_public_project: Project,
  94. default_user_headers: dict,
  95. session: Session,
  96. ):
  97. starting_time = datetime(1900, 1, 1)
  98. payload = {
  99. "title": "être mort",
  100. "starting_time": starting_time.isoformat(),
  101. "ending_time": (starting_time + timedelta(minutes=60)).isoformat(),
  102. "required_volunteers": 2,
  103. }
  104. # Test normal payload
  105. response = await client.post(
  106. app.url_path_for("create_slot", project_id=default_project_id),
  107. json=payload,
  108. headers=default_user_headers,
  109. )
  110. assert response.status_code == 200
  111. result = session.execute(select(Slot).where(Slot.project_id == default_project_id))
  112. slots = result.scalars().all()
  113. assert len(slots) > 1
  114. slot = [s for s in slots if s.id != default_slot_id][0]
  115. assert slot.required_volunteers == 2
  116. async def test_update_slot(
  117. client: AsyncClient,
  118. default_public_project: Project,
  119. default_user_headers: dict,
  120. session: Session,
  121. ):
  122. # Test without autentication
  123. response = await client.post(
  124. app.url_path_for(
  125. "update_slot",
  126. project_id=default_project_id,
  127. slot_id=default_slot_id,
  128. )
  129. )
  130. assert response.status_code == 401
  131. starting_time = datetime(2000, 1, 1, tzinfo=timezone.utc)
  132. payload = {
  133. "title": "être mort 2 fois",
  134. "starting_time": starting_time.isoformat(),
  135. "ending_time": (starting_time + timedelta(minutes=60)).isoformat(),
  136. "required_volunteers": 2,
  137. }
  138. # test invalid project_id
  139. response = await client.post(
  140. app.url_path_for(
  141. "update_slot",
  142. project_id=uuid.uuid4(),
  143. slot_id=default_slot_id,
  144. ),
  145. json=payload,
  146. headers=default_user_headers,
  147. )
  148. assert response.status_code == 404
  149. # test invalid slot_id
  150. response = await client.post(
  151. app.url_path_for(
  152. "update_slot",
  153. project_id=default_project_id,
  154. slot_id=uuid.uuid4(),
  155. ),
  156. json=payload,
  157. headers=default_user_headers,
  158. )
  159. assert response.status_code == 404
  160. # Test normal payload
  161. for k, v in payload.items():
  162. response = await client.post(
  163. app.url_path_for(
  164. "update_slot",
  165. project_id=default_project_id,
  166. slot_id=default_slot_id,
  167. ),
  168. json={k: v},
  169. headers=default_user_headers,
  170. )
  171. assert response.status_code == 200
  172. assert response.json()["id"] == default_slot_id
  173. if "time" in k:
  174. assert datetime.fromisoformat(response.json()[k]) == datetime.fromisoformat(v)
  175. else:
  176. assert response.json()[k] == v
  177. async def test_update_slot_remove_template(
  178. client: AsyncClient,
  179. default_public_project: Project,
  180. default_user_headers: dict,
  181. session: Session,
  182. ):
  183. response = await client.post(
  184. app.url_path_for(
  185. "update_slot",
  186. project_id=default_project_id,
  187. slot_id=default_slot_id,
  188. ),
  189. json={"template_id": default_template_id},
  190. headers=default_user_headers,
  191. )
  192. assert response.status_code == 200
  193. slot = session.execute(select(Slot).where(Slot.project_id == default_project_id)).scalar_one()
  194. assert slot.template_id == default_template_id
  195. response = await client.post(
  196. app.url_path_for(
  197. "update_slot",
  198. project_id=default_project_id,
  199. slot_id=default_slot_id,
  200. ),
  201. json={"template_id": ""},
  202. headers=default_user_headers,
  203. )
  204. assert response.status_code == 200
  205. session.refresh(slot)
  206. assert slot.template_id is None
  207. async def test_update_bad_slot_template(
  208. client: AsyncClient,
  209. default_public_project: Project,
  210. default_user_headers: dict,
  211. ):
  212. path = app.url_path_for(
  213. "update_slot",
  214. project_id=default_project_id,
  215. slot_id=default_slot_id,
  216. )
  217. response = await client.post(
  218. path,
  219. json={"template_id": "invalid uuid"},
  220. headers=default_user_headers,
  221. )
  222. assert response.status_code == 422
  223. response = await client.post(
  224. path,
  225. json={"template_id": str(uuid.uuid4())},
  226. headers=default_user_headers,
  227. )
  228. assert response.status_code == 400
  229. async def test_update_slot_template(
  230. client: AsyncClient,
  231. default_public_project: Project,
  232. default_user_headers: dict,
  233. session: Session,
  234. ):
  235. path = app.url_path_for(
  236. "update_slot",
  237. project_id=default_project_id,
  238. slot_id=default_slot_id,
  239. )
  240. template = SlotTemplate(project_id=default_project_id, title="template 0")
  241. session.add(template)
  242. session.commit()
  243. response = await client.post(
  244. path,
  245. json={"template_id": template.id},
  246. headers=default_user_headers,
  247. )
  248. assert response.status_code == 200
  249. assert response.json()["template_id"] == template.id
  250. session.refresh(template)
  251. assert len(template.slots) == 1
  252. response = await client.post(
  253. path,
  254. json={"template_id": None},
  255. headers=default_user_headers,
  256. )
  257. assert response.status_code == 200
  258. assert response.json()["template_id"] is None
  259. async def test_update_slot_volunteers(
  260. client: AsyncClient,
  261. default_public_project: Project,
  262. default_user_headers: dict,
  263. session: Session,
  264. ):
  265. response = await client.post(
  266. app.url_path_for(
  267. "update_slot",
  268. project_id=default_project_id,
  269. slot_id=default_slot_id,
  270. ),
  271. json={"volunteers": []},
  272. headers=default_user_headers,
  273. )
  274. assert response.status_code == 200
  275. result = session.execute(select(Volunteer).where(Volunteer.id == default_volunteer_id))
  276. volunteer = result.scalars().first()
  277. assert volunteer is not None
  278. assert volunteer.slots_id == []
  279. response = await client.post(
  280. app.url_path_for(
  281. "update_slot",
  282. project_id=default_project_id,
  283. slot_id=default_slot_id,
  284. ),
  285. json={"volunteers": [default_volunteer_id]},
  286. headers=default_user_headers,
  287. )
  288. assert response.status_code == 200
  289. session.refresh(volunteer)
  290. assert volunteer is not None
  291. assert volunteer.slots_id == [default_slot_id]
  292. # An invalid volunteer list
  293. response = await client.post(
  294. app.url_path_for(
  295. "update_slot",
  296. project_id=default_project_id,
  297. slot_id=default_slot_id,
  298. ),
  299. json={"volunteers": [str(uuid.uuid4())]},
  300. headers=default_user_headers,
  301. )
  302. assert response.status_code == 400
  303. # An invalid volunteer list
  304. response = await client.post(
  305. app.url_path_for(
  306. "update_slot",
  307. project_id=default_project_id,
  308. slot_id=default_slot_id,
  309. ),
  310. json={"volunteers": ["not uuid str"]},
  311. headers=default_user_headers,
  312. )
  313. assert response.status_code == 422
  314. async def test_delete_slot_fail(
  315. client: AsyncClient,
  316. default_user_headers: dict,
  317. session: Session,
  318. default_public_project: Project,
  319. ):
  320. # Fail deleting the project due to not logged in
  321. response = await client.delete(
  322. app.url_path_for(
  323. "delete_slot",
  324. project_id=default_project_id,
  325. slot_id=default_slot_id,
  326. )
  327. )
  328. assert response.status_code == 401
  329. result = session.execute(select(Slot).where(Slot.id == default_slot_id))
  330. slot = result.scalars().first()
  331. assert slot is not None
  332. # Cannot delete non uuid string
  333. response = await client.delete(
  334. app.url_path_for("delete_slot", project_id=default_project_id, slot_id="not uidstr"),
  335. headers=default_user_headers,
  336. )
  337. assert response.status_code == 422
  338. async def test_delete_slot(
  339. client: AsyncClient,
  340. default_user_headers: dict,
  341. session: Session,
  342. default_public_project: Project,
  343. ):
  344. # Proper deletion
  345. response = await client.delete(
  346. app.url_path_for(
  347. "delete_slot",
  348. project_id=default_project_id,
  349. slot_id=default_slot_id,
  350. ),
  351. headers=default_user_headers,
  352. )
  353. assert response.status_code == 200
  354. result = session.execute(select(Slot).where(Slot.id == default_slot_id))
  355. slot = result.scalars().first()
  356. assert slot is None
  357. # check deletion is cascaded to volunteers
  358. result = session.execute(select(Volunteer).where(Volunteer.id == default_volunteer_id))
  359. volunteer: Volunteer | None = result.scalars().first()
  360. assert volunteer is not None
  361. assert default_slot_id not in volunteer.slots_id
  362. # can delete random uuid
  363. response = await client.delete(
  364. app.url_path_for("delete_slot", project_id=default_project_id, slot_id=uuid.uuid4()),
  365. headers=default_user_headers,
  366. )
  367. assert response.status_code == 200
  368. async def test_delete_slot_idempotent(
  369. client: AsyncClient,
  370. default_user_headers: dict,
  371. session: Session,
  372. default_public_project: Project,
  373. ):
  374. # Idempotence test
  375. url = app.url_path_for("delete_slot", project_id=default_project_id, slot_id=default_slot_id)
  376. response = await client.delete(url, headers=default_user_headers)
  377. response = await client.delete(url, headers=default_user_headers)
  378. assert response.status_code == 200