Skip to content

Commit 0a2d427

Browse files
committed
add demo plugin for UAT
1 parent 8726cd4 commit 0a2d427

File tree

4 files changed

+254
-0
lines changed

4 files changed

+254
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"sdk_version": "0.1.4",
3+
"plugin_version": "0.0.1",
4+
"name": "pharmacy_search_demo",
5+
"description": "Demo plugin for KOALA-4355: exercises all pharmacy search filter parameters",
6+
"components": {
7+
"protocols": [
8+
{
9+
"class": "pharmacy_search_demo.protocols.demo:PharmacySearchDemo",
10+
"description": "Runs all pharmacy search test cases and returns results"
11+
}
12+
],
13+
"commands": [],
14+
"content": [],
15+
"effects": [],
16+
"views": []
17+
},
18+
"secrets": [],
19+
"tags": {},
20+
"references": [],
21+
"license": "",
22+
"diagram": false,
23+
"readme": "./README.md"
24+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# pharmacy_search_demo
2+
3+
Demo plugin for [KOALA-4355](https://canvasmedical.atlassian.net/browse/KOALA-4355) — exercises all new pharmacy search filter parameters added in [canvas-plugins#1542](https://github.com/canvas-medical/canvas-plugins/pull/1542).
4+
5+
## Usage
6+
7+
```
8+
GET /plugin-io/api/pharmacy_search_demo/run
9+
```
10+
11+
Requires a logged-in staff session (`StaffSessionAuthMixin`). Returns a JSON report of all test cases with pass/fail status.

example-plugins/pharmacy_search_demo/protocols/__init__.py

Whitespace-only changes.
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
"""Demo for KOALA-4355: improved pharmacy search filters.
2+
3+
Install this plugin and hit GET /plugin-io/api/pharmacy_search_demo/run
4+
to exercise every new search_pharmacies parameter and get a pass/fail report.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import traceback
10+
from collections.abc import Callable
11+
from http import HTTPStatus
12+
from typing import Any
13+
14+
from canvas_sdk.effects.simple_api import JSONResponse, Response
15+
from canvas_sdk.handlers.simple_api import SimpleAPIRoute, StaffSessionAuthMixin
16+
from canvas_sdk.utils.http import pharmacy_http
17+
from logger import log
18+
19+
CheckFn = Callable[[list[dict]], tuple[bool, str]]
20+
21+
22+
class PharmacySearchDemo(StaffSessionAuthMixin, SimpleAPIRoute):
23+
"""Runs all pharmacy search test cases and returns a JSON report."""
24+
25+
PATH = "/run"
26+
27+
def get(self) -> list[Response]:
28+
"""Execute every test case and return results."""
29+
results: list[dict[str, Any]] = []
30+
31+
for case in TEST_CASES:
32+
name = case["name"]
33+
kwargs = case["kwargs"]
34+
check = case["check"]
35+
36+
try:
37+
log.info(f"[pharmacy_search_demo] running: {name}")
38+
pharmacies = pharmacy_http.search_pharmacies(**kwargs)
39+
passed, detail = check(pharmacies)
40+
results.append(
41+
{
42+
"name": name,
43+
"passed": passed,
44+
"detail": detail,
45+
"result_count": len(pharmacies),
46+
}
47+
)
48+
except Exception as exc:
49+
log.error(f"[pharmacy_search_demo] {name} error: {exc}")
50+
results.append(
51+
{
52+
"name": name,
53+
"passed": False,
54+
"detail": f"exception: {exc}",
55+
"traceback": traceback.format_exc(),
56+
}
57+
)
58+
59+
passed_count = sum(1 for r in results if r["passed"])
60+
total = len(results)
61+
62+
return [
63+
JSONResponse(
64+
status_code=HTTPStatus.OK,
65+
content={
66+
"summary": f"{passed_count}/{total} passed",
67+
"all_passed": passed_count == total,
68+
"results": results,
69+
},
70+
)
71+
]
72+
73+
74+
# ---------------------------------------------------------------------------
75+
# Check helpers
76+
# ---------------------------------------------------------------------------
77+
78+
79+
def _is_nonempty(pharmacies: list[dict]) -> tuple[bool, str]:
80+
if pharmacies:
81+
return True, f"returned {len(pharmacies)} result(s)"
82+
return False, "expected results but got none"
83+
84+
85+
def _is_empty_ok(pharmacies: list[dict]) -> tuple[bool, str]:
86+
return True, f"returned {len(pharmacies)} result(s) (empty is acceptable)"
87+
88+
89+
def _all_in_state(state: str) -> CheckFn:
90+
def check(pharmacies: list[dict]) -> tuple[bool, str]:
91+
if not pharmacies:
92+
return False, "no results to verify"
93+
mismatches = [
94+
p.get("state", p.get("State", ""))
95+
for p in pharmacies
96+
if (p.get("state") or p.get("State") or "").upper() != state.upper()
97+
]
98+
if mismatches:
99+
return False, f"{len(mismatches)} pharmacy(ies) not in state {state}: {mismatches[:5]}"
100+
return True, f"all {len(pharmacies)} result(s) in state {state}"
101+
102+
return check
103+
104+
105+
def _all_zip_startswith(prefix: str) -> CheckFn:
106+
def check(pharmacies: list[dict]) -> tuple[bool, str]:
107+
if not pharmacies:
108+
return False, "no results to verify"
109+
mismatches = []
110+
for p in pharmacies:
111+
zip_code = str(p.get("zip_code") or p.get("ZipCode") or p.get("zip") or "")
112+
if not zip_code.startswith(prefix):
113+
mismatches.append(zip_code)
114+
if mismatches:
115+
return False, f"{len(mismatches)} zip(s) don't start with {prefix}: {mismatches[:5]}"
116+
return True, f"all {len(pharmacies)} result(s) have zip starting with {prefix}"
117+
118+
return check
119+
120+
121+
def _org_name_contains(substr: str) -> CheckFn:
122+
def check(pharmacies: list[dict]) -> tuple[bool, str]:
123+
if not pharmacies:
124+
return False, "no results to verify"
125+
mismatches = []
126+
for p in pharmacies:
127+
name = p.get("organization_name") or p.get("OrganizationName") or p.get("name") or ""
128+
if substr.lower() not in name.lower():
129+
mismatches.append(name)
130+
if mismatches:
131+
return (
132+
False,
133+
f"{len(mismatches)} name(s) don't contain '{substr}': {mismatches[:5]}",
134+
)
135+
return True, f"all {len(pharmacies)} result(s) contain '{substr}' in name"
136+
137+
return check
138+
139+
140+
def _has_location_fields(pharmacies: list[dict]) -> tuple[bool, str]:
141+
"""Just verify the call succeeded — location ordering is server-side."""
142+
if not pharmacies:
143+
return False, "no results to verify"
144+
return True, f"returned {len(pharmacies)} result(s) with location ordering"
145+
146+
147+
# ---------------------------------------------------------------------------
148+
# Test cases
149+
# ---------------------------------------------------------------------------
150+
151+
TEST_CASES: list[dict[str, Any]] = [
152+
{
153+
"name": "1. basic text search",
154+
"kwargs": {"search_term": "walgreens"},
155+
"check": _is_nonempty,
156+
},
157+
{
158+
"name": "2. empty/no search term sends no search= param",
159+
"kwargs": {"search_term": None},
160+
"check": _is_empty_ok,
161+
},
162+
{
163+
"name": "3. filter by state",
164+
"kwargs": {"search_term": None, "state": "CA"},
165+
"check": _all_in_state("CA"),
166+
},
167+
{
168+
"name": "4. filter by organization name (icontains)",
169+
"kwargs": {"search_term": None, "organization_name": "CVS"},
170+
"check": _org_name_contains("CVS"),
171+
},
172+
{
173+
"name": "5. filter by zip code prefix",
174+
"kwargs": {"search_term": None, "zip_code_prefix": "902"},
175+
"check": _all_zip_startswith("902"),
176+
},
177+
{
178+
"name": "6. filter by multiple zip code prefixes",
179+
"kwargs": {"search_term": None, "zip_code_prefix": "902,100"},
180+
"check": _is_nonempty,
181+
},
182+
{
183+
"name": "7. filter by NCPDP ID",
184+
"kwargs": {"search_term": None, "ncpdp_id": "0000000"},
185+
"check": _is_empty_ok,
186+
},
187+
{
188+
"name": "8. filter by specialty type",
189+
"kwargs": {"search_term": None, "specialty_type": "retail"},
190+
"check": _is_nonempty,
191+
},
192+
{
193+
"name": "9. filter by exact ID (nonexistent)",
194+
"kwargs": {"search_term": None, "id": 999999999},
195+
"check": _is_empty_ok,
196+
},
197+
{
198+
"name": "10. location-based ordering",
199+
"kwargs": {
200+
"search_term": "pharmacy",
201+
"latitude": "34.05",
202+
"longitude": "-118.24",
203+
},
204+
"check": _has_location_fields,
205+
},
206+
{
207+
"name": "11. combined filters: state + organization name",
208+
"kwargs": {
209+
"search_term": None,
210+
"state": "CA",
211+
"organization_name": "Walgreens",
212+
},
213+
"check": lambda pharmacies: (
214+
(True, f"all {len(pharmacies)} result(s) match combined filters")
215+
if pharmacies
216+
else (False, "no results for combined filter")
217+
),
218+
},
219+
]

0 commit comments

Comments
 (0)