Skip to content

Commit 2ba2e46

Browse files
Finish integration tests for officer module (#102)
* Fix profile_picture_url not updating on patch * add tests for POST /officers/term & insert site_user * add PATCH /officers/info tests & is_active checks for github & discord modules * finish all integration tests, woo! * test loading data locally & add a script for migrating --------- Co-authored-by: Micah Baker <[email protected]>
1 parent 8c2048f commit 2ba2e46

File tree

17 files changed

+450
-63
lines changed

17 files changed

+450
-63
lines changed

src/__init__.py

Whitespace-only changes.

src/alembic/versions/166f3772fce7_auth_officer_init.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ def upgrade() -> None:
4545
sa.Column("start_date", sa.Date(), nullable=False),
4646
sa.Column("end_date", sa.Date(), nullable=True),
4747
sa.Column("nickname", sa.String(length=128), nullable=True),
48-
sa.Column("favourite_course_0", sa.String(length=32), nullable=True),
49-
sa.Column("favourite_course_1", sa.String(length=32), nullable=True),
50-
sa.Column("favourite_pl_0", sa.String(length=32), nullable=True),
51-
sa.Column("favourite_pl_1", sa.String(length=32), nullable=True),
48+
sa.Column("favourite_course_0", sa.String(length=64), nullable=True),
49+
sa.Column("favourite_course_1", sa.String(length=64), nullable=True),
50+
sa.Column("favourite_pl_0", sa.String(length=64), nullable=True),
51+
sa.Column("favourite_pl_1", sa.String(length=64), nullable=True),
5252
sa.Column("biography", sa.Text(), nullable=True),
5353
sa.Column("photo_url", sa.Text(), nullable=True),
5454
)
5555
op.create_table(
5656
"officer_info",
5757
sa.Column("legal_name", sa.String(length=128), nullable=False),
58-
sa.Column("discord_id", sa.String(length=18), nullable=True),
58+
sa.Column("discord_id", sa.String(length=32), nullable=True),
5959
sa.Column("discord_name", sa.String(length=32), nullable=True),
6060
sa.Column("discord_nickname", sa.String(length=32), nullable=True),
6161
sa.Column("computing_id", sa.String(length=32), sa.ForeignKey("site_user.computing_id"), primary_key=True),

src/auth/__init__.py

Whitespace-only changes.

src/auth/crud.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,35 +101,36 @@ async def get_site_user(db_session: AsyncSession, session_id: str) -> None | Sit
101101
)
102102

103103

104+
async def site_user_exists(db_session: AsyncSession, computing_id: str) -> bool:
105+
user = await db_session.scalar(
106+
sqlalchemy
107+
.select(SiteUser)
108+
.where(SiteUser.computing_id == computing_id)
109+
)
110+
return user is not None
111+
112+
104113
# update the optional user info for a given site user (e.g., display name, profile picture, ...)
105114
async def update_site_user(
106115
db_session: AsyncSession,
107116
session_id: str,
108117
profile_picture_url: str
109-
) -> None | SiteUserData:
118+
) -> bool:
110119
query = (
111120
sqlalchemy
112121
.select(UserSession)
113122
.where(UserSession.session_id == session_id)
114123
)
115124
user_session = await db_session.scalar(query)
116125
if user_session is None:
117-
return None
126+
return False
118127

119128
query = (
120129
sqlalchemy
121130
.update(SiteUser)
122131
.where(SiteUser.computing_id == user_session.computing_id)
123132
.values(profile_picture_url = profile_picture_url)
124-
.returning(SiteUser) # returns all columns of SiteUser
125133
)
126-
user = await db_session.scalar(query)
127-
if user is None:
128-
return None
134+
await db_session.execute(query)
129135

130-
return SiteUserData(
131-
user_session.computing_id,
132-
user.first_logged_in.isoformat(),
133-
user.last_logged_in.isoformat(),
134-
user.profile_picture_url
135-
)
136+
return True

src/auth/urls.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import requests # TODO: make this async
77
import xmltodict
88
from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
9-
from fastapi.responses import JSONResponse, RedirectResponse
9+
from fastapi.responses import JSONResponse, PlainTextResponse, RedirectResponse
1010

