test_slot.py 13 KB

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