package hashid import "testing" func TestRoundtrip(t *testing.T) { // 覆盖边界与大量随机值,验证 Decode(Encode(n)) == n 严格成立 cases := []uint32{0, 1, 2, 3, 7, 15, 16, 255, 256, 4095, 4096, 65535, 65536, 1 << 31, (1 << 32) - 1} for _, n := range cases { got, err := Decode(Encode(n)) if err != nil { t.Fatalf("Decode(Encode(%d)) 返回错误: %v", n, err) } if got != n { t.Fatalf("往返不一致: 输入 %d, 得到 %d", n, got) } } // 随机大批量 for n := uint32(1); n < 5000; n++ { got, err := Decode(Encode(n)) if err != nil || got != n { t.Fatalf("往返不一致: 输入 %d, 得到 %d (err=%v)", n, got, err) } } } func TestNonSequential(t *testing.T) { Init("test-salt") a, b, c := Encode(100), Encode(101), Encode(102) if a == b || b == c || a == c { t.Fatalf("相邻 id 编码结果出现了相等: %s %s %s", a, b, c) } } func TestInvalid(t *testing.T) { if _, err := Decode(""); err == nil { t.Fatal("空串应返回错误") } if _, err := Decode("!!!"); err == nil { t.Fatal("含非法字符应返回错误") } } // TestTyped 验证带类型前缀的编解码:同数字不同前缀得到不同串,且能正确还原类型与数字主键。 func TestTyped(t *testing.T) { Init("test-salt") r := EncodeTyped(TypeRunway, 5) s := EncodeTyped(TypeSnap, 5) if r == s { t.Fatalf("同数字不同前缀应得到不同串: runway=%q snap=%q", r, s) } if r[0] != TypeRunway || s[0] != TypeSnap { t.Fatalf("前缀字符不正确: runway=%q snap=%q", r, s) } tr, nr, err := DecodeTyped(r) if err != nil || tr != TypeRunway || nr != 5 { t.Fatalf("DecodeTyped(runway) 失败: type=%c num=%d err=%v", tr, nr, err) } ts, ns, err := DecodeTyped(s) if err != nil || ts != TypeSnap || ns != 5 { t.Fatalf("DecodeTyped(snap) 失败: type=%c num=%d err=%v", ts, ns, err) } // 无前缀旧串仍可用 Decode 还原(兼容历史链接) old := Encode(5) if no, err := Decode(old); err != nil || no != 5 { t.Fatalf("旧无前缀串解码失败: num=%d err=%v", no, err) } // 长度不足 2 视为非法 if _, _, err := DecodeTyped("r"); err == nil { t.Fatal("长度不足 2 的 typed 串应返回错误") } } // TestTypedWithType 验证新编码(类型进密码、无前缀): // - 往返严格成立 DecodeWithType(EncodeWithType(n, typ), typ) == n; // - 同数字不同类别得到完全不相关的串(runway#5 ≠ snap#5),且串本身不含类型字母; // - 用错误 typ 解码得到错误主键(≠ n),从而查库自然 404。 func TestTypedWithType(t *testing.T) { Init("test-salt") // 往返 for _, typ := range []byte{TypeRunway, TypeSnap, TypeRunwayImage, TypeSnapImage} { for _, n := range []uint32{0, 1, 5, 999, 1 << 20, (1 << 32) - 1} { got, err := DecodeWithType(EncodeWithType(n, typ), typ) if err != nil || got != n { t.Fatalf("往返不一致: typ=%c n=%d got=%d err=%v", typ, n, got, err) } } } // 同数字不同类别:串不同且不相关,且都不含类型字母 r := EncodeWithType(5, TypeRunway) s := EncodeWithType(5, TypeSnap) if r == s { t.Fatalf("同数字不同类别应得到不同串: runway=%q snap=%q", r, s) } if r[0] == TypeRunway || r[0] == TypeSnap || s[0] == TypeRunway || s[0] == TypeSnap { t.Fatalf("新编码不应含类型字母前缀: runway=%q snap=%q", r, s) } // 用错误类别解码得不到原主键 if wrong, err := DecodeWithType(r, TypeSnap); err != nil || wrong == 5 { t.Fatalf("用错误类别解码应得不到原主键: got=%d err=%v", wrong, err) } // 空串报错 if _, err := DecodeWithType("", TypeRunway); err == nil { t.Fatal("空串应返回错误") } }