package engine import ( "testing" ) // L1186 测试: FileHistoryView 窄接口契约 // // 验证: // 1. *FileHistory 通过结构化 typing 实现 FileHistoryView (编译期保证) // 2. 从 Engine.FileHistoryView() 拿到的值在无快照时返回合理默认 // 3. 接口类型可以被 mock 替换 (用于测试消费者代码) // mockFileHistoryView 是纯 mock 实现, 证明接口可被任意类型实现. // 消费者测试代码可以用类似的 mock 隔离 engine 依赖. type mockFileHistoryView struct { canRollbackResult bool canRollbackFiles []string snapshotCount int canRollbackCalls []string } func (m *mockFileHistoryView) CanRollback(messageID string) (bool, []string) { m.canRollbackCalls = append(m.canRollbackCalls, messageID) return m.canRollbackResult, m.canRollbackFiles } func (m *mockFileHistoryView) SnapshotCount() int { return m.snapshotCount } // TestFileHistoryView_StructuralTyping 验证 *FileHistory 满足 FileHistoryView. // 这是编译期断言, 如果接口 / 实现不匹配编译就会失败. func TestFileHistoryView_StructuralTyping(t *testing.T) { var _ FileHistoryView = (*FileHistory)(nil) // compile-time assertion } // TestFileHistoryView_MockImplementation 验证消费者可以用 mock 替换接口. // 这是 L1186 抽象的核心价值: 消费者的代码不需要真实 FileHistory 也能跑测试. func TestFileHistoryView_MockImplementation(t *testing.T) { m := &mockFileHistoryView{ canRollbackResult: true, canRollbackFiles: []string{"/tmp/a.txt", "/tmp/b.txt"}, snapshotCount: 42, } // consumer 代码接受 FileHistoryView 接口, 而非 *FileHistory 具体类型 var view FileHistoryView = m if count := view.SnapshotCount(); count != 42 { t.Errorf("SnapshotCount() = %d, want 42", count) } can, files := view.CanRollback("msg-X") if !can { t.Errorf("CanRollback = false, want true") } if len(files) != 2 || files[0] != "/tmp/a.txt" || files[1] != "/tmp/b.txt" { t.Errorf("CanRollback files = %v, want [/tmp/a.txt /tmp/b.txt]", files) } // 验证 mock 记录了调用 if len(m.canRollbackCalls) != 1 || m.canRollbackCalls[0] != "msg-X" { t.Errorf("CanRollback calls = %v, want [msg-X]", m.canRollbackCalls) } } // TestFileHistoryView_RealImplementation 验证 FileHistory 真实行为通过窄接口仍然正确. func TestFileHistoryView_RealImplementation(t *testing.T) { baseDir := t.TempDir() fh := NewFileHistoryWithDir(baseDir, nil) // 通过接口调用, 而非具体类型 var view FileHistoryView = fh if count := view.SnapshotCount(); count != 0 { t.Errorf("SnapshotCount() on fresh history = %d, want 0", count) } can, files := view.CanRollback("nonexistent") if can { t.Errorf("CanRollback('nonexistent') = true, want false") } if len(files) != 0 { t.Errorf("CanRollback files = %v, want empty", files) } }