canvas 0.32.0__py3-none-any.whl → 0.33.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of canvas might be problematic. Click here for more details.

Files changed (257) hide show
  1. {canvas-0.32.0.dist-info → canvas-0.33.1.dist-info}/METADATA +2 -1
  2. canvas-0.33.1.dist-info/RECORD +272 -0
  3. canvas_generated/messages/effects_pb2.py +2 -2
  4. canvas_generated/messages/effects_pb2.pyi +4 -0
  5. canvas_sdk/__init__.py +3 -0
  6. canvas_sdk/commands/__init__.py +1 -1
  7. canvas_sdk/commands/base.py +3 -0
  8. canvas_sdk/commands/commands/__init__.py +1 -0
  9. canvas_sdk/commands/commands/adjust_prescription.py +3 -0
  10. canvas_sdk/commands/commands/allergy.py +7 -0
  11. canvas_sdk/commands/commands/assess.py +2 -0
  12. canvas_sdk/commands/commands/close_goal.py +3 -0
  13. canvas_sdk/commands/commands/diagnose.py +3 -0
  14. canvas_sdk/commands/commands/exam.py +3 -0
  15. canvas_sdk/commands/commands/family_history.py +3 -0
  16. canvas_sdk/commands/commands/follow_up.py +3 -0
  17. canvas_sdk/commands/commands/goal.py +3 -0
  18. canvas_sdk/commands/commands/history_present_illness.py +3 -0
  19. canvas_sdk/commands/commands/imaging_order.py +3 -0
  20. canvas_sdk/commands/commands/instruct.py +3 -0
  21. canvas_sdk/commands/commands/lab_order.py +3 -0
  22. canvas_sdk/commands/commands/medical_history.py +3 -0
  23. canvas_sdk/commands/commands/medication_statement.py +2 -0
  24. canvas_sdk/commands/commands/past_surgical_history.py +3 -0
  25. canvas_sdk/commands/commands/perform.py +3 -0
  26. canvas_sdk/commands/commands/plan.py +3 -0
  27. canvas_sdk/commands/commands/prescribe.py +8 -0
  28. canvas_sdk/commands/commands/questionnaire/__init__.py +3 -13
  29. canvas_sdk/commands/commands/questionnaire/question.py +10 -0
  30. canvas_sdk/commands/commands/reason_for_visit.py +3 -0
  31. canvas_sdk/commands/commands/refer.py +3 -0
  32. canvas_sdk/commands/commands/refill.py +3 -0
  33. canvas_sdk/commands/commands/remove_allergy.py +3 -0
  34. canvas_sdk/commands/commands/resolve_condition.py +3 -0
  35. canvas_sdk/commands/commands/review_of_systems.py +3 -0
  36. canvas_sdk/commands/commands/stop_medication.py +3 -0
  37. canvas_sdk/commands/commands/structured_assessment.py +3 -0
  38. canvas_sdk/commands/commands/task.py +7 -0
  39. canvas_sdk/commands/commands/update_diagnosis.py +3 -0
  40. canvas_sdk/commands/commands/update_goal.py +3 -0
  41. canvas_sdk/commands/commands/vitals.py +3 -0
  42. canvas_sdk/commands/constants.py +8 -0
  43. canvas_sdk/effects/__init__.py +1 -1
  44. canvas_sdk/effects/banner_alert/__init__.py +1 -1
  45. canvas_sdk/effects/banner_alert/add_banner_alert.py +3 -0
  46. canvas_sdk/effects/banner_alert/remove_banner_alert.py +3 -0
  47. canvas_sdk/effects/base.py +7 -0
  48. canvas_sdk/effects/billing_line_item/__init__.py +5 -1
  49. canvas_sdk/effects/billing_line_item/add_billing_line_item.py +3 -0
  50. canvas_sdk/effects/billing_line_item/remove_billing_line_item.py +3 -0
  51. canvas_sdk/effects/billing_line_item/update_billing_line_item.py +3 -0
  52. canvas_sdk/effects/launch_modal.py +3 -0
  53. canvas_sdk/effects/patient_chart_summary_configuration.py +3 -0
  54. canvas_sdk/effects/patient_portal/__init__.py +1 -0
  55. canvas_sdk/effects/patient_portal/application_configuration.py +3 -0
  56. canvas_sdk/effects/patient_portal/form_result.py +3 -0
  57. canvas_sdk/effects/patient_portal_menu_configuration.py +3 -0
  58. canvas_sdk/effects/patient_profile_configuration.py +6 -1
  59. canvas_sdk/effects/protocol_card/__init__.py +1 -1
  60. canvas_sdk/effects/protocol_card/protocol_card.py +6 -0
  61. canvas_sdk/effects/questionnaire_result.py +3 -0
  62. canvas_sdk/effects/send_invite.py +46 -0
  63. canvas_sdk/effects/show_button.py +3 -0
  64. canvas_sdk/effects/simple_api.py +9 -0
  65. canvas_sdk/effects/surescripts/__init__.py +2 -2
  66. canvas_sdk/effects/surescripts/surescripts_messages.py +7 -0
  67. canvas_sdk/effects/task/__init__.py +6 -1
  68. canvas_sdk/effects/task/task.py +8 -0
  69. canvas_sdk/effects/update_user.py +81 -0
  70. canvas_sdk/effects/widgets/__init__.py +1 -1
  71. canvas_sdk/effects/widgets/portal_widget.py +3 -0
  72. canvas_sdk/events/__init__.py +6 -1
  73. canvas_sdk/events/base.py +3 -0
  74. canvas_sdk/handlers/__init__.py +1 -1
  75. canvas_sdk/handlers/action_button.py +6 -0
  76. canvas_sdk/handlers/application.py +3 -0
  77. canvas_sdk/handlers/base.py +3 -0
  78. canvas_sdk/handlers/cron_task.py +3 -0
  79. canvas_sdk/handlers/simple_api/__init__.py +3 -2
  80. canvas_sdk/handlers/simple_api/api.py +26 -1
  81. canvas_sdk/handlers/simple_api/exceptions.py +10 -0
  82. canvas_sdk/handlers/simple_api/security.py +21 -5
  83. canvas_sdk/handlers/simple_api/tools.py +9 -0
  84. canvas_sdk/protocols/__init__.py +1 -1
  85. canvas_sdk/protocols/base.py +3 -0
  86. canvas_sdk/protocols/clinical_quality_measure.py +6 -1
  87. canvas_sdk/protocols/timeframe.py +3 -0
  88. canvas_sdk/questionnaires/__init__.py +1 -1
  89. canvas_sdk/questionnaires/utils.py +7 -0
  90. canvas_sdk/templates/__init__.py +1 -1
  91. canvas_sdk/templates/utils.py +3 -0
  92. canvas_sdk/utils/__init__.py +1 -1
  93. canvas_sdk/utils/http.py +69 -35
  94. canvas_sdk/utils/plugins.py +4 -0
  95. canvas_sdk/utils/stats.py +11 -0
  96. canvas_sdk/v1/__init__.py +1 -0
  97. canvas_sdk/v1/apps.py +3 -0
  98. canvas_sdk/v1/data/__init__.py +2 -2
  99. canvas_sdk/v1/data/allergy_intolerance.py +3 -0
  100. canvas_sdk/v1/data/appointment.py +7 -0
  101. canvas_sdk/v1/data/assessment.py +3 -0
  102. canvas_sdk/v1/data/banner_alert.py +3 -0
  103. canvas_sdk/v1/data/base.py +3 -0
  104. canvas_sdk/v1/data/billing.py +7 -0
  105. canvas_sdk/v1/data/care_team.py +7 -0
  106. canvas_sdk/v1/data/command.py +3 -0
  107. canvas_sdk/v1/data/common.py +18 -0
  108. canvas_sdk/v1/data/condition.py +7 -0
  109. canvas_sdk/v1/data/coverage.py +14 -0
  110. canvas_sdk/v1/data/detected_issue.py +3 -0
  111. canvas_sdk/v1/data/device.py +3 -0
  112. canvas_sdk/v1/data/imaging.py +7 -0
  113. canvas_sdk/v1/data/lab.py +16 -0
  114. canvas_sdk/v1/data/medication.py +3 -0
  115. canvas_sdk/v1/data/note.py +9 -0
  116. canvas_sdk/v1/data/observation.py +9 -0
  117. canvas_sdk/v1/data/organization.py +3 -0
  118. canvas_sdk/v1/data/patient.py +20 -3
  119. canvas_sdk/v1/data/practicelocation.py +7 -0
  120. canvas_sdk/v1/data/protocol_override.py +7 -0
  121. canvas_sdk/v1/data/questionnaire.py +16 -3
  122. canvas_sdk/v1/data/reason_for_visit.py +3 -0
  123. canvas_sdk/v1/data/staff.py +3 -0
  124. canvas_sdk/v1/data/task.py +12 -0
  125. canvas_sdk/v1/data/team.py +8 -1
  126. canvas_sdk/v1/data/user.py +5 -1
  127. canvas_sdk/v1/models.py +2 -0
  128. canvas_sdk/value_set/__init__.py +1 -0
  129. canvas_sdk/value_set/_utilities.py +16 -0
  130. canvas_sdk/value_set/custom.py +4 -0
  131. canvas_sdk/value_set/hcc2018.py +3 -0
  132. canvas_sdk/value_set/v2022/__init__.py +1 -0
  133. canvas_sdk/value_set/v2022/adverse_event.py +3 -0
  134. canvas_sdk/value_set/v2022/allergy.py +5 -0
  135. canvas_sdk/value_set/v2022/assessment.py +5 -0
  136. canvas_sdk/value_set/v2022/communication.py +5 -0
  137. canvas_sdk/value_set/v2022/condition.py +5 -0
  138. canvas_sdk/value_set/v2022/device.py +5 -0
  139. canvas_sdk/value_set/v2022/diagnostic_study.py +5 -0
  140. canvas_sdk/value_set/v2022/encounter.py +5 -0
  141. canvas_sdk/value_set/v2022/immunization.py +5 -0
  142. canvas_sdk/value_set/v2022/individual_characteristic.py +5 -0
  143. canvas_sdk/value_set/v2022/intervention.py +5 -0
  144. canvas_sdk/value_set/v2022/laboratory_test.py +5 -0
  145. canvas_sdk/value_set/v2022/medication.py +5 -0
  146. canvas_sdk/value_set/v2022/physical_exam.py +5 -0
  147. canvas_sdk/value_set/v2022/procedure.py +5 -0
  148. canvas_sdk/value_set/v2022/symptom.py +3 -0
  149. canvas_sdk/value_set/value_set.py +9 -0
  150. canvas_sdk/views/__init__.py +1 -0
  151. logger/__init__.py +2 -0
  152. logger/logger.py +3 -0
  153. plugin_runner/aws_headers.py +1 -1
  154. plugin_runner/load_all_plugins.py +202 -0
  155. plugin_runner/plugin_runner.py +26 -24
  156. plugin_runner/sandbox.py +497 -115
  157. protobufs/canvas_generated/messages/effects.proto +3 -0
  158. settings.py +5 -2
  159. canvas-0.32.0.dist-info/RECORD +0 -364
  160. canvas_cli/apps/auth/tests.py +0 -155
  161. canvas_cli/apps/plugin/tests.py +0 -85
  162. canvas_cli/conftest.py +0 -28
  163. canvas_cli/tests.py +0 -217
  164. canvas_cli/utils/context/tests.py +0 -131
  165. canvas_cli/utils/print/tests.py +0 -69
  166. canvas_cli/utils/urls/tests.py +0 -12
  167. canvas_cli/utils/validators/tests.py +0 -37
  168. canvas_sdk/commands/tests/protocol/__init__.py +0 -0
  169. canvas_sdk/commands/tests/protocol/tests.py +0 -83
  170. canvas_sdk/commands/tests/schema/__init__.py +0 -0
  171. canvas_sdk/commands/tests/schema/tests.py +0 -108
  172. canvas_sdk/commands/tests/test_base_command.py +0 -81
  173. canvas_sdk/commands/tests/test_utils.py +0 -375
  174. canvas_sdk/commands/tests/unit/__init__.py +0 -0
  175. canvas_sdk/commands/tests/unit/tests.py +0 -278
  176. canvas_sdk/effects/banner_alert/tests.py +0 -288
  177. canvas_sdk/effects/protocol_card/tests.py +0 -191
  178. canvas_sdk/questionnaires/tests/__init__.py +0 -0
  179. canvas_sdk/questionnaires/tests/test_utils.py +0 -74
  180. canvas_sdk/templates/tests/__init__.py +0 -0
  181. canvas_sdk/templates/tests/test_utils.py +0 -43
  182. canvas_sdk/tests/__init__.py +0 -0
  183. canvas_sdk/tests/handlers/__init__.py +0 -0
  184. canvas_sdk/tests/handlers/test_simple_api.py +0 -1167
  185. canvas_sdk/utils/tests.py +0 -72
  186. canvas_sdk/value_set/tests/test_value_sets.py +0 -72
  187. plugin_runner/tests/__init__.py +0 -0
  188. plugin_runner/tests/fixtures/plugins/example_plugin/CANVAS_MANIFEST.json +0 -29
  189. plugin_runner/tests/fixtures/plugins/example_plugin/README.md +0 -12
  190. plugin_runner/tests/fixtures/plugins/example_plugin/__init__.py +0 -0
  191. plugin_runner/tests/fixtures/plugins/example_plugin/protocols/__init__.py +0 -0
  192. plugin_runner/tests/fixtures/plugins/example_plugin/protocols/my_protocol.py +0 -18
  193. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/CANVAS_MANIFEST.json +0 -38
  194. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/README.md +0 -11
  195. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/__init__.py +0 -0
  196. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/protocols/my_protocol.py +0 -33
  197. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/__init__.py +0 -3
  198. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/templates/base.py +0 -6
  199. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/__init__.py +0 -5
  200. plugin_runner/tests/fixtures/plugins/test_implicit_imports_plugin/utils/base.py +0 -4
  201. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/CANVAS_MANIFEST.json +0 -52
  202. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/README.md +0 -11
  203. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/protocols/__init__.py +0 -0
  204. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/protocols/my_protocol.py +0 -39
  205. plugin_runner/tests/fixtures/plugins/test_load_questionnaire/questionnaires/example_questionnaire.yml +0 -61
  206. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/CANVAS_MANIFEST.json +0 -29
  207. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/README.md +0 -12
  208. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/__init__.py +0 -0
  209. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/other_module/base.py +0 -10
  210. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/__init__.py +0 -0
  211. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_plugin/protocols/my_protocol.py +0 -18
  212. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/CANVAS_MANIFEST.json +0 -29
  213. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/README.md +0 -12
  214. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/__init__.py +0 -0
  215. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/other_module/base.py +0 -10
  216. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/__init__.py +0 -0
  217. plugin_runner/tests/fixtures/plugins/test_module_forbidden_imports_runtime_plugin/protocols/my_protocol.py +0 -18
  218. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/CANVAS_MANIFEST.json +0 -29
  219. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/README.md +0 -12
  220. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/__init__.py +0 -0
  221. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/other_module/base.py +0 -3
  222. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/__init__.py +0 -0
  223. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v1/protocols/my_protocol.py +0 -18
  224. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/CANVAS_MANIFEST.json +0 -29
  225. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/README.md +0 -12
  226. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/__init__.py +0 -0
  227. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/other_module/base.py +0 -6
  228. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/__init__.py +0 -0
  229. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v2/protocols/my_protocol.py +0 -18
  230. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/CANVAS_MANIFEST.json +0 -29
  231. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/README.md +0 -12
  232. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/__init__.py +0 -0
  233. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/other_module/base.py +0 -8
  234. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/__init__.py +0 -0
  235. plugin_runner/tests/fixtures/plugins/test_module_imports_outside_plugin_v3/protocols/my_protocol.py +0 -18
  236. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/CANVAS_MANIFEST.json +0 -29
  237. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/README.md +0 -12
  238. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/__init__.py +0 -0
  239. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/other_module/base.py +0 -3
  240. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/__init__.py +0 -0
  241. plugin_runner/tests/fixtures/plugins/test_module_imports_plugin/protocols/my_protocol.py +0 -18
  242. plugin_runner/tests/fixtures/plugins/test_render_template/CANVAS_MANIFEST.json +0 -47
  243. plugin_runner/tests/fixtures/plugins/test_render_template/README.md +0 -11
  244. plugin_runner/tests/fixtures/plugins/test_render_template/protocols/__init__.py +0 -0
  245. plugin_runner/tests/fixtures/plugins/test_render_template/protocols/my_protocol.py +0 -43
  246. plugin_runner/tests/fixtures/plugins/test_render_template/templates/template.html +0 -10
  247. plugin_runner/tests/fixtures/plugins/test_simple_api/CANVAS_MANIFEST.json +0 -47
  248. plugin_runner/tests/fixtures/plugins/test_simple_api/README.md +0 -11
  249. plugin_runner/tests/fixtures/plugins/test_simple_api/__init__.py +0 -0
  250. plugin_runner/tests/fixtures/plugins/test_simple_api/protocols/__init__.py +0 -0
  251. plugin_runner/tests/fixtures/plugins/test_simple_api/protocols/my_protocol.py +0 -43
  252. plugin_runner/tests/test_application.py +0 -65
  253. plugin_runner/tests/test_plugin_installer.py +0 -127
  254. plugin_runner/tests/test_plugin_runner.py +0 -388
  255. plugin_runner/tests/test_sandbox.py +0 -137
  256. {canvas-0.32.0.dist-info → canvas-0.33.1.dist-info}/WHEEL +0 -0
  257. {canvas-0.32.0.dist-info → canvas-0.33.1.dist-info}/entry_points.txt +0 -0
