test_slot.py 11 KB

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