Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions routers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from schemas.biocommons import AppId
from schemas.responses import FieldError
from services.email_queue import enqueue_email
from services.institutions import is_australian_research_institution_email

logger = logging.getLogger("uvicorn.error")

Expand Down Expand Up @@ -139,6 +140,26 @@ async def get_registration_info(
return RegistrationInfo(app="biocommons")


class AustralianResearchInstitutionResponse(BaseModel):
is_australian_research_institution: bool


@router.get(
"/register/check-australian-research-institution",
response_model=AustralianResearchInstitutionResponse,
)
async def check_australian_research_institution(
email: Annotated[str, Query()],
):
"""
Check whether the email domain belongs to an Australian Research Institution
as listed at https://site.usegalaxy.org.au/list-of-institutions.html.
"""
return AustralianResearchInstitutionResponse(
is_australian_research_institution=await is_australian_research_institution_email(email),
)


class SendWelcomeEmailRequest(BaseModel):
email: str

Expand Down
18 changes: 18 additions & 0 deletions services/institutions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import httpx

GALAXY_AU_VALIDATE_URL = "https://site.usegalaxy.org.au/institution/validate"


async def is_australian_research_institution_email(email: str) -> bool:
"""Check email against Galaxy Australia's institution validation API.

Returns False on any network or parse error so registration is never
blocked by an upstream outage.
"""
try:
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get(GALAXY_AU_VALIDATE_URL, params={"email": email})
response.raise_for_status()
return response.json().get("valid", False)
except Exception:
return False
55 changes: 55 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import httpx
import pytest
import respx
from httpx import Response
from sqlmodel import select

from auth0.client import get_auth0_client
from db.models import EmailNotification
from main import app
from routers import utils
from services.institutions import GALAXY_AU_VALIDATE_URL
from tests.datagen import AppMetadataFactory, Auth0UserDataFactory


Expand Down Expand Up @@ -158,6 +162,57 @@ def test_send_welcome_email_returns_404_when_user_not_verified(override_auth0_cl
assert len(queued) == 0


# --- /utils/register/check-australian-research-institution endpoint tests ---

@respx.mock
def test_check_australian_research_institution_valid(test_client):
respx.get(GALAXY_AU_VALIDATE_URL).mock(return_value=Response(200, json={"valid": True}))
resp = test_client.get(
"/utils/register/check-australian-research-institution",
params={"email": "researcher@sydney.edu.au"},
)
assert resp.status_code == 200
assert resp.json() == {"is_australian_research_institution": True}


@respx.mock
def test_check_australian_research_institution_invalid(test_client):
respx.get(GALAXY_AU_VALIDATE_URL).mock(return_value=Response(200, json={"valid": False}))
resp = test_client.get(
"/utils/register/check-australian-research-institution",
params={"email": "user@gmail.com"},
)
assert resp.status_code == 200
assert resp.json() == {"is_australian_research_institution": False}


@respx.mock
def test_check_australian_research_institution_upstream_error_returns_false(test_client):
respx.get(GALAXY_AU_VALIDATE_URL).mock(return_value=Response(500))
resp = test_client.get(
"/utils/register/check-australian-research-institution",
params={"email": "researcher@sydney.edu.au"},
)
assert resp.status_code == 200
assert resp.json() == {"is_australian_research_institution": False}


@respx.mock
def test_check_australian_research_institution_upstream_timeout_returns_false(test_client):
respx.get(GALAXY_AU_VALIDATE_URL).mock(side_effect=httpx.TimeoutException("timeout"))
resp = test_client.get(
"/utils/register/check-australian-research-institution",
params={"email": "researcher@sydney.edu.au"},
)
assert resp.status_code == 200
assert resp.json() == {"is_australian_research_institution": False}


def test_check_australian_research_institution_missing_email(test_client):
resp = test_client.get("/utils/register/check-australian-research-institution")
assert resp.status_code == 422


def test_send_welcome_email_suppresses_duplicate(override_auth0_client, test_client, test_db_session):
"""A second request for the same user should not enqueue a second email."""
user = Auth0UserDataFactory.build(email="user@example.com", email_verified=True)
Expand Down
Loading