package hash import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func writeTempFile(t *testing.T, data []byte) string { t.Helper() dir := t.TempDir() p := filepath.Join(dir, "master.bin") require.NoError(t, os.WriteFile(p, data, 0o644)) return p } func TestSHA256Hex_Deterministic(t *testing.T) { a := SHA256Hex([]byte("hello")) b := SHA256Hex([]byte("hello")) assert.Equal(t, a, b) // 已知向量 assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", a) } func TestFileSHA256_MatchesBytes(t *testing.T) { data := []byte("the quick brown fox") p := writeTempFile(t, data) got, err := FileSHA256(p) require.NoError(t, err) assert.Equal(t, SHA256Hex(data), got) } func TestSegmentHashes_SmallSegments(t *testing.T) { // 25 字节,分段 10 → 3 段(10/10/5) data := []byte("0123456789ABCDEFGHIJ12345") p := writeTempFile(t, data) segs, err := SegmentHashes(p, 10) require.NoError(t, err) require.Len(t, segs, 3) assert.Equal(t, SHA256Hex(data[0:10]), segs[0]) assert.Equal(t, SHA256Hex(data[10:20]), segs[1]) assert.Equal(t, SHA256Hex(data[20:25]), segs[2]) } func TestMerkleTree_RootStableAndChangesOnEdit(t *testing.T) { leaves := []string{ SHA256Hex([]byte("ep1")), SHA256Hex([]byte("ep2")), SHA256Hex([]byte("ep3")), SHA256Hex([]byte("ep4")), } root1 := BuildMerkleTree(leaves).Root() root2 := BuildMerkleTree(leaves).Root() assert.Equal(t, root1, root2, "同样叶子根应一致") assert.NotEmpty(t, root1) // 改第3集 → 根变化 edited := append([]string(nil), leaves...) edited[2] = SHA256Hex([]byte("ep3-tampered")) root3 := BuildMerkleTree(edited).Root() assert.NotEqual(t, root1, root3, "篡改任一集,根必变") } func TestMerkleTree_OddLeaves(t *testing.T) { leaves := []string{ SHA256Hex([]byte("a")), SHA256Hex([]byte("b")), SHA256Hex([]byte("c")), } mt := BuildMerkleTree(leaves) assert.NotEmpty(t, mt.Root()) } func TestLocateChangedLeaves(t *testing.T) { old := []string{"h1", "h2", "h3", "h4"} neu := []string{"h1", "x2", "h3", "x4"} changed := LocateChangedLeaves(old, neu) assert.Equal(t, []int{1, 3}, changed, "应定位到第2集和第4集被改") } func TestComputeFile_FullPackage(t *testing.T) { data := make([]byte, 25*1024) // 25KB for i := range data { data[i] = byte(i % 251) } p := writeTempFile(t, data) pkg, err := ComputeFile(p, Options{SegmentSize: 10 * 1024}) require.NoError(t, err) require.NoError(t, pkg.Validate()) assert.Equal(t, int64(25*1024), pkg.FileSize) assert.Len(t, pkg.SegmentHashes, 3) assert.NotEmpty(t, pkg.MerkleRoot) assert.Equal(t, SHA256Hex(data), pkg.FileSHA256) } func TestComputeFile_EmptyFileRejected(t *testing.T) { p := writeTempFile(t, []byte{}) _, err := ComputeFile(p, Options{}) assert.ErrorIs(t, err, ErrEmptyInput) } func TestComputeFile_MissingFile(t *testing.T) { _, err := ComputeFile("/no/such/file.bin", Options{}) assert.Error(t, err) } func TestHashPackage_ValidateMissingFields(t *testing.T) { assert.Error(t, (&HashPackage{MerkleRoot: "x"}).Validate()) // 缺 file_sha256 assert.Error(t, (&HashPackage{FileSHA256: "x"}).Validate()) // 缺 merkle_root assert.NoError(t, (&HashPackage{FileSHA256: "a", MerkleRoot: "b"}).Validate()) } func TestPerceptualHash_IdenticalAndDifferent(t *testing.T) { // 全黑与全白图,aHash/dHash 应可区分 black := make([][]uint8, 16) white := make([][]uint8, 16) grad := make([][]uint8, 16) for y := 0; y < 16; y++ { black[y] = make([]uint8, 16) white[y] = make([]uint8, 16) grad[y] = make([]uint8, 16) for x := 0; x < 16; x++ { white[y][x] = 255 grad[y][x] = uint8(x * 16) // 水平渐变 } } imgBlack := newGrayTestImage(black) imgWhite := newGrayTestImage(white) imgGrad := newGrayTestImage(grad) // 同一图的哈希稳定 assert.Equal(t, AHash(imgGrad), AHash(imgGrad)) assert.Equal(t, DHash(imgGrad), DHash(imgGrad)) // 渐变图的 dHash 应与纯色不同 assert.NotEqual(t, DHash(imgGrad), DHash(imgBlack)) // 汉明距离:渐变 vs 纯白 应 > 0 d, err := HammingDistance(DHash(imgGrad), DHash(imgWhite)) require.NoError(t, err) assert.Greater(t, d, 0) } func TestHammingDistance_LengthMismatch(t *testing.T) { _, err := HammingDistance("ffff", "ffffffff") assert.Error(t, err) }