smart_app_launch_test_kit 0.4.3 → 0.4.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/smart_app_launch/ehr_launch_group_stu2_2.rb +54 -0
- data/lib/smart_app_launch/smart_access_brands_examples/r4_capability_statement.json +198 -0
- data/lib/smart_app_launch/smart_access_brands_group.rb +59 -0
- data/lib/smart_app_launch/smart_access_brands_retrieval_group.rb +21 -0
- data/lib/smart_app_launch/smart_access_brands_retrieve_bundle_test.rb +31 -0
- data/lib/smart_app_launch/smart_access_brands_suite.rb +77 -0
- data/lib/smart_app_launch/smart_access_brands_validate_brands_test.rb +104 -0
- data/lib/smart_app_launch/smart_access_brands_validate_bundle_test.rb +45 -0
- data/lib/smart_app_launch/smart_access_brands_validate_endpoint_urls_test.rb +120 -0
- data/lib/smart_app_launch/smart_access_brands_validate_endpoints_test.rb +70 -0
- data/lib/smart_app_launch/smart_access_brands_validation_group.rb +24 -0
- data/lib/smart_app_launch/smart_stu2_2_suite.rb +243 -0
- data/lib/smart_app_launch/standalone_launch_group_stu2_2.rb +52 -0
- data/lib/smart_app_launch/token_introspection_access_token_group_stu2_2.rb +28 -0
- data/lib/smart_app_launch/token_introspection_group_stu2_2.rb +68 -0
- data/lib/smart_app_launch/token_payload_validation.rb +129 -60
- data/lib/smart_app_launch/token_response_body_test_stu2_2.rb +32 -0
- data/lib/smart_app_launch/version.rb +1 -1
- data/lib/smart_app_launch/well_known_endpoint_test.rb +5 -1
- data/lib/smart_app_launch_test_kit.rb +2 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1565daad4304e65881d1a3a373d29c8674d19d57bf46342a1cdc95db6c29b6f4
|
4
|
+
data.tar.gz: 4923648577e4d53631a8efa7ccae1f254957d490e579359415ca4f9d426ac5fe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1266d06f6b1bd55a93e84b93cdd0de9d628ac5d5eb084dc38817e5639869a35ccb2f5173163f0966786937ef3821a5b1e317233af2be5e042afca840f4649c3
|
7
|
+
data.tar.gz: 5101dabbab6be10be047d9e378f43fc939d605f7adcb5277c07c738f2aabbf80e5b565cb1a1885ed35184ac5b5bdf2b9a4311396498bb02766ed925986ce2433
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require_relative 'ehr_launch_group_stu2'
|
2
|
+
require_relative 'token_response_body_test_stu2_2'
|
3
|
+
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class EHRLaunchGroupSTU22 < EHRLaunchGroupSTU2
|
6
|
+
id :smart_ehr_launch_stu2_2
|
7
|
+
description %(
|
8
|
+
# Background
|
9
|
+
|
10
|
+
The [EHR
|
11
|
+
Launch](http://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#launch-app-ehr-launch)
|
12
|
+
is one of two ways in which an app can be launched, the other being
|
13
|
+
Standalone launch. In an EHR launch, the app is launched from an
|
14
|
+
existing EHR session or portal by a redirect to the registered launch
|
15
|
+
URL. The EHR provides the app two parameters:
|
16
|
+
|
17
|
+
* `iss` - Which contains the FHIR server url
|
18
|
+
* `launch` - An identifier needed for authorization
|
19
|
+
|
20
|
+
# Test Methodology
|
21
|
+
|
22
|
+
Inferno will wait for the EHR server redirect upon execution. When the
|
23
|
+
redirect is received Inferno will check for the presence of the `iss`
|
24
|
+
and `launch` parameters. The security of the authorization endpoint is
|
25
|
+
then checked and authorization is attempted using the provided `launch`
|
26
|
+
identifier.
|
27
|
+
|
28
|
+
For more information on the #{title} see:
|
29
|
+
|
30
|
+
* [SMART EHR Launch Sequence](http://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#launch-app-ehr-launch)
|
31
|
+
)
|
32
|
+
|
33
|
+
config(
|
34
|
+
inputs: {
|
35
|
+
use_pkce: {
|
36
|
+
default: 'true',
|
37
|
+
locked: true
|
38
|
+
},
|
39
|
+
pkce_code_challenge_method: {
|
40
|
+
default: 'S256',
|
41
|
+
locked: true
|
42
|
+
},
|
43
|
+
requested_scopes: {
|
44
|
+
default: 'launch openid fhirUser offline_access user/*.rs'
|
45
|
+
}
|
46
|
+
}
|
47
|
+
)
|
48
|
+
|
49
|
+
test from: :smart_token_response_body_stu2_2
|
50
|
+
|
51
|
+
token_response_body_index = children.find_index { |child| child.id.to_s.end_with? 'token_response_body' }
|
52
|
+
children[token_response_body_index] = children.pop
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
{
|
2
|
+
"resourceType": "CapabilityStatement",
|
3
|
+
"id": "examplehealth-r4",
|
4
|
+
"text": {
|
5
|
+
"status": "generated",
|
6
|
+
"div": "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n\t\t\t<p>The EHR Server supports the following transactions for the resource Person: read, vread, \n update, history, search(name,gender), create and updates.</p>\n\t\t\t<p>The EHR System supports the following message: admin-notify::Person.</p>\n\t\t\t<p>The EHR Application has a \n <a href=\"http://fhir.hl7.org/base/Profilebc054d23-75e1-4dc6-aca5-838b6b1ac81d/_history/b5fdd9fc-b021-4ea1-911a-721a60663796\">general document profile</a>.\n </p>\n\t\t</div>"
|
7
|
+
},
|
8
|
+
"url": "urn:uuid:68D043B5-9ECF-4559-A57A-396E0D452311",
|
9
|
+
"version": "20130510",
|
10
|
+
"name": "ACME-EHR",
|
11
|
+
"title": "ACME EHR capability statement",
|
12
|
+
"status": "draft",
|
13
|
+
"experimental": true,
|
14
|
+
"date": "2012-01-04",
|
15
|
+
"publisher": "ACME Corporation",
|
16
|
+
"contact": [
|
17
|
+
{
|
18
|
+
"name": "System Administrator",
|
19
|
+
"telecom": [
|
20
|
+
{
|
21
|
+
"system": "email",
|
22
|
+
"value": "wile@acme.org"
|
23
|
+
}
|
24
|
+
]
|
25
|
+
}
|
26
|
+
],
|
27
|
+
"description": "This is the FHIR capability statement for the main EHR at ACME for the private interface - it does not describe the public interface",
|
28
|
+
"useContext": [
|
29
|
+
{
|
30
|
+
"code": {
|
31
|
+
"system": "http://terminology.hl7.org/CodeSystem/usage-context-type",
|
32
|
+
"code": "focus"
|
33
|
+
},
|
34
|
+
"valueCodeableConcept": {
|
35
|
+
"coding": [
|
36
|
+
{
|
37
|
+
"system": "http://terminology.hl7.org/CodeSystem/variant-state",
|
38
|
+
"code": "positive"
|
39
|
+
}
|
40
|
+
]
|
41
|
+
}
|
42
|
+
}
|
43
|
+
],
|
44
|
+
"jurisdiction": [
|
45
|
+
{
|
46
|
+
"coding": [
|
47
|
+
{
|
48
|
+
"system": "urn:iso:std:iso:3166",
|
49
|
+
"code": "US",
|
50
|
+
"display": "United States of America (the)"
|
51
|
+
}
|
52
|
+
]
|
53
|
+
}
|
54
|
+
],
|
55
|
+
"purpose": "Main EHR capability statement, published for contracting and operational support",
|
56
|
+
"copyright": "Copyright © Acme Healthcare and GoodCorp EHR Systems",
|
57
|
+
"kind": "instance",
|
58
|
+
"instantiates": [
|
59
|
+
"http://ihe.org/fhir/CapabilityStatement/pixm-client"
|
60
|
+
],
|
61
|
+
"software": {
|
62
|
+
"name": "EHR",
|
63
|
+
"version": "0.00.020.2134",
|
64
|
+
"releaseDate": "2012-01-04"
|
65
|
+
},
|
66
|
+
"implementation": {
|
67
|
+
"description": "main EHR at ACME",
|
68
|
+
"url": "http://10.2.3.4/fhir"
|
69
|
+
},
|
70
|
+
"fhirVersion": "4.0.1",
|
71
|
+
"format": [
|
72
|
+
"xml",
|
73
|
+
"json"
|
74
|
+
],
|
75
|
+
"patchFormat": [
|
76
|
+
"application/xml-patch+xml",
|
77
|
+
"application/json-patch+json"
|
78
|
+
],
|
79
|
+
"implementationGuide": [
|
80
|
+
"http://hl7.org/fhir/us/lab"
|
81
|
+
],
|
82
|
+
"rest": [
|
83
|
+
{
|
84
|
+
"mode": "server",
|
85
|
+
"documentation": "Main FHIR endpoint for acem health",
|
86
|
+
"security": {
|
87
|
+
"cors": true,
|
88
|
+
"service": [
|
89
|
+
{
|
90
|
+
"coding": [
|
91
|
+
{
|
92
|
+
"system": "http://terminology.hl7.org/CodeSystem/restful-security-service",
|
93
|
+
"code": "SMART-on-FHIR"
|
94
|
+
}
|
95
|
+
]
|
96
|
+
}
|
97
|
+
],
|
98
|
+
"description": "See Smart on FHIR documentation"
|
99
|
+
},
|
100
|
+
"resource": [
|
101
|
+
{
|
102
|
+
"type": "Patient",
|
103
|
+
"profile": "http://registry.fhir.org/r4/StructureDefinition/7896271d-57f6-4231-89dc-dcc91eab2416",
|
104
|
+
"supportedProfile": [
|
105
|
+
"http://registry.fhir.org/r4/StructureDefinition/00ab9e7a-06c7-4f77-9234-4154ca1e3347"
|
106
|
+
],
|
107
|
+
"documentation": "This server does not let the clients create identities.",
|
108
|
+
"interaction": [
|
109
|
+
{
|
110
|
+
"code": "read"
|
111
|
+
},
|
112
|
+
{
|
113
|
+
"code": "vread",
|
114
|
+
"documentation": "Only supported for patient records since 12-Dec 2012"
|
115
|
+
},
|
116
|
+
{
|
117
|
+
"code": "update"
|
118
|
+
},
|
119
|
+
{
|
120
|
+
"code": "history-instance"
|
121
|
+
},
|
122
|
+
{
|
123
|
+
"code": "create"
|
124
|
+
},
|
125
|
+
{
|
126
|
+
"code": "history-type"
|
127
|
+
}
|
128
|
+
],
|
129
|
+
"versioning": "versioned-update",
|
130
|
+
"readHistory": true,
|
131
|
+
"updateCreate": false,
|
132
|
+
"conditionalCreate": true,
|
133
|
+
"conditionalRead": "full-support",
|
134
|
+
"conditionalUpdate": false,
|
135
|
+
"conditionalDelete": "not-supported",
|
136
|
+
"searchInclude": [
|
137
|
+
"Organization"
|
138
|
+
],
|
139
|
+
"searchRevInclude": [
|
140
|
+
"Person"
|
141
|
+
],
|
142
|
+
"searchParam": [
|
143
|
+
{
|
144
|
+
"name": "identifier",
|
145
|
+
"definition": "http://hl7.org/fhir/SearchParameter/Patient-identifier",
|
146
|
+
"type": "token",
|
147
|
+
"documentation": "Only supports search by institution MRN"
|
148
|
+
},
|
149
|
+
{
|
150
|
+
"name": "general-practitioner",
|
151
|
+
"definition": "http://hl7.org/fhir/SearchParameter/Patient-general-practitioner",
|
152
|
+
"type": "reference"
|
153
|
+
}
|
154
|
+
]
|
155
|
+
}
|
156
|
+
],
|
157
|
+
"interaction": [
|
158
|
+
{
|
159
|
+
"code": "transaction"
|
160
|
+
},
|
161
|
+
{
|
162
|
+
"code": "history-system"
|
163
|
+
}
|
164
|
+
],
|
165
|
+
"compartment": [
|
166
|
+
"http://hl7.org/fhir/CompartmentDefinition/patient"
|
167
|
+
]
|
168
|
+
}
|
169
|
+
],
|
170
|
+
"messaging": [
|
171
|
+
{
|
172
|
+
"endpoint": [
|
173
|
+
{
|
174
|
+
"protocol": {
|
175
|
+
"system": "http://terminology.hl7.org/CodeSystem/message-transport",
|
176
|
+
"code": "mllp"
|
177
|
+
},
|
178
|
+
"address": "mllp:10.1.1.10:9234"
|
179
|
+
}
|
180
|
+
],
|
181
|
+
"reliableCache": 30,
|
182
|
+
"documentation": "ADT A08 equivalent for external system notifications",
|
183
|
+
"supportedMessage": [
|
184
|
+
{
|
185
|
+
"mode": "receiver",
|
186
|
+
"definition": "MessageDefinition/example"
|
187
|
+
}
|
188
|
+
]
|
189
|
+
}
|
190
|
+
],
|
191
|
+
"document": [
|
192
|
+
{
|
193
|
+
"mode": "consumer",
|
194
|
+
"documentation": "Basic rules for all documents in the EHR system",
|
195
|
+
"profile": "http://fhir.hl7.org/base/Profilebc054d23-75e1-4dc6-aca5-838b6b1ac81d/_history/b5fdd9fc-b021-4ea1-911a-721a60663796"
|
196
|
+
}
|
197
|
+
]
|
198
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative 'smart_access_brands_validation_group'
|
2
|
+
require_relative 'smart_access_brands_retrieval_group'
|
3
|
+
|
4
|
+
module SMARTAppLaunch
|
5
|
+
class SMARTAccessBrandsGroup < Inferno::TestGroup
|
6
|
+
id :retrieve_and_validate_smart_access_brands
|
7
|
+
title 'Retrieve and Validate SMART Access Brands Bundle'
|
8
|
+
description %(
|
9
|
+
Verify that the Brand Bundle Publisher makes its User-access Brands publication publicly available
|
10
|
+
in the format defined by the [User Access Brand Bundle Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-brands-bundle.html)
|
11
|
+
with valid Endpoint and Organization entries according to the
|
12
|
+
[User Access Endpoint Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-endpoint.html)
|
13
|
+
and the [User Access Brand Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-brand.html)
|
14
|
+
respectively. This test group will issue a HTTP GET request against the supplied URL to retrieve the publisher's
|
15
|
+
User-access Brands list and ensure the list is publicly accessible. It will then ensure that the returned
|
16
|
+
User-access Brands list publication is in the User Access Brand Bundle Profile format with valid User Access
|
17
|
+
Brands and User Access Endpoints.
|
18
|
+
|
19
|
+
For systems that provide the User Access Brands Bundle at a public endpoint, please run
|
20
|
+
this test with the User Access Brands Publication URL input populated and the User Access Brands Bundle
|
21
|
+
input left blank. While it is the expectation of the specification for the User Access Brands Bundle to be served
|
22
|
+
at a public-facing endpoint, testers can validate a User Access Brands Bundle not served at a public endpoint by
|
23
|
+
running these tests with the User Access Brands Bundle input populated and the User Access Brands Publication URL
|
24
|
+
input left blank. This will cause these group of retrieval group of tests to skip, rather than pass completely,
|
25
|
+
as being served at an stable location is considered a requirement of the spec.
|
26
|
+
)
|
27
|
+
|
28
|
+
input_instructions <<~INSTRUCTIONS
|
29
|
+
For systems that make their User Access Brand Bundle available at a public endpoint, please input
|
30
|
+
the User Access Brand Publication URL to retrieve the Bundle from there in order to perform validation, and leave
|
31
|
+
the User Access Brand Bundle input blank.
|
32
|
+
|
33
|
+
While it is the expectation of the specification for the User Access Brands Bundle to be publicly available,
|
34
|
+
for systems that do not have a User Access Brand Bundle served at a public endpoint, testers can validate by
|
35
|
+
providing the User Access Brand Bundle as an input and leaving the User Access Brand Publication URL input blank.
|
36
|
+
INSTRUCTIONS
|
37
|
+
|
38
|
+
run_as_group
|
39
|
+
|
40
|
+
input :user_access_brands_publication_url,
|
41
|
+
title: 'User Access Brands Publication URL',
|
42
|
+
description: %(The URL to the developer's public User Access Brands Publication. Insert your User Access
|
43
|
+
Brands publication URL if you host your Bundle at a public-facing endpoint and want Inferno to retrieve the
|
44
|
+
Bundle from there.),
|
45
|
+
optional: true
|
46
|
+
|
47
|
+
input :user_access_brands_bundle,
|
48
|
+
title: 'User Access Brands Bundle',
|
49
|
+
description: %(The developer's User Access Brands Publication in the JSON string format. If this input is
|
50
|
+
populated, Inferno will use the Bundle inserted here to do validation. Insert your User Access Brands
|
51
|
+
Bundle in the JSON format in this input to validate your list without Inferno needing to access the Bundle
|
52
|
+
via a public-facing endpoint.),
|
53
|
+
type: 'textarea',
|
54
|
+
optional: true
|
55
|
+
|
56
|
+
group from: :smart_access_brands_retrieval
|
57
|
+
group from: :smart_access_brands_validation
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'smart_access_brands_retrieve_bundle_test'
|
2
|
+
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class SMARTAccessBrandsRetrievalGroup < Inferno::TestGroup
|
5
|
+
id :smart_access_brands_retrieval
|
6
|
+
title 'Retrieve SMART Access Brands Bundle'
|
7
|
+
description %(
|
8
|
+
A publisher's User Access Brand Bundle must be publicly available. This test
|
9
|
+
issues a HTTP GET request against the supplied URL and expects to receive
|
10
|
+
the User Access Brand Bundle at this location.
|
11
|
+
)
|
12
|
+
run_as_group
|
13
|
+
|
14
|
+
http_client do
|
15
|
+
url :user_access_brands_publication_url
|
16
|
+
headers Accept: 'application/json, application/fhir+json'
|
17
|
+
end
|
18
|
+
|
19
|
+
test from: :smart_access_brands_retrieve_bundle
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module SMARTAppLaunch
|
2
|
+
class SMARTAccessBrandsRetrievalTest < Inferno::Test
|
3
|
+
id :smart_access_brands_retrieve_bundle
|
4
|
+
title 'Server returns publicly accessible SMART Access Brands Bundle'
|
5
|
+
description %(
|
6
|
+
Verify that the publisher's User Access Brands Bundle can be publicly
|
7
|
+
accessed at the supplied URL location.
|
8
|
+
)
|
9
|
+
|
10
|
+
makes_request :bundle_request
|
11
|
+
|
12
|
+
input :user_access_brands_publication_url,
|
13
|
+
optional: true
|
14
|
+
|
15
|
+
run do
|
16
|
+
skip_if user_access_brands_publication_url.blank?, %(
|
17
|
+
No User Access Brands Publication endpoint URL inputted. It is an expectation of the specification for the
|
18
|
+
User Access Brands Bundle to be publicly available'
|
19
|
+
)
|
20
|
+
|
21
|
+
get(tags: ['smart_access_brands_bundle'])
|
22
|
+
assert_response_status(200)
|
23
|
+
assert(request.headers.any? { |header| header.name == 'access-control-allow-origin' }, %(
|
24
|
+
All GET requests must support Cross-Origin Resource Sharing (CORS) for all GET requests to the artifacts
|
25
|
+
described in this guide.))
|
26
|
+
unless request.headers.any? { |header| header.name == 'etag' }
|
27
|
+
add_message('warning', 'Brand Bundle HTTP responses should include an Etag header')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require_relative 'smart_access_brands_group'
|
2
|
+
|
3
|
+
module SMARTAppLaunch
|
4
|
+
class SMARTAccessBrandsSuite < Inferno::TestSuite
|
5
|
+
id 'smart_access_brands'
|
6
|
+
title 'SMART User-access Brands and Endpoints STU2.2'
|
7
|
+
short_title 'SMART User-access Brands'
|
8
|
+
version VERSION
|
9
|
+
|
10
|
+
description <<~DESCRIPTION
|
11
|
+
The SMART User-access Brands Test Suite verifies that Brand Bundle Publishers publish valid User-access
|
12
|
+
Brand Bundles according to the SMART App Launch IG
|
13
|
+
[User-access Brands and Endpoints](https://hl7.org/fhir/smart-app-launch/STU2.2/brands.html#user-access-brands-and-endpoints)
|
14
|
+
requirements.
|
15
|
+
|
16
|
+
The specification defines FHIR profiles for Endpoint, Organization, and Bundle resources that help users connect
|
17
|
+
their apps to health data providers. It outlines the process for data providers to publish FHIR Endpoint and
|
18
|
+
Organization resources, where each Organization includes essential branding information such as the organization's
|
19
|
+
name, logo, and user access details. Apps present branded Organizations to help users select the right data
|
20
|
+
providers.
|
21
|
+
|
22
|
+
This test suite is currently designed to fetch and validate a single User-Access Brand Bundle. It does not
|
23
|
+
currently evaluate the system's ability to allow Health Data Providers to manage all data elements marked
|
24
|
+
"Must-Support" in the "User Access Brand" and "User Access Endpoint" profiles.
|
25
|
+
DESCRIPTION
|
26
|
+
|
27
|
+
input_instructions <<~INSTRUCTIONS
|
28
|
+
For systems that make their User Access Brand Bundle available at a public endpoint, please input
|
29
|
+
the User Access Brand Publication URL to retrieve the Bundle from there in order to perform validation, and leave
|
30
|
+
the User Access Brand Bundle input blank.
|
31
|
+
|
32
|
+
Although it is expected that systems do have their Bundle publicly available, for systems that do not have a
|
33
|
+
User Access Brand Bundle served at a public endpoint, testers can validate by providing the User Access Brand
|
34
|
+
Bundle as an input and leaving the User Access Brand Publication URL input blank.
|
35
|
+
INSTRUCTIONS
|
36
|
+
|
37
|
+
VALIDATION_MESSAGE_FILTERS = [
|
38
|
+
/\A\S+: \S+: URL value '.*' does not resolve/,
|
39
|
+
%r{\A\S+: \S+: Bundled or contained reference not found within the bundle/resource} # Validator issue with Brand profile: https://chat.fhir.org/#narrow/stream/291844-FHIR-Validator/topic/SMART.20v2.2E2.20User.20Access.20Brands.3A.20Brand.20validation.20error.3F/near/466321024
|
40
|
+
].freeze
|
41
|
+
|
42
|
+
fhir_resource_validator do
|
43
|
+
igs 'hl7.fhir.uv.smart-app-launch#2.2.0'
|
44
|
+
|
45
|
+
cli_context({
|
46
|
+
# Allow example URLs because we give tester option to follow URLs anyhow
|
47
|
+
# (configurable) and its nice to be able to have the examples in the IG pass
|
48
|
+
allowExampleUrls: true
|
49
|
+
})
|
50
|
+
|
51
|
+
message_filters = VALIDATION_MESSAGE_FILTERS
|
52
|
+
|
53
|
+
exclude_message do |message|
|
54
|
+
message_filters.any? { |filter| filter.match? message.message }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Dir.each_child(File.join(__dir__, '/smart_access_brands_examples/')) do |filename|
|
59
|
+
resource_example = File.read(File.join(__dir__, '/smart_access_brands_examples/', filename))
|
60
|
+
if filename.end_with?('.erb')
|
61
|
+
erb_template = ERB.new(resource_example)
|
62
|
+
resource_example = JSON.parse(erb_template.result).to_json
|
63
|
+
filename = filename.delete_suffix('.erb')
|
64
|
+
headers = { 'Content-Type' => 'application/json', 'Access-Control-Allow-Origin' => '*',
|
65
|
+
'Etag' => SecureRandom.hex(32) }
|
66
|
+
else
|
67
|
+
filename = "#{filename.delete_suffix('.json')}/metadata"
|
68
|
+
headers = { 'Content-Type' => 'application/json' }
|
69
|
+
end
|
70
|
+
route_handler = proc { [200, headers, [resource_example]] }
|
71
|
+
|
72
|
+
route :get, File.join('/examples/', filename), route_handler
|
73
|
+
end
|
74
|
+
|
75
|
+
group from: :retrieve_and_validate_smart_access_brands
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module SMARTAppLaunch
|
2
|
+
class SMARTAccessBrandsValidateBrands < Inferno::Test
|
3
|
+
id :smart_access_brands_valid_brands
|
4
|
+
title 'Service Base URL List contains valid Brand resources'
|
5
|
+
description %(
|
6
|
+
Verify that Bundle of User Access Brands and Endpoints contains Brands that are valid
|
7
|
+
Organization resources according to the [User Access Brand Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-brand.html).
|
8
|
+
|
9
|
+
Along with validating the Organization resources, this test also ensures:
|
10
|
+
- Each endpoint referenced in the Organization portal extension also appear in Organization.endpoint
|
11
|
+
- Any endpoints referenced by the Organization must appear in the Bundle
|
12
|
+
|
13
|
+
This test does not currently validate availability or format of Brand or Portal logos.
|
14
|
+
)
|
15
|
+
|
16
|
+
input :user_access_brands_bundle,
|
17
|
+
optional: true
|
18
|
+
|
19
|
+
def find_referenced_endpoint(bundle_resource, endpoint_id_ref)
|
20
|
+
bundle_resource
|
21
|
+
.entry
|
22
|
+
.map(&:resource)
|
23
|
+
.select { |resource| resource.resourceType == 'Endpoint' }
|
24
|
+
.map(&:id)
|
25
|
+
.select { |endpoint_id| endpoint_id_ref.include? endpoint_id }
|
26
|
+
end
|
27
|
+
|
28
|
+
def find_extension(extension_array, extension_name)
|
29
|
+
extension_array.find do |extension|
|
30
|
+
extension.url.ends_with?(extension_name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_all_extensions(extension_array, extension_name)
|
35
|
+
extension_array.select do |extension|
|
36
|
+
extension.url == extension_name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_portal_endpoints(portal_endpoints, organization_endpoints)
|
41
|
+
portal_endpoints.each do |portal_endpoint|
|
42
|
+
portal_endpoint_found = organization_endpoints.any? do |endpoint_reference|
|
43
|
+
portal_endpoint.valueReference.reference == endpoint_reference
|
44
|
+
end
|
45
|
+
assert(portal_endpoint_found, %(
|
46
|
+
Portal endpoints must also appear at Organization.endpoint. The portal endpoint with reference
|
47
|
+
#{portal_endpoint.valueReference.reference} was not found at Organization.endpoint.))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def skip_message
|
52
|
+
%(
|
53
|
+
No User Access Brands request was made in the previous test, and no User Access Brands Bundle was provided as
|
54
|
+
input instead. Either provide a User Access Brands Publication URL to retrieve the Bundle via a HTTP GET
|
55
|
+
request, or provide the Bundle as an input.
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
run do
|
60
|
+
bundle_response = if user_access_brands_bundle.blank?
|
61
|
+
load_tagged_requests('smart_access_brands_bundle')
|
62
|
+
skip skip_message if requests.length != 1
|
63
|
+
requests.first.response_body
|
64
|
+
else
|
65
|
+
user_access_brands_bundle
|
66
|
+
end
|
67
|
+
|
68
|
+
skip_if bundle_response.blank?, 'No SMART Access Brands Bundle contained in the response'
|
69
|
+
|
70
|
+
assert_valid_json(bundle_response)
|
71
|
+
bundle_resource = FHIR.from_contents(bundle_response)
|
72
|
+
|
73
|
+
skip_if bundle_resource.entry.empty?, 'The given Bundle does not contain any resources'
|
74
|
+
assert_valid_bundle_entries(bundle: bundle_resource,
|
75
|
+
resource_types: {
|
76
|
+
Organization: 'http://hl7.org/fhir/smart-app-launch/StructureDefinition/user-access-brand'
|
77
|
+
})
|
78
|
+
|
79
|
+
organization_resources = bundle_resource
|
80
|
+
.entry
|
81
|
+
.map(&:resource)
|
82
|
+
.select { |resource| resource.resourceType == 'Organization' }
|
83
|
+
|
84
|
+
organization_resources.each do |organization|
|
85
|
+
endpoint_references = organization.endpoint.map(&:reference)
|
86
|
+
|
87
|
+
if organization.extension.present?
|
88
|
+
portal_extension = find_extension(organization.extension, '/organization-portal')
|
89
|
+
if portal_extension.present?
|
90
|
+
portal_endpoints = find_all_extensions(portal_extension.extension, 'portalEndpoint')
|
91
|
+
check_portal_endpoints(portal_endpoints, endpoint_references)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
endpoint_references.each do |endpoint_id_ref|
|
96
|
+
organization_referenced_endpts = find_referenced_endpoint(bundle_resource, endpoint_id_ref)
|
97
|
+
assert !organization_referenced_endpts.empty?,
|
98
|
+
"Organization with id: #{organization.id} references an Endpoint that is not contained in this
|
99
|
+
bundle."
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module SMARTAppLaunch
|
2
|
+
class SMARTAccessBrandsValidateBundle < Inferno::Test
|
3
|
+
id :smart_access_brands_valid_bundle
|
4
|
+
title 'Server returns valid Bundle resource according to the User Access Brands Bundle Profile'
|
5
|
+
description %(
|
6
|
+
Verify that the returned Bundle is a valid User Access Brands Bundle according to the
|
7
|
+
[User Access Brand Bundle Profile](https://hl7.org/fhir/smart-app-launch/STU2.2/StructureDefinition-user-access-brands-bundle.html).
|
8
|
+
|
9
|
+
This test also ensures the Bundle is the 'collection' type and that it is not empty.
|
10
|
+
)
|
11
|
+
|
12
|
+
input :user_access_brands_bundle,
|
13
|
+
optional: true
|
14
|
+
|
15
|
+
def skip_message
|
16
|
+
%(
|
17
|
+
No User Access Brands request was made in the previous test, and no User Access Brands Bundle was provided as
|
18
|
+
input instead. Either provide a User Access Brands Publication URL to retrieve the Bundle via a HTTP GET
|
19
|
+
request, or provide the Bundle as an input.
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
run do
|
24
|
+
bundle_response = if user_access_brands_bundle.blank?
|
25
|
+
load_tagged_requests('smart_access_brands_bundle')
|
26
|
+
skip skip_message if requests.length != 1
|
27
|
+
requests.first.response_body
|
28
|
+
else
|
29
|
+
user_access_brands_bundle
|
30
|
+
end
|
31
|
+
|
32
|
+
skip_if bundle_response.blank?, 'No SMART Access Brands Bundle contained in the response'
|
33
|
+
|
34
|
+
assert_valid_json(bundle_response)
|
35
|
+
bundle_resource = FHIR.from_contents(bundle_response)
|
36
|
+
assert_resource_type(:bundle, resource: bundle_resource)
|
37
|
+
assert_valid_resource(resource: bundle_resource, profile_url: 'http://hl7.org/fhir/smart-app-launch/StructureDefinition/user-access-brands-bundle')
|
38
|
+
|
39
|
+
assert(bundle_resource.type == 'collection', 'The SMART Access Brands Bundle must be type `collection`')
|
40
|
+
assert(bundle_resource.timestamp.present?,
|
41
|
+
'Bundle.timestamp must be populated to advertise the timestamp of the last change to the contents')
|
42
|
+
assert !bundle_resource.entry.empty?, 'The given Bundle does not contain any brands or endpoints.'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|