|
8 | 8 |
|
9 | 9 | from types import SimpleNamespace |
10 | 10 |
|
11 | | -from agenta.sdk.agents import RunContextTrace, RunContextWorkflow |
| 11 | +from agenta.sdk.agents import ( |
| 12 | + RunContextProject, |
| 13 | + RunContextTrace, |
| 14 | + RunContextWorkflow, |
| 15 | +) |
12 | 16 |
|
13 | 17 | from agenta.sdk.agents import tracing |
14 | 18 |
|
@@ -50,8 +54,10 @@ def boom(): |
50 | 54 | assert ctx.workflow == RunContextWorkflow(is_draft=True) |
51 | 55 |
|
52 | 56 |
|
53 | | -def test_run_context_none_when_both_empty(monkeypatch): |
54 | | - # No workflow identity and no trace -> no run context at all (the key is omitted on the wire). |
| 57 | +def test_run_context_none_when_all_empty(monkeypatch): |
| 58 | + # No project, no workflow identity, and no trace -> no run context at all (the key is omitted |
| 59 | + # on the wire). |
| 60 | + monkeypatch.setattr(tracing, "_run_context_project", lambda: None) |
55 | 61 | monkeypatch.setattr(tracing, "_run_context_workflow", lambda: None) |
56 | 62 | monkeypatch.setattr(tracing, "_run_context_trace", lambda: None) |
57 | 63 | assert tracing.run_context() is None |
@@ -84,3 +90,59 @@ def test_run_context_workflow_normalizes_application_references(monkeypatch): |
84 | 90 | assert workflow.revision.id == "revision-id" |
85 | 91 | assert workflow.revision.version == "v2" |
86 | 92 | assert workflow.is_draft is False |
| 93 | + |
| 94 | + |
| 95 | +def test_run_context_project_stamped_from_server_baggage(monkeypatch): |
| 96 | + # The owning project id is read from the SERVER-derived request context (the authenticated |
| 97 | + # OTel baggage on TracingContext), never from anything the caller sends. This is the source |
| 98 | + # the runner trusts to scope its keep-alive pool. |
| 99 | + monkeypatch.setattr( |
| 100 | + tracing.TracingContext, |
| 101 | + "get", |
| 102 | + lambda: SimpleNamespace(baggage={"project_id": "proj-42"}), |
| 103 | + ) |
| 104 | + |
| 105 | + project = tracing._run_context_project() |
| 106 | + |
| 107 | + assert project == RunContextProject(id="proj-42") |
| 108 | + |
| 109 | + |
| 110 | +def test_run_context_project_none_without_baggage(monkeypatch): |
| 111 | + # No baggage / no project_id in the request state -> no project scope; the field is omitted |
| 112 | + # and the runner falls back to the mount-derived scope. |
| 113 | + monkeypatch.setattr( |
| 114 | + tracing.TracingContext, |
| 115 | + "get", |
| 116 | + lambda: SimpleNamespace(baggage=None), |
| 117 | + ) |
| 118 | + assert tracing._run_context_project() is None |
| 119 | + |
| 120 | + monkeypatch.setattr( |
| 121 | + tracing.TracingContext, |
| 122 | + "get", |
| 123 | + lambda: SimpleNamespace(baggage={"other": "x"}), |
| 124 | + ) |
| 125 | + assert tracing._run_context_project() is None |
| 126 | + |
| 127 | + |
| 128 | +def test_run_context_keeps_project_when_workflow_and_trace_fail(monkeypatch): |
| 129 | + # The project scope is its own failure domain: a run that holds only a project id (no workflow, |
| 130 | + # no trace) still ships `runContext.project` so the runner can key its keep-alive pool on it. |
| 131 | + def boom(): |
| 132 | + raise RuntimeError("unavailable") |
| 133 | + |
| 134 | + monkeypatch.setattr( |
| 135 | + tracing, |
| 136 | + "_run_context_project", |
| 137 | + lambda: RunContextProject(id="proj-42"), |
| 138 | + ) |
| 139 | + monkeypatch.setattr(tracing, "_run_context_workflow", boom) |
| 140 | + monkeypatch.setattr(tracing, "_run_context_trace", boom) |
| 141 | + |
| 142 | + ctx = tracing.run_context() |
| 143 | + assert ctx is not None |
| 144 | + assert ctx.project == RunContextProject(id="proj-42") |
| 145 | + assert ctx.workflow is None |
| 146 | + assert ctx.trace is None |
| 147 | + # The project rides the wire under the snake_case `project.id` binding namespace. |
| 148 | + assert ctx.to_wire() == {"project": {"id": "proj-42"}} |
0 commit comments