mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-09-26 21:21:53 +08:00
test: 补充单元测试覆盖率 29.9% → 36.6%
新建 18 个测试文件,追加 30 个已有测试文件,覆盖协议解析、 错误分类、CEL 表达式求值、YAML 反序列化、字节编码等纯函数。
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// Padding 测试
|
||||
// =============================================================================
|
||||
|
||||
func TestPadding_BasicBlockAlignment(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
blockSize int
|
||||
wantLen int // 期望长度
|
||||
}{
|
||||
{
|
||||
name: "空输入填充整个块",
|
||||
input: []byte{},
|
||||
blockSize: 16,
|
||||
wantLen: 16,
|
||||
},
|
||||
{
|
||||
name: "15字节填充1字节",
|
||||
input: make([]byte, 15),
|
||||
blockSize: 16,
|
||||
wantLen: 16,
|
||||
},
|
||||
{
|
||||
name: "整块对齐追加完整块",
|
||||
input: make([]byte, 16),
|
||||
blockSize: 16,
|
||||
wantLen: 32,
|
||||
},
|
||||
{
|
||||
name: "1字节填充15字节",
|
||||
input: []byte{0x01},
|
||||
blockSize: 16,
|
||||
wantLen: 16,
|
||||
},
|
||||
{
|
||||
name: "blockSize=8时的对齐",
|
||||
input: make([]byte, 5),
|
||||
blockSize: 8,
|
||||
wantLen: 8,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := Padding(tt.input, tt.blockSize)
|
||||
if len(result) != tt.wantLen {
|
||||
t.Errorf("Padding() len=%d, want %d", len(result), tt.wantLen)
|
||||
}
|
||||
// 验证填充字节值符合 PKCS7 规范
|
||||
if len(result) > 0 {
|
||||
padLen := int(result[len(result)-1])
|
||||
if padLen == 0 || padLen > tt.blockSize {
|
||||
t.Errorf("填充字节值 %d 超出 blockSize=%d", padLen, tt.blockSize)
|
||||
}
|
||||
// 验证所有填充字节相同
|
||||
for i := len(result) - padLen; i < len(result); i++ {
|
||||
if result[i] != byte(padLen) {
|
||||
t.Errorf("填充字节[%d]=%d 不等于 padLen=%d", i, result[i], padLen)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPadding_ResultLength(t *testing.T) {
|
||||
// 任意长度输入,结果都应该是 blockSize 的整数倍
|
||||
blockSize := 16
|
||||
for inputLen := 0; inputLen < 50; inputLen++ {
|
||||
input := make([]byte, inputLen)
|
||||
result := Padding(input, blockSize)
|
||||
if len(result)%blockSize != 0 {
|
||||
t.Errorf("输入长度 %d: 填充后长度 %d 不是 %d 的倍数", inputLen, len(result), blockSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AESCBCEncrypt 测试
|
||||
// =============================================================================
|
||||
|
||||
func TestAESCBCEncrypt_ValidKey128(t *testing.T) {
|
||||
// 128-bit AES key (16 bytes)
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 16))
|
||||
result := AESCBCEncrypt(key)
|
||||
if result == "" {
|
||||
t.Error("有效的128位密钥应返回非空结果")
|
||||
}
|
||||
// 结果应为有效的 base64
|
||||
_, err := base64.StdEncoding.DecodeString(result)
|
||||
if err != nil {
|
||||
t.Errorf("AESCBCEncrypt 结果应为有效 base64: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESCBCEncrypt_ValidKey256(t *testing.T) {
|
||||
// 256-bit AES key (32 bytes)
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 32))
|
||||
result := AESCBCEncrypt(key)
|
||||
if result == "" {
|
||||
t.Error("有效的256位密钥应返回非空结果")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESCBCEncrypt_InvalidBase64Key(t *testing.T) {
|
||||
result := AESCBCEncrypt("!!!not-valid-base64!!!")
|
||||
if result != "" {
|
||||
t.Error("无效 base64 密钥应返回空字符串")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESCBCEncrypt_InvalidKeySize(t *testing.T) {
|
||||
// AES 要求密钥为 16/24/32 字节,10 字节无效
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 10))
|
||||
result := AESCBCEncrypt(key)
|
||||
if result != "" {
|
||||
t.Error("无效密钥长度应返回空字符串")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESCBCEncrypt_NonDeterministic(t *testing.T) {
|
||||
// 因为 IV 是随机的,两次加密结果应不同
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 16))
|
||||
r1 := AESCBCEncrypt(key)
|
||||
r2 := AESCBCEncrypt(key)
|
||||
if r1 == r2 {
|
||||
// 极小概率相同,记录即可
|
||||
t.Log("两次加密结果相同(极低概率事件)")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// AESGCMEncrypt 测试
|
||||
// =============================================================================
|
||||
|
||||
func TestAESGCMEncrypt_ValidKey128(t *testing.T) {
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 16))
|
||||
result := AESGCMEncrypt(key)
|
||||
if result == "" {
|
||||
t.Error("有效的128位密钥应返回非空结果")
|
||||
}
|
||||
_, err := base64.StdEncoding.DecodeString(result)
|
||||
if err != nil {
|
||||
t.Errorf("AESGCMEncrypt 结果应为有效 base64: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESGCMEncrypt_InvalidKey(t *testing.T) {
|
||||
result := AESGCMEncrypt("invalid-base64!!!")
|
||||
if result != "" {
|
||||
t.Error("无效 base64 密钥应返回空字符串")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAESGCMEncrypt_NonDeterministic(t *testing.T) {
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 16))
|
||||
r1 := AESGCMEncrypt(key)
|
||||
r2 := AESGCMEncrypt(key)
|
||||
// GCM nonce 随机,结果不应相同
|
||||
if r1 == r2 {
|
||||
t.Log("两次 GCM 加密结果相同(极低概率事件)")
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GetShrioCookie 测试
|
||||
// =============================================================================
|
||||
|
||||
func TestGetShrioCookie_CBCMode(t *testing.T) {
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 16))
|
||||
result := GetShrioCookie(key, "cbc")
|
||||
if result == "" {
|
||||
t.Error("CBC 模式应返回非空 cookie")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetShrioCookie_GCMMode(t *testing.T) {
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 16))
|
||||
result := GetShrioCookie(key, "gcm")
|
||||
if result == "" {
|
||||
t.Error("GCM 模式应返回非空 cookie")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetShrioCookie_DefaultMode(t *testing.T) {
|
||||
// 非 gcm 模式走 CBC
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 16))
|
||||
result := GetShrioCookie(key, "other")
|
||||
cbcResult := AESCBCEncrypt(key)
|
||||
// 两个结果都应为非空 base64,但由于随机 IV 不一定相同
|
||||
if result == "" {
|
||||
t.Error("默认(非gcm)模式应使用 CBC 加密并返回非空结果")
|
||||
}
|
||||
_ = cbcResult
|
||||
}
|
||||
|
||||
func TestGetShrioCookie_RealShiroKey(t *testing.T) {
|
||||
// 使用真实的 Shiro 默认密钥
|
||||
shiroDefaultKey := "kPH+bIxk5D2deZiIxcaaaA=="
|
||||
result := GetShrioCookie(shiroDefaultKey, "cbc")
|
||||
if result == "" {
|
||||
t.Error("使用默认 Shiro 密钥应能生成有效 cookie")
|
||||
}
|
||||
// 验证结果是 base64 编码
|
||||
decoded, err := base64.StdEncoding.DecodeString(result)
|
||||
if err != nil {
|
||||
t.Errorf("结果应为有效 base64: %v", err)
|
||||
}
|
||||
// CBC 模式:IV(16字节) + 密文,结果至少 32 字节
|
||||
if len(decoded) < 32 {
|
||||
t.Errorf("CBC 加密结果太短: %d 字节", len(decoded))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetShrioCookie_ResultIsBase64(t *testing.T) {
|
||||
key := base64.StdEncoding.EncodeToString(make([]byte, 16))
|
||||
for _, mode := range []string{"cbc", "gcm"} {
|
||||
result := GetShrioCookie(key, mode)
|
||||
if result == "" {
|
||||
t.Errorf("mode=%s: 结果不应为空", mode)
|
||||
continue
|
||||
}
|
||||
// base64 只含 [A-Za-z0-9+/=]
|
||||
if strings.ContainsAny(result, " \t\n\r") {
|
||||
t.Errorf("mode=%s: base64 结果不应含空白字符", mode)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user