1111
import database
1212
from auth import crud
@@ -130,8 +130,9 @@ async def update_user(
130130
if session_id is None:
131131
raise HTTPException(status_code=401, detail="User must be authenticated to get their info")
132132

133-
user_info = await crud.update_site_user(db_session, session_id, profile_picture_url)
134-
if user_info is None:
133+
ok = await crud.update_site_user(db_session, session_id, profile_picture_url)
134+
await db_session.commit()
135+
if not ok:
135136
raise HTTPException(status_code=401, detail="Could not find user with session_id, please log in")
136137

137-
return JSONResponse(user_info.serializable_dict())
138+
return PlainTextResponse("ok")

src/constants.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
COMPUTING_ID_MAX = 8
1616

1717
# see https://support.discord.com/hc/en-us/articles/4407571667351-How-to-Find-User-IDs-for-Law-Enforcement#:~:text=Each%20Discord%20user%20is%20assigned,user%20and%20cannot%20be%20changed.
18-
DISCORD_ID_LEN = 18
18+
# NOTE: the length got updated to 19 in july 2024. See https://www.reddit.com/r/discordapp/comments/ucrp1r/only_3_months_until_discord_ids_hit_19_digits/
19+
# I set us to 32 just in case...
20+
DISCORD_ID_LEN = 32
1921

2022
# https://github.com/discord/discord-api-docs/blob/main/docs/resources/User.md
2123
DISCORD_NAME_LEN = 32

src/discord/discord.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ class Channel:
4343
name: str
4444
permission_overwrites: list[str] | None = None
4545

46+
def is_active() -> bool:
47+
# if there is no discord token, then consider the module inactive; calling functions may fail without warning!
48+
return os.environ.get("DISCORD_TOKEN") is not None
49+
4650
async def _discord_request(
4751
url: str,
4852
token: str

src/github/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# TODO: does this allow importing anything from the module?
22
import logging
3+
import os
34

45
from github.internals import add_user_to_team, list_members, list_team_members, list_teams, remove_user_from_team
56
from github.types import GithubUserPermissions
@@ -32,6 +33,10 @@
3233
if kind == "auto"
3334
]
3435

36+
def is_active() -> bool:
37+
# if there is no github token, then consider the module inactive; calling functions may fail without warning!
38+
return os.environ.get("GITHUB_TOKEN") is not None
39+
3540
def officer_teams(position: str) -> list[str]:
3641
if position == OfficerPosition.DIRECTOR_OF_ARCHIVES:
3742
return ["doa", "officers"]

src/officers/constants.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ def position_list() -> list[str]:
3030
def length_in_semesters(position: str) -> int | None:
3131
# TODO (#101): ask the committee to maintain a json file with all the important details from the constitution
3232
"""How many semester position is active for, according to the CSSS Constitution"""
33-
return _LENGTH_MAP[position]
33+
if position not in _LENGTH_MAP:
34+
# this can occur for legacy positions
35+
return None
36+
else:
37+
return _LENGTH_MAP[position]
3438

3539
@staticmethod
3640
def to_email(position: str) -> str | None:

src/officers/crud.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import logging
2+
from datetime import datetime
23

34
import sqlalchemy
45
from fastapi import HTTPException
56

7+
import auth.crud
8+
import auth.tables
69
import database
710
import utils
811
from data import semesters
@@ -157,6 +160,14 @@ async def create_new_officer_info(
157160
new_officer_info: OfficerInfo
158161
) -> bool:
159162
"""Return False if the officer already exists & don't do anything."""
163+
if not await auth.crud.site_user_exists(db_session, new_officer_info.computing_id):
164+
# if computing_id has not been created as a site_user yet, add them
165+
db_session.add(auth.tables.SiteUser(
166+
computing_id=new_officer_info.computing_id,
167+
first_logged_in=datetime.now(),
168+
last_logged_in=datetime.now()
169+
))
170+
160171
existing_officer_info = await db_session.scalar(
161172
sqlalchemy
162173
.select(OfficerInfo)

0 commit comments

Comments
 (0)