@@ -1,1167 +0,0 @@
1
- import json
2
- import re
3
- from base64 import b64decode, b64encode
4
- from collections.abc import Callable, Iterable, Mapping, Sequence
5
- from http import HTTPStatus
6
- from types import SimpleNamespace
7
- from typing import Any, TypeVar
8
- from uuid import uuid4
9
-
10
- import pytest
11
- from _pytest.fixtures import SubRequest
12
-
13
- from canvas_sdk.effects.simple_api import (
14
- Effect,
15
- EffectType,
16
- HTMLResponse,
17
- JSONResponse,
18
- PlainTextResponse,
19
- Response,
20
- )
21
- from canvas_sdk.events import Event, EventRequest, EventType
22
- from canvas_sdk.handlers.simple_api import api
23
- from canvas_sdk.handlers.simple_api.api import (
24
- FileFormPart,
25
- FormPart,
26
- Request,
27
- SimpleAPI,
28
- SimpleAPIBase,
29
- SimpleAPIRoute,
30
- StringFormPart,
31
- )
32
- from canvas_sdk.handlers.simple_api.security import (
33
- APIKeyAuthMixin,
34
- APIKeyCredentials,
35
- AuthSchemeMixin,
36
- BasicAuthMixin,
37
- BasicCredentials,
38
- BearerCredentials,
39
- Credentials,
40
- PatientSessionAuthMixin,
41
- SessionCredentials,
42
- StaffSessionAuthMixin,
43
- )
44
- from canvas_sdk.handlers.simple_api.tools import (
45
- CaseInsensitiveMultiDict,
46
- MultiDict,
47
- separate_headers,
48
- )
49
- from plugin_runner.exceptions import PluginError
50
-
51
- REQUEST_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"]
52
- HEADERS_RAW = {
53
- "Canvas-Plugins-Test-Header-1": "test header 1",
54
- "Canvas-Plugins-Test-Header-2": "test header 2a, test header 2b",
55
- }
56
- HEADERS = CaseInsensitiveMultiDict(separate_headers(HEADERS_RAW))
57
-
58
- FORM = b64decode(
59
- "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"
60
- )
61
- FILE = b64decode(
62
- "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"
63
- )
64
-
65
-
66
- class NoAuthMixin:
67
- """Mixin to bypass authentication for tests that are not related to authentication."""
68
-
69
- def authenticate(self, credentials: Credentials) -> bool:
70
- """Authenticate the request."""
71
- return True
72
-
73
-
74
- class RouteNoAuth(NoAuthMixin, SimpleAPIRoute):
75
- """Route class that bypasses authentication."""
76
-
77
- pass
78
-
79
-
80
- class APINoAuth(NoAuthMixin, SimpleAPI):
81
- """API class that bypasses authentication."""
82
-
83
- pass
84
-
85
-
86
- def make_event(
87
- event_type: EventType,
88
- method: str,
89
- path: str,
90
- query_string: str | None = None,
91
- body: bytes | None = None,
92
- headers: Mapping[str, str] | None = None,
93
- ) -> Event:
94
- """Make a SIMPLE_API_REQUEST event suitable for testing."""
95
- if event_type == EventType.SIMPLE_API_AUTHENTICATE:
96
- body = b""
97
-
98
- return Event(
99
- event_request=EventRequest(
100
- type=event_type,
101
- target=None,
102
- context=json.dumps(
103
- {
104
- "method": method,
105
- "path": path,
106
- "query_string": query_string or "",
107
- "body": b64encode(body or b"").decode(),
108
- "headers": dict(headers) if headers else {},
109
- },
110
- indent=None,
111
- separators=(",", ":"),
112
- ),
113
- target_type=None,
114
- )
115
- )
116
-
117
-
118
- def handle_request(
119
- cls: type[SimpleAPIBase],
120
- method: str,
121
- path: str,
122
- query_string: str | None = None,
123
- body: bytes | None = None,
124
- headers: Mapping[str, str] | None = None,
125
- ) -> list[Effect]:
126
- """
127
- Mimic the two-pass request handling in home-app.
128
-
129
- First, handle the authentication event, and if it succeeds, handle the request event.
130
- """
131
- handler = cls(
132
- make_event(EventType.SIMPLE_API_AUTHENTICATE, method, path, query_string, body, headers)
133
- )
134
- effects = handler.compute()
135
-
136
- payload = json.loads(effects[0].payload)
137
- if payload["status_code"] != HTTPStatus.OK:
138
- return effects
139
-
140
- handler = cls(
141
- make_event(EventType.SIMPLE_API_REQUEST, method, path, query_string, body, headers)
142
- )
143
-
144
- return handler.compute()
145
-
146
-
147
- T = TypeVar("T")
148
-
149
-
150
- @pytest.mark.parametrize(
151
- argnames="func,expected_value",
152
- argvalues=[
153
- (lambda m: m["b"], 2),
154
- (lambda m: m["a"], 1),
155
- (lambda m: len(m), 2),
156
- (lambda m: next(iter(m.items())), ("a", 1)),
157
- (lambda m: "a" in m, True),
158
- (lambda m: "d" in m, False),
159
- (lambda m: m.get("a"), 1),
160
- (lambda m: m.get("d", 4), 4),
161
- (lambda m: m.get("d"), None),
162
- (lambda m: m.get_list("a"), [1, 3]),
163
- (
164
- lambda m: [(k, v) for k, v in m.items()],
165
- [("a", 1), ("b", 2)],
166
- ),
167
- (
168
- lambda m: [(k, v) for k, v in m.multi_items()],
169
- [("a", 1), ("b", 2), ("a", 3)],
170
- ),
171
- (lambda m: list(m.keys()), ["a", "b"]),
172
- (lambda m: list(reversed(m)), ["b", "a"]),
173
- (lambda m: list(m.values()), [1, 2]),
174
- (lambda m: m == MultiDict((("a", 1), ("b", 2), ("a", 3))), True),
175
- (lambda m: m != MultiDict((("a", 1), ("b", 2))), True),
176
- ],
177
- ids=[
178
- "[] single value from single value",
179
- "[] single value from multiple values",
180
- "len",
181
- "iter",
182
- "in",
183
- "not in",
184
- "get",
185
- "get default",
186
- "get no default",
187
- "get_list",
188
- "items",
189
- "multi_items",
190
- "keys",
191
- "reversed",
192
- "values",
193
- "==",
194
- "!=",
195
- ],
196
- )
197
- def test_multidict(func: Callable[[MultiDict[str, int]], T], expected_value: T) -> None:
198
- """Test the methods and functionality of MultiDict."""
199
- multidict = MultiDict((("a", 1), ("b", 2), ("a", 3)))
200
- assert func(multidict) == expected_value
201
-
202
-
203
- @pytest.mark.parametrize(
204
- argnames="func,expected_value",
205
- argvalues=[
206
- (lambda m: m["b"] == m["B"] == 2, True),
207
- (lambda m: "a" in m and "A" in m, True),
208
- (lambda m: "d" not in m and "D" not in m, True),
209
- (lambda m: m.get("a") == m.get("A") == 1, True),
210
- (lambda m: m.get_list("a") == m.get_list("A") == [1, 3], True),
211
- ],
212
- ids=["[]", "in", "not in", "get", "get_list"],
213
- )
214
- def test_case_insensitive_multidict(
215
- func: Callable[[MultiDict[str, int]], T], expected_value: T
216
- ) -> None:
217
- """Test the methods and functionality of CaseInsensitiveMultiDict."""
218
- multidict = CaseInsensitiveMultiDict((("a", 1), ("b", 2), ("A", 3)))
219
- assert func(multidict) == expected_value
220
-
221
-
222
- @pytest.mark.parametrize(
223
- argnames="method,body,headers",
224
- argvalues=[
225
- ("GET", b"", HEADERS_RAW),
226
- (
227
- "POST",
228
- b'{"message": "JSON request"}',
229
- {"Content-Type": "application/json"} | HEADERS_RAW,
230
- ),
231
- (
232
- "POST",
233
- b"plain text request",
234
- {"Content-Type": "text/plain"} | HEADERS_RAW,
235
- ),
236
- ("POST", b"<html></html>", {"Content-Type": "text/html"} | HEADERS_RAW),
237
- ],
238
- ids=["no body", "JSON", "plain text", "HTML"],
239
- )
240
- def test_request(
241
- method: str,
242
- body: bytes,
243
- headers: Mapping[str, str],
244
- ) -> None:
245
- """Test the construction of a Request object and access to its attributes."""
246
- path = "/route"
247
- query_string = "value1=a&value2=b"
248
- request = Request(
249
- make_event(EventType.SIMPLE_API_REQUEST, method, path, query_string, body, headers),
250
- path_pattern=re.compile(path),
251
- )
252
-
253
- assert request.method == method
254
- assert request.path == path
255
- assert request.query_string == query_string
256
- assert request.body == body
257
-
258
- assert request.headers == CaseInsensitiveMultiDict(separate_headers(headers))
259
- assert request.headers["canvas-plugins-test-header-1"] == "test header 1"
260
- assert request.headers["canvas-plugins-test-header-2"] == "test header 2a"
261
- assert request.headers.get_list("canvas-plugins-test-header-1") == ["test header 1"]
262
- assert request.headers.get_list("canvas-plugins-test-header-2") == [
263
- "test header 2a",
264
- "test header 2b",
265
- ]
266
-
267
- assert request.query_params == MultiDict((("value1", "a"), ("value2", "b")))
268
-
269
- assert request.content_type == request.headers.get("content-type")
270
-
271
- if request.content_type:
272
- if request.content_type == "application/json":
273
- assert request.json() == json.loads(body)
274
- elif request.content_type.startswith("text/"):
275
- assert request.text() == body.decode()
276
-
277
-
278
- @pytest.mark.parametrize(
279
- argnames="body,content_type,expected_form_data",
280
- argvalues=[
281
- (
282
- b"part1=value1&part2=value2&part1=value3",
283
- "application/x-www-form-urlencoded",
284
- MultiDict(
285
- (
286
- ("part1", StringFormPart(name="part1", value="value1")),
287
- ("part2", StringFormPart(name="part2", value="value2")),
288
- ("part1", StringFormPart(name="part1", value="value3")),
289
- )
290
- ),
291
- ),
292
- (
293
- FORM,
294
- "multipart/form-data; boundary=--------------------------966149001464621638881292",
295
- MultiDict(
296
- (
297
- ("part1", StringFormPart(name="part1", value="value1")),
298
- ("part2", StringFormPart(name="part2", value="value2")),
299
- (
300
- "part1",
301
- FileFormPart(
302
- name="part1",
303
- filename="Sydney.jpg",
304
- content=FILE,
305
- content_type="image/jpeg",
306
- ),
307
- ),
308
- )
309
- ),
310
- ),
311
- ],
312
- ids=["x-www-form-urlencoded", "multipart/form-data"],
313
- )
314
- def test_request_form(
315
- body: bytes, content_type: str, expected_form_data: Mapping[str, Sequence[FormPart]]
316
- ) -> None:
317
- """Test the parsing of form data from the request body."""
318
- request = Request(
319
- make_event(
320
- EventType.SIMPLE_API_REQUEST,
321
- method="POST",
322
- path="/route",
323
- body=body,
324
- headers={"Content-Type": content_type},
325
- ),
326
- path_pattern=re.compile("/route"),
327
- )
328
-
329
- assert request.form_data() == expected_form_data
330
-
331
-
332
- def response_body(effects: Iterable[Effect]) -> bytes:
333
- """Given a list of effects, find the response object and return the body."""
334
- for effect in effects:
335
- if effect.type == EffectType.SIMPLE_API_RESPONSE:
336
- payload = json.loads(effect.payload)
337
- return b64decode(payload["body"].encode())
338
-
339
- pytest.fail("No response effect was found in the list of effects")
340
-
341
-
342
- def json_response_body(effects: Iterable[Effect]) -> Any:
343
- """Given a list of effects, find the response object and return the JSON body."""
344
- return json.loads(response_body(effects))
345
-
346
-
347
- @pytest.mark.parametrize(argnames="method", argvalues=REQUEST_METHODS, ids=REQUEST_METHODS)
348
- def test_request_routing_route(method: str) -> None:
349
- """Test request routing for SimpleAPIRoute plugins."""
350
-
351
- class Route(RouteNoAuth):
352
- PATH = "/route"
353
-
354
- def get(self) -> list[Response | Effect]:
355
- return [
356
- JSONResponse(
357
- {"method": "GET"},
358
- )
359
- ]
360
-
361
- def post(self) -> list[Response | Effect]:
362
- return [
363
- JSONResponse(
364
- {"method": "POST"},
365
- )
366
- ]
367
-
368
- def put(self) -> list[Response | Effect]:
369
- return [
370
- JSONResponse(
371
- {"method": "PUT"},
372
- )
373
- ]
374
-
375
- def delete(self) -> list[Response | Effect]:
376
- return [
377
- JSONResponse(
378
- {"method": "DELETE"},
379
- )
380
- ]
381
-
382
- def patch(self) -> list[Response | Effect]:
383
- return [
384
- JSONResponse(
385
- {"method": "PATCH"},
386
- )
387
- ]
388
-
389
- effects = handle_request(Route, method, path="/route")
390
- body = json_response_body(effects)
391
-
392
- assert body["method"] == method
393
-
394
-
395
- @pytest.mark.parametrize(
396
- argnames="path", argvalues=["/route1", "/route2"], ids=["route1", "route2"]
397
- )
398
- @pytest.mark.parametrize(
399
- argnames="prefix",
400
- argvalues=["/prefix", "", None],
401
- ids=["with prefix", "empty prefix", "no prefix"],
402
- )
403
- @pytest.mark.parametrize(
404
- argnames="decorator,method",
405
- argvalues=[
406
- (api.get, "GET"),
407
- (api.post, "POST"),
408
- (api.put, "PUT"),
409
- (api.delete, "DELETE"),
410
- (api.patch, "PATCH"),
411
- ],
412
- ids=REQUEST_METHODS,
413
- )
414
- def test_request_routing_api(
415
- decorator: Callable[[str], Callable], method: str, prefix: str | None, path: str
416
- ) -> None:
417
- """Test request routing for SimpleAPI plugins."""
418
-
419
- class API(APINoAuth):
420
- PREFIX = prefix
421
-
422
- @decorator("/route1")
423
- def route1(self) -> list[Response | Effect]:
424
- return [
425
- JSONResponse(
426
- {"method": method},
427
- )
428
- ]
429
-
430
- @decorator("/route2")
431
- def route2(self) -> list[Response | Effect]:
432
- return [
433
- JSONResponse(
434
- {"method": method},
435
- )
436
- ]
437
-
438
- effects = handle_request(API, method, path=f"{prefix or ''}{path}")
439
- body = json_response_body(effects)
440
-
441
- assert body["method"] == method
442
-
443
-
444
- @pytest.mark.parametrize(
445
- argnames="prefix_pattern,path_pattern,body_func,path,expected_body",
446
- argvalues=[
447
- (
448
- "/prefix",
449
- "/path/<param>",
450
- lambda params: {"param": params["param"]},
451
- "/prefix/path/value",
452
- {"param": "value"},
453
- ),
454
- (
455
- "/prefix",
456
- "/path1/<param1>/path2/<param2>",
457
- lambda params: {"param1": params["param1"], "param2": params["param2"]},
458
- "/prefix/path1/value1/path2/value2",
459
- {"param1": "value1", "param2": "value2"},
460
- ),
461
- (
462
- "/prefix/<param>",
463
- "/path",
464
- lambda params: {"param": params["param"]},
465
- "/prefix/value/path",
466
- {"param": "value"},
467
- ),
468
- (
469
- "/<param1>/prefix/<param2>",
470
- "/path",
471
- lambda params: {"param1": params["param1"], "param2": params["param2"]},
472
- "/value1/prefix/value2/path",
473
- {"param1": "value1", "param2": "value2"},
474
- ),
475
- (
476
- "/prefix/<param1>",
477
- "/path/<param2>",
478
- lambda params: {"param1": params["param1"], "param2": params["param2"]},
479
- "/prefix/value1/path/value2",
480
- {"param1": "value1", "param2": "value2"},
481
- ),
482
- ],
483
- ids=[
484
- "single parameter in path",
485
- "multiple parameters in path",
486
- "single parameter in prefix",
487
- "multiple parameters in prefix",
488
- "parameters in path and prefix",
489
- ],
490
- )
491
- def test_request_routing_path_pattern(
492
- prefix_pattern: str,
493
- path_pattern: str,
494
- body_func: Callable[[Mapping[str, str]], Mapping[str, str]],
495
- path: str,
496
- expected_body: Mapping[str, str],
497
- ) -> None:
498
- """Test Request routing for routes that use path patterns."""
499
-
500
- class API(APINoAuth):
501
- PREFIX = prefix_pattern
502
-
503
- @api.get(path_pattern)
504
- def route(self) -> list[Response | Effect]:
505
- return [JSONResponse(body_func(self.request.path_params))]
506
-
507
- effects = handle_request(API, "GET", path=path)
508
- body = json_response_body(effects)
509
-
510
- assert body == expected_body
511
-
512
-
513
- @pytest.mark.parametrize(
514
- argnames="path_pattern1,path_pattern2,path,expected_body",
515
- argvalues=[
516
- ("/path/<value>", "/path/test", "/prefix/path/value", {"handler_method": "first"}),
517
- ("/path/<value>", "/path/test", "/prefix/path/test", {"handler_method": "first"}),
518
- ("/path/test", "/path/<value>", "/prefix/path/test", {"handler_method": "first"}),
519
- ("/path/test", "/path/<value>", "/prefix/path/value", {"handler_method": "second"}),
520
- (
521
- "/path/<value>",
522
- "/path/<value>/test",
523
- "/prefix/path/value/test",
524
- {"handler_method": "second"},
525
- ),
526
- ],
527
- ids=[
528
- "pattern registered first, path matches only pattern",
529
- "pattern registered first, path matches both pattern and fixed",
530
- "fixed registered first, path matches both pattern and fixed",
531
- "fixed registered first, path matches only pattern",
532
- "two patterns share the same first two segments and then diverge",
533
- ],
534
- )
535
- def test_request_routing_path_pattern_multiple_matches(
536
- path_pattern1: str, path_pattern2: str, path: str, expected_body: Mapping[str, str]
537
- ) -> None:
538
- """Test request routing for path patterns where a path matches multiple routes in a handler."""
539
-
540
- class API(APINoAuth):
541
- PREFIX = "/prefix"
542
-
543
- @api.get(path_pattern1)
544
- def route1(self) -> list[Response | Effect]:
545
- return [JSONResponse({"handler_method": "first"})]
546
-
547
- @api.get(path_pattern2)
548
- def route2(self) -> list[Response | Effect]:
549
- return [JSONResponse({"handler_method": "second"})]
550
-
551
- effects = handle_request(API, "GET", path=path)
552
- body = json_response_body(effects)
553
-
554
- assert body == expected_body
555
-
556
-
557
- def test_request_lifecycle() -> None:
558
- """Test the request-response lifecycle."""
559
-
560
- class Route(RouteNoAuth):
561
- PATH = "/route"
562
-
563
- def post(self) -> list[Response | Effect]:
564
- return [
565
- JSONResponse(
566
- {
567
- "method": self.request.method,
568
- "path": self.request.path,
569
- "query_string": self.request.query_string,
570
- "body": self.request.json(),
571
- "headers": dict(self.request.headers),
572
- },
573
- )
574
- ]
575
-
576
- effects = handle_request(
577
- Route,
578
- method="POST",
579
- path="/route",
580
- query_string="value1=a&value2=b",
581
- body=b'{"message": "JSON request"}',
582
- headers=HEADERS,
583
- )
584
- body = json_response_body(effects)
585
-
586
- assert body == {
587
- "body": {"message": "JSON request"},
588
- "headers": {k.lower(): v for k, v in HEADERS.items()},
589
- "method": "POST",
590
- "path": "/route",
591
- "query_string": "value1=a&value2=b",
592
- }
593
-
594
-
595
- @pytest.mark.parametrize(
596
- argnames="response,expected_effects",
597
- argvalues=[
598
- (
599
- lambda: [
600
- Effect(type=EffectType.CREATE_TASK, payload="create task"),
601
- Effect(type=EffectType.ADD_BANNER_ALERT, payload="add banner alert"),
602
- ],
603
- [
604
- Effect(type=EffectType.CREATE_TASK, payload="create task"),
605
- Effect(type=EffectType.ADD_BANNER_ALERT, payload="add banner alert"),
606
- ],
607
- ),
608
- (
609
- lambda: [
610
- JSONResponse(
611
- content={"message": "JSON response"},
612
- status_code=HTTPStatus.ACCEPTED,
613
- headers=HEADERS,
614
- ),
615
- Effect(type=EffectType.CREATE_TASK, payload="create task"),
616
- ],
617
- [
618
- JSONResponse(
619
- content={"message": "JSON response"},
620
- status_code=HTTPStatus.ACCEPTED,
621
- headers=HEADERS,
622
- ).apply(),
623
- Effect(type=EffectType.CREATE_TASK, payload="create task"),
624
- ],
625
- ),
626
- (
627
- lambda: [
628
- JSONResponse(
629
- content={"message": "JSON response"},
630
- status_code=HTTPStatus.ACCEPTED,
631
- headers=HEADERS,
632
- ).apply(),
633
- Effect(type=EffectType.CREATE_TASK, payload="create task"),
634
- ],
635
- [
636
- JSONResponse(
637
- content={"message": "JSON response"},
638
- status_code=HTTPStatus.ACCEPTED,
639
- headers=HEADERS,
640
- ).apply(),
641
- Effect(type=EffectType.CREATE_TASK, payload="create task"),
642
- ],
643
- ),
644
- (lambda: [], []),
645
- (
646
- lambda: [Response(), Response()],
647
- [Response(status_code=HTTPStatus.INTERNAL_SERVER_ERROR).apply()],
648
- ),
649
- (
650
- lambda: [
651
- JSONResponse(
652
- content={"message": "JSON response"},
653
- status_code=HTTPStatus.BAD_REQUEST,
654
- headers=HEADERS,
655
- ),
656
- Effect(type=EffectType.CREATE_TASK, payload="create task"),
657
- ],
658
- [
659
- JSONResponse(
660
- content={"message": "JSON response"},
661
- status_code=HTTPStatus.BAD_REQUEST,
662
- headers=HEADERS,
663
- ).apply()
664
- ],
665
- ),
666
- (
667
- lambda: [
668
- JSONResponse(
669
- content={"message": "JSON response"},
670
- status_code=HTTPStatus.BAD_REQUEST,
671
- headers=HEADERS,
672
- ).apply(),
673
- Effect(type=EffectType.CREATE_TASK, payload="create task"),
674
- ],
675
- [
676
- JSONResponse(
677
- content={"message": "JSON response"},
678
- status_code=HTTPStatus.BAD_REQUEST,
679
- headers=HEADERS,
680
- ).apply()
681
- ],
682
- ),
683
- (
684
- lambda: [
685
- JSONResponse(content={"message": 1 / 0}, status_code=HTTPStatus.OK, headers=HEADERS)
686
- ],
687
- [Response(status_code=HTTPStatus.INTERNAL_SERVER_ERROR).apply()],
688
- ),
689
- ],
690
- ids=[
691
- "list of effects",
692
- "list of effects with response object",
693
- "list of effects with response effect",
694
- "no response",
695
- "multiple responses",
696
- "handler returns error response object",
697
- "handler returns error response effect",
698
- "exception in handler",
699
- ],
700
- )
701
- def test_response(response: Callable, expected_effects: Sequence[Effect]) -> None:
702
- """Test the construction and return of different kinds of responses."""
703
-
704
- class Route(RouteNoAuth):
705
- PATH = "/route"
706
-
707
- def get(self) -> list[Response | Effect]:
708
- return response()
709
-
710
- effects = handle_request(Route, method="GET", path="/route")
711
-
712
- assert effects == expected_effects
713
-
714
-
715
- @pytest.mark.parametrize(
716
- argnames="response,expected_payload",
717
- argvalues=[
718
- (
719
- Response(
720
- content=b"%PDF-1.4\n%\xd3\xeb\xe9\xe1",
721
- status_code=HTTPStatus.ACCEPTED,
722
- headers=HEADERS,
723
- content_type="application/pdf",
724
- ),
725
- '{"headers": {"canvas-plugins-test-header-1": "test header 1", '
726
- '"canvas-plugins-test-header-2": "test header 2a", "Content-Type": "application/pdf"}, '
727
- '"body": "JVBERi0xLjQKJdPr6eE=", "status_code": 202}',
728
- ),
729
- (
730
- JSONResponse(
731
- content={"message": "JSON response"},
732
- status_code=HTTPStatus.ACCEPTED,
733
- headers=HEADERS,
734
- ),
735
- '{"headers": {"canvas-plugins-test-header-1": "test header 1", '
736
- '"canvas-plugins-test-header-2": "test header 2a", "Content-Type": "application/json"},'
737
- ' "body": "eyJtZXNzYWdlIjogIkpTT04gcmVzcG9uc2UifQ==", "status_code": 202}',
738
- ),
739
- (
740
- PlainTextResponse(
741
- content="plain text response", status_code=HTTPStatus.ACCEPTED, headers=HEADERS
742
- ),
743
- '{"headers": {"canvas-plugins-test-header-1": "test header 1", '
744
- '"canvas-plugins-test-header-2": "test header 2a", "Content-Type": "text/plain"}, '
745
- '"body": "cGxhaW4gdGV4dCByZXNwb25zZQ==", "status_code": 202}',
746
- ),
747
- (
748
- HTMLResponse(content="<html></html>", status_code=HTTPStatus.ACCEPTED, headers=HEADERS),
749
- '{"headers": {"canvas-plugins-test-header-1": "test header 1", '
750
- '"canvas-plugins-test-header-2": "test header 2a", "Content-Type": "text/html"}, '
751
- '"body": "PGh0bWw+PC9odG1sPg==", "status_code": 202}',
752
- ),
753
- (
754
- Response(status_code=HTTPStatus.NO_CONTENT, headers=HEADERS),
755
- '{"headers": {"canvas-plugins-test-header-1": "test header 1", '
756
- '"canvas-plugins-test-header-2": "test header 2a"}, "body": "", "status_code": 204}',
757
- ),
758
- ],
759
- ids=["binary", "JSON", "plain text", "HTML", "no content"],
760
- )
761
- def test_response_type(response: Response, expected_payload: str) -> None:
762
- """Test the Response object with different types of content."""
763
- assert response.apply() == Effect(type=EffectType.SIMPLE_API_RESPONSE, payload=expected_payload)
764
-
765
-
766
- def test_override_base_handler_attributes_error() -> None:
767
- """Test the enforcement of the error that occurs when base handler attributes are overridden."""
768
- with pytest.raises(PluginError):
769
-
770
- class API(APINoAuth):
771
- @api.get("/route")
772
- def compute(self) -> list[Response | Effect]: # type: ignore[override]
773
- return []
774
-
775
-
776
- def test_multiple_handlers_for_route_error() -> None:
777
- """
778
- Test the enforcement of the error that occurs when a route is assigned to multiple handlers.
779
- """
780
- with pytest.raises(PluginError):
781
-
782
- class API(APINoAuth):
783
- @api.get("/route")
784
- def route1(self) -> list[Response | Effect]:
785
- return []
786
-
787
- @api.get("/route")
788
- def route2(self) -> list[Response | Effect]:
789
- return []
790
-
791
-
792
- def test_invalid_prefix_error() -> None:
793
- """Test the enforcement of the error that occurs when an API has an invalid prefix."""
794
- with pytest.raises(PluginError):
795
-
796
- class API(APINoAuth):
797
- PREFIX = "prefix"
798
-
799
- @api.get("/route")
800
- def route(self) -> list[Response | Effect]:
801
- return []
802
-
803
-
804
- def test_invalid_path_error() -> None:
805
- """Test the enforcement of the error that occurs when a route has an invalid path."""
806
- with pytest.raises(PluginError):
807
-
808
- class Route(RouteNoAuth):
809
- PATH = "route"
810
-
811
- def get(self) -> list[Response | Effect]:
812
- return []
813
-
814
- with pytest.raises(PluginError):
815
-
816
- class API(APINoAuth):
817
- @api.get("route")
818
- def route(self) -> list[Response | Effect]:
819
- return []
820
-
821
-
822
- def test_invalid_path_pattern_error() -> None:
823
- """Test the enforcement of the error that occurs when a route has an invalid path pattern."""
824
- with pytest.raises(PluginError):
825
-
826
- class Route(RouteNoAuth):
827
- PATH = "/path1/<value>/<path2>/<value>"
828
-
829
- def get(self) -> list[Response | Effect]:
830
- return []
831
-
832
-
833
- def test_route_missing_path_error() -> None:
834
- """
835
- Test the enforcement of the error that occurs when a SimpleAPIRoute is missing a PATH value.
836
- """
837
- with pytest.raises(PluginError):
838
-
839
- class Route(RouteNoAuth):
840
- def get(self) -> list[Response | Effect]:
841
- return []
842
-
843
-
844
- def test_route_has_prefix_error() -> None:
845
- """Test the enforcement of the error that occurs when a SimpleAPIRoute has a PREFIX value."""
846
- with pytest.raises(PluginError):
847
-
848
- class Route(RouteNoAuth):
849
- PREFIX = "/prefix"
850
- PATH = "/route"
851
-
852
- def get(self) -> list[Response | Effect]:
853
- return []
854
-
855
-
856
- def test_route_that_uses_api_decorator_error() -> None:
857
- """
858
- Test the enforcement of the error that occurs when a SimpleAPIRoute uses the api decorator.
859
- """
860
- with pytest.raises(PluginError):
861
-
862
- class Route(RouteNoAuth):
863
- PREFIX = "/prefix"
864
- PATH = "/route"
865
-
866
- def get(self) -> list[Response | Effect]:
867
- return []
868
-
869
- @api.get("/route")
870
- def route(self) -> list[Response | Effect]:
871
- return []
872
-
873
-
874
- def basic_headers(username: str, password: str) -> dict[str, str]:
875
- """Given a username and password, return headers that include a basic authentication header."""
876
- return {"Authorization": f"Basic {b64encode(f'{username}:{password}'.encode()).decode()}"}
877
-
878
-
879
- def bearer_headers(token: str) -> dict[str, str]:
880
- """Given a token, return headers that include a bearer authentication header."""
881
- return {"Authorization": f"Bearer {token}"}
882
-
883
-
884
- def api_key_headers(api_key: str) -> dict[str, str]:
885
- """Given an API key, return headers that include an API key authentication header."""
886
- return {"Authorization": api_key}
887
-
888
-
889
- def custom_headers(api_key: str, app_key: str) -> dict[str, str]:
890
- """
891
- Given an API key and an app key, return headers that include custom authentication headers.
892
- """
893
- return {"API-Key": api_key, "App-Key": app_key}
894
-
895
-
896
- def session_headers(id: str, type: str) -> dict[str, str]:
897
- """
898
- Given an id and a type, return headers that include the expected session based auth headers.
899
- """
900
- return {"canvas-logged-in-user-type": type, "canvas-logged-in-user-id": id}
901
-
902
-
903
- USERNAME = uuid4().hex
904
- PASSWORD = uuid4().hex
905
- TOKEN = uuid4().hex
906
- API_KEY = uuid4().hex
907
- APP_KEY = uuid4().hex
908
-
909
-
910
- @pytest.fixture(
911
- params=[
912
- (
913
- BasicCredentials,
914
- lambda _, credentials: credentials.username == USERNAME
915
- and credentials.password == PASSWORD,
916
- basic_headers(USERNAME, PASSWORD),
917
- ),
918
- (
919
- BearerCredentials,
920
- lambda _, credentials: credentials.token == TOKEN,
921
- bearer_headers(TOKEN),
922
- ),
923
- (
924
- APIKeyCredentials,
925
- lambda _, credentials: credentials.key == API_KEY,
926
- api_key_headers(API_KEY),
927
- ),
928
- (
929
- SessionCredentials,
930
- lambda _, credentials: credentials.logged_in_user["type"] == "Staff",
931
- session_headers("abc123", "Staff"),
932
- ),
933
- (
934
- Credentials,
935
- lambda request, _: request.headers.get("API-Key") == API_KEY
936
- and request.headers.get("App-Key") == APP_KEY,
937
- custom_headers(API_KEY, APP_KEY),
938
- ),
939
- ],
940
- ids=["basic", "bearer", "API key", "custom", "session"],
941
- )
942
- def authenticated_route(request: SubRequest) -> SimpleNamespace:
943
- """
944
- Parametrized test fixture that returns a Route class with authentication.
945
-
946
- It will also return a set of headers that will pass authentication for the route.
947
- """
948
- credentials_cls, authenticate_impl, headers = request.param
949
-
950
- class Route(SimpleAPIRoute):
951
- PATH = "/route"
952
-
953
- def authenticate(self, credentials: credentials_cls) -> bool: # type: ignore[valid-type]
954
- return authenticate_impl(self.request, credentials)
955
-
956
- def get(self) -> list[Response | Effect]:
957
- return [Effect(type=EffectType.CREATE_TASK, payload="create task")]
958
-
959
- return SimpleNamespace(cls=Route, headers=headers)
960
-
961
-
962
- def test_authentication(authenticated_route: SimpleNamespace) -> None:
963
- """Test that valid credentials result in a successful response."""
964
- effects = handle_request(
965
- authenticated_route.cls, method="GET", path="/route", headers=authenticated_route.headers
966
- )
967
-
968
- assert effects == [Effect(type=EffectType.CREATE_TASK, payload="create task")]
969
-
970
-
971
- @pytest.mark.parametrize(
972
- argnames="headers",
973
- argvalues=[
974
- basic_headers(username=uuid4().hex, password=uuid4().hex),
975
- basic_headers(username="", password=uuid4().hex),
976
- basic_headers(username=uuid4().hex, password=""),
977
- bearer_headers(token=uuid4().hex),
978
- bearer_headers(token=""),
979
- api_key_headers(api_key=uuid4().hex),
980
- api_key_headers(api_key=""),
981
- custom_headers(api_key=uuid4().hex, app_key=uuid4().hex),
982
- custom_headers(api_key="", app_key=uuid4().hex),
983
- custom_headers(api_key=uuid4().hex, app_key=""),
984
- {},
985
- ],
986
- ids=[
987
- "basic",
988
- "basic missing username",
989
- "basic missing password",
990
- "bearer",
991
- "bearer missing token",
992
- "API key",
993
- "API key missing value",
994
- "custom",
995
- "custom missing API key",
996
- "custom missing app key",
997
- "no authentication headers",
998
- ],
999
- )
1000
- def test_authentication_failure(
1001
- authenticated_route: SimpleNamespace, headers: Mapping[str, str]
1002
- ) -> None:
1003
- """Test that invalid credentials result in a failure response."""
1004
- effects = handle_request(authenticated_route.cls, method="GET", path="/route", headers=headers)
1005
-
1006
- assert json.loads(effects[0].payload)["status_code"] == HTTPStatus.UNAUTHORIZED
1007
-
1008
-
1009
- @pytest.mark.parametrize(
1010
- argnames="credentials_cls,headers",
1011
- argvalues=[
1012
- (BasicCredentials, basic_headers(USERNAME, PASSWORD)),
1013
- (BearerCredentials, bearer_headers(TOKEN)),
1014
- (APIKeyCredentials, api_key_headers(API_KEY)),
1015
- (Credentials, custom_headers(API_KEY, APP_KEY)),
1016
- (SessionCredentials, session_headers("abc123", "Patient")),
1017
- ],
1018
- ids=["basic", "bearer", "API key", "custom", "session"],
1019
- )
1020
- def test_authentication_exception(
1021
- credentials_cls: type[Credentials], headers: Mapping[str, str]
1022
- ) -> None:
1023
- """Test that an exception occurring during authentication results in a failure response."""
1024
-
1025
- class Route(SimpleAPIRoute):
1026
- PATH = "/route"
1027
-
1028
- def authenticate(self, credentials: credentials_cls) -> bool: # type: ignore[valid-type]
1029
- raise RuntimeError
1030
-
1031
- def get(self) -> list[Response | Effect]:
1032
- return [Effect(type=EffectType.CREATE_TASK, payload="create task")]
1033
-
1034
- effects = handle_request(Route, method="GET", path="/route", headers=headers)
1035
-
1036
- assert effects == [Response(status_code=HTTPStatus.INTERNAL_SERVER_ERROR).apply()]
1037
-
1038
-
1039
- @pytest.mark.parametrize(
1040
- argnames="mixin_cls,secrets,headers,expected_effects",
1041
- argvalues=[
1042
- (
1043
- BasicAuthMixin,
1044
- {"simpleapi-basic-username": USERNAME, "simpleapi-basic-password": PASSWORD},
1045
- basic_headers(USERNAME, PASSWORD),
1046
- [Effect(type=EffectType.CREATE_TASK, payload="create task")],
1047
- ),
1048
- (
1049
- BasicAuthMixin,
1050
- {"simpleapi-basic-username": USERNAME, "simpleapi-basic-password": PASSWORD},
1051
- basic_headers(uuid4().hex, uuid4().hex),
1052
- [
1053
- JSONResponse(
1054
- content={"error": "Provided credentials are invalid"},
1055
- status_code=HTTPStatus.UNAUTHORIZED,
1056
- ).apply()
1057
- ],
1058
- ),
1059
- (
1060
- BasicAuthMixin,
1061
- {},
1062
- basic_headers(USERNAME, PASSWORD),
1063
- [
1064
- JSONResponse(
1065
- content={"error": "Provided credentials are invalid"},
1066
- status_code=HTTPStatus.UNAUTHORIZED,
1067
- ).apply()
1068
- ],
1069
- ),
1070
- (
1071
- APIKeyAuthMixin,
1072
- {"simpleapi-api-key": API_KEY},
1073
- api_key_headers(API_KEY),
1074
- [Effect(type=EffectType.CREATE_TASK, payload="create task")],
1075
- ),
1076
- (
1077
- APIKeyAuthMixin,
1078
- {"simpleapi-api-key": API_KEY},
1079
- api_key_headers(uuid4().hex),
1080
- [
1081
- JSONResponse(
1082
- content={"error": "Provided credentials are invalid"},
1083
- status_code=HTTPStatus.UNAUTHORIZED,
1084
- ).apply()
1085
- ],
1086
- ),
1087
- (
1088
- APIKeyAuthMixin,
1089
- {},
1090
- api_key_headers(API_KEY),
1091
- [
1092
- JSONResponse(
1093
- content={"error": "Provided credentials are invalid"},
1094
- status_code=HTTPStatus.UNAUTHORIZED,
1095
- ).apply()
1096
- ],
1097
- ),
1098
- (
1099
- StaffSessionAuthMixin,
1100
- {},
1101
- session_headers("abc123", "Staff"),
1102
- [Effect(type=EffectType.CREATE_TASK, payload="create task")],
1103
- ),
1104
- (
1105
- StaffSessionAuthMixin,
1106
- {},
1107
- session_headers("abc123", "Patient"),
1108
- [
1109
- JSONResponse(
1110
- content={"error": "Provided credentials are invalid"},
1111
- status_code=HTTPStatus.UNAUTHORIZED,
1112
- ).apply()
1113
- ],
1114
- ),
1115
- (
1116
- PatientSessionAuthMixin,
1117
- {},
1118
- session_headers("abc123", "Patient"),
1119
- [Effect(type=EffectType.CREATE_TASK, payload="create task")],
1120
- ),
1121
- (
1122
- PatientSessionAuthMixin,
1123
- {},
1124
- session_headers("abc123", "Staff"),
1125
- [
1126
- JSONResponse(
1127
- content={"error": "Provided credentials are invalid"},
1128
- status_code=HTTPStatus.UNAUTHORIZED,
1129
- ).apply()
1130
- ],
1131
- ),
1132
- ],
1133
- ids=[
1134
- "basic valid",
1135
- "basic invalid",
1136
- "basic missing secret",
1137
- "API key valid",
1138
- "API key invalid",
1139
- "API key missing secret",
1140
- "Staff session valid",
1141
- "Staff session invalid",
1142
- "Patient session valid",
1143
- "Patient session invalid",
1144
- ],
1145
- )
1146
- def test_authentication_mixins(
1147
- mixin_cls: type[AuthSchemeMixin],
1148
- secrets: dict[str, str],
1149
- headers: Mapping[str, str],
1150
- expected_effects: Sequence[Effect],
1151
- ) -> None:
1152
- """
1153
- Test that the provided authentication mixins behave correctly in success and failure scenarios.
1154
- """
1155
-
1156
- class Route(mixin_cls, SimpleAPIRoute): # type: ignore[misc,valid-type]
1157
- PATH = "/route"
1158
-
1159
- def __init__(self, *args: Any, **kwargs: Any) -> None:
1160
- super().__init__(*args, **kwargs)
1161
- self.secrets = secrets
1162
-
1163
- def get(self) -> list[Response | Effect]:
1164
- return [Effect(type=EffectType.CREATE_TASK, payload="create task")]
1165
-
1166
- effects = handle_request(Route, method="GET", path="/route", headers=headers)
1167
- assert effects == expected_effects