Skip to content

Commit cdeacb0

Browse files
committed
fix: all test_endpoint tests work
1 parent 79fa4e7 commit cdeacb0

File tree

5 files changed

+67
-39
lines changed

5 files changed

+67
-39
lines changed

src/elections/models.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class ElectionResponse(BaseModel):
4141
class ElectionParams(BaseModel):
4242
slug: str
4343
name: str
44-
type: ElectionTypeEnum
44+
type: ElectionTypeEnum | None = None
4545
datetime_start_nominations: str
4646
datetime_start_voting: str
4747
datetime_end_voting: str
@@ -56,15 +56,16 @@ class ElectionUpdateParams(BaseModel):
5656
available_positions: list[str] | None = None
5757
survey_link: str | None = None
5858

59-
class RegistrationParams(BaseModel):
59+
class NomineeApplicationParams(BaseModel):
6060
election_name: str
6161
computing_id: str
6262
position: OfficerPositionEnum
6363

64-
class RegistrationUpdateParams(RegistrationParams):
64+
class NomineeApplicationUpdateParams(BaseModel):
65+
position: OfficerPositionEnum | None = None
6566
speech: str | None = None
6667

67-
class RegistrantModel(BaseModel):
68+
class NomineeApplicationModel(BaseModel):
6869
computing_id: str
6970
nominee_election: str
7071
position: str

src/elections/tables.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@
1717
DISCORD_NICKNAME_LEN,
1818
)
1919
from database import Base
20-
from elections.models import ElectionStatusEnum, ElectionUpdateParams
20+
from elections.models import (
21+
ElectionStatusEnum,
22+
ElectionUpdateParams,
23+
NomineeApplicationUpdateParams,
24+
NomineeUpdateParams,
25+
)
26+
from officers.types import OfficerPositionEnum
2127

2228
MAX_ELECTION_NAME = 64
2329
MAX_ELECTION_SLUG = 64
@@ -163,7 +169,7 @@ class NomineeApplication(Base):
163169

164170
computing_id: Mapped[str] = mapped_column(ForeignKey("election_nominee_info.computing_id"), primary_key=True)
165171
nominee_election: Mapped[str] = mapped_column(ForeignKey("election.slug"), primary_key=True)
166-
position: Mapped[str] = mapped_column(String(64), primary_key=True)
172+
position: Mapped[OfficerPositionEnum] = mapped_column(String(64), primary_key=True)
167173

168174
speech: Mapped[str | None] = mapped_column(Text)
169175

@@ -189,3 +195,9 @@ def to_update_dict(self) -> dict:
189195
"speech": self.speech,
190196
}
191197

198+
def update_from_params(self, params: NomineeApplicationUpdateParams):
199+
update_data = params.model_dump(exclude_unset=True)
200+
for k, v in update_data.items():
201+
setattr(self, k, v)
202+
203+

src/elections/urls.py

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
ElectionStatusEnum,
1515
ElectionTypeEnum,
1616
ElectionUpdateParams,
17+
NomineeApplicationModel,
18+
NomineeApplicationParams,
19+
NomineeApplicationUpdateParams,
1720
NomineeInfoModel,
1821
NomineeUpdateParams,
19-
RegistrantModel,
20-
RegistrationParams,
21-
RegistrationUpdateParams,
2222
)
2323
from elections.tables import Election, NomineeApplication, NomineeInfo
2424
from officers.constants import COUNCIL_REP_ELECTION_POSITIONS, GENERAL_ELECTION_POSITIONS
@@ -349,7 +349,7 @@ async def update_election(
349349
election = await elections.crud.get_election(db_session, slugified_name)
350350
if election is None:
351351
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="couldn't find updated election")
352-
return JSONResponse(election.private_details(current_time))
352+
return JSONResponse(election.private_details(datetime.now()))
353353

