Skip to content

Commit 7e0329f

Browse files
committed
test(unit): cover steps, workflows, runtime errors, and health
1 parent a7b5ad3 commit 7e0329f

8 files changed

Lines changed: 229 additions & 0 deletions

tests/unit/test_arg_expr.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from __future__ import annotations
2+
3+
from fastapi_cloudflow.core.arg import Arg, ArgExpr
4+
5+
6+
def test_arg_env_and_str() -> None:
7+
e = Arg.env("FOO")
8+
assert isinstance(e, ArgExpr)
9+
assert str(e) == '${sys.get_env("FOO")}' # wrapped as ${...}
10+
11+
12+
def test_arg_concat_and_div() -> None:
13+
base = Arg.env("BASE")
14+
p = base / "steps" / Arg.env("NAME")
15+
s = base + "/hello"
16+
# Validate expression strings
17+
assert isinstance(p, ArgExpr)
18+
assert "/steps" in p.expr and 'sys.get_env("NAME")' in p.expr
19+
assert isinstance(s, ArgExpr)
20+
assert "/hello" in s.expr
21+
22+
23+
def test_arg_param_and_ctx() -> None:
24+
assert str(Arg.param("foo")) == "${params.foo}"
25+
assert str(Arg.ctx("run_id")) == "${ctx.run_id}"

tests/unit/test_echo_name_steps.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from __future__ import annotations
2+
3+
from fastapi.testclient import TestClient
4+
from main import app
5+
6+
7+
def test_extract_name_and_name_shout() -> None:
8+
c = TestClient(app)
9+
10+
# extract-name expects the httpbin response shape: {"json": {...}}
11+
r1 = c.post(
12+
"/steps/extract-name",
13+
headers={"X-Workflow-Name": "unit"},
14+
json={"json": {"name": "Ada Lovelace"}},
15+
)
16+
assert r1.status_code == 200
17+
extracted = r1.json()
18+
assert extracted["echoed_name"] == "Ada Lovelace"
19+
20+
# name-shout expects EchoOut
21+
r2 = c.post(
22+
"/steps/name-shout",
23+
headers={"X-Workflow-Name": "unit", "X-Workflow-Run-Id": "test"},
24+
json=extracted,
25+
)
26+
assert r2.status_code == 200
27+
shouted = r2.json()
28+
assert shouted["name_upper"] == "ADA LOVELACE"
29+
assert shouted["length"] == len("ADA LOVELACE")

tests/unit/test_health.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from __future__ import annotations
2+
3+
from fastapi.testclient import TestClient
4+
from main import app
5+
6+
7+
def test_health() -> None:
8+
c = TestClient(app)
9+
r = c.get("/health")
10+
assert r.status_code == 200
11+
assert r.json()["status"] == "ok"

tests/unit/test_joke_flow_steps.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from __future__ import annotations
2+
3+
from fastapi.testclient import TestClient
4+
from main import app
5+
6+
7+
def test_joke_split_and_rate() -> None:
8+
c = TestClient(app)
9+
10+
# Manually simulate the payload of joke-fetch output
11+
fake = {
12+
"id": "abc",
13+
"joke": "Why did the chicken cross the road? To get to the other side!",
14+
"status": 200,
15+
}
16+
17+
r1 = c.post(
18+
"/steps/joke-split",
19+
headers={"X-Workflow-Name": "unit"},
20+
json=fake,
21+
)
22+
assert r1.status_code == 200
23+
bits = r1.json()
24+
assert "setup" in bits and "punch" in bits
25+
26+
r2 = c.post(
27+
"/steps/joke-rate",
28+
headers={"X-Workflow-Name": "unit", "X-Workflow-Run-Id": "test"},
29+
json=bits,
30+
)
31+
assert r2.status_code == 200
32+
rated = r2.json()
33+
assert isinstance(rated.get("rating"), int)

