Skip to content

Commit fe8249e

Browse files
committed
Message integration tests work
Signed-off-by: Mitch Gaffigan <mitch.gaffigan@comcast.net>
1 parent a21363b commit fe8249e

17 files changed

Lines changed: 1289 additions & 37 deletions

File tree

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,4 @@ release.properties
6262
replay_pid*
6363
.github/
6464
ci/
65+
.github/

.github/workflows/build.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,5 +127,14 @@ jobs:
127127
oie-ci-runner:${{ github.sha }} \
128128
--workspace /workspace \
129129
--configuration "${{ matrix.configuration }}" \
130-
--server-image "${{ needs.build.outputs.server_image }}"
130+
--server-image "${{ needs.build.outputs.server_image }}" \
131+
--results-root ci/test-results
132+
133+
- name: Upload Docker Smoke Test Results
134+
if: ${{ !cancelled() }}
135+
uses: actions/upload-artifact@v4
136+
with:
137+
name: Test Results Docker Smoke - ${{ matrix.configuration }}
138+
path: |
139+
ci/test-results/**/*.xml
131140

ci/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
test-results/
2+
__pycache__/

ci/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,14 +179,17 @@ channels/
179179
Test fixture are split into the original data:
180180

181181
- `source` is the payload sent into the deployed channel.
182-
- `source_metadata.yml` is the metadata sent with the source payload. (Optional)
182+
- `source_sourcemap.yml` is the metadata sent with the source payload. (Optional)
183183

184184
And the optional assertions:
185185

186+
- `source_metadata.yml` is a dictionary of assertions for the source message metadata after submission.
186187
- `source_status` is the asserted status enumeration text for the source message after submission.
188+
- `source_response` is the asserted response payload if the channel gives a response to the source submission.
187189
- `source_transformed` is the asserted transformed source payload if the channel transforms the message before delivery.
188190
- `dest01_transformed` asserts byte-identical transformed content for destination 1 if the channel transforms the message before delivery.
189191
- `dest01` asserts byte-identical sent content for destination 1.
192+
- `dest01_response` asserts byte-identical response content if destination 1 gives a response to the message delivery.
190193
- `dest01_metadata.yml` is a dictionary of assertions for destination 1 metadata.
191194
- `dest01_status` asserts the status of the message at destination 1.
192195
- `dest02*` repeats the same pattern for destination 2, and so on.

ci/runner/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
FROM docker:27-cli
22

3-
RUN apk add --no-cache bash curl python3 py3-lxml
3+
RUN apk add --no-cache bash curl python3 py3-lxml py3-yaml
44

55
WORKDIR /app
66
COPY *.py /app/

ci/runner/api.py

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import importlib
12
import ssl
2-
import urllib.error
33
import urllib.parse
4+
import urllib.error
45
import urllib.request
56
from http.cookiejar import CookieJar
7+
from xml.etree.ElementTree import Element, SubElement, tostring
68

79
REQUESTED_WITH_HEADER = "OpenIntegrationEngine-CI"
10+
MAX_REQUEST_TIMEOUT_SECONDS = 15
811

912

1013
class ApiClient:
@@ -18,8 +21,8 @@ def request(
1821
method: str = "GET",
1922
data: bytes | None = None,
2023
content_type: str | None = None,
21-
accept: str = "application/json",
22-
timeout: int = 15,
24+
accept: str = "application/xml",
25+
timeout: int = MAX_REQUEST_TIMEOUT_SECONDS,
2326
) -> tuple[int, str]:
2427
headers = {
2528
"Accept": accept,
@@ -51,17 +54,88 @@ def create_channel(self, channel_xml: bytes) -> None:
5154
method="POST",
5255
data=channel_xml,
5356
content_type="application/xml",
54-
accept="application/json",
57+
accept="*/*",
5558
)
5659

5760
def deploy_channel(self, channel_id: str) -> None:
58-
self.request(f"/api/channels/{channel_id}/_deploy", method="POST")
61+
self.request(f"/api/channels/{channel_id}/_deploy", method="POST", accept="*/*")
62+
63+
def get_channel_status(self, channel_id: str):
64+
_, body = self.request(
65+
f"/api/channels/{channel_id}/status",
66+
accept="application/xml",
67+
)
68+
return parse_xml(body)
5969

6070
def undeploy_channel(self, channel_id: str) -> None:
61-
self.request(f"/api/channels/{channel_id}/_undeploy", method="POST")
71+
self.request(f"/api/channels/{channel_id}/_undeploy", method="POST", accept="*/*")
6272

6373
def remove_channel(self, channel_id: str) -> None:
64-
self.request(f"/api/channels/{channel_id}", method="DELETE")
74+
self.request(f"/api/channels/{channel_id}", method="DELETE", accept="*/*")
75+
76+
def process_message(self, channel_id: str, raw_data: str, source_map: dict[str, object] | None = None) -> int:
77+
raw_message_xml = build_raw_message_xml(raw_data, source_map or {})
78+
_, body = self.request(
79+
f"/api/channels/{channel_id}/messagesWithObj",
80+
method="POST",
81+
data=raw_message_xml,
82+
content_type="application/xml",
83+
accept="application/xml",
84+
timeout=MAX_REQUEST_TIMEOUT_SECONDS,
85+
)
86+
return parse_xml(body)
87+
88+
def get_message_content(self, channel_id: str, message_id: int, meta_data_ids: list[int] | None = None):
89+
query = ""
90+
if meta_data_ids:
91+
query = "?" + urllib.parse.urlencode([("metaDataId", meta_data_id) for meta_data_id in meta_data_ids])
92+
_, body = self.request(
93+
f"/api/channels/{channel_id}/messages/{message_id}{query}",
94+
accept="application/xml",
95+
timeout=MAX_REQUEST_TIMEOUT_SECONDS,
96+
)
97+
return parse_xml(body)
98+
99+
def search_message(self, channel_id: str, message_id: int):
100+
query = urllib.parse.urlencode(
101+
{
102+
"minMessageId": message_id,
103+
"maxMessageId": message_id,
104+
"includeContent": "true",
105+
"offset": 0,
106+
"limit": 1,
107+
}
108+
)
109+
_, body = self.request(
110+
f"/api/channels/{channel_id}/messages?{query}",
111+
accept="application/xml",
112+
timeout=MAX_REQUEST_TIMEOUT_SECONDS,
113+
)
114+
return parse_xml(body)
115+
116+
117+
def parse_xml(body: str):
118+
etree = importlib.import_module("lxml.etree")
119+
return etree.fromstring(body.encode("utf-8"))
120+
121+
122+
def build_raw_message_xml(raw_data: str, source_map: dict[str, object]) -> bytes:
123+
raw_message = Element("com.mirth.connect.donkey.model.message.RawMessage")
124+
SubElement(raw_message, "overwrite").text = "false"
125+
SubElement(raw_message, "imported").text = "false"
126+
SubElement(raw_message, "rawData").text = raw_data
127+
128+
source_map_element = SubElement(raw_message, "sourceMap")
129+
source_map_element.set("class", "linked-hash-map")
130+
for key, value in source_map.items():
131+
if isinstance(value, dict):
132+
raise RuntimeError(f"Nested source metadata is not supported for message submission: {key}")
133+
entry = SubElement(source_map_element, "entry")
134+
SubElement(entry, "string").text = str(key)
135+
SubElement(entry, "string").text = str(value)
136+
137+
SubElement(raw_message, "binary").text = "false"
138+
return tostring(raw_message, encoding="utf-8", xml_declaration=True)
65139

66140

67141
def build_opener() -> urllib.request.OpenerDirector:

0 commit comments

Comments
 (0)