|
21 | 21 | format_baggage, |
22 | 22 | format_traceparent, |
23 | 23 | get_header, |
| 24 | + merge_baggage, |
24 | 25 | parse_baggage, |
25 | 26 | parse_traceparent, |
26 | 27 | ) |
@@ -139,50 +140,73 @@ def test_format_encodes_keys_with_reserved_chars(self): |
139 | 140 | # The serialized header must round-trip back to the original key/value. |
140 | 141 | assert parse_baggage(formatted) == entries |
141 | 142 |
|
142 | | - def test_unrelated_baggage_value_is_byte_preserved(self): |
| 143 | + def test_parse_decodes_standard_encoder_values(self): |
| 144 | + # Per the W3C Baggage spec, values are percent-encoded, and standard |
| 145 | + # encoders (e.g. OpenTelemetry's propagator) percent-encode `:` as |
| 146 | + # `%3A`. We must fully decode inbound values to interoperate, otherwise |
| 147 | + # `braintrust.parent=project_id%3Aabc` is not recognized downstream. |
| 148 | + assert parse_baggage(f"{BRAINTRUST_PARENT_KEY}=project_id%3Aabc123") == { |
| 149 | + BRAINTRUST_PARENT_KEY: "project_id:abc123" |
| 150 | + } |
| 151 | + |
| 152 | + |
| 153 | +class TestMergeBaggage: |
| 154 | + def test_adds_braintrust_parent_when_no_existing(self): |
| 155 | + merged = merge_baggage(None, "project_id:abc") |
| 156 | + assert parse_baggage(merged) == {BRAINTRUST_PARENT_KEY: "project_id:abc"} |
| 157 | + |
| 158 | + def test_none_parent_and_no_existing_returns_none(self): |
| 159 | + assert merge_baggage(None, None) is None |
| 160 | + assert merge_baggage("", None) is None |
| 161 | + |
| 162 | + def test_preserves_unrelated_baggage_byte_for_byte(self): |
143 | 163 | # An upstream vendor may percent-encode octets outside the small set |
144 | 164 | # Braintrust itself encodes (e.g. `/` as `%2F`). Forwarding such baggage |
145 | | - # must be a transparent relay: parse -> format must reproduce the exact |
146 | | - # inbound wire bytes for keys Braintrust does not own, otherwise we |
147 | | - # silently rewrite another vendor's value (`path=a%2Fb` -> `path=a/b`). |
148 | | - inbound = "path=a%2Fb,user=alice" |
149 | | - formatted = format_baggage(parse_baggage(inbound)) |
150 | | - assert formatted == inbound |
151 | | - |
152 | | - def test_round_trip_does_not_decode_unowned_percent_sequences(self): |
| 165 | + # must be a transparent relay: the raw member is forwarded unchanged, so |
| 166 | + # we never silently rewrite another vendor's value (`path=a%2Fb` must not |
| 167 | + # become `path=a/b`). |
| 168 | + merged = merge_baggage("path=a%2Fb,user=alice", "project_id:abc") |
| 169 | + assert "path=a%2Fb" in merged |
| 170 | + assert "user=alice" in merged |
| 171 | + assert f"{BRAINTRUST_PARENT_KEY}=project_id:abc" in merged |
| 172 | + |
| 173 | + def test_does_not_decode_unowned_percent_sequences(self): |
153 | 174 | # `%41` is the percent-encoding of `A`. A transparent relay must not |
154 | | - # collapse `a%41b` to `aAb`; the inbound wire form must survive a |
155 | | - # parse -> format round trip unchanged. |
156 | | - inbound = "k=a%41b" |
157 | | - assert format_baggage(parse_baggage(inbound)) == inbound |
158 | | - |
159 | | - def test_bare_percent_is_encoded_and_round_trips(self): |
160 | | - # A literal `%` that is not part of a valid percent-escape must still be |
161 | | - # encoded as `%25` so the value round-trips and the next hop does not |
162 | | - # mis-decode it. |
163 | | - for value in ["50% off", "100%", "a%4"]: |
164 | | - entries = {"k": value} |
165 | | - assert parse_baggage(format_baggage(entries)) == entries |
166 | | - |
167 | | - def test_braintrust_owned_value_with_reserved_chars_round_trips(self): |
168 | | - # Braintrust does own its `braintrust.parent` value, which can carry an |
169 | | - # arbitrary `project_name` containing reserved characters. That value |
170 | | - # must be encoded on emit and decoded on parse. |
171 | | - entries = {BRAINTRUST_PARENT_KEY: "project_name:a,b c"} |
172 | | - assert parse_baggage(format_baggage(entries)) == entries |
173 | | - |
174 | | - def test_mixed_owned_and_unowned_values_round_trip(self): |
175 | | - # A well-formed inbound header carrying both a Braintrust value with an |
176 | | - # encoded reserved char and an upstream vendor's pre-encoded value must |
177 | | - # preserve both: the owned value is decoded then re-encoded, the unowned |
178 | | - # escape is forwarded byte-for-byte. |
179 | | - inbound = f"{BRAINTRUST_PARENT_KEY}=project_name:a%2Cb,vendor=x%2Fy" |
180 | | - formatted = format_baggage(parse_baggage(inbound)) |
181 | | - parsed = parse_baggage(formatted) |
182 | | - assert parsed[BRAINTRUST_PARENT_KEY] == "project_name:a,b" |
183 | | - assert parsed["vendor"] == "x%2Fy" |
184 | | - # The unowned escape survives on the wire byte-for-byte. |
185 | | - assert "vendor=x%2Fy" in formatted |
| 175 | + # collapse `a%41b` to `aAb`; the inbound wire form is forwarded unchanged. |
| 176 | + merged = merge_baggage("k=a%41b", None) |
| 177 | + assert merged == "k=a%41b" |
| 178 | + |
| 179 | + def test_replaces_existing_braintrust_parent(self): |
| 180 | + # A stale inbound braintrust.parent must be dropped in favor of the |
| 181 | + # value we supply, not duplicated. |
| 182 | + merged = merge_baggage( |
| 183 | + f"{BRAINTRUST_PARENT_KEY}=project_id:old,vendor=x", |
| 184 | + "project_id:new", |
| 185 | + ) |
| 186 | + parsed = parse_baggage(merged) |
| 187 | + assert parsed[BRAINTRUST_PARENT_KEY] == "project_id:new" |
| 188 | + assert parsed["vendor"] == "x" |
| 189 | + # Only one braintrust.parent member is emitted. |
| 190 | + assert merged.count(f"{BRAINTRUST_PARENT_KEY}=") == 1 |
| 191 | + |
| 192 | + def test_drops_existing_braintrust_parent_when_no_new_value(self): |
| 193 | + # If we have no braintrust.parent to add, an inbound one is still |
| 194 | + # consumed (it is ours to own) rather than forwarded raw. |
| 195 | + merged = merge_baggage(f"{BRAINTRUST_PARENT_KEY}=project_id:old,vendor=x", None) |
| 196 | + assert merged == "vendor=x" |
| 197 | + |
| 198 | + def test_encodes_braintrust_parent_with_reserved_chars(self): |
| 199 | + # Braintrust owns its braintrust.parent value, which can carry an |
| 200 | + # arbitrary project_name containing reserved characters. That value must |
| 201 | + # be encoded on emit and decode back cleanly. |
| 202 | + merged = merge_baggage(None, "project_name:a,b c") |
| 203 | + assert parse_baggage(merged) == {BRAINTRUST_PARENT_KEY: "project_name:a,b c"} |
| 204 | + |
| 205 | + def test_skips_malformed_existing_members(self): |
| 206 | + merged = merge_baggage("garbage,,k=v,no-equals", "project_id:abc") |
| 207 | + parsed = parse_baggage(merged) |
| 208 | + assert parsed["k"] == "v" |
| 209 | + assert parsed[BRAINTRUST_PARENT_KEY] == "project_id:abc" |
186 | 210 |
|
187 | 211 |
|
188 | 212 | def test_get_header_case_insensitive(): |
|
0 commit comments