Skip to content

Commit 08b282e

Browse files
end2end testing for engine (#1137)
* First Commit * add on workflow call * remove extra colon * add check for deployment token * add logging to verify the path of log zips * update the case in path name * update test file name in workflow * correct path of dataset for selenium test * Update selenium_test_editor.py * lint update * check for content too in the tests * deploy link getter fix * letter case fix * dependcy * selenium fixes * update for json retrival * add blinker install * final fix * final final fix * Update selenium_test_editor.py * update the test results * lint update * commit id * commit id fix * editor commit id * commit extracting * commit extracting * final fix for commit id * check for log file in recursive directory * lint update --------- Co-authored-by: Samuel Johnson <96841389+SFJohnson24@users.noreply.github.com>
1 parent 1c345b2 commit 08b282e

7 files changed

Lines changed: 377 additions & 5 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import os
2+
import requests
3+
import zipfile
4+
import io
5+
import re
6+
import glob
7+
8+
GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
9+
if GITHUB_TOKEN is None:
10+
raise ValueError("GITHUB_TOKEN environment variable not set")
11+
12+
WORKFLOW_RUNS_URL = (
13+
"https://api.github.com/repos/cdisc-org/conformance-rules-editor/actions/runs"
14+
)
15+
16+
headers = {"Authorization": f"token {GITHUB_TOKEN}"}
17+
response = requests.get(WORKFLOW_RUNS_URL, headers=headers)
18+
response.raise_for_status()
19+
workflow_data = response.json()
20+
21+
first_run_id = workflow_data["workflow_runs"][0]["id"]
22+
print(f"First workflow run ID: {first_run_id}")
23+
24+
logs_url = f"https://api.github.com/repos/cdisc-org/conformance-rules-editor/actions/runs/{first_run_id}/logs"
25+
26+
logs_response = requests.get(logs_url, headers=headers)
27+
logs_response.raise_for_status()
28+
29+
with zipfile.ZipFile(io.BytesIO(logs_response.content)) as zip_file:
30+
zip_file.extractall("logs")
31+
32+
print(os.listdir("logs"))
33+
print(os.listdir(os.path.join("logs", "Build and Deploy Preview")))
34+
35+
log_dir = os.path.join("logs")
36+
# List all .txt files in the directory
37+
file_path = None
38+
for filename in os.listdir(log_dir):
39+
if filename.endswith(".txt") and "Build and Deploy Preview" in filename:
40+
file_path = os.path.join(log_dir, filename)
41+
print(f"Found log file: {file_path}")
42+
43+
commit_files = glob.glob(
44+
os.path.join(
45+
log_dir, "Build and Deploy Preview", "[0-9]_Build and Deploy Preview.txt"
46+
)
47+
)
48+
49+
if not file_path:
50+
print("No matching Build and Deploy Preview log file found")
51+
52+
if not commit_files:
53+
print("No matching SHA Commit log file found")
54+
55+
use_file_path = None
56+
if file_path:
57+
use_file_path = file_path
58+
elif commit_files:
59+
use_file_path = commit_files[0]
60+
else:
61+
raise FileNotFoundError("No log file found")
62+
63+
64+
if not os.path.exists(use_file_path):
65+
raise FileNotFoundError(f"{use_file_path} not found")
66+
67+
68+
with open(use_file_path, "r", encoding="utf-8", errors="ignore") as f:
69+
log_content = f.read()
70+
71+
# Find preview URL
72+
preview_match = re.search(
73+
r"https:\/\/[a-z0-9-]+\.centralus\.azurestaticapps\.net", log_content
74+
)
75+
if preview_match:
76+
preview_url = preview_match.group(0)
77+
print("ICYFLOWER Deploy Link Found:")
78+
print(preview_url)
79+
# Save it to GitHub Actions environment
80+
with open(os.environ["GITHUB_ENV"], "a") as env_file:
81+
env_file.write(f"RULE_EDITOR_URL={preview_url}\n")
82+
else:
83+
print("No ICYFLOWER deploy link found in the logs.")
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import os
2+
import sys
3+
import json
4+
import time
5+
6+
from seleniumwire import webdriver
7+
from selenium.webdriver.chrome.service import Service
8+
from selenium.webdriver.chrome.options import Options
9+
from selenium.webdriver.common.by import By
10+
from selenium.webdriver.support.ui import WebDriverWait
11+
from selenium.webdriver.support import expected_conditions as EC
12+
from webdriver_manager.chrome import ChromeDriverManager
13+
import brotli
14+
15+
# Get the Preview Deployment URL
16+
RULE_EDITOR_URL = os.getenv("RULE_EDITOR_URL")
17+
if not RULE_EDITOR_URL:
18+
print("RULE_EDITOR_URL is not set! Test failed.")
19+
sys.exit(1)
20+
21+
print(f"Running tests on: {RULE_EDITOR_URL}")
22+
23+
# Configure Chrome options
24+
chrome_options = Options()
25+
chrome_options.add_argument("--ignore-certificate-errors")
26+
chrome_options.add_argument("--window-size=1920,1080")
27+
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
28+
chrome_options.add_argument("--headless=new") # Headless mode
29+
30+
# Initialize driver using selenium-wire
31+
service = Service(ChromeDriverManager().install())
32+
driver = webdriver.Chrome(service=service, options=chrome_options)
33+
wait = WebDriverWait(driver, 20)
34+
35+
try:
36+
print("Opening Rule Editor site...")
37+
driver.get(RULE_EDITOR_URL)
38+
39+
print("Searching for rule CG0006...")
40+
rule_search_field = wait.until(
41+
EC.element_to_be_clickable((By.XPATH, '//*[@id="mui-10"]'))
42+
)
43+
rule_search_field.click()
44+
rule_search_field.send_keys("CG0006")
45+
46+
search_result = wait.until(
47+
EC.element_to_be_clickable(
48+
(By.XPATH, '//*[@id="rulesList"]/table/tbody/tr/td[1]')
49+
)
50+
)
51+
search_result.click()
52+
print("Rule selected.")
53+
54+
print("Switching to test tab...")
55+
test_tab_button = wait.until(
56+
EC.element_to_be_clickable(
57+
(By.XPATH, '//*[@id="root"]/div/div[3]/div/div[1]/div/div/div/button[2]')
58+
)
59+
)
60+
61+
test_tab_button.click()
62+
time.sleep(4) # wait for the schema validation to complete
63+
print("Opening upload dataset tab...")
64+
upload_dataset_tab = wait.until(
65+
EC.element_to_be_clickable(
66+
(By.XPATH, '//*[@id="tabpanel-1"]/div[5]/div[1]/div[2]')
67+
)
68+
)
69+
upload_dataset_tab.click()
70+
71+
print("Uploading dataset file...")
72+
file_input = wait.until(
73+
EC.presence_of_element_located(
74+
(
75+
By.XPATH,
76+
'//*[@id="tabpanel-1"]/div[5]/div[2]/div/div/div/div/label/input',
77+
)
78+
)
79+
)
80+
file_path = os.path.abspath(".github/test/unit-test-coreid-CG0006-negative 1.xlsx")
81+
file_input.send_keys(file_path)
82+
83+
print("Waiting for error result to appear...")
84+
error_result = wait.until(
85+
EC.visibility_of_element_located(
86+
(By.XPATH, '//*[@id="tabpanel-1"]/div[6]/div[1]/div[1]/span/div/span')
87+
)
88+
)
89+
print("Error result displayed.")
90+
91+
# Give a few seconds for the POST request to complete
92+
time.sleep(3)
93+
94+
# Find the rule execution API call
95+
rule_exec_response = None
96+
for request in driver.requests:
97+
if "/api/rules/execute" in request.url:
98+
if request.response:
99+
try:
100+
raw_body = request.response.body
101+
decompressed = brotli.decompress(raw_body).decode("utf-8")
102+
rule_exec_response = json.loads(decompressed)
103+
print("Captured and decoded response from /api/rules/execute")
104+
break
105+
except Exception as e:
106+
print("Error decoding response body:", e)
107+
108+
# Expected content
109+
expected_json = {
110+
"DM": [
111+
{
112+
"executionStatus": "success",
113+
"dataset": "dm.xpt",
114+
"domain": "DM",
115+
"variables": [],
116+
"message": None,
117+
"errors": [],
118+
}
119+
],
120+
"FA": [
121+
{
122+
"executionStatus": "success",
123+
"dataset": "fa.xpt",
124+
"domain": "FA",
125+
"variables": ["$val_dy", "FADTC", "FADY", "RFSTDTC"],
126+
"message": (
127+
"FADY is not calculated correctly even though the date portion of FADTC is complete, "
128+
"the date portion of DM.RFSTDTC is a complete date, and FADY is not empty."
129+
),
130+
"errors": [
131+
{
132+
"value": {
133+
"FADY": 35,
134+
"RFSTDTC": "2012-11-15",
135+
"FADTC": "2012-12-02",
136+
"$val_dy": 18,
137+
},
138+
"dataset": "fa.xpt",
139+
"row": 1,
140+
"USUBJID": "CDISC002",
141+
"SEQ": 1,
142+
},
143+
{
144+
"value": {
145+
"FADY": 3,
146+
"RFSTDTC": "2013-10-08",
147+
"FADTC": "2013-10-12",
148+
"$val_dy": 5,
149+
},
150+
"dataset": "fa.xpt",
151+
"row": 2,
152+
"USUBJID": "CDISC004",
153+
"SEQ": 2,
154+
},
155+
{
156+
"value": {
157+
"FADY": -30,
158+
"RFSTDTC": "2013-01-05",
159+
"FADTC": "2012-12-02",
160+
"$val_dy": -34,
161+
},
162+
"dataset": "fa.xpt",
163+
"row": 4,
164+
"USUBJID": "CDISC007",
165+
"SEQ": 4,
166+
},
167+
{
168+
"value": {
169+
"FADY": 230,
170+
"RFSTDTC": "2014-05-11",
171+
"FADTC": "2014-12-02",
172+
"$val_dy": 206,
173+
},
174+
"dataset": "fa.xpt",
175+
"row": 5,
176+
"USUBJID": "CDISC008",
177+
"SEQ": 5,
178+
},
179+
],
180+
}
181+
],
182+
"IE": [
183+
{
184+
"executionStatus": "success",
185+
"dataset": "ie.xpt",
186+
"domain": "IE",
187+
"variables": ["$val_dy", "IEDTC", "IEDY", "RFSTDTC"],
188+
"message": (
189+
"IEDY is not calculated correctly even though the date portion of IEDTC is complete, "
190+
"the date portion of DM.RFSTDTC is a complete date, and IEDY is not empty."
191+
),
192+
"errors": [
193+
{
194+
"value": {
195+
"RFSTDTC": "2022-03-20",
196+
"IEDTC": "2022-03-17",
197+
"$val_dy": -3,
198+
"IEDY": -4,
199+
},
200+
"dataset": "ie.xpt",
201+
"row": 1,
202+
"USUBJID": "CDISC-TEST-001",
203+
"SEQ": 1,
204+
}
205+
],
206+
}
207+
],
208+
"LB": [
209+
{
210+
"executionStatus": "success",
211+
"dataset": "lb.xpt",
212+
"domain": "LB",
213+
"variables": ["$val_dy", "LBDTC", "LBDY", "RFSTDTC"],
214+
"message": (
215+
"LBDY is not calculated correctly even though the date portion of LBDTC is complete, "
216+
"the date portion of DM.RFSTDTC is a complete date, and LBDY is not empty."
217+
),
218+
"errors": [
219+
{
220+
"value": {
221+
"RFSTDTC": "2022-03-20",
222+
"LBDY": 2,
223+
"LBDTC": "2022-03-30",
224+
"$val_dy": 11,
225+
},
226+
"dataset": "lb.xpt",
227+
"row": 1,
228+
"USUBJID": "CDISC-TEST-001",
229+
"SEQ": 1,
230+
}
231+
],
232+
}
233+
],
234+
}
235+
236+
# Compare result
237+
if rule_exec_response == expected_json:
238+
print("Test Passed: API response matches expected JSON.")
239+
else:
240+
print("Test Failed: API response does NOT match expected JSON.")
241+
print("Received:")
242+
print(json.dumps(rule_exec_response, indent=2))
243+
244+
245+
except Exception as e:
246+
print(f"Test Failed due to exception: {e}")
247+
sys.exit(1)
248+
249+
finally:
250+
driver.quit()
29.6 KB
Binary file not shown.

.github/workflows/automated-ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ jobs:
88
test_suite:
99
uses: ./.github/workflows/test_suite.yml
1010
secrets: inherit
11+
test_rule_editor_preview:
12+
uses: ./.github/workflows/test_rule_editor_preview.yml
13+
secrets: inherit
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Run Tests on Preview Deployment
2+
3+
on:
4+
workflow_call:
5+
workflow_dispatch:
6+
7+
jobs:
8+
extract_and_test:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout repo
13+
uses: actions/checkout@v3
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: 3.9
19+
20+
- name: Install dependencies
21+
run: pip install requests selenium webdriver-manager pyperclip==1.9.0 selenium-wire blinker==1.4 brotli
22+
23+
- name: Extract Deploy Preview URL
24+
env:
25+
GITHUB_TOKEN: ${{ github.token }}
26+
run: python .github/test/extract_deploy_link.py
27+
28+
- name: Print commit SHA
29+
run: |
30+
echo "Commit ID: ${GITHUB_SHA}"
31+
- name: Run tests using preview URL
32+
33+
run: python .github/test/selenium_test_editor.py

cdisc_rules_engine/interfaces/data_service_interface.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def concat_split_datasets(
7171
self,
7272
func_to_call: Callable,
7373
datasets_metadata: Iterable[DatasetMetadata],
74-
**kwargs
74+
**kwargs,
7575
):
7676
"""
7777
Accepts a list of split dataset filenames,

0 commit comments

Comments
 (0)