test_slot.py 12 KB

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