a329d4906b
- 方案文档: AVCC 体系建设、IPTV TCS 需求(0-req)/PRD(1-prd)/任务(2-task)/二三四期任务 - tcs-iptv: Go 后端(哈希SDK/MA码生成/可信数据空间mock/业务编排/HTTP API+HMAC鉴权) - web-console: React+AntD 监管大屏(角色工作台/全流程演示/监管片库) - 一剧一码+集级哈希, 集级下架/恢复, 全栈测试通过
152 lines
4.3 KiB
Go
152 lines
4.3 KiB
Go
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)
|
||
}
|