package fingerprint_test import ( "strings" "testing" "git.wntrmute.dev/kyle/crossbar/internal/fingerprint" ) const conv1 = `{"model":"m","messages":[{"role":"system","content":"You are the project A assistant."},{"role":"user","content":"Add a config loader."},{"role":"assistant","content":"Sure."},{"role":"user","content":"Now tests."}]}` const conv1later = `{"model":"m","messages":[{"role":"system","content":"You are the project A assistant."},{"role":"user","content":"Add a config loader."},{"role":"assistant","content":"Sure."},{"role":"user","content":"Now tests."},{"role":"assistant","content":"Done."},{"role":"user","content":"And docs."}]}` const conv2 = `{"model":"m","messages":[{"role":"system","content":"You are the project A assistant."},{"role":"user","content":"Fix the flaky test."}]}` const conv3 = `{"model":"m","messages":[{"role":"system","content":"You are the project B assistant."},{"role":"user","content":"Add a config loader."}]}` func TestSameConversationSameKey(t *testing.T) { a := fingerprint.Of([]byte(conv1)) b := fingerprint.Of([]byte(conv1later)) if a == "" || a != b { t.Errorf("later turns of one conversation must keep the key: %q vs %q", a, b) } if len(a) != 64 || strings.Trim(a, "0123456789abcdef") != "" { t.Errorf("key must be lowercase hex sha256 (64 chars), got %q", a) } } func TestDifferentConversationsDifferentKeys(t *testing.T) { a, b, c := fingerprint.Of([]byte(conv1)), fingerprint.Of([]byte(conv2)), fingerprint.Of([]byte(conv3)) if a == b { t.Errorf("different first user message must change the key") } if a == c { t.Errorf("different system prompt must change the key") } } func TestNoUserMessageIsEmpty(t *testing.T) { for _, body := range []string{ `{"model":"m","messages":[{"role":"system","content":"only a system prompt"}]}`, `{"model":"m","messages":[]}`, `{"model":"m"}`, `{"input":"an embeddings request"}`, `not json at all`, ``, } { if got := fingerprint.Of([]byte(body)); got != "" { t.Errorf("Of(%q) = %q, want empty", body, got) } } } func TestOnlyTheFirstFourKiBCount(t *testing.T) { long := strings.Repeat("x", 5000) a := `{"messages":[{"role":"user","content":"` + long + `A"}]}` b := `{"messages":[{"role":"user","content":"` + long + `B"}]}` if fingerprint.Of([]byte(a)) != fingerprint.Of([]byte(b)) { t.Errorf("bytes after the first 4 KiB of a message must not change the key") } c := `{"messages":[{"role":"user","content":"A` + long + `"}]}` if fingerprint.Of([]byte(a)) == fingerprint.Of([]byte(c)) { t.Errorf("bytes inside the first 4 KiB must change the key") } } func TestContentPartsAreFlattened(t *testing.T) { plain := `{"messages":[{"role":"user","content":"hello world"}]}` parts := `{"messages":[{"role":"user","content":[{"type":"text","text":"hello world"}]}]}` if fingerprint.Of([]byte(plain)) != fingerprint.Of([]byte(parts)) { t.Errorf("a content array of text parts must fingerprint like the joined text") } }