From 8e2da8c5dc138a3e6d4096f853c9832590d9c27c Mon Sep 17 00:00:00 2001 From: c-d-p Date: Sun, 27 Apr 2025 02:08:36 +0200 Subject: [PATCH] fixes for pytest --- backend/__pycache__/main.cpython-312.pyc | Bin 1809 -> 1756 bytes .../__pycache__/celery_app.cpython-312.pyc | Bin 527 -> 527 bytes .../core/__pycache__/config.cpython-312.pyc | Bin 1509 -> 1509 bytes .../core/__pycache__/database.cpython-312.pyc | Bin 2180 -> 2180 bytes .../admin/__pycache__/api.cpython-312.pyc | Bin 3846 -> 3846 bytes .../__pycache__/models.cpython-312.pyc | Bin 1226 -> 1221 bytes .../__pycache__/service.cpython-312.pyc | Bin 4995 -> 4996 bytes .../__pycache__/tasks.cpython-312.pyc | Bin 10555 -> 10536 bytes .../__pycache__/service.cpython-312.pyc | Bin 5000 -> 5029 bytes .../todo/__pycache__/models.cpython-312.pyc | Bin 988 -> 985 bytes .../user/__pycache__/api.cpython-312.pyc | Bin 5627 -> 5627 bytes ...test_calendar.cpython-312-pytest-8.3.5.pyc | Bin 53176 -> 66186 bytes backend/tests/test_calendar.py | 226 +++++++++++++++--- 13 files changed, 189 insertions(+), 37 deletions(-) diff --git a/backend/__pycache__/main.cpython-312.pyc b/backend/__pycache__/main.cpython-312.pyc index 69448adb1a467d0615de19702affd424fd134026..6dcde1431ca2eac398ab1a86b9ca2bf10f9260cf 100644 GIT binary patch delta 384 zcmbQpcZXN~G%qg~0}w3XG^vt};Ygm#P6(;ku zim@sJ4Og42#rlyA#41vltjH!&uMcE00&($SAn}2jk&*E}gXet)g}V&WcNvWCGicpq zkh;yl_kcmLf&Dsz;6(<(37VG~WUe!)USv>RkiI;3VeS&D%=UqUK}39l>J4GB3929X85o7XFmNz3eGr@ckxc;rg_>&o delta 392 zcmcb^JCRTQG%qg~0}$lPb7ah9Wng#=;=lkql<~P@qq;t$NIF9b&mzVY-W0xcMi5@Z zznXP(2BRJmqsZjx%o#jFK;^|sK%#-+$z*O8O(sp|$pI|lu~p(3B_#z``uc{JMtX*3 z7J7zymR1%97KT;QXc9)Irg}z3hG5B%TZ~$oEVpY@H8H0oqX^^{%f!JkIe<+fl!H;^ zqZk8^%w=xb29BF-yb~&}uqiY!e_&%^WVy~Dbdf=5g4q=YDJ1?47NJi}tio(Sqr|0e b2#ZZn1rr}c85o7XFmN+6eUO{HhfM(hsNrG^ diff --git a/backend/core/__pycache__/celery_app.cpython-312.pyc b/backend/core/__pycache__/celery_app.cpython-312.pyc index cd9070601a8f8589dae02d45ee5e66e8e0554784..d8775c4bba4f260c1a6be3c02eeae41dd816f3f0 100644 GIT binary patch delta 28 icmeBY>1W|R&CAQh00grVcr&Ut@}6d7d^q_gqZt5Y#|Rq$ delta 28 icmeBY>1W|R&CAQh00b=Iycy*ic~3Jk-kf}s(F_1wHV1J4 diff --git a/backend/core/__pycache__/config.cpython-312.pyc b/backend/core/__pycache__/config.cpython-312.pyc index 1fe617524646bf27d059acb159bc1f1bd9bed77a..c9e6c775a47a2a77c8a4d15f4e13f3816613ef2f 100644 GIT binary patch delta 61 zcmaFL{gj*cG%qg~0}#wg;LVWV$oqnsQDO5pW?4o?mdPTlJ0xWoB|B=qFaW8K4vY*u NBHdPv97QTX^#J#)4;ugg delta 61 zcmaFL{gj*cG%qg~0}%KK@n$G&Y!T!=&CAQh00grVcr#Qt^6Ie&@B?|pZ9t-d;R!omL;YrNmN-U6h0Uv2g%}w% QHt%Iy$jHbxIi5om0K)zbnE(I) delta 64 zcmZn>Y!T!=&CAQh00agHc{0>C^6Ie&@Bw+nZ9t-d;Ry$S2mfYomN-U6`OT|Xg%}yt QH}7Rz$jHbtIi5om0Jbj(^b diff --git a/backend/modules/admin/__pycache__/api.cpython-312.pyc b/backend/modules/admin/__pycache__/api.cpython-312.pyc index b5d6c1038f2a915a5cea39b45d4d9064a6203c02..9fde4715d72c1c3159b5ad3661c4701f6bc612b9 100644 GIT binary patch delta 124 zcmZpZYm?(W&CAQh00grVcr%JO@-E?K#%=nQB#4eHpS_c5gQzmKv diff --git a/backend/modules/calendar/__pycache__/models.cpython-312.pyc b/backend/modules/calendar/__pycache__/models.cpython-312.pyc index 07aaa4f2afd12503f3e683334a7844c0b3592d5b..707d1f449e81f69d9e1798f2203821cf72405ca2 100644 GIT binary patch delta 69 zcmX@bd6bj)G%qg~0}#wg;LTv%$eYT{*gv_Lxm0*U_{^*oQkUgSc9dS>b8T>c$SppJ VWv3t?qZ8uhKVtCa}e)BCbj~glHx^^ zfAUY{KM;C6;$Xx9udmEZtZX0HCeIRh&e*=WNzjy$@#y3oLb9AZ42w3IA_ U*3^N}R=%G>;vWnqrwhvh0R4j`t^fc4 diff --git a/backend/modules/calendar/__pycache__/tasks.cpython-312.pyc b/backend/modules/calendar/__pycache__/tasks.cpython-312.pyc index 5b748d34b8596d6f3b97dc144a7478854f2fbcdb..49665c9b3082c9587259dc8ff6ce83fe17ea6323 100644 GIT binary patch delta 1223 zcmZXTPfR0K9LL|xq|gpClj*_P;A zT{xLFYa*lvKx3%UL=Jm^aBz*r^f=MWZz84>(~HR-Jq$3bn{e?>Syqj6nBU8L-}ig( z_xb+b_tCv5_cP1V2!Ed?*{RWr1MUlwa34M*3)z5#XXn485L!n@0peT_``54LZ0v)H zhu-yOoxDz@n3C5#nj?@7r)8*$0UtLglI5`2DWyvJD`lF)vu)XBci4?JDDQ&qeq|YM ztO9@UTxUgWMd}m2C#doMuY>73dx5Va`@P>L4ieeAR<*7bt138P%5*CVy`CcMqurYD zX2$w>S#iO8W6(JcL{ z5KBOG5C%U0*J56zGpQf#(-g-FsVYZuRFi}0-gGNewPe~djY=G%lh88-Q}eK}lwDX+ z7gk_?6;|(oc>gIyx&?fOyLhW(vqLNPZ&yH|7V6rewnJ%x>m$&Ygyb@aD|w|Z;WLge z9Tqu%w*{6>tbdSstn~k}Hs`VWUtn#9mN+!`Lhl&xxIzJ!n1Vk*hez z3tP-4qX{7>uZK_rG;~AH4d@<#fk{|%O<_3LD*A<0VW@0`a1%7eAU+7OAs8A4*9g7< zR{0Qay}ph3uU7e)At7EmL6nWlF^(+bRT;15IIp2b8C7s6_WWgb;xWR%Go8Z1GlM~l z8}G5uIV+d7ifWBj)VLsoUWH&i)L(RG0tSa5F$~NI&S0Z)1{-Z=u+TWe;ZLOp7Ev(` z{D?3Od_w!v;xBY$y+tQ|sZ=PrMziup;cre^C#`NdCtk{m5mk(2#cEZo*1ToAojaYM zckM4^-qk52)fkABWbBAkzGug>9ad*?0z*WsC(HO$#+PM6DicbN9WfDH>ZjK}zNR_k z?Kiexfyy?;saT*BJ9Zmx&cf^*@OPe0J_kkb7)LHJC z;YQvN=3(B_x6AA>ny+TRe!uc@Q)Vf13%YST4MB7mye0@q#vmlw1mTv?4Eazke?$;J zr0$2WtQ8PMZ91kS9QIUHXj28R9P*RtgZ)e+emEsUPx87RcCYNU7d68 zefPX%-*;j;}1G)#YDv5L!pse55x$7+P;n7x4F| z9$3~}G}4*)*>cmk?N1IRZ^PSTAo^2WAZ-*9HASvBwms5YkDDGfNp;{Hfzb&V32F#U z;toQ=(n+!i@wZ8at4%y8>aBqr?(a?VIP~_tCYY}+)2cV3cq6IW1>mX=Yi@mE z825tBE$jAHq1OY0v*4M7xfO`rQ)6)@7N@>3+|2BoNecsOEBPHEgNtN}O192#o(Ee4 zH1++eyM|48$cz(vx(1)ZjMTA(hYDl!8`rXGynsB2!y~N zroRWvRn@RR5}llC0JO3 zu_*YKVfjNat)z4@jpz#kI8$QXn%$gDRW%%lj)M~Mz+854vG%d>NPuhYirJkl$g9?$ zpuExx_uWSvGKpzJ>6T^FB3*#`W~gg{me=nDf=ghF#`g`&^wk-KEpbzX3MX7`1*Z#K zUhqwVcM7Ja!5T^#!dWAtPjYZ$U|(1MGHXCZWyc89anhVoK~&)~FBr3wy3aHxV) z5u8siJ-f1VWw%ZqfRPChoGBrAAlQ=zN_fa?1x%bRLVT&p*%Z#Ea(0EYOA{&1L9hAw zozL!UH%Q&myKuD^`aIA(2!jz=Sb<0kVt1izb)Q@FXU>Qq_QQaeh3N~zWC(&`2(ugQ zbMr5=JhIs7B3_MW`-*$W!KGWz-On5My5RjGc@>67+2%*5!5&hDup)%n1}OV9tKWTA z`vl`}6T!=_ySW%&m-;nCA=!Vz;3yvvexoLZOEuDsvG}z37%@D_K}0w!9eqIl=3K{O ivQSUkm7;7R6jb3@2#ht+}P14TRRFIah>Fuf2M zbUiToVqo<3!1RlO=@$YrE@Wh1$jQB)Q+6?@zU+EV^~Id(3)wXn1Zsgk;pO|x#vmg3 zy&A(^zrWN#EL6P6uZU`)?T)My!Y7n=fB7I{G+3dt~kOv667F#$v1 z*LQa`sm&IG*BBXdC-Vzyd2=xMF$OVyWKLydl)NDz`~kv{k%uzWwV(_ms~si>m=6RW Z;Mi$*(Z~xT`hb=53oA%xkrOcS0swZR>f--H@$(IoFD&FukHi^ShpcGQK(f z!T-+x@$y!B$2*0d!WvIu&v9;pU1xD@a$8i)%FuQiC&S7-3UN)4R6)Xp2j7H;7<+`H zkDHEC>%3dPLNjzDsGCLta+|*zKyL)2NtDvKkjCgDMlzVm;>G3WnOtjROux`5IC@9%?+t7;SUDQNvIi&=zqMmCt~|Lz|K%SfQ}Hzb diff --git a/backend/modules/todo/__pycache__/models.cpython-312.pyc b/backend/modules/todo/__pycache__/models.cpython-312.pyc index 537ee043d520ce56d01f598a1c09f34591c777ad..884c88b99eb0f74a3a883a61739fad93d821b987 100644 GIT binary patch delta 54 zcmcb^ev_T|G%qg~0}#wg;LR}F$h(7yF=_HKra0CaVwZUoChIUS@fMt0HliK GfeHbd0S&VN delta 57 zcmcb~eutg+G%qg~0}woz7vvCU`Mtr;1OHgj@*XJoY7%*g$R anK5wlKfXUqj76J&3$idVa%>X$%?JR{Nfh7! delta 70 zcmeyZ{ac&&G%qg~0}y1N=E-=qk(Yy&k$oz7v(amSstr-~&H*<1+XJoY9%*g$R anK5AVKfXUqjD?$j3$idVvTYLi%?JS7sTB|a diff --git a/backend/tests/__pycache__/test_calendar.cpython-312-pytest-8.3.5.pyc b/backend/tests/__pycache__/test_calendar.cpython-312-pytest-8.3.5.pyc index 29c3508e7bdfe42963a4f840f3806f0ef401b048..346b54c691a2837381ef66d66e2e3b556309e194 100644 GIT binary patch literal 66186 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 Gi2i>(pB?c4 literal 53176 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 None: assert response.status_code == status.HTTP_401_UNAUTHORIZED -def test_create_event_success(db: Session, client: TestClient) -> None: +def test_create_event_success( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: """Test creating a calendar event successfully.""" user, password = generators.create_user(db) login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] payload = create_event_payload() + # Mock the celery task sending + mock_send_task = mocker.patch( + "core.celery_app.celery_app.send_task" + ) # Corrected patch target + response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, @@ -49,7 +58,7 @@ def test_create_event_success(db: Session, client: TestClient) -> None: data = response.json() assert data["title"] == payload["title"] assert data["description"] == payload["description"] - # Remove the '+ "Z"' as the API doesn't add it + # Assert with Z suffix assert data["start"] == payload["start"] assert data["end"] == payload["end"] assert data["all_day"] == payload["all_day"] @@ -62,6 +71,11 @@ def test_create_event_success(db: Session, client: TestClient) -> None: assert event_in_db.user_id == user.id assert event_in_db.title == payload["title"] + # Assert that the task was called correctly + mock_send_task.assert_called_once_with( + "modules.calendar.tasks.schedule_event_notifications", args=[data["id"]] + ) + # --- Test Get Events --- @@ -72,36 +86,49 @@ def test_get_events_unauthorized(client: TestClient) -> None: assert response.status_code == status.HTTP_401_UNAUTHORIZED -def test_get_events_success(db: Session, client: TestClient) -> None: +def test_get_events_success( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test getting all calendar events for a user.""" - user, password = generators.create_user(db) + user, password = generators.create_user( + db, username="testuser_get_events" + ) # Unique username login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + # Create a couple of events for the user payload1 = create_event_payload(0, 1) - client.post( + create_rsp1 = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload1, ) + assert create_rsp1.status_code == status.HTTP_201_CREATED + payload2 = create_event_payload(2, 3) - client.post( + create_rsp2 = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload2, ) + assert create_rsp2.status_code == status.HTTP_201_CREATED # Create an event for another user (should not be returned) - other_user, other_password = generators.create_user(db) + other_user, other_password = generators.create_user( + db, username="otheruser_get_events" + ) # Unique username other_login_rsp = generators.login(db, other_user.username, other_password) other_access_token = other_login_rsp["access_token"] other_payload = create_event_payload(4, 5) - client.post( + create_rsp_other = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {other_access_token}"}, json=other_payload, ) + assert create_rsp_other.status_code == status.HTTP_201_CREATED response = client.get( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"} @@ -115,35 +142,51 @@ def test_get_events_success(db: Session, client: TestClient) -> None: assert data[1]["user_id"] == user.id -def test_get_events_filtered(db: Session, client: TestClient) -> None: +def test_get_events_filtered( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test getting filtered calendar events for a user.""" - user, password = generators.create_user(db) + user, password = generators.create_user( + db, username="testuser_filter_events" + ) # Unique username login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + # Create events payload1 = create_event_payload(0, 1) # Today -> Tomorrow - client.post( + create_rsp1 = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload1, ) + assert create_rsp1.status_code == status.HTTP_201_CREATED + payload2 = create_event_payload(5, 6) # In 5 days -> In 6 days - client.post( + create_rsp2 = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload2, ) + assert create_rsp2.status_code == status.HTTP_201_CREATED + payload3 = create_event_payload(10, 11) # In 10 days -> In 11 days - client.post( + create_rsp3 = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload3, ) + assert create_rsp3.status_code == status.HTTP_201_CREATED # Filter for events starting within the next week - start_filter = datetime.utcnow().isoformat() - end_filter = (datetime.utcnow() + timedelta(days=7)).isoformat() + start_filter = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") + end_filter = ( + (datetime.now(timezone.utc) + timedelta(days=7)) + .isoformat() + .replace("+00:00", "Z") + ) response = client.get( "/api/calendar/events", @@ -157,7 +200,11 @@ def test_get_events_filtered(db: Session, client: TestClient) -> None: assert data[1]["title"] == payload2["title"] # Filter for events starting after 8 days - start_filter_late = (datetime.utcnow() + timedelta(days=8)).isoformat() + start_filter_late = ( + (datetime.now(timezone.utc) + timedelta(days=8)) + .isoformat() + .replace("+00:00", "Z") + ) response = client.get( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, @@ -172,34 +219,48 @@ def test_get_events_filtered(db: Session, client: TestClient) -> None: # --- Test Get Event By ID --- -def test_get_event_by_id_unauthorized(db: Session, client: TestClient) -> None: +def test_get_event_by_id_unauthorized( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test getting a specific event without authentication.""" user, password = generators.create_user(db) login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] payload = create_event_payload() + + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload, ) + assert create_response.status_code == status.HTTP_201_CREATED event_id = create_response.json()["id"] response = client.get(f"/api/calendar/events/{event_id}") assert response.status_code == status.HTTP_401_UNAUTHORIZED -def test_get_event_by_id_success(db: Session, client: TestClient) -> None: +def test_get_event_by_id_success( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test getting a specific event successfully.""" user, password = generators.create_user(db) login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] payload = create_event_payload() + + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload, ) + assert create_response.status_code == status.HTTP_201_CREATED event_id = create_response.json()["id"] response = client.get( @@ -210,6 +271,9 @@ def test_get_event_by_id_success(db: Session, client: TestClient) -> None: data = response.json() assert data["id"] == event_id assert data["title"] == payload["title"] + # Assert datetime with Z suffix + assert data["start"] == payload["start"] + assert data["end"] == payload["end"] assert data["user_id"] == user.id @@ -227,20 +291,31 @@ def test_get_event_by_id_not_found(db: Session, client: TestClient) -> None: assert response.status_code == status.HTTP_404_NOT_FOUND -def test_get_event_by_id_forbidden(db: Session, client: TestClient) -> None: +def test_get_event_by_id_forbidden( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test getting another user's event.""" - user1, password_user1 = generators.create_user(db) - user2, password_user2 = generators.create_user(db) + user1, password_user1 = generators.create_user( + db, username="user1_forbidden_get" + ) # Unique username + user2, password_user2 = generators.create_user( + db, username="user2_forbidden_get" + ) # Unique username # Log in as user1 and create an event login_rsp1 = generators.login(db, user1.username, password_user1) access_token1 = login_rsp1["access_token"] payload = create_event_payload() + + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token1}"}, json=payload, ) + assert create_response.status_code == status.HTTP_201_CREATED event_id = create_response.json()["id"] # Log in as user2 and try to get user1's event @@ -259,17 +334,24 @@ def test_get_event_by_id_forbidden(db: Session, client: TestClient) -> None: # --- Test Update Event --- -def test_update_event_unauthorized(db: Session, client: TestClient) -> None: +def test_update_event_unauthorized( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test updating an event without authentication.""" user, password = generators.create_user(db) login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] payload = create_event_payload() + + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload, ) + assert create_response.status_code == status.HTTP_201_CREATED event_id = create_response.json()["id"] update_payload = {"title": "Updated Title"} @@ -277,12 +359,20 @@ def test_update_event_unauthorized(db: Session, client: TestClient) -> None: assert response.status_code == status.HTTP_401_UNAUTHORIZED -def test_update_event_success(db: Session, client: TestClient) -> None: +def test_update_event_success( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test updating an event successfully.""" user, password = generators.create_user(db) login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] payload = create_event_payload() + + # Mock celery task for creation + mocker.patch( + "core.celery_app.celery_app.send_task", return_value=None + ) # Mock for creation + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, @@ -299,6 +389,13 @@ def test_update_event_success(db: Session, client: TestClient) -> None: "all_day": not payload["all_day"], # Toggle all_day } + # Mock celery task for update (needs separate mock) + mock_send_task_update = mocker.patch( + "modules.calendar.service.celery_app.send_task" + ) + # Mock cancel notifications as well, as it's called synchronously in the service + mocker.patch("modules.calendar.tasks.cancel_event_notifications") + response = client.patch( f"/api/calendar/events/{event_id}", headers={"Authorization": f"Bearer {access_token}"}, @@ -310,7 +407,8 @@ def test_update_event_success(db: Session, client: TestClient) -> None: assert data["title"] == update_payload["title"] assert data["description"] == update_payload["description"] assert data["all_day"] == update_payload["all_day"] - assert data["start"] == payload["start"] # Check correct field name 'start' + # Assert datetime with Z suffix + assert data["start"] == payload["start"] assert data["user_id"] == user.id # Verify in DB @@ -320,6 +418,17 @@ def test_update_event_success(db: Session, client: TestClient) -> None: assert event_in_db.description == update_payload["description"] assert event_in_db.all_day == update_payload["all_day"] + # Assert that the update task was called correctly + mock_send_task_update.assert_called_once_with( + "modules.calendar.tasks.schedule_event_notifications", args=[event_id] + ) + # Assert cancel was NOT called because update doesn't cancel + # mock_cancel_notifications.assert_not_called() # Update: cancel IS called in update path via re-schedule + # Actually, schedule_event_notifications calls cancel_event_notifications first. + # So we need to mock cancel_event_notifications called *within* schedule_event_notifications + # OR mock schedule_event_notifications itself. Let's stick to mocking send_task. + # The cancel mock added earlier handles the direct call in the service layer if any. + def test_update_event_not_found(db: Session, client: TestClient) -> None: """Test updating a non-existent event.""" @@ -337,20 +446,31 @@ def test_update_event_not_found(db: Session, client: TestClient) -> None: assert response.status_code == status.HTTP_404_NOT_FOUND -def test_update_event_forbidden(db: Session, client: TestClient) -> None: +def test_update_event_forbidden( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test updating another user's event.""" - user1, password_user1 = generators.create_user(db) - user2, password_user2 = generators.create_user(db) + user1, password_user1 = generators.create_user( + db, username="user1_forbidden_update" + ) # Unique username + user2, password_user2 = generators.create_user( + db, username="user2_forbidden_update" + ) # Unique username # Log in as user1 and create an event login_rsp1 = generators.login(db, user1.username, password_user1) access_token1 = login_rsp1["access_token"] payload = create_event_payload() + + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token1}"}, json=payload, ) + assert create_response.status_code == status.HTTP_201_CREATED event_id = create_response.json()["id"] # Log in as user2 and try to update user1's event @@ -371,29 +491,42 @@ def test_update_event_forbidden(db: Session, client: TestClient) -> None: # --- Test Delete Event --- -def test_delete_event_unauthorized(db: Session, client: TestClient) -> None: +def test_delete_event_unauthorized( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test deleting an event without authentication.""" user, password = generators.create_user(db) login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] payload = create_event_payload() + + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, json=payload, ) + assert create_response.status_code == status.HTTP_201_CREATED event_id = create_response.json()["id"] response = client.delete(f"/api/calendar/events/{event_id}") assert response.status_code == status.HTTP_401_UNAUTHORIZED -def test_delete_event_success(db: Session, client: TestClient) -> None: +def test_delete_event_success( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: """Test deleting an event successfully.""" user, password = generators.create_user(db) login_rsp = generators.login(db, user.username, password) access_token = login_rsp["access_token"] payload = create_event_payload() + + # Mock the celery task sending for creation + mocker.patch("core.celery_app.celery_app.send_task") + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token}"}, @@ -408,12 +541,20 @@ def test_delete_event_success(db: Session, client: TestClient) -> None: event_in_db = db.query(CalendarEvent).filter(CalendarEvent.id == event_id).first() assert event_in_db is not None + # Mock the cancel_event_notifications function to prevent Redis call + mock_cancel_notifications = mocker.patch( + "modules.calendar.service.cancel_event_notifications" # Target the function as used in service.py + ) + response = client.delete( f"/api/calendar/events/{event_id}", headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_204_NO_CONTENT + # Assert that cancel_event_notifications was called + mock_cancel_notifications.assert_called_once_with(event_id) + # Verify event is deleted from DB event_in_db = db.query(CalendarEvent).filter(CalendarEvent.id == event_id).first() assert event_in_db is None @@ -441,20 +582,31 @@ def test_delete_event_not_found(db: Session, client: TestClient) -> None: assert response.status_code == status.HTTP_404_NOT_FOUND -def test_delete_event_forbidden(db: Session, client: TestClient) -> None: +def test_delete_event_forbidden( + db: Session, client: TestClient, mocker: MockerFixture +) -> None: # Add mocker """Test deleting another user's event.""" - user1, password_user1 = generators.create_user(db) - user2, password_user2 = generators.create_user(db) + user1, password_user1 = generators.create_user( + db, username="user1_forbidden_delete" + ) # Unique username + user2, password_user2 = generators.create_user( + db, username="user2_forbidden_delete" + ) # Unique username # Log in as user1 and create an event login_rsp1 = generators.login(db, user1.username, password_user1) access_token1 = login_rsp1["access_token"] payload = create_event_payload() + + # Mock celery task for creation + mocker.patch("core.celery_app.celery_app.send_task") + create_response = client.post( "/api/calendar/events", headers={"Authorization": f"Bearer {access_token1}"}, json=payload, ) + assert create_response.status_code == status.HTTP_201_CREATED event_id = create_response.json()["id"] # Log in as user2 and try to delete user1's event