354354
@router.delete(
355355
"/{election_name:str}",
@@ -384,7 +384,7 @@ async def delete_election(
384384
@router.get(
385385
"/registration/{election_name:str}",
386386
description="get all the registrations of a single election",
387-
response_model=list[RegistrantModel],
387+
response_model=list[NomineeApplicationModel],
388388
responses={
389389
401: { "description": "Not logged in", "model": DetailModel },
390390
404: { "description": "Election with slug does not exist", "model": DetailModel }
@@ -415,7 +415,7 @@ async def get_election_registrations(
415415
@router.post(
416416
"/register",
417417
description="Register for a specific position in this election, but doesn't set a speech. Returns the created entry.",
418-
response_model=RegistrantModel,
418+
response_model=NomineeApplicationModel,
419419
responses={
420420
400: { "description": "Bad request", "model": DetailModel },
421421
401: { "description": "Not logged in", "model": DetailModel },
@@ -427,7 +427,7 @@ async def get_election_registrations(
427427
async def register_in_election(
428428
request: Request,
429429
db_session: database.DBSession,
430-
body: RegistrationParams,
430+
body: NomineeApplicationParams,
431431
):
432432
await admin_or_raise(request, db_session)
433433

@@ -493,9 +493,9 @@ async def register_in_election(
493493
return registrant
494494

495495
@router.patch(
496-
"/registration/{election_name:str}/{computing_id:str}",
496+
"/registration/{election_name:str}/{position:str}/{computing_id:str}",
497497
description="update the application of a specific registrant and return the changed entry",
498-
response_model=RegistrantModel,
498+
response_model=NomineeApplicationModel,
499499
responses={
500500
400: { "description": "Bad request", "model": DetailModel },
501501
401: { "description": "Not logged in", "model": DetailModel },
@@ -507,9 +507,10 @@ async def register_in_election(
507507
async def update_registration(
508508
request: Request,
509509
db_session: database.DBSession,
510-
body: RegistrationUpdateParams,
510+
body: NomineeApplicationUpdateParams,
511511
election_name: str,
512512
computing_id: str,
513+
position: OfficerPositionEnum
513514
):
514515
await admin_or_raise(request, db_session)
515516

@@ -528,18 +529,22 @@ async def update_registration(
528529
)
529530

530531
# self updates can only be done during nomination period. Officer updates can be done whenever
531-
elif election.status(datetime.now()) != ElectionStatusEnum.NOMINATIONS:
532+
if election.status(datetime.now()) != ElectionStatusEnum.NOMINATIONS:
532533
raise HTTPException(
533534
status_code=status.HTTP_400_BAD_REQUEST,
534535
detail="speeches can only be updated during the nomination period"
535536
)
536537

537-
elif not await elections.crud.get_all_registrations_of_user(db_session, computing_id, slugified_name):
538+
registration = await elections.crud.get_one_registration_in_election(db_session, computing_id, slugified_name, position)
539+
540+
if not registration:
538541
raise HTTPException(
539542
status_code=status.HTTP_404_NOT_FOUND,
540-
detail="applicant not yet registered in this election"
543+
detail="no registration record found"
541544
)
542545

546+
registration.update_from_params(body)
547+
543548
await elections.crud.update_registration(db_session, NomineeApplication(
544549
computing_id=computing_id,
545550
nominee_election=slugified_name,
@@ -549,7 +554,7 @@ async def update_registration(
549554
await db_session.commit()
550555

551556
registrant = await elections.crud.get_one_registration_in_election(
552-
db_session, body.computing_id, slugified_name, body.position
557+
db_session, registration.computing_id, slugified_name, body.position
553558
)
554559
if not registrant:
555560
raise HTTPException(

src/load_test_db.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
# tables, or the current python context will not be able to find them & they won't be loaded
1313
from auth.crud import create_user_session, update_site_user
1414
from database import SQLALCHEMY_TEST_DATABASE_URL, Base, DatabaseSessionManager
15-
from elections.crud import create_election, create_nominee_info, update_election
16-
from elections.tables import Election, NomineeInfo
15+
from elections.crud import add_registration, create_election, create_nominee_info, update_election
16+
from elections.tables import Election, NomineeApplication, NomineeInfo
1717
from officers.constants import OfficerPosition
1818
from officers.crud import (
1919
create_new_officer_info,
@@ -364,6 +364,15 @@ async def load_test_elections_data(db_session: AsyncSession):
364364
))
365365
await db_session.commit()
366366

367+
async def load_test_election_nominee_application_data(db_session: AsyncSession):
368+
await add_registration(db_session, NomineeApplication(
369+
computing_id=SYSADMIN_COMPUTING_ID,
370+
nominee_election="test-election-2",
371+
position="vice-president",
372+
speech=None
373+
))
374+
await db_session.commit()
375+
367376
# ----------------------------------------------------------------- #
368377

369378
async def async_main(sessionmanager):
@@ -373,6 +382,7 @@ async def async_main(sessionmanager):
373382
await load_test_officers_data(db_session)
374383
await load_sysadmin(db_session)
375384
await load_test_elections_data(db_session)
385+
await load_test_election_nominee_application_data(db_session)
376386

377387
if __name__ == "__main__":
378388
response = input(f"This will reset the {SQLALCHEMY_TEST_DATABASE_URL} database, are you okay with this? (y/N): ")

tests/integration/test_elections.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -127,24 +127,24 @@ async def test_endpoints(client, database_setup):
127127
})
128128
assert response.status_code == 401 # unauthorized access to register candidates
129129

130-
response = await client.patch(f"/elections/{election_name}", params={
130+
response = await client.patch(f"/elections/{election_name}", json={
131131
"type": "general_election",
132132
"datetime_start_nominations": "2025-08-18T09:00:00Z",
133133
"datetime_start_voting": "2025-09-03T09:00:00Z",
134134
"datetime_end_voting": "2025-09-18T23:59:59Z",
135-
"available_positions": "president,treasurer",
135+
"available_positions": ["president", "treasurer"],
136136
"survey_link": "https://youtu.be/dQw4w9WgXcQ?si=kZROi2tu-43MXPM5"
137137

138138
})
139139
assert response.status_code == 401
140140

141-
response = await client.patch(f"/elections/registration/{election_name}/pkn4", params={
141+
response = await client.patch(f"/elections/registration/{election_name}/vice-president/{load_test_db.SYSADMIN_COMPUTING_ID}", json={
142142
"position": "president",
143143
"speech": "I would like to run for president because I'm the best in Valorant at SFU."
144144
})
145145
assert response.status_code == 401
146146

147-
response = await client.put("/elections/nominee/info", params={
147+
response = await client.patch("/elections/nominee/jdo12", json={
148148
"full_name": "John Doe VI",
149149
"linked_in": "linkedin.com/john-doe-vi",
150150
"instagram": "john_vi",
@@ -156,7 +156,7 @@ async def test_endpoints(client, database_setup):
156156
response = await client.delete(f"/elections/{election_name}")
157157
assert response.status_code == 401
158158

159-
response = await client.delete(f"/elections/registration/{election_name}/president")
159+
response = await client.delete(f"/elections/registration/{election_name}/vice-president/{load_test_db.SYSADMIN_COMPUTING_ID}")
160160
assert response.status_code == 401
161161

162162

@@ -190,7 +190,7 @@ async def test_endpoints_admin(client, database_setup):
190190
assert response.status_code == 200
191191

192192
# ensure that authorized users can create an election
193-
response = await client.post("/elections/testElection4", params={
193+
response = await client.post("/elections/testElection4", json={
194194
"election_type": "general_election",
195195
"datetime_start_nominations": (datetime.now() - timedelta(days=1)).isoformat(),
196196
"datetime_start_voting": (datetime.now() + timedelta(days=7)).isoformat(),
@@ -200,7 +200,7 @@ async def test_endpoints_admin(client, database_setup):
200200
})
201201
assert response.status_code == 200
202202
# ensure that user can create elections without knowing each position type
203-
response = await client.post("/elections/byElection4", params={
203+
response = await client.post("/elections/byElection4", json={
204204
"election_type": "by_election",
205205
"datetime_start_nominations": (datetime.now() - timedelta(days=1)).isoformat(),
206206
"datetime_start_voting": (datetime.now() + timedelta(days=7)).isoformat(),
@@ -210,7 +210,7 @@ async def test_endpoints_admin(client, database_setup):
210210
assert response.status_code == 200
211211

212212
# try creating an invalid election name
213-
response = await client.post("/elections/list", params={
213+
response = await client.post("/elections/list", json={
214214
"election_type": "by_election",
215215
"datetime_start_nominations": (datetime.now() - timedelta(days=1)).isoformat(),
216216
"datetime_start_voting": (datetime.now() + timedelta(days=7)).isoformat(),
@@ -223,21 +223,21 @@ async def test_endpoints_admin(client, database_setup):
223223

224224

225225
# try to register for a past election -> should say nomination period expired
226-
response = await client.post("/elections/registration/test election 1", params={
226+
response = await client.post("/elections/registration/test election 1", json={
227227
"position": "president",
228228
})
229229
assert response.status_code == 400
230230
assert "nomination period" in response.json()["detail"]
231231

232232
# try to register for an invalid position
233-
response = await client.post(f"/elections/registration/{election_name}", params={
233+
response = await client.post(f"/elections/registration/{election_name}", json={
234234
"position": "CEO",
235235
})
236236
assert response.status_code == 400
237237
assert "invalid position" in response.json()["detail"]
238238

239239
# try to register in an unknown election
240-
response = await client.post("/elections/registration/unknownElection12345", params={
240+
response = await client.post("/elections/registration/unknownElection12345", json={
241241
"position": "president",
242242
})
243243
assert response.status_code == 404
@@ -246,7 +246,7 @@ async def test_endpoints_admin(client, database_setup):
246246

247247

248248
# register for an election correctly
249-
response = await client.post(f"/elections/registration/{election_name}", params={
249+
response = await client.post(f"/elections/registration/{election_name}", json={
250250
"position": "president",
251251
})
252252
assert response.status_code == 200
@@ -255,14 +255,14 @@ async def test_endpoints_admin(client, database_setup):
255255
assert response.status_code == 200
256256

257257
# duplicate registration
258-
response = await client.post(f"/elections/registration/{election_name}", params={
258+
response = await client.post(f"/elections/registration/{election_name}", json={
259259
"position": "president",
260260
})
261261
assert response.status_code == 400
262262
assert "registered" in response.json()["detail"]
263263

264264
# update the above election
265-
response = await client.patch("/elections/testElection4", params={
265+
response = await client.patch("/elections/testElection4", json={
266266
"election_type": "general_election",
267267
"datetime_start_nominations": (datetime.now() - timedelta(days=1)).isoformat(),
268268
"datetime_start_voting": (datetime.now() + timedelta(days=7)).isoformat(),
@@ -273,14 +273,14 @@ async def test_endpoints_admin(client, database_setup):
273273
assert response.status_code == 200
274274

275275
# update the registration
276-
response = await client.patch(f"/elections/registration/{election_name}/pkn4", params={
276+
response = await client.patch(f"/elections/registration/{election_name}/pkn4", json={
277277
"position": "president",
278278
"speech": "Vote for me as treasurer"
279279
})
280280
assert response.status_code == 200
281281

282282
# try updating a non-registered election
283-
response = await client.patch("/elections/registration/testElection4/pkn4", params={
283+
response = await client.patch("/elections/registration/testElection4/pkn4", json={
284284
"position": "president",
285285
"speech": "Vote for me as president, I am good at valorant."
286286
})
@@ -299,7 +299,7 @@ async def test_endpoints_admin(client, database_setup):
299299
assert response.status_code == 200
300300

301301
# update nominee info
302-
response = await client.put("/elections/nominee/info", params={
302+
response = await client.put("/elections/nominee/info", json={
303303
"full_name": "Puneet N",
304304
"linked_in": "linkedin.com/not-my-linkedin",
305305
})

0 commit comments

Comments
 (0)