Skip to content

Commit 1b71364

Browse files
Add organization name to client resolve response (#69)
1 parent ed833ca commit 1b71364

6 files changed

Lines changed: 22 additions & 7 deletions

File tree

app/db/models/client.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ class ClientEntity(CommonColumns):
3636
source_id: Mapped[str | None] = mapped_column("source_id", String, nullable=True)
3737

3838
organization: Mapped["OrganizationEntity"] = relationship(back_populates="clients", lazy="raise")
39+
40+
@property
41+
def organization_name(self) -> str | None:
42+
return self.organization.name if self.organization else None

app/db/repository/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from sqlalchemy import ColumnElement, and_, select, update
55
from sqlalchemy.exc import SQLAlchemyError
6+
from sqlalchemy.orm import joinedload
67

78
from app.db.decorator import repository
89
from app.db.models.client import ClientEntity
@@ -44,6 +45,7 @@ def get_by_credentials(self, common_name: str, oin: Oin, org_ura: UraNumber) ->
4445
ClientEntity.deleted_at.is_(None),
4546
)
4647
)
48+
.options(joinedload(ClientEntity.organization))
4749
)
4850
return self.db_session.execute(stmt).scalar()
4951

app/models/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
OIN_DESCRIPTION = "The OIN of the client"
1212
SOURCE_ID_DESCRIPTION = "The optional source ID of the client"
1313
SCOPES_DESCRIPTION = "The space separated scopes granted to the client"
14+
ORGANIZATION_NAME_DESCRIPTION = "The name of the organization the client acts on behalf of"
1415

1516

1617
class ClientResolveRequest(BaseModel):
@@ -23,6 +24,7 @@ class ClientResolveResponse(BaseModel):
2324
model_config = ConfigDict(from_attributes=True)
2425
scopes: str | None = Field(default=None, description=SCOPES_DESCRIPTION)
2526
source_id: str | None = Field(default=None, description=SOURCE_ID_DESCRIPTION)
27+
organization_name: str | None = Field(default=None, description=ORGANIZATION_NAME_DESCRIPTION)
2628

2729

2830
class ClientCreate(BaseModel):

tests/conftest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,16 @@ def make_client_entity(
131131
source_id: str | None = None,
132132
scopes: str | None = None,
133133
deleted_at: datetime | None = None,
134+
org_entity: OrganizationEntity | None = None,
134135
) -> ClientEntity:
135136
return ClientEntity(
136137
id=id or uuid4(),
137-
organization_id=organization_id or uuid4(),
138+
organization_id=organization_id or (org_entity.id if org_entity else uuid4()),
138139
oin=oin,
139140
common_name=common_name,
140141
source_id=source_id,
141142
scopes=scopes,
142143
created_at=FIXED_CREATED_AT,
143144
deleted_at=deleted_at,
145+
organization=org_entity,
144146
)

tests/db/test_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from app.db.models.client import ClientEntity
88
from app.db.repository.client import ClientRepository
99
from app.models.ura import UraNumber
10-
from tests.conftest import TEST_COMMON_NAME, TEST_OIN, TEST_REGISTER_ID
10+
from tests.conftest import TEST_COMMON_NAME, TEST_OIN, TEST_ORG_NAME, TEST_REGISTER_ID
1111

1212

1313
def test_add_one(
@@ -167,6 +167,7 @@ def test_get_by_credentials_matches_via_organization_register_id(
167167
)
168168
assert result is not None
169169
assert result.id == client_entity.id
170+
assert result.organization.name == TEST_ORG_NAME
170171

171172

172173
def test_get_by_credentials_unknown_org_ura_returns_none(

tests/routers/test_resolve.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
from fastapi.testclient import TestClient
55

6-
from tests.conftest import TEST_REGISTER_ID, VALID_OIN, make_client_entity
6+
from tests.conftest import TEST_REGISTER_ID, VALID_OIN, make_client_entity, make_organization_entity
77

88
RESOLVE = "/clients/resolve"
99

@@ -16,25 +16,29 @@ def _body(**overrides: object) -> dict[str, object]:
1616

1717
@pytest.mark.parametrize("scopes", ["read write", "read", ""])
1818
def test_resolve_returns_scopes_and_source_id(api: TestClient, mock_client_service: MagicMock, scopes: str) -> None:
19-
mock_client_service.resolve.return_value = make_client_entity(scopes=scopes, source_id="source-1")
19+
org_entity = make_organization_entity(name="Test Organization")
20+
client_entity = make_client_entity(scopes=scopes, source_id="source-1", org_entity=org_entity)
21+
mock_client_service.resolve.return_value = client_entity
2022

2123
response = api.post(RESOLVE, json=_body())
2224

2325
assert response.status_code == 200
24-
assert response.json() == {"scopes": scopes, "source_id": "source-1"}
26+
assert response.json() == {"scopes": scopes, "source_id": "source-1", "organization_name": "Test Organization"}
2527
call = mock_client_service.resolve.call_args
2628
assert str(call.kwargs["oin"]) == str(VALID_OIN)
2729
assert call.kwargs["common_name"] == "Client"
2830
assert str(call.kwargs["org_ura"]) == str(TEST_REGISTER_ID)
2931

3032

3133
def test_resolve_returns_no_source_id_when_absent(api: TestClient, mock_client_service: MagicMock) -> None:
32-
mock_client_service.resolve.return_value = make_client_entity(scopes="read", source_id=None)
34+
org_entity = make_organization_entity(name="Test Organization")
35+
client_entity = make_client_entity(scopes="read", source_id=None, org_entity=org_entity)
36+
mock_client_service.resolve.return_value = client_entity
3337

3438
response = api.post(RESOLVE, json=_body())
3539

3640
assert response.status_code == 200
37-
assert response.json() == {"scopes": "read"}
41+
assert response.json() == {"scopes": "read", "organization_name": "Test Organization"}
3842

3943

4044
def test_resolve_unknown_client_returns_404(api: TestClient, mock_client_service: MagicMock) -> None:

0 commit comments

Comments
 (0)