Files
dela 1b61541ff5
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
feat: 接入 CoinGlass 清算热力图隔离链路
2026-05-25 16:57:22 +08:00

129 lines
3.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package coinglass
import (
"bytes"
"compress/gzip"
"crypto/aes"
"encoding/base64"
"errors"
"testing"
"github.com/stretchr/testify/require"
)
// helper把 plaintext 用 V2 通用管线反向加密,产出可喂给 decryptBlob 的 base64 ciphertext。
// 形态plaintext → '"plaintext"' → gzip → pkcs7 pad → AES-ECB → base64
func encryptForTest(t *testing.T, key []byte, plaintext string) string {
t.Helper()
// CoinGlass 服务器端会在 plaintext 外加引号decryptBlob 反向 strip
// 测试也照样加引号 → 验证 strip 逻辑。
quoted := []byte(`"` + plaintext + `"`)
var buf bytes.Buffer
gz := gzip.NewWriter(&buf)
_, err := gz.Write(quoted)
require.NoError(t, err)
require.NoError(t, gz.Close())
padded := pkcs7Pad(buf.Bytes(), aes.BlockSize)
enc, err := aesECBEncrypt(key, padded)
require.NoError(t, err)
return base64.StdEncoding.EncodeToString(enc)
}
func TestDecryptBlob_RoundTrip(t *testing.T) {
cases := []struct {
name string
key []byte
text string
}{
{"16-byte key (AES-128)", []byte("0123456789abcdef"), `{"hello":"world"}`},
{"32-byte key (AES-256)", []byte("1f68efd73f8d4921acc0dead41dd39bc"), `{"foo":42,"bar":"baz"}`},
{"empty json object", []byte("0123456789abcdef"), `{}`},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
ct := encryptForTest(t, tc.key, tc.text)
got, err := decryptBlob(ct, tc.key)
require.NoError(t, err)
require.Equal(t, tc.text, got)
})
}
}
func TestDecryptBlob_BadCiphertext(t *testing.T) {
key := []byte("0123456789abcdef")
_, err := decryptBlob("!!!not_base64!!!", key)
require.Error(t, err)
require.ErrorIs(t, err, ErrDecryptFail)
short := base64.StdEncoding.EncodeToString([]byte("toosmall"))
_, err = decryptBlob(short, key)
require.ErrorIs(t, err, ErrDecryptFail)
}
func TestPKCS7_PadUnpadRoundTrip(t *testing.T) {
cases := [][]byte{
[]byte(""),
[]byte("a"),
[]byte("0123456789abcde"), // 15 → pad 1
[]byte("0123456789abcdef"), // 16 → pad 16 (full block)
[]byte("0123456789abcdef0"),
}
for _, in := range cases {
padded := pkcs7Pad(in, 16)
require.Zero(t, len(padded)%16)
out, err := pkcs7Unpad(padded, 16)
require.NoError(t, err)
require.Equal(t, in, out)
}
}
func TestPKCS7_UnpadRejectsBadInput(t *testing.T) {
bad := []byte("0123456789abcdef") // pad byte = 'f' (0x66 = 102 > 16)
_, err := pkcs7Unpad(bad, 16)
require.Error(t, err)
_, err = pkcs7Unpad([]byte{}, 16)
require.Error(t, err)
}
func TestUserKeyFromSeed_TruncatesTo16(t *testing.T) {
seed := "863f08689c97435b" // v=77 constant from bundle
got := userKeyFromSeed(seed)
require.Len(t, got, 16)
// base64("863f08689c97435b") = "ODYzZjA4Njg5Yzk3NDM1Yg==" → first 16 = "ODYzZjA4Njg5Yzk3"
require.Equal(t, "ODYzZjA4Njg5Yzk3", string(got))
}
func TestBodyKeyFromToken_RawASCIIFirst16(t *testing.T) {
token := "dfa89d5d00cf4000extra_chars"
got := bodyKeyFromToken(token)
require.Len(t, got, 16)
require.Equal(t, "dfa89d5d00cf4000", string(got))
}
func TestDecryptUniversal_TwoStage(t *testing.T) {
seed := "863f08689c97435b" // v=77 bundle constant
userKey := userKeyFromSeed(seed)
token := "dfa89d5d00cf4000" // exactly 16 chars (matches cg.md L79 example)
bodyKey := []byte(token)
plaintext := `{"success":true,"code":"0","msg":"","data":{"priceMin":110000.0}}`
userHeaderB64 := encryptForTest(t, userKey, token)
bodyDataB64 := encryptForTest(t, bodyKey, plaintext)
got, err := DecryptUniversal(seed, userHeaderB64, bodyDataB64)
require.NoError(t, err)
require.JSONEq(t, plaintext, string(got))
}
func TestDecryptUniversal_PropagatesDecryptFail(t *testing.T) {
_, err := DecryptUniversal("seed", "!!!", "!!!")
require.Error(t, err)
require.True(t, errors.Is(err, ErrDecryptFail))
}