A run says whether the dashboard, the CLI or a script asked for it
`POST /runs/flows/{name}` hardcoded `cause: "api"`, so every row in the
history claimed the same origin. The body now carries an optional `cause`,
closed to the values the column knows — the dashboard sends nothing and stays
"api", `fluksio run` says "cli", and the SDK client says "sdk".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Gf7WaExcJ9bs3kfJXB3nK
This commit is contained in:
@@ -311,6 +311,44 @@ def test_a_running_run_reports_how_long_it_has_been_going(
|
||||
assert rows[0]["duration_ms"] >= 30_000
|
||||
|
||||
|
||||
def test_a_run_records_which_caller_asked_for_it(
|
||||
client, superuser_token_headers, monkeypatch
|
||||
):
|
||||
"""The dashboard, the CLI and the SDK are told apart by what they send.
|
||||
|
||||
Client-supplied, so the vocabulary is closed: a column nobody can write
|
||||
free text into is one a table can group by.
|
||||
"""
|
||||
seen: dict[str, object] = {}
|
||||
|
||||
class Recorder:
|
||||
def submit(self, name, **kwargs):
|
||||
seen.update(kwargs)
|
||||
return Run(
|
||||
id="cause-1",
|
||||
flow=name,
|
||||
cause=str(kwargs["cause"]),
|
||||
created_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(client.app.state, "run_service", Recorder())
|
||||
url = f"{settings.API_V1_STR}/runs/flows/demo"
|
||||
|
||||
answer = client.post(url, headers=superuser_token_headers, json={"cause": "cli"})
|
||||
assert answer.status_code == 202
|
||||
assert (seen["cause"], answer.json()["cause"]) == ("cli", "cli")
|
||||
|
||||
# Nothing said still means the dashboard, which is the only caller that
|
||||
# does not name itself.
|
||||
client.post(url, headers=superuser_token_headers, json={})
|
||||
assert seen["cause"] == "api"
|
||||
|
||||
refused = client.post(
|
||||
url, headers=superuser_token_headers, json={"cause": "somewhere else"}
|
||||
)
|
||||
assert refused.status_code == 422
|
||||
|
||||
|
||||
def test_overview_is_not_read_as_a_run_id(client, superuser_token_headers):
|
||||
"""`/overview` is declared before `/{run_id}`, which would swallow it."""
|
||||
answer = client.get(
|
||||
|
||||
@@ -200,9 +200,10 @@ def test_a_local_run_always_waits(monkeypatch) -> None:
|
||||
def get_flow(self, name: str) -> dict[str, object]:
|
||||
return {"definition": {"inputs": []}}
|
||||
|
||||
def submit(self, flow, params, seed=None, no_cache=False):
|
||||
def submit(self, flow, params, seed=None, no_cache=False, cause="sdk"):
|
||||
submitted["flow"] = flow
|
||||
submitted["no_cache"] = no_cache
|
||||
submitted["cause"] = cause
|
||||
return FakeHandle()
|
||||
|
||||
def run(self, run_id: str) -> dict[str, object]:
|
||||
@@ -216,7 +217,13 @@ def test_a_local_run_always_waits(monkeypatch) -> None:
|
||||
|
||||
args = _parser().parse_args(["run", "train", "--local", "--no-sync", "--no-cache"])
|
||||
assert cli.cmd_run(args, []) == 0
|
||||
assert submitted == {"flow": "train", "no_cache": True, "waited": True}
|
||||
# "cli" rather than the SDK's default: the history says which asked.
|
||||
assert submitted == {
|
||||
"flow": "train",
|
||||
"no_cache": True,
|
||||
"waited": True,
|
||||
"cause": "cli",
|
||||
}
|
||||
|
||||
|
||||
def test_ctrl_c_while_waiting_cancels_the_run(monkeypatch) -> None:
|
||||
@@ -240,7 +247,7 @@ def test_ctrl_c_while_waiting_cancels_the_run(monkeypatch) -> None:
|
||||
def get_flow(self, name: str) -> dict[str, object]:
|
||||
return {"definition": {"inputs": []}}
|
||||
|
||||
def submit(self, flow, params, seed=None, no_cache=False):
|
||||
def submit(self, flow, params, seed=None, no_cache=False, cause="sdk"):
|
||||
return FakeHandle()
|
||||
|
||||
def cancel(self, run_id: str) -> None:
|
||||
|
||||
Reference in New Issue
Block a user