280 lines
7.5 KiB
Go
280 lines
7.5 KiB
Go
package hexx_test
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/hex"
|
||
"strings"
|
||
"testing"
|
||
|
||
"code.yun.ink/pkg/encryptx/hexx"
|
||
)
|
||
|
||
// payloads 覆盖空、单字节、ASCII、UTF-8 与全字节值等输入。
|
||
var payloads = [][]byte{
|
||
nil,
|
||
{},
|
||
{0x00},
|
||
{0x0f, 0xf0},
|
||
[]byte("Hello, World!"),
|
||
[]byte("中文与 emoji 🚀"),
|
||
bytes.Repeat([]byte{0xde, 0xad, 0xbe, 0xef}, 64),
|
||
func() []byte {
|
||
b := make([]byte, 256)
|
||
for i := range b {
|
||
b[i] = byte(i)
|
||
}
|
||
return b
|
||
}(),
|
||
}
|
||
|
||
// TestRoundTrip 校验大小写两种编码都能往返还原。
|
||
func TestRoundTrip(t *testing.T) {
|
||
for i, payload := range payloads {
|
||
lower := hexx.Encode(payload)
|
||
upper := hexx.EncodeUpper(payload)
|
||
|
||
if len(lower) != len(payload)*2 {
|
||
t.Errorf("用例 %d: Encode 长度 = %d, 期望 %d", i, len(lower), len(payload)*2)
|
||
}
|
||
if lower != strings.ToLower(lower) {
|
||
t.Errorf("用例 %d: Encode 应输出小写: %q", i, lower)
|
||
}
|
||
if upper != strings.ToUpper(upper) {
|
||
t.Errorf("用例 %d: EncodeUpper 应输出大写: %q", i, upper)
|
||
}
|
||
if !strings.EqualFold(lower, upper) {
|
||
t.Errorf("用例 %d: 大小写编码内容不一致: %q vs %q", i, lower, upper)
|
||
}
|
||
|
||
decodedLower, err := hexx.Decode(lower)
|
||
if err != nil {
|
||
t.Fatalf("用例 %d: Decode(小写) 失败: %v", i, err)
|
||
}
|
||
if !bytes.Equal(decodedLower, payload) {
|
||
t.Errorf("用例 %d: Decode(小写) 往返不一致", i)
|
||
}
|
||
|
||
decodedUpper, err := hexx.DecodeUpper(upper)
|
||
if err != nil {
|
||
t.Fatalf("用例 %d: DecodeUpper(大写) 失败: %v", i, err)
|
||
}
|
||
if !bytes.Equal(decodedUpper, payload) {
|
||
t.Errorf("用例 %d: DecodeUpper 往返不一致", i)
|
||
}
|
||
|
||
// 大写编码也必须能被宽松的 Decode 正确解码。
|
||
decodedLoose, err := hexx.Decode(upper)
|
||
if err != nil {
|
||
t.Fatalf("用例 %d: Decode(大写) 失败: %v", i, err)
|
||
}
|
||
if !bytes.Equal(decodedLoose, payload) {
|
||
t.Errorf("用例 %d: Decode(大写) 往返不一致", i)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestDecodeMixedCase 校验 Decode 在语义上接受大小写混合输入。
|
||
func TestDecodeMixedCase(t *testing.T) {
|
||
cases := []struct {
|
||
in string
|
||
want []byte
|
||
}{
|
||
{"deadbeef", []byte{0xde, 0xad, 0xbe, 0xef}},
|
||
{"DEADBEEF", []byte{0xde, 0xad, 0xbe, 0xef}},
|
||
{"DeAdBeEf", []byte{0xde, 0xad, 0xbe, 0xef}},
|
||
{"dEaDbEeF", []byte{0xde, 0xad, 0xbe, 0xef}},
|
||
{"0a1B2c3D", []byte{0x0a, 0x1b, 0x2c, 0x3d}},
|
||
}
|
||
for _, c := range cases {
|
||
got, err := hexx.Decode(c.in)
|
||
if err != nil {
|
||
t.Errorf("Decode(%q) 返回错误: %v", c.in, err)
|
||
continue
|
||
}
|
||
if !bytes.Equal(got, c.want) {
|
||
t.Errorf("Decode(%q) = %x, 期望 %x", c.in, got, c.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestEncodeMatchesStdlib 与标准库交叉验证,确保封装未引入行为差异。
|
||
func TestEncodeMatchesStdlib(t *testing.T) {
|
||
for i, payload := range payloads {
|
||
if got, want := hexx.Encode(payload), hex.EncodeToString(payload); got != want {
|
||
t.Errorf("用例 %d: Encode = %q, 标准库 = %q", i, got, want)
|
||
}
|
||
if got, want := hexx.EncodeUpper(payload), strings.ToUpper(hex.EncodeToString(payload)); got != want {
|
||
t.Errorf("用例 %d: EncodeUpper = %q, 标准库 = %q", i, got, want)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestIsValid 校验 IsValid 对合法与非法输入的判定。
|
||
func TestIsValid(t *testing.T) {
|
||
valid := []string{
|
||
"00", "ff", "FF", "deadbeef", "DEADBEEF", "DeAdBeEf",
|
||
"0123456789abcdefABCDEF",
|
||
}
|
||
for _, s := range valid {
|
||
if !hexx.IsValid(s) {
|
||
t.Errorf("IsValid(%q) = false, 期望 true", s)
|
||
}
|
||
}
|
||
|
||
invalid := []struct {
|
||
name string
|
||
in string
|
||
}{
|
||
{"空串", ""},
|
||
{"奇数长度", "a"},
|
||
{"奇数长度三字符", "abc"},
|
||
{"奇数长度长串", strings.Repeat("ab", 20) + "c"},
|
||
{"非十六进制字母", "zz"},
|
||
{"含 g", "0g"},
|
||
{"含 0x 前缀", "0x1f"},
|
||
{"含空格", "de ad"},
|
||
{"含换行", "dead\n"},
|
||
{"含连字符", "de-ad"},
|
||
{"非 ASCII 字符", "中文"},
|
||
{"emoji", "🚀"},
|
||
{"含 tab", "aa\tbb"},
|
||
}
|
||
for _, c := range invalid {
|
||
if hexx.IsValid(c.in) {
|
||
t.Errorf("IsValid(%q)(%s)= true, 期望 false", c.in, c.name)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestDecodeRejectsInvalid 校验 Decode 对非法输入返回 nil 与 error。
|
||
func TestDecodeRejectsInvalid(t *testing.T) {
|
||
invalid := []struct {
|
||
name string
|
||
in string
|
||
}{
|
||
{"奇数长度", "abc"},
|
||
{"奇数长度单字符", "a"},
|
||
{"非十六进制字符", "zz"},
|
||
{"包含 g", "0g"},
|
||
{"0x 前缀", "0x1f"},
|
||
{"含空格", "de ad"},
|
||
{"含换行", "beef\n"},
|
||
{"含连字符", "de-ad"},
|
||
{"非 ASCII", "中文"},
|
||
{"emoji", "🚀"},
|
||
{"长度与内容都非法", "xyz"},
|
||
}
|
||
|
||
for _, c := range invalid {
|
||
got, err := hexx.Decode(c.in)
|
||
if err == nil {
|
||
t.Errorf("Decode(%q)(%s)应当失败,却成功得到 %x", c.in, c.name, got)
|
||
}
|
||
if got != nil {
|
||
t.Errorf("Decode(%q)(%s)失败时应返回 nil,实际 %x", c.in, c.name, got)
|
||
}
|
||
if err != nil && err.Error() == "" {
|
||
t.Errorf("Decode(%q) 返回了空错误信息", c.in)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestDecodeUpperStrict 校验 DecodeUpper 在语义上要求输入是大写形式。
|
||
func TestDecodeUpperStrict(t *testing.T) {
|
||
// 纯大写(含数字)必须成功。
|
||
uppers := []struct {
|
||
in string
|
||
want []byte
|
||
}{
|
||
{"DEADBEEF", []byte{0xde, 0xad, 0xbe, 0xef}},
|
||
{"00FF", []byte{0x00, 0xff}},
|
||
{"0A1B2C3D", []byte{0x0a, 0x1b, 0x2c, 0x3d}},
|
||
}
|
||
for _, c := range uppers {
|
||
got, err := hexx.DecodeUpper(c.in)
|
||
if err != nil {
|
||
t.Errorf("DecodeUpper(%q) 返回错误: %v", c.in, err)
|
||
continue
|
||
}
|
||
if !bytes.Equal(got, c.want) {
|
||
t.Errorf("DecodeUpper(%q) = %x, 期望 %x", c.in, got, c.want)
|
||
}
|
||
}
|
||
|
||
// 含小写字母的输入必须被拒绝,并返回 nil。
|
||
rejected := []string{"deadbeef", "DeadBeef", "DEADbeef", "de"}
|
||
for _, in := range rejected {
|
||
got, err := hexx.DecodeUpper(in)
|
||
if err == nil {
|
||
t.Errorf("DecodeUpper(%q) 应当失败(含小写字母),却成功得到 %x", in, got)
|
||
}
|
||
if got != nil {
|
||
t.Errorf("DecodeUpper(%q) 失败时应返回 nil,实际 %x", in, got)
|
||
}
|
||
}
|
||
|
||
// 大写但仍非法的输入同样必须失败。
|
||
for _, in := range []string{"0X1F", "GG", "ABC", "DE AD"} {
|
||
got, err := hexx.DecodeUpper(in)
|
||
if err == nil {
|
||
t.Errorf("DecodeUpper(%q) 应当失败,却成功得到 %x", in, got)
|
||
}
|
||
if got != nil {
|
||
t.Errorf("DecodeUpper(%q) 失败时应返回 nil,实际 %x", in, got)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestDecodeEmpty 校验空串的边界语义。
|
||
func TestDecodeEmpty(t *testing.T) {
|
||
got, err := hexx.Decode("")
|
||
if err != nil {
|
||
t.Errorf("Decode(\"\") 应成功: %v", err)
|
||
}
|
||
if len(got) != 0 {
|
||
t.Errorf("Decode(\"\") 得到 %d 字节,期望 0", len(got))
|
||
}
|
||
// IsValid 明确把空串视为非法。
|
||
if hexx.IsValid("") {
|
||
t.Error("IsValid(\"\") 应为 false")
|
||
}
|
||
}
|
||
|
||
// TestNoPanicOnAnyInput 用一批畸形输入整体验证不 panic。
|
||
func TestNoPanicOnAnyInput(t *testing.T) {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
t.Fatalf("畸形输入导致 panic: %v", r)
|
||
}
|
||
}()
|
||
|
||
inputs := []string{
|
||
"", "0", "00", "000", strings.Repeat("f", 511), strings.Repeat("F", 512),
|
||
"\x00\x01\x02", "%$#@!", "ff", "🇨🇳", "AB",
|
||
}
|
||
for _, in := range inputs {
|
||
_ = hexx.IsValid(in)
|
||
_, _ = hexx.Decode(in)
|
||
_, _ = hexx.DecodeUpper(in)
|
||
_ = hexx.Encode([]byte(in))
|
||
_ = hexx.EncodeUpper([]byte(in))
|
||
}
|
||
}
|
||
|
||
// TestEncodeDecodeNil 校验 nil 输入的处理。
|
||
func TestEncodeDecodeNil(t *testing.T) {
|
||
if got := hexx.Encode(nil); got != "" {
|
||
t.Errorf("Encode(nil) = %q, 期望空串", got)
|
||
}
|
||
if got := hexx.EncodeUpper(nil); got != "" {
|
||
t.Errorf("EncodeUpper(nil) = %q, 期望空串", got)
|
||
}
|
||
got, err := hexx.Decode(hexx.Encode(nil))
|
||
if err != nil {
|
||
t.Errorf("Decode(Encode(nil)) 失败: %v", err)
|
||
}
|
||
if len(got) != 0 {
|
||
t.Errorf("Decode(Encode(nil)) 得到 %d 字节,期望 0", len(got))
|
||
}
|
||
}
|