diff --git a/db/types.py b/db/types.py index df76c5f1..794397a6 100644 --- a/db/types.py +++ b/db/types.py @@ -29,6 +29,7 @@ class PlatformEnum(str, Enum): GALAXY = "galaxy" BPA_DATA_PORTAL = "bpa_data_portal" SBP = "sbp" + EDNA_EXPLORER = "edna_explorer" class PlatformMembershipData(BaseModel): diff --git a/migrations/versions/39caad32922e_edna_explorer.py b/migrations/versions/39caad32922e_edna_explorer.py new file mode 100644 index 00000000..1c736155 --- /dev/null +++ b/migrations/versions/39caad32922e_edna_explorer.py @@ -0,0 +1,31 @@ +"""edna-explorer + +Revision ID: 39caad32922e +Revises: 4000ecb2796d +Create Date: 2026-07-17 10:24:19.065484 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision: str = '39caad32922e' +down_revision: Union[str, None] = '4000ecb2796d' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/routers/admin.py b/routers/admin.py index aae0bef6..ce6bd689 100644 --- a/routers/admin.py +++ b/routers/admin.py @@ -78,6 +78,7 @@ "galaxy": {"enum": PlatformEnum.GALAXY, "name": "Galaxy Australia"}, "bpa_data_portal": {"enum": PlatformEnum.BPA_DATA_PORTAL, "name": "Bioplatforms Australia Data Portal"}, "sbp": {"enum": PlatformEnum.SBP, "name": "Structural Biology Platform"}, + "edna_explorer": {"enum": PlatformEnum.EDNA_EXPLORER, "name": "eDNA Explorer"}, } GROUP_MAPPING: dict[BundleType, dict] = { diff --git a/tests/admin_api/test_admin.py b/tests/admin_api/test_admin.py index 041f0dac..7b001ca2 100644 --- a/tests/admin_api/test_admin.py +++ b/tests/admin_api/test_admin.py @@ -503,7 +503,7 @@ def test_get_filter_options(test_client, as_admin_user, test_db_session, persist options = resp.json() assert isinstance(options, list) - assert len(options) == 5 + assert len(options) == 6 for option in options: assert "id" in option @@ -512,13 +512,14 @@ def test_get_filter_options(test_client, as_admin_user, test_db_session, persist assert isinstance(option["name"], str) option_ids = {opt["id"] for opt in options} - expected_ids = {"galaxy", "bpa_data_portal", "sbp", "tsi", "sbp_workflow_execution"} + expected_ids = {"galaxy", "bpa_data_portal", "sbp", "edna_explorer", "tsi", "sbp_workflow_execution"} assert option_ids == expected_ids option_dict = {opt["id"]: opt["name"] for opt in options} assert option_dict["galaxy"] == "Galaxy Australia" assert option_dict["bpa_data_portal"] == "Bioplatforms Australia Data Portal" assert option_dict["sbp"] == "Structural Biology Platform" + assert option_dict["edna_explorer"] == "eDNA Explorer" assert option_dict["sbp_workflow_execution"] == "Structural Biology Platform Bundle" assert option_dict["tsi"] == "Threatened Species Initiative" diff --git a/tests/test_biocommons_admin.py b/tests/test_biocommons_admin.py index be41e5c4..dbe6a64a 100644 --- a/tests/test_biocommons_admin.py +++ b/tests/test_biocommons_admin.py @@ -120,6 +120,28 @@ def test_create_platform(test_client, as_admin_user, test_db_session, persistent assert [role.name for role in platform_from_db.admin_roles] == [admin_role.name] +def test_create_platform_edna_explorer(test_client, as_admin_user, test_db_session, persistent_factories): + """eDNA Explorer is created like any other platform; access stays manual-approval only (not in DEFAULT_PLATFORMS).""" + Auth0RoleFactory.create_sync(name="biocommons/platform/edna_explorer") + admin_role = Auth0RoleFactory.create_sync(name="biocommons/role/edna_explorer/admin") + resp = test_client.post( + "/biocommons-admin/platforms/create", + json={ + "id": PlatformEnum.EDNA_EXPLORER.value, + "name": "eDNA Explorer", + "admin_roles": [admin_role.name], + }, + ) + assert resp.status_code == HTTPStatus.OK + body = resp.json() + assert body["id"] == PlatformEnum.EDNA_EXPLORER.value + assert body["name"] == "eDNA Explorer" + platform_from_db = test_db_session.get(Platform, PlatformEnum.EDNA_EXPLORER) + assert platform_from_db is not None + test_db_session.refresh(platform_from_db) + assert [role.name for role in platform_from_db.admin_roles] == [admin_role.name] + + def test_create_platform_duplicate_id(test_client, as_admin_user, test_db_session, persistent_factories): Auth0RoleFactory.create_sync(name="biocommons/platform/galaxy") admin_role = Auth0RoleFactory.create_sync(name="biocommons/role/galaxy/admin") diff --git a/tests/test_biocommons_register.py b/tests/test_biocommons_register.py index 30f3b1ab..5981a4da 100644 --- a/tests/test_biocommons_register.py +++ b/tests/test_biocommons_register.py @@ -773,3 +773,35 @@ def test_registration_endpoint_no_bundle(test_db_session, test_client, galaxy_pl assert platform in platform_ids for pm in user.platform_memberships: assert pm.approval_status.value == "approved" + + +def test_edna_explorer_not_in_default_platforms(): + """eDNA Explorer must be manual-approval only, so it must never be auto-granted at registration.""" + assert PlatformEnum.EDNA_EXPLORER not in DEFAULT_PLATFORMS + + +def test_registration_does_not_grant_edna_explorer(test_db_session, test_client, galaxy_platform, bpa_platform, sbp_platform, mock_auth0_client, mock_recaptcha_verify,): + """A newly-registered user must not receive an eDNA Explorer membership (manual approval only).""" + registration = BiocommonsRegistrationRequest( + first_name="No", + last_name="Edna", + email="no.edna@example.com", + username="no_edna", + password="StrongPass1!", + bundles=None, + recaptcha_token="mock-token", + ) + auth0_data = Auth0UserDataFactory.build( + email="no.edna@example.com", + username="no_edna", + name="Test User", + ) + mock_auth0_client.create_user.return_value = Auth0UserDataFactory.build( + user_id=auth0_data.user_id, email=auth0_data.email, username=auth0_data.username + ) + + test_client.post("/biocommons/register", json=registration.model_dump(mode="json")) + + user = test_db_session.get(BiocommonsUser, auth0_data.user_id) + platform_ids = {pm.platform_id for pm in user.platform_memberships} + assert PlatformEnum.EDNA_EXPLORER not in platform_ids