tests/unit/test_order_flow.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from __future__ import annotations
2+
3+
from fastapi.testclient import TestClient
4+
from main import app
5+
6+
7+
def test_price_order() -> None:
8+
c = TestClient(app)
9+
r = c.post(
10+
"/steps/price-order",
11+
headers={"X-Workflow-Name": "unit"},
12+
json={"account_id": 1, "sku": "abc", "qty": 1},
13+
)
14+
assert r.status_code == 200
15+
body = r.json()
16+
assert body["order_id"].startswith("o-")
17+
assert "price" in body
18+
19+
20+
def test_auth_payment() -> None:
21+
c = TestClient(app)
22+
draft = {"order_id": "o-test", "price": "12.34"}
23+
r = c.post(
24+
"/steps/auth-payment",
25+
headers={"X-Workflow-Name": "unit", "X-Workflow-Run-Id": "test"},
26+
json=draft,
27+
)
28+
assert r.status_code == 200
29+
body = r.json()
30+
assert body["order_id"].startswith("o-")
31+
assert body["status"] == "approved"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from __future__ import annotations
2+
3+
from fastapi.testclient import TestClient
4+
from main import app
5+
6+
7+
def test_build_story_and_summarize() -> None:
8+
c = TestClient(app)
9+
10+
story = {"topic": "AI", "mood": "playful", "author_id": 7}
11+
r1 = c.post(
12+
"/steps/build-story",
13+
headers={"X-Workflow-Name": "unit"},
14+
json=story,
15+
)
16+
assert r1.status_code == 200
17+
post = r1.json()
18+
assert post["title"]
19+
assert post["userId"] == 7
20+
21+
# summarize-post expects PostOut shape
22+
r2 = c.post(
23+
"/steps/summarize-post",
24+
headers={"X-Workflow-Name": "unit", "X-Workflow-Run-Id": "test"},
25+
json=post,
26+
)
27+
assert r2.status_code == 200
28+
summary = r2.json()
29+
assert "slug" in summary and "short_title" in summary

tests/unit/test_runtime_errors.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from __future__ import annotations
2+
3+
from fastapi.testclient import TestClient
4+
from main import app
5+
6+
7+
def test_malformed_json_returns_422() -> None:
8+
c = TestClient(app)
9+
r = c.post(
10+
"/steps/price-order",
11+
headers={"X-Workflow-Name": "unit", "Content-Type": "application/json"},
12+
data="{not-json}",
13+
)
14+
assert r.status_code == 422
15+
assert "Malformed JSON" in r.text
16+
17+
18+
def test_missing_body_returns_422() -> None:
19+
c = TestClient(app)
20+
r = c.post("/steps/price-order", headers={"X-Workflow-Name": "unit"})
21+
assert r.status_code == 422
22+
assert ("Request body required" in r.text) or ("Malformed JSON body" in r.text)
23+
24+
25+
def test_payload_wrapper_is_accepted() -> None:
26+
c = TestClient(app)
27+
wrapped = {"payload": {"account_id": 1, "sku": "abc", "qty": 1}}
28+
r = c.post("/steps/price-order", headers={"X-Workflow-Name": "unit"}, json=wrapped)
29+
assert r.status_code == 200
30+
31+
32+
def test_validation_error_returns_422() -> None:
33+
c = TestClient(app)
34+
# Missing required fields
35+
r = c.post("/steps/price-order", headers={"X-Workflow-Name": "unit"}, json={})
36+
assert r.status_code == 422
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
from __future__ import annotations
2+
3+
from fastapi.testclient import TestClient
4+
from main import app
5+
6+
7+
def test_hash_password() -> None:
8+
c = TestClient(app)
9+
r = c.post(
10+
"/steps/hash-password",
11+
headers={"X-Workflow-Name": "unit"},
12+
json={"email": "user@example.com", "password": "hunter2asdf"},
13+
)
14+
assert r.status_code == 200
15+
draft = r.json()
16+
assert draft["hashed_password"].startswith("hashed:")
17+
18+
19+
def test_validate_cart_and_summarize() -> None:
20+
c = TestClient(app)
21+
r1 = c.post(
22+
"/steps/validate-cart",
23+
headers={"X-Workflow-Name": "unit"},
24+
json={"total": 10.0, "currency": "USD"},
25+
)
26+
assert r1.status_code == 200
27+
r2 = c.post(
28+
"/steps/summarize-charge",
29+
headers={"X-Workflow-Name": "unit", "X-Workflow-Run-Id": "test"},
30+
json={"status": "approved", "psp_id": "p-1"},
31+
)
32+
assert r2.status_code == 200
33+
res = r2.json()
34+
assert res["ok"] is True
35+
assert res["txn_id"] == "p-1"

0 commit comments

Comments
 (0)