Skip to content

Commit fa47cc9

Browse files
test: add CLI and Python SDK integration tests
Local-only integration tests that exercise the full API surface against a running ASE workspace: runtimes (list, get, filter by id/kind), flows (list), flow runs (list, get, trigger, count verification, pagination, status filter), and run_flow with empty spec. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8aa62b6 commit fa47cc9

File tree

2 files changed

+411
-0
lines changed

2 files changed

+411
-0
lines changed

tests/integration.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"""Integration tests for the ascend-ops Python SDK.
2+
3+
Requires a running ASE workspace with ASCEND_SERVICE_ACCOUNT_ID,
4+
ASCEND_SERVICE_ACCOUNT_KEY, and ASCEND_INSTANCE_API_URL set.
5+
"""
6+
7+
import os
8+
import sys
9+
import time
10+
11+
from ascend_ops import Client
12+
13+
PASS = 0
14+
FAIL = 0
15+
16+
17+
def check(condition: bool, label: str, detail: str = ""):
18+
global PASS, FAIL
19+
if condition:
20+
print(f" PASS: {label}")
21+
PASS += 1
22+
else:
23+
print(f" FAIL: {label}{detail}")
24+
FAIL += 1
25+
26+
27+
def main():
28+
global PASS, FAIL
29+
30+
# ---------- preflight ----------
31+
32+
print("=== preflight ===")
33+
34+
for var in ("ASCEND_SERVICE_ACCOUNT_ID", "ASCEND_SERVICE_ACCOUNT_KEY", "ASCEND_INSTANCE_API_URL"):
35+
if not os.environ.get(var):
36+
print(f"ERROR: {var} is not set", file=sys.stderr)
37+
sys.exit(1)
38+
check(True, "env vars set")
39+
40+
client = Client()
41+
check(True, "client created")
42+
43+
# ---------- runtimes ----------
44+
45+
print("=== runtimes ===")
46+
47+
runtimes = client.list_runtimes()
48+
check(isinstance(runtimes, list), "list_runtimes returns list")
49+
check(len(runtimes) > 0, "list_runtimes returns at least 1 runtime", f"got {len(runtimes)}")
50+
51+
if not runtimes:
52+
print("ERROR: cannot continue without at least one runtime", file=sys.stderr)
53+
sys.exit(1)
54+
55+
runtime = runtimes[0]
56+
runtime_uuid = runtime["uuid"]
57+
runtime_id = runtime["id"]
58+
print(f" using runtime: {runtime_id} ({runtime_uuid})")
59+
60+
# get runtime
61+
got = client.get_runtime(uuid=runtime_uuid)
62+
check(got["uuid"] == runtime_uuid, "get_runtime returns correct uuid")
63+
64+
for field in ("uuid", "id", "title", "kind", "project_uuid", "environment_uuid", "created_at", "updated_at"):
65+
check(got.get(field) is not None, f"get_runtime has field '{field}'", f"value: {got.get(field)}")
66+
67+
# filter by id
68+
filtered = client.list_runtimes(id=runtime_id)
69+
check(len(filtered) == 1, "list_runtimes(id=...) returns exactly 1", f"got {len(filtered)}")
70+
check(filtered[0]["uuid"] == runtime_uuid, "filtered runtime has correct uuid")
71+
72+
# filter by kind
73+
kind = runtime["kind"]
74+
by_kind = client.list_runtimes(kind=kind)
75+
check(len(by_kind) >= 1, f"list_runtimes(kind={kind!r}) returns >= 1", f"got {len(by_kind)}")
76+
check(all(r["kind"] == kind for r in by_kind), "all results match kind filter")
77+
78+
# ---------- flows ----------
79+
80+
print("=== flows ===")
81+
82+
flows = client.list_flows(runtime_uuid=runtime_uuid)
83+
check(isinstance(flows, list), "list_flows returns list")
84+
check(len(flows) > 0, "list_flows returns at least 1 flow", f"got {len(flows)}")
85+
86+
if not flows:
87+
print("ERROR: cannot continue without at least one flow", file=sys.stderr)
88+
sys.exit(1)
89+
90+
flow_name = flows[0]["name"]
91+
print(f" using flow: {flow_name}")
92+
93+
# verify all flows have name
94+
check(all("name" in f for f in flows), "all flows have 'name' field")
95+
96+
# ---------- flow runs (before) ----------
97+
98+
print("=== flow runs (before trigger) ===")
99+
100+
runs_before = client.list_flow_runs(runtime_uuid=runtime_uuid, flow_name=flow_name)
101+
check(isinstance(runs_before, list), "list_flow_runs returns list")
102+
runs_before_count = len(runs_before)
103+
check(True, f"list_flow_runs returned {runs_before_count} run(s) before trigger")
104+
105+
# test get_flow_run on existing run
106+
if runs_before:
107+
existing_run = runs_before[0]
108+
got_run = client.get_flow_run(runtime_uuid=runtime_uuid, name=existing_run["name"])
109+
check(got_run["name"] == existing_run["name"], "get_flow_run returns correct run")
110+
111+
for field in ("name", "flow", "status", "runtime_uuid", "build_uuid", "created_at"):
112+
check(got_run.get(field) is not None, f"get_flow_run has field '{field}'")
113+
114+
# verify status is a known value
115+
check(
116+
got_run["status"] in ("pending", "running", "succeeded", "failed"),
117+
f"flow run status is valid: {got_run['status']}",
118+
)
119+
120+
# test pagination
121+
limited = client.list_flow_runs(runtime_uuid=runtime_uuid, flow_name=flow_name, limit=1)
122+
check(len(limited) <= 1, "list_flow_runs(limit=1) returns at most 1", f"got {len(limited)}")
123+
124+
if runs_before_count > 1:
125+
offset_runs = client.list_flow_runs(runtime_uuid=runtime_uuid, flow_name=flow_name, offset=1, limit=1)
126+
check(len(offset_runs) <= 1, "list_flow_runs(offset=1, limit=1) returns at most 1")
127+
if offset_runs and runs_before_count > 1:
128+
check(
129+
offset_runs[0]["name"] != runs_before[0]["name"],
130+
"offset=1 returns different run than offset=0",
131+
)
132+
133+
# ---------- trigger flow run ----------
134+
135+
print("=== trigger flow run ===")
136+
137+
trigger = client.run_flow(runtime_uuid=runtime_uuid, flow_name=flow_name)
138+
check(isinstance(trigger, dict), "run_flow returns dict")
139+
check(trigger.get("event_uuid") is not None, f"run_flow has event_uuid: {trigger.get('event_uuid')}")
140+
check(trigger.get("event_type") == "ScheduleFlowRun", f"event_type is ScheduleFlowRun")
141+
142+
# ---------- flow runs (after) ----------
143+
144+
print("=== flow runs (after trigger) ===")
145+
146+
time.sleep(2)
147+
148+
runs_after = client.list_flow_runs(runtime_uuid=runtime_uuid, flow_name=flow_name)
149+
runs_after_count = len(runs_after)
150+
check(
151+
runs_after_count > runs_before_count,
152+
f"flow run count increased: {runs_before_count} -> {runs_after_count}",
153+
f"expected > {runs_before_count}, got {runs_after_count}",
154+
)
155+
156+
# verify newest run
157+
if runs_after:
158+
newest = runs_after[0]
159+
check(True, f"newest run: {newest['name']} (status: {newest['status']})")
160+
161+
# get the new run
162+
got_new = client.get_flow_run(runtime_uuid=runtime_uuid, name=newest["name"])
163+
check(got_new["name"] == newest["name"], "get_flow_run on new run works")
164+
165+
# ---------- status filter ----------
166+
167+
print("=== status filter ===")
168+
169+
for status in ("pending", "running", "succeeded", "failed"):
170+
by_status = client.list_flow_runs(runtime_uuid=runtime_uuid, status=status)
171+
check(isinstance(by_status, list), f"list_flow_runs(status={status!r}) returns list")
172+
if by_status:
173+
wrong = [r for r in by_status if r["status"] != status]
174+
check(len(wrong) == 0, f"all {status} runs have correct status", f"{len(wrong)} have wrong status")
175+
176+
# ---------- run_flow with empty spec ----------
177+
178+
print("=== run_flow with spec ===")
179+
180+
trigger2 = client.run_flow(runtime_uuid=runtime_uuid, flow_name=flow_name, spec={})
181+
check(trigger2.get("event_uuid") is not None, "run_flow with empty spec works")
182+
183+
# ---------- summary ----------
184+
185+
print()
186+
print("=== results ===")
187+
total = PASS + FAIL
188+
print(f"{PASS}/{total} passed")
189+
if FAIL > 0:
190+
print(f"{FAIL} FAILED")
191+
sys.exit(1)
192+
print("all tests passed")
193+
194+
195+
if __name__ == "__main__":
196+
main()

0 commit comments

Comments
 (0)