diff --git a/.forgejo/workflows/publish.yaml b/.forgejo/workflows/publish.yaml deleted file mode 100644 index 4069cb3..0000000 --- a/.forgejo/workflows/publish.yaml +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: ci -on: - push: - branches: - - main - -jobs: - build_and_publish: - runs-on: docker - container: - image: ghcr.io/catthehacker/ubuntu:runner-latest - steps: - - uses: actions/checkout@v4 - - uses: astral-sh/ruff-action@v3 - - uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 - with: - version: "0.11.19" - - run: uv python install # Gets Python version from pyproject.toml - - run: uv sync - - run: uv run pytest test - env: - ENVIRONMENT: testing diff --git a/pyproject.toml b/pyproject.toml index b3b7b9d..5d0bf09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,11 +2,6 @@ add-bounds = "major" exclude-newer = "P2W" -[tool.ruff] -exclude = [ - ".alembic" -] - [project] name = "cloud-api" version = "0.1.0" diff --git a/src/auth/dependencies.py b/src/auth/dependencies.py index 959a830..754892f 100644 --- a/src/auth/dependencies.py +++ b/src/auth/dependencies.py @@ -66,9 +66,6 @@ def get_super_admin_list(): def empty_su_list(): return [] -def testing_su_list(): - return ["admin@test.com"] - su_list_dependency = Annotated[list[User], Depends(get_super_admin_list)] async def user_model_super_admin(user_model: user_model_claims_dependency, super_admin_emails: su_list_dependency): diff --git a/src/iam/router.py b/src/iam/router.py index 016cebc..ce0074e 100644 --- a/src/iam/router.py +++ b/src/iam/router.py @@ -39,7 +39,7 @@ from src.iam.schemas import IAMGetGroupPermissionsResponse, IAMGetGroupUsersResp GroupSchema, IAMPostGroupResponse, IAMPutGroupPermissionRequest, IAMPutGroupPermissionResponse, \ IAMPutGroupUserRequest, IAMPutGroupUserResponse, IAMDeleteGroupPermissionRequest, IAMDeleteGroupPermissionResponse, \ IAMDeleteGroupUserRequest, IAMDeleteGroupUserResponse, IAMGetPermissionsResponse, IAMPostPermissionRequest, \ - IAMPostPermissionResponse, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse + IAMPostPermissionResponse, PermissionSchema, IAMDeletePermissionRequest, IAMGetPermissionsSearchRequest, IAMGetPermissionsSearchResponse router = APIRouter( tags=["IAM"], @@ -195,7 +195,7 @@ async def delete_permission(db: db_dependency, su: super_admin_dependency, perm_ @router.post("/permissions/search", response_model=IAMGetPermissionsSearchResponse) -async def post_permissions(db: db_dependency, org_model: org_model_root_claim_body_dependency, request_model: IAMGetPermissionsSearchRequest): +async def get_permissions(db: db_dependency, org_model: org_model_root_claim_body_dependency, request_model: IAMGetPermissionsSearchRequest): permission_query = db.query(Perm) if request_model.service_id is not None: diff --git a/src/iam/service.py b/src/iam/service.py index c6a1030..d887bfd 100644 --- a/src/iam/service.py +++ b/src/iam/service.py @@ -11,7 +11,7 @@ from src.database import db_dependency from src.schemas import ResourceName from src.auth.exceptions import UnauthorizedException -from fastapi import Request, Depends +from fastapi import HTTPException, status, Request, Depends def valid_service_key(db: db_dependency, request: Request, rn: ResourceName) -> bool: diff --git a/src/organisation/router.py b/src/organisation/router.py index f4c2e34..8e8bd36 100644 --- a/src/organisation/router.py +++ b/src/organisation/router.py @@ -22,6 +22,7 @@ from fastapi.params import Query from psycopg.errors import UniqueViolation from sqlalchemy.exc import IntegrityError +from src.auth.exceptions import UnauthorizedException from src.contact.schemas import ContactModel from src.exceptions import UnprocessableContentException, ConflictException from src.contact.models import Contact diff --git a/src/user/models.py b/src/user/models.py index 97c0f22..d7e0cd4 100644 --- a/src/user/models.py +++ b/src/user/models.py @@ -42,5 +42,5 @@ class User(Base): def groups(self): result = defaultdict(list) for group in self.group_rel: - result[group.org_rel.name].append({"name": group.name, "id": group.id}) + result[group.org_rel.name].append(group.name) return dict(result) diff --git a/src/user/schemas.py b/src/user/schemas.py index 211004c..0dfffb9 100644 --- a/src/user/schemas.py +++ b/src/user/schemas.py @@ -48,7 +48,7 @@ class UserResponse(CustomBaseModel): last_name: str email: str organisations: list[Optional[dict[str, str|int]]] - groups: Optional[dict[str, list[dict[str, str|int]]]] = None + groups: Optional[dict[str, list[str]]] = None class OrgResponse(CustomBaseModel): diff --git a/test/conftest.py b/test/conftest.py index e16e799..26a4f64 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -11,7 +11,7 @@ from src.organisation.models import Organisation as Org from src.contact.models import Contact from src.iam.models import Group, Permission from src.auth.service import get_current_user, get_dev_user -from src.auth.dependencies import empty_su_list, get_super_admin_list, testing_su_list +from src.auth.dependencies import empty_su_list, get_super_admin_list from src.main import app # inited FastAPI app from src.database import engine, Base, get_db @@ -39,7 +39,6 @@ async def default_client(db_session) -> AsyncGenerator[AsyncClient, None]: return db_session app.dependency_overrides[get_db] = get_db_override app.dependency_overrides[get_current_user] = get_dev_user - app.dependency_overrides[get_super_admin_list] = testing_su_list transport = ASGITransport(app=app) async with AsyncClient(transport=transport, base_url="http://localhost:8000/api/v1") as ac: yield ac diff --git a/test/test_auth_approval.py b/test/test_auth_approval.py index f395865..d3df353 100644 --- a/test/test_auth_approval.py +++ b/test/test_auth_approval.py @@ -6,6 +6,8 @@ Delete endpoints are currently skipped because the testing system cannot use bod import pytest from httpx import AsyncClient +from .conftest import default_client + from src.organisation.models import Organisation as Org, OrgUsers from src.user.models import User from src.iam.models import Group @@ -81,7 +83,7 @@ async def test_get_org_groups_auth_approval(default_client: AsyncClient): @pytest.mark.anyio async def test_get_org_contact_auth_approval(default_client: AsyncClient): - resp = await default_client.get("/org/contact?org_id=1&contact_type=billing") + resp = await default_client.get(f"/org/contact?org_id=1&contact_type=billing") assert resp.status_code != 422 assert resp.status_code == 200 diff --git a/test/test_auth_general.py b/test/test_auth_general.py index 6aeba96..533307b 100644 --- a/test/test_auth_general.py +++ b/test/test_auth_general.py @@ -3,8 +3,11 @@ import pytest from httpx import AsyncClient +from .conftest import no_su_client + from src.organisation.models import Organisation as Org from src.user.models import User +from src.iam.models import Group @pytest.mark.anyio diff --git a/test/test_auth_root.py b/test/test_auth_root.py index 16e3afa..ea7abd9 100644 --- a/test/test_auth_root.py +++ b/test/test_auth_root.py @@ -5,6 +5,8 @@ DELETE endpoints are not tested import pytest from httpx import AsyncClient +from .conftest import no_su_client + from src.organisation.models import Organisation as Org from src.user.models import User from src.iam.models import Group @@ -68,7 +70,7 @@ async def test_get_org_groups_auth_root(no_su_client: AsyncClient): @pytest.mark.anyio async def test_get_org_contact_auth_root(no_su_client: AsyncClient): - resp = await no_su_client.get("/org/contact?org_id=2&contact_type=billing") + resp = await no_su_client.get(f"/org/contact?org_id=2&contact_type=billing") assert resp.status_code != 422 assert resp.status_code == 401 assert "Must be the org's root user" in resp.json()["detail"] diff --git a/test/test_auth_user.py b/test/test_auth_user.py index 6f1c655..c219dcb 100644 --- a/test/test_auth_user.py +++ b/test/test_auth_user.py @@ -4,6 +4,8 @@ This testing module removes the testing user override to verify that endpoints w import pytest from httpx import AsyncClient +from .conftest import no_user_client + @pytest.mark.anyio async def test_get_self_db_auth_user(no_user_client: AsyncClient): diff --git a/test/test_healthcheck.py b/test/test_healthcheck.py index 6fdb9be..4435a18 100644 --- a/test/test_healthcheck.py +++ b/test/test_healthcheck.py @@ -1,6 +1,7 @@ import pytest from httpx import AsyncClient +from .conftest import default_client @pytest.mark.anyio async def test_healthcheck(default_client: AsyncClient): diff --git a/test/test_iam.py b/test/test_iam.py index 24a6478..70fbf6d 100644 --- a/test/test_iam.py +++ b/test/test_iam.py @@ -7,7 +7,7 @@ from src.user.models import User from src.organisation.models import Organisation as Org from src.iam.models import Group -from .conftest import generate_query_and_status +from .conftest import default_client, db_session, generate_query_and_status @pytest.mark.anyio @@ -25,7 +25,7 @@ async def test_post_act_on_resource_endpoint_success(default_client: AsyncClient data = resp.json() assert resp.status_code == 200 - assert data is True + assert data == True @pytest.mark.parametrize( @@ -133,7 +133,7 @@ async def test_get_group_permissions_success(default_client: AsyncClient): assert resp.status_code == 200 assert "permissions" in data - assert isinstance(data["permissions"], list) + assert type(data["permissions"]) == list assert data["permissions"][0]["service_name"] == "Test Service" assert data["permissions"][0]["resource"] == "test_resource" assert data["permissions"][0]["action"] == "read" @@ -175,7 +175,7 @@ async def test_get_group_users_success(default_client: AsyncClient): assert resp.status_code == 200 assert "users" in data - assert isinstance(data["users"], list) + assert type(data["users"]) == list assert data["users"][0]["id"] == 1 assert data["users"][0]["first_name"] == "Admin" assert data["users"][0]["last_name"] == "Test" @@ -218,7 +218,7 @@ async def test_post_group_success(default_client: AsyncClient): assert resp.status_code == 200 assert "group" in data - assert isinstance(data["group"], dict) + assert type(data["group"]) == dict assert data["group"]["name"] == "New Group" assert data["group"]["id"] == 2 @@ -255,12 +255,12 @@ async def test_put_group_perm_success(default_client: AsyncClient, db_session): assert resp.status_code == 200 assert "group" in data - assert isinstance(data["group"], dict) + assert type(data["group"]) == dict assert data["group"]["name"] == "Test Group Two" assert data["group"]["id"] == 2 assert "permissions" in data - assert isinstance(data["permissions"], list) + assert type(data["permissions"]) == list assert data["permissions"][0]["service_name"] == "Test Service" assert data["permissions"][0]["resource"] == "test_resource" assert data["permissions"][0]["action"] == "read" @@ -316,7 +316,7 @@ async def test_put_group_perm_mismatch(default_client: AsyncClient, db_session, db_session.add(Org(name="Another Test Org", root_user_id=1, billing_contact_id=1, owner_contact_id=2, security_contact_id=3, status="approved")) db_session.add(Group(name="Another Test Group", org_id=2)) db_session.flush() - resp = await default_client.put("/iam/group/permission", json=body) + resp = await default_client.put(f"/iam/group/permission", json=body) assert resp.status_code == 401 assert resp.json()["detail"] == "Group does not belong to this organization" @@ -332,12 +332,12 @@ async def test_put_group_user_success(default_client: AsyncClient, db_session): assert resp.status_code == 200 assert "group" in data - assert isinstance(data["group"], dict) + assert type(data["group"]) == dict assert data["group"]["name"] == "Test Group" assert data["group"]["id"] == 1 assert "users" in data - assert isinstance(data["users"], list) + assert type(data["users"]) == list assert data["users"][1]["id"] == 2 assert data["users"][1]["first_name"] == "User" assert data["users"][1]["last_name"] == "Test" @@ -387,7 +387,7 @@ async def test_get_permissions_success(default_client: AsyncClient): assert resp.status_code == 200 assert "permissions" in data - assert isinstance(data["permissions"], list) + assert type(data["permissions"]) == list assert data["permissions"][0]["service_name"] == "Test Service" assert data["permissions"][0]["resource"] == "test_resource" assert data["permissions"][0]["action"] == "read" @@ -470,7 +470,7 @@ async def test_post_perm_search_success(default_client: AsyncClient, db_session, assert resp.status_code == 200 assert "permissions" in data - assert isinstance(data["permissions"], list) + assert type(data["permissions"]) == list assert data["permissions"][0]["service_name"] == "Test Service" assert data["permissions"][0]["resource"] == "test_resource" assert data["permissions"][0]["action"] == "read" diff --git a/test/test_organisation.py b/test/test_organisation.py index 4b60aa7..8e697e7 100644 --- a/test/test_organisation.py +++ b/test/test_organisation.py @@ -6,7 +6,7 @@ from httpx import AsyncClient from src.organisation.models import Organisation, OrgUsers from src.user.models import User -from .conftest import generate_query_and_status +from .conftest import default_client, generate_query_and_status @pytest.mark.anyio @@ -151,7 +151,7 @@ async def test_get_org_users_success(default_client: AsyncClient): assert resp.status_code == 200 assert "users" in data - assert isinstance(data["users"], list) + assert type(data["users"]) == list assert len(data["users"]) == 1 assert data["users"][0] == "admin@test.com" @@ -181,7 +181,7 @@ async def test_post_org_user_success(default_client: AsyncClient, db_session): assert resp.status_code == 200 assert "users" in data - assert isinstance(data["users"], list) + assert type(data["users"]) == list assert "user@test.org" in data["users"] @@ -264,7 +264,7 @@ async def test_get_org_groups_success(default_client: AsyncClient): assert resp.status_code == 200 assert "groups" in data - assert isinstance(data["groups"], list) + assert type(data["groups"]) == list assert "Test Group" in data["groups"] @@ -379,7 +379,7 @@ async def test_patch_org_contact_success(default_client: AsyncClient, key: str, ], ) @pytest.mark.anyio -async def test_patch_org_contact_status_checks(default_client: AsyncClient, body: dict[str, str], expected_status: int): +async def test_patch_org_status_status_checks(default_client: AsyncClient, body: dict[str, str], expected_status: int): resp = await default_client.patch("/org/contact", json=body) assert resp.status_code == expected_status diff --git a/test/test_service.py b/test/test_service.py index c13eb80..5a1e7ad 100644 --- a/test/test_service.py +++ b/test/test_service.py @@ -4,7 +4,7 @@ import pytest from httpx import AsyncClient -from .conftest import generate_query_and_status +from .conftest import default_client, generate_query_and_status @pytest.mark.anyio @@ -38,7 +38,7 @@ async def test_post_service_success(default_client: AsyncClient): assert "service" in data assert data["service"]["name"] == "New Test Service" assert data["service"]["id"] == 2 - assert isinstance(data["service"]["api_key"], str) + assert type(data["service"]["api_key"]) == str @pytest.mark.parametrize( @@ -64,7 +64,7 @@ async def test_patch_service_success(default_client: AsyncClient): assert "service" in data assert data["service"]["name"] == "Test Service" assert data["service"]["id"] == 1 - assert isinstance(data["service"]["api_key"], str) + assert type(data["service"]["api_key"]) == str @pytest.mark.parametrize( diff --git a/test/test_user.py b/test/test_user.py index a50e654..bdcd500 100644 --- a/test/test_user.py +++ b/test/test_user.py @@ -6,7 +6,7 @@ import pytest from httpx import AsyncClient -from .conftest import generate_query_and_status +from .conftest import default_client, generate_query_and_status @pytest.mark.anyio async def test_get_self_db_success(default_client: AsyncClient):