Refuse a port the function cannot take, and read a failing node as degraded
Docs / docs (push) Successful in 33s
Playwright Tests / test-playwright (1, 2) (push) Failing after 1m26s
Playwright Tests / test-playwright (2, 2) (push) Failing after 15s
pre-commit / pre-commit (push) Failing after 1m43s
Test Backend / test-backend (push) Failing after 2m46s
Compose Smoke Test / test-compose (push) Failing after 12s
Playwright Tests / merge-reports (push) Canceled after 0s
Docs / docs (push) Successful in 33s
Playwright Tests / test-playwright (1, 2) (push) Failing after 1m26s
Playwright Tests / test-playwright (2, 2) (push) Failing after 15s
pre-commit / pre-commit (push) Failing after 1m43s
Test Backend / test-backend (push) Failing after 2m46s
Compose Smoke Test / test-compose (push) Failing after 12s
Playwright Tests / merge-reports (push) Canceled after 0s
A python node's ports and settings arrive as keyword arguments, so a declared name its `process` does not take was a TypeError on every call — and a node that loads fine and fails every time it runs is the quiet kind of broken: the hosted demo did it 720 times an hour for two days and the health badge read ok throughout. `_build_node` now reads a written body with `ast` and refuses the mismatch at load, so the node is an error on the canvas and an issue on publish. Skipped for `**kwargs`, a decorated or absent `process`, and the template a new node opens with. The SDK's generated shim always takes `**settings`, so synced flows are untouched. `/observability/summary` names a node that has failed in the last fifteen minutes and reads degraded while it does, which is what would have made the badge amber. `nodes.failing` carries the count. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SkgNtaR6JspnHBFFP6crZj
This commit is contained in:
@@ -447,3 +447,38 @@ def test_a_timeseries_window_can_be_named(
|
||||
assert response.status_code == 200
|
||||
# Two of the four minutes: `since` inclusive, `until` exclusive.
|
||||
assert sum(point["executions"] for point in response.json()) == 2
|
||||
|
||||
|
||||
def test_a_node_failing_on_every_call_makes_the_summary_degraded(
|
||||
client: TestClient, superuser_token_headers: dict[str, str]
|
||||
) -> None:
|
||||
"""A node that loads and then raises each time it runs is not in `error`.
|
||||
|
||||
It is named while its last failure is recent, and drops out once it is not.
|
||||
"""
|
||||
import time
|
||||
|
||||
from fluksio.flow.controller import LoadedNode
|
||||
|
||||
controller = client.app.state.flow_controller
|
||||
before = controller.loaded
|
||||
controller.loaded = {
|
||||
"house.calc": LoadedNode(
|
||||
id="house.calc", flow="house", last_error="boom", last_error_ts=time.time()
|
||||
),
|
||||
"house.old": LoadedNode(
|
||||
id="house.old",
|
||||
flow="house",
|
||||
last_error="boom",
|
||||
last_error_ts=time.time() - 3600,
|
||||
),
|
||||
}
|
||||
try:
|
||||
body = client.get(f"{PREFIX}/summary", headers=superuser_token_headers).json()
|
||||
finally:
|
||||
controller.loaded = before
|
||||
|
||||
assert body["status"] == "degraded"
|
||||
assert body["nodes"]["failing"] == 1
|
||||
(problem,) = [p for p in body["problems"] if "failing" in p]
|
||||
assert "house.calc" in problem and "house.old" not in problem
|
||||
|
||||
Reference in New Issue
Block a user