package cache import ( "sync" "testing" ) // TestPromptHashTracker_FirstCallAlwaysChanged 首次调用必须返回 changed=true func TestPromptHashTracker_FirstCallAlwaysChanged(t *testing.T) { tr := NewPromptHashTracker() changed := tr.Check([]byte("hello world")) if !changed { t.Error("first Check should always return changed=true (no baseline hash)") } } // TestPromptHashTracker_SameContentNoChange 相同内容不触发变化 func TestPromptHashTracker_SameContentNoChange(t *testing.T) { tr := NewPromptHashTracker() content := []byte("system prompt v1") tr.Check(content) // 建立基准 changed := tr.Check(content) if changed { t.Error("same content should not return changed=true") } } // TestPromptHashTracker_DifferentContentChanged 内容不同触发变化 func TestPromptHashTracker_DifferentContentChanged(t *testing.T) { tr := NewPromptHashTracker() tr.Check([]byte("system prompt v1")) changed := tr.Check([]byte("system prompt v2 — added new tool description")) if !changed { t.Error("different content should return changed=true") } } // TestPromptHashTracker_EmptyContent 空内容也有合法哈希 func TestPromptHashTracker_EmptyContent(t *testing.T) { tr := NewPromptHashTracker() changed := tr.Check([]byte{}) if !changed { t.Error("first Check with empty content should return changed=true") } // 再次调用相同空内容,不变 changed = tr.Check([]byte{}) if changed { t.Error("second Check with same empty content should return changed=false") } } // TestPromptHashTracker_Reset 重置后下次调用视为首次 func TestPromptHashTracker_Reset(t *testing.T) { tr := NewPromptHashTracker() content := []byte("stable system prompt") tr.Check(content) // 建立基准 // 正常情况:相同内容不变化 if tr.Check(content) { t.Error("pre-reset: same content should not change") } // Reset 后:即使相同内容也返回 changed=true tr.Reset() if !tr.Check(content) { t.Error("post-reset: Check should return changed=true regardless of content") } } // TestPromptHashTracker_ResetClearsHash Reset 后 LastHash 应清零 func TestPromptHashTracker_ResetClearsHash(t *testing.T) { tr := NewPromptHashTracker() tr.Check([]byte("some content")) _, hasFirst := tr.LastHash() if !hasFirst { t.Error("LastHash should return hasFirst=true after Check") } tr.Reset() h, hasFirst := tr.LastHash() if hasFirst { t.Error("LastHash should return hasFirst=false after Reset") } var zero [32]byte if h != zero { t.Error("LastHash should return zero hash after Reset") } } // TestPromptHashTracker_SequentialChanges 多次内容变化都能检测到 func TestPromptHashTracker_SequentialChanges(t *testing.T) { tr := NewPromptHashTracker() steps := []struct { content string wantChanged bool }{ {"v1", true}, // 首次:变化 {"v1", false}, // 相同:不变 {"v2", true}, // 变化 {"v2", false}, // 相同:不变 {"v1", true}, // 回退也算变化 {"v1", false}, // 再次相同:不变 } for i, step := range steps { got := tr.Check([]byte(step.content)) if got != step.wantChanged { t.Errorf("step %d (content=%q): Check() = %v, want %v", i, step.content, got, step.wantChanged) } } } // TestPromptHashTracker_ConcurrentSafe 并发调用不 panic/race func TestPromptHashTracker_ConcurrentSafe(t *testing.T) { tr := NewPromptHashTracker() var wg sync.WaitGroup const goroutines = 50 for i := 0; i < goroutines; i++ { wg.Add(1) go func(i int) { defer wg.Done() // 偶数 goroutine 用 v1,奇数 goroutine 用 v2,引发竞态 content := []byte("content-v1") if i%2 == 1 { content = []byte("content-v2") } // Check 和 Reset 并发 if i%10 == 0 { tr.Reset() } else { tr.Check(content) } }(i) } wg.Wait() // 只要不 panic/race 即通过 } // TestPromptHashTracker_NilContent nil 与空 []byte 不等价 func TestPromptHashTracker_NilContent(t *testing.T) { tr := NewPromptHashTracker() // nil 和 空 slice 的 sha256 相同(sha256.Sum256(nil) == sha256.Sum256([]byte{})) tr.Check(nil) if tr.Check([]byte{}) { t.Error("nil and empty slice should produce same hash (both sha256 of empty)") } }