Skip to content

Commit 44a20c7

Browse files
feat: add DocumentReference, SnapshotImage, PatientIdentificationCard, and related models (#1481)
1 parent c3e1de6 commit 44a20c7

File tree

16 files changed

+899
-3
lines changed

16 files changed

+899
-3
lines changed

.claude/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"additionalDirectories": [
3+
"../canvas/home-app"
4+
]
5+
}

canvas_sdk/tests/data/test_utils.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from unittest.mock import MagicMock, patch
44

55
import pytest
6+
from pytest_django.fixtures import SettingsWrapper
67

7-
from canvas_sdk.v1.data.utils import generate_mrn, quantize
8+
from canvas_sdk.v1.data.utils import generate_mrn, presigned_url, quantize
89

910

1011
@pytest.mark.parametrize(
@@ -93,3 +94,59 @@ def test_raises_runtime_error_after_max_attempts(mock_patient: MagicMock) -> Non
9394
generate_mrn()
9495

9596
assert mock_patient.objects.filter.call_count == 100
97+
98+
99+
def test_presigned_url_generates_valid_url(settings: SettingsWrapper) -> None:
100+
"""Test that presigned_url generates a valid URL with expected components."""
101+
settings.AWS_ACCESS_KEY_ID = "test-access-key"
102+
settings.AWS_SECRET_ACCESS_KEY = "test-secret-key"
103+
settings.MEDIA_S3_BUCKET_NAME = "test-bucket"
104+
settings.AWS_REGION = "us-west-2"
105+
106+
url = presigned_url("path/to/file.pdf")
107+
108+
assert url.startswith("https://test-bucket.s3.us-west-2.amazonaws.com/")
109+
assert "/path/to/file.pdf?" in url
110+
assert "X-Amz-Algorithm=AWS4-HMAC-SHA256" in url
111+
assert "X-Amz-Credential=" in url
112+
assert "X-Amz-Date=" in url
113+
assert "X-Amz-Expires=3600" in url
114+
assert "X-Amz-SignedHeaders=host" in url
115+
assert "X-Amz-Signature=" in url
116+
117+
118+
def test_presigned_url_with_custom_expiry(settings: SettingsWrapper) -> None:
119+
"""Test that presigned_url respects custom expiry time."""
120+
settings.AWS_ACCESS_KEY_ID = "test-access-key"
121+
settings.AWS_SECRET_ACCESS_KEY = "test-secret-key"
122+
settings.MEDIA_S3_BUCKET_NAME = "test-bucket"
123+
settings.AWS_REGION = "us-west-2"
124+
125+
url = presigned_url("path/to/file.pdf", expires_in=7200)
126+
127+
assert "X-Amz-Expires=7200" in url
128+
129+
130+
def test_presigned_url_removes_bucket_prefix(settings: SettingsWrapper) -> None:
131+
"""Test that presigned_url removes bucket prefix from key."""
132+
settings.AWS_ACCESS_KEY_ID = "test-access-key"
133+
settings.AWS_SECRET_ACCESS_KEY = "test-secret-key"
134+
settings.MEDIA_S3_BUCKET_NAME = "test-bucket"
135+
settings.AWS_REGION = "us-west-2"
136+
137+
url = presigned_url("test-bucket/path/to/file.pdf")
138+
139+
# The bucket prefix should be removed, so path should be /path/to/file.pdf
140+
assert "/path/to/file.pdf?" in url
141+
assert "/test-bucket/path/to/file.pdf?" not in url
142+
143+
144+
def test_presigned_url_raises_error_without_credentials(settings: SettingsWrapper) -> None:
145+
"""Test that presigned_url raises ValueError when AWS credentials are missing."""
146+
settings.AWS_ACCESS_KEY_ID = ""
147+
settings.AWS_SECRET_ACCESS_KEY = ""
148+
settings.MEDIA_S3_BUCKET_NAME = "test-bucket"
149+
settings.AWS_REGION = "us-west-2"
150+
151+
with pytest.raises(ValueError, match="AWS credentials not configured"):
152+
presigned_url("path/to/file.pdf")
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from unittest.mock import patch
2+
3+
from canvas_sdk.v1.data.document_reference import DocumentReference
4+
5+
6+
def test_document_url_with_document() -> None:
7+
"""document_url returns a presigned URL when document is set."""
8+
doc_ref = DocumentReference()
9+
doc_ref.document = "some/key.pdf"
10+
doc_ref.document_absolute_url = "https://example.com/fallback.pdf"
11+
12+
with patch(
13+
"canvas_sdk.v1.data.document_reference.presigned_url",
14+
return_value="https://s3.example.com/presigned",
15+
) as mock:
16+
assert doc_ref.document_url == "https://s3.example.com/presigned"
17+
mock.assert_called_once_with("some/key.pdf")
18+
19+
20+
def test_document_url_falls_back_to_absolute_url() -> None:
21+
"""document_url returns document_absolute_url when document is empty."""
22+
doc_ref = DocumentReference()
23+
doc_ref.document = ""
24+
doc_ref.document_absolute_url = "https://example.com/fallback.pdf"
25+
26+
assert doc_ref.document_url == "https://example.com/fallback.pdf"
27+
28+
29+
def test_document_url_returns_none_when_both_empty() -> None:
30+
"""document_url returns None when both document and document_absolute_url are empty."""
31+
doc_ref = DocumentReference()
32+
doc_ref.document = ""
33+
doc_ref.document_absolute_url = None
34+
35+
assert doc_ref.document_url is None
36+
37+
38+
def test_document_reference_str() -> None:
39+
"""__str__ returns a readable representation."""
40+
doc_ref = DocumentReference()
41+
doc_ref.id = "abc123"
42+
43+
assert str(doc_ref) == "DocumentReference(id=abc123)"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from unittest.mock import patch
2+
3+
from canvas_sdk.v1.data.message import MessageAttachment
4+
5+
6+
def test_file_url_with_file() -> None:
7+
"""file_url returns a presigned URL when file is set."""
8+
attachment = MessageAttachment()
9+
attachment.file = "attachments/doc.pdf"
10+
11+
with patch(
12+
"canvas_sdk.v1.data.message.presigned_url",
13+
return_value="https://s3.example.com/presigned",
14+
) as mock:
15+
assert attachment.file_url == "https://s3.example.com/presigned"
16+
mock.assert_called_once_with("attachments/doc.pdf")
17+
18+
19+
def test_file_url_without_file() -> None:
20+
"""file_url returns None when file is empty."""
21+
attachment = MessageAttachment()
22+
attachment.file = ""
23+
24+
assert attachment.file_url is None

canvas_sdk/tests/v1/data/test_patient.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
from unittest.mock import patch
2+
13
import pytest
24

3-
from canvas_sdk.v1.data.patient import PatientMetadata
5+
from canvas_sdk.v1.data.patient import PatientIdentificationCard, PatientMetadata
46

57

68
@pytest.mark.django_db
@@ -13,3 +15,33 @@ def test_patient_metadata_supports_large_values() -> None:
1315

1416
assert metadata.value == large_value
1517
assert len(metadata.value) == 1000
18+
19+
20+
def test_patient_identification_card_str() -> None:
21+
"""__str__ returns a readable representation."""
22+
card = PatientIdentificationCard()
23+
card.dbid = 5
24+
card.title = "Driver License"
25+
26+
assert str(card) == "PatientIdentificationCard(dbid=5, title=Driver License)"
27+
28+
29+
def test_patient_identification_card_image_url_with_image() -> None:
30+
"""image_url returns a presigned URL when image is set."""
31+
card = PatientIdentificationCard()
32+
card.image = "id_cards/front.jpg"
33+
34+
with patch(
35+
"canvas_sdk.v1.data.patient.presigned_url",
36+
return_value="https://s3.example.com/presigned",
37+
) as mock:
38+
assert card.image_url == "https://s3.example.com/presigned"
39+
mock.assert_called_once_with("id_cards/front.jpg")
40+
41+
42+
def test_patient_identification_card_image_url_without_image() -> None:
43+
"""image_url returns None when image is empty."""
44+
card = PatientIdentificationCard()
45+
card.image = ""
46+
47+
assert card.image_url is None
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
from unittest.mock import patch
2+
3+
from canvas_sdk.v1.data.snapshot import Snapshot, SnapshotImage
4+
5+
6+
def test_snapshot_str() -> None:
7+
"""__str__ returns a readable representation."""
8+
snapshot = Snapshot()
9+
snapshot.dbid = 42
10+
snapshot.title = "Front view"
11+
12+
assert str(snapshot) == "Snapshot(dbid=42, title=Front view)"
13+
14+
15+
def test_snapshot_image_str() -> None:
16+
"""__str__ returns a readable representation."""
17+
image = SnapshotImage()
18+
image.dbid = 7
19+
image.title = "Left side"
20+
image.tag = "wound"
21+
22+
assert str(image) == "SnapshotImage(dbid=7, title=Left side, tag=wound)"
23+
24+
25+
def test_snapshot_image_url_with_image() -> None:
26+
"""image_url returns a presigned URL when image is set."""
27+
image = SnapshotImage()
28+
image.image = "snapshots/img.jpg"
29+
30+
with patch(
31+
"canvas_sdk.v1.data.snapshot.presigned_url",
32+
return_value="https://s3.example.com/presigned",
33+
) as mock:
34+
assert image.image_url == "https://s3.example.com/presigned"
35+
mock.assert_called_once_with("snapshots/img.jpg")
36+
37+
38+
def test_snapshot_image_url_without_image() -> None:
39+
"""image_url returns None when image is empty."""
40+
image = SnapshotImage()
41+
image.image = ""
42+
43+
assert image.image_url is None

canvas_sdk/v1/data/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@
3535
from .detected_issue import DetectedIssue, DetectedIssueEvidence
3636
from .device import Device
3737
from .discount import Discount
38+
from .document_reference import (
39+
DocumentReference,
40+
DocumentReferenceCategory,
41+
DocumentReferenceCoding,
42+
DocumentReferenceStatus,
43+
)
3844
from .encounter import Encounter
3945
from .external_event import ExternalEvent, ExternalVisit
4046
from .facility import Facility
@@ -108,6 +114,7 @@
108114
PatientContactPoint,
109115
PatientExternalIdentifier,
110116
PatientFacilityAddress,
117+
PatientIdentificationCard,
111118
PatientMetadata,
112119
PatientSetting,
113120
)
@@ -147,6 +154,7 @@
147154
from .reason_for_visit import ReasonForVisitSettingCoding
148155
from .referral import Referral, ReferralReport, ReferralReview
149156
from .service_provider import ServiceProvider
157+
from .snapshot import Snapshot, SnapshotImage
150158
from .specialty_report_template import (
151159
SpecialtyReportTemplate,
152160
SpecialtyReportTemplateField,
@@ -209,6 +217,10 @@
209217
"DetectedIssueEvidence",
210218
"Device",
211219
"Discount",
220+
"DocumentReference",
221+
"DocumentReferenceCategory",
222+
"DocumentReferenceCoding",
223+
"DocumentReferenceStatus",
212224
"EligibilitySummary",
213225
"Encounter",
214226
"Event",
@@ -285,6 +297,7 @@
285297
"PatientContactPoint",
286298
"PatientExternalIdentifier",
287299
"PatientFacilityAddress",
300+
"PatientIdentificationCard",
288301
"PatientPosting",
289302
"PatientSetting",
290303
"PatientMetadata",
@@ -315,6 +328,8 @@
315328
"SpecialtyReportTemplate",
316329
"SpecialtyReportTemplateField",
317330
"SpecialtyReportTemplateFieldOption",
331+
"Snapshot",
332+
"SnapshotImage",
318333
"Staff",
319334
"StaffAddress",
320335
"StaffLicense",

0 commit comments

Comments
 (0)