package lua import ( "encoding/json" "reflect" "testing" ) // The hook payload used to be JSON round-tripped on every call. It no longer is, // so the fast path and the old JSON path must be indistinguishable — otherwise // a plugin silently sees a different payload than before, which is the worst // kind of change: it compiles, passes a smoke test, and misprices traffic. // // These tests therefore compare the two paths on the SAME inputs rather than // asserting the fast path's output in isolation. func viaJSON(t *testing.T, payload map[string]interface{}) interface{} { t.Helper() b, err := json.Marshal(payload) if err != nil { t.Fatalf("marshal: %v", err) } var decoded interface{} if err := json.Unmarshal(b, &decoded); err != nil { t.Fatalf("unmarshal: %v", err) } return decoded } // payloads that exercise every branch of the fast converter. func payloadCases() []map[string]interface{} { return []map[string]interface{}{ {"model": "deepseek-v4.1-flash", "source": "commandcode", "ok": true}, // Numbers of every width: a converter that only knows float64 turns // ints into something else, and a token count that arrives as a string // makes a Lua hook do arithmetic on nil. {"i": 42, "i8": int8(8), "i16": int16(16), "i32": int32(32), "i64": int64(1 << 40), "u": uint(7), "u64": uint64(1 << 50), "f32": float32(1.5), "f64": 2.25}, // Zero, negative, and very large values must stay numbers. {"zero": 0, "neg": -17, "huge": 1e308, "tiny": 1e-308}, // Slices and string maps: common in payloads and absent from a switch // that only knows []interface{} / map[string]interface{}. {"msgs": []interface{}{"a", "b"}, "tags": []string{"x", "y"}}, {"kv": map[string]string{"a": "1", "b": "2"}}, // Nesting, which is where a shallow converter silently drops a level. {"usage": map[string]interface{}{ "prompt_tokens": 2048, "cache_hit_tokens": 1024, "nested": map[string]interface{}{"deep": []interface{}{1, "two", true, nil}}, }}, {"nil_field": nil, "empty_map": map[string]interface{}{}, "empty_slice": []interface{}{}}, // A value the fast path does NOT model: it must fall back to JSON and // still arrive, not disappear. {"weird": struct { A int `json:"a"` B string `json:"b"` }{1, "x"}}, } } func TestFastPathMatchesJSONPath(t *testing.T) { for i, p := range payloadCases() { want := viaJSON(t, p) got := plainForLua(p) if !reflect.DeepEqual(want, got) { t.Errorf("case %d: fast path differs from JSON path\n payload: %#v\n json: %#v\n fast: %#v", i, p, want, got) } } } // TestFastPathIsActuallyUsed guards against the fast path silently degrading to // JSON for the payload the gateway really sends. If a future payload gains a // type the converter does not model, this still works (it falls back) but the // optimization is gone — and the next person measuring the hook would be // measuring the old cost without knowing why. func TestFastPathIsActuallyUsed(t *testing.T) { // This mirrors the real request_end payload shape from the gateway. realistic := map[string]interface{}{ "stage": "request_end", "kind": "end", "model": "deepseek-v4.1-flash", "source": "commandcode", "key": "stress-key", "ok": true, "status": 200, "duration_ms": 1234, "usage": map[string]interface{}{ "prompt_tokens": float64(2048), "completion_tokens": float64(512), "cache_hit_tokens": float64(1024), "total_tokens": float64(3584), }, "walk": []interface{}{ map[string]interface{}{"kind": "tier_skip", "tier": 1, "source": "", "model": "", "reason": "cooldown"}, map[string]interface{}{"kind": "selected", "tier": 2, "source": "commandcode", "model": "m", "reason": ""}, }, "ts": float64(1700000000), } if _, ok := fastToPlain(realistic); !ok { t.Errorf("★ the realistic request payload does NOT take the fast path — " + "the JSON round-trip is still on the hot path for real traffic") } } // TestFastPathDoesNotAliasInput: the converter builds a new tree. If it ever // returned the caller's map directly, a Lua hook's writes could not reach Go — // but worse, a later mutation of the payload would race with the snapshot the // persistence saver is holding. func TestFastPathDoesNotAliasInput(t *testing.T) { src := map[string]interface{}{ "usage": map[string]interface{}{"prompt_tokens": float64(1)}, "list": []interface{}{"a"}, } out, ok := fastToPlain(src) if !ok { t.Fatal("fast path declined a plain payload") } m := out.(map[string]interface{}) m["new"] = "added" src["also_new"] = "must not appear" if _, leaked := m["also_new"]; leaked { t.Error("output map aliases the input map") } if _, leaked := src["new"]; leaked { t.Error("writing to the output mutated the input") } // And the nested maps must be copies too. inner := m["usage"].(map[string]interface{}) inner["prompt_tokens"] = float64(999) if src["usage"].(map[string]interface{})["prompt_tokens"] != float64(1) { t.Error("nested map is shared, not copied — a hook could mutate the payload") } list := m["list"].([]interface{}) list[0] = "changed" if src["list"].([]interface{})[0] != "a" { t.Error("nested slice is shared, not copied") } }