Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Testing instrumented code

Assert your own calls carry the labels you expect, in your own test suite, with no proxy and no network.

How-to guideLast updated

On this page

Instrumentation rots quietly. Somebody refactors a call out of its step block, the labels stop being set, and nothing fails — you find out a month later when a dashboard cannot answer a question it used to answer.

The fix is a test that asserts your calls carry the labels you expect. The SDK ships a small kit for exactly that, and it needs no proxy, no network and no fixtures of your own.

What you get

Two pieces per language, and they mirror each other.

Python TypeScript What it does
RecordingTransport(...) recordingFetch(...) captures each request’s final headers, returns a canned response
assert_tt_headers(...) assertTtHeaders(...) asserts the expected x-tt-* headers, with a readable diff

Both are optional modules. Python’s needs httpx installed, since it builds on that transport interface; TypeScript’s is built in.

“Final headers” is the important part: what is captured is the map after the adapter has injected, which is what actually would have gone on the wire.

A test that catches the refactor

import tokentriage as tt
from tokentriage.testing import RecordingTransport, assert_tt_headers

def test_my_calls_are_attributed():
    tt.init(base_url="http://tt.local")
    transport = RecordingTransport(response_headers={"x-tt-request-id": "tt_test"})
    client = tt.httpx_client(transport=transport, base_url="http://tt.local")

    with tt.trace(agent="my-agent", user="u-1"):
        with tt.step(step_class="user_turn"):
            resp = client.post("http://tt.local/v1/chat/completions", json={})
            rcpt = tt.receipt(resp)

    assert_tt_headers(
        transport.last_request_headers,
        {"x-tt-agent": "my-agent", "x-tt-user": "u-1", "x-tt-step-class": "user_turn"},
    )
    assert rcpt.request_id == "tt_test"

http://tt.local does not resolve and nothing tries to reach it. The captured headers are what the adapter built:

x-tt-trace-id  = bbbf47dfd02974f1483f8759f5598148
x-tt-step-id   = e0aad7fe4d96b2f7
x-tt-agent     = my-agent
x-tt-step-class= user_turn
x-tt-user      = u-1

Move the call outside the step block and the assertion on x-tt-step-class fails, which is the whole point.

When it fails

The diff names the header, both values, and what was present, so you do not have to print the map yourself:

assert_tt_headers mismatch:
  'x-tt-agent': expected 'wrong-agent', got 'my-agent'
  x-tt-* headers present: x-tt-agent, x-tt-step-class, x-tt-step-id, x-tt-trace-id, x-tt-user

TypeScript prints the same shape and throws an error named AssertionError, matching what Python raises, so a shared CI log reads the same either way.

Matching is case-insensitive. Both take an absent argument for the opposite assertion — that a header is not there, which is how you test that off-origin requests stay clean.

Canned responses

The recorder returns a TokenTriage-shaped response you control, so you can test your handling of states that are awkward to produce live:

RecordingTransport(
    response_headers={"x-tt-request-id": "tt_x", "x-tt-cache": "hit",
                      "x-tt-cache-kind": "semantic", "x-tt-cache-verified": "none"},
    status=200,
)

A 429 with refusal headers, an unverified semantic cache hit, a response with no request id at all — each is a header dict away, and each exercises a branch of your code that is otherwise hard to reach.

What it will not fabricate

The kit builds wire shapes. It does not build money.

There is no way to construct a PRICED cost in either language, in a test or anywhere else, because Cost has no public constructor and the only producers are the receipt parser and the ledger row mapper over server JSON. If you want to test your handling of a priced row, feed your code a row from a real fetch, or test the branch through render output rather than by minting a cost object.

That is a deliberate limit rather than a missing feature. A test fixture that can mint a dollar is a path by which a dollar can be minted.

Testing the disabled path

Set TOKENTRIAGE_DISABLED in the test environment and assert headers() is empty and your request still succeeds. That is worth one test, because the off state is the configuration your service will run in the first time somebody sets the variable in a deployment and forgets.

Receipt parsing keeps working while disabled, so a test that asserts a receipt still parses under the kill switch is checking the behaviour you actually want: instrumentation off, reading still safe.