Every given test compiled against a panic-only interface skeleton (go vet clean); nothing was implemented. modernc.org/sqlite v1.59.0 vetted in a scratch module (WAL works); go.sum given. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
71 lines
2.9 KiB
Go
71 lines
2.9 KiB
Go
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")
|
|
}
|
|
}
|