Files
Roger b3bbc0dadf feat: add CountEntries to skip decryption for list --detail (#562)
* feat: add CountEntries to skip decryption for list --detail (#549)
* test: add CountEntries and countCategory tests at browser level
* fix: address review feedback on CountRows and countLocalStorage
* test: add CountRows unit tests
2026-04-07 22:28:39 +08:00

297 lines
8.5 KiB
Go

package chromium
import (
"encoding/binary"
"testing"
"unicode/utf16"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// ---------------------------------------------------------------------------
// decodeChromiumString
// ---------------------------------------------------------------------------
func TestDecodeChromiumString(t *testing.T) {
tests := []struct {
name string
input []byte
want string
wantErr string
}{
{
name: "latin1 ascii",
input: testEncodeLatin1("abc123"),
want: "abc123",
},
{
name: "latin1 non-ascii",
input: append([]byte{chromiumStringLatin1Format}, 0x6E, 0x61, 0xEF, 0x76, 0x65), // "naïve" in Latin-1
want: "na\u00efve", // U+00EF = ï
},
{
name: "utf16le ascii",
input: testEncodeUTF16("hello"),
want: "hello",
},
{
name: "utf16le japanese",
input: testEncodeUTF16("テスト"),
want: "テスト",
},
{
name: "utf16le empty content",
input: []byte{chromiumStringUTF16Format},
want: "",
},
{
name: "unknown format",
input: []byte{2, 'x'},
wantErr: "unknown chromium string format",
},
{
name: "invalid utf16 byte length",
input: []byte{chromiumStringUTF16Format, 0x61},
wantErr: "invalid UTF-16 byte length",
},
{
name: "empty input",
input: []byte{},
wantErr: "empty chromium string",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := decodeChromiumString(tt.input)
if tt.wantErr != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
return
}
require.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}
// ---------------------------------------------------------------------------
// parseLocalStorageEntry
// ---------------------------------------------------------------------------
func TestParseLocalStorageEntry(t *testing.T) {
tests := []struct {
name string
key []byte
value []byte
wantParsed bool
wantMeta bool
wantURL string
wantKey string
wantValue string
}{
{
name: "skip VERSION",
key: []byte(localStorageVersionKey),
wantParsed: false,
},
{
name: "META entry",
key: []byte(localStorageMetaPrefix + "https://example.com"),
value: []byte{0x08, 0x96, 0x01},
wantParsed: true,
wantMeta: true,
wantURL: "https://example.com",
wantValue: "meta data, value bytes is [8 150 1]",
},
{
name: "METAACCESS entry",
key: []byte(localStorageMetaAccessKey + "https://example.com"),
value: []byte{0x10, 0x20},
wantParsed: true,
wantMeta: true,
wantURL: "https://example.com",
wantValue: "meta data, value bytes is [16 32]",
},
{
name: "latin1 data entry",
key: append([]byte("_https://example.com\x00"), testEncodeLatin1("token")...),
value: testEncodeLatin1("abc123"),
wantParsed: true,
wantURL: "https://example.com",
wantKey: "token",
wantValue: "abc123",
},
{
name: "utf16 data entry",
key: append([]byte("_https://example.com\x00"), testEncodeUTF16("テスト")...),
value: testEncodeUTF16("データ"),
wantParsed: true,
wantURL: "https://example.com",
wantKey: "テスト",
wantValue: "データ",
},
{
name: "missing origin separator",
key: []byte("_https://example.com"),
value: testEncodeLatin1("abc123"),
wantParsed: true,
wantURL: "",
wantKey: "",
wantValue: "abc123",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
entry, parsed := parseLocalStorageEntry(tt.key, tt.value)
assert.Equal(t, tt.wantParsed, parsed)
if !parsed {
return
}
assert.Equal(t, tt.wantMeta, entry.IsMeta)
assert.Equal(t, tt.wantURL, entry.URL)
assert.Equal(t, tt.wantKey, entry.Key)
assert.Equal(t, tt.wantValue, entry.Value)
})
}
}
// ---------------------------------------------------------------------------
// extractLocalStorage (integration with LevelDB)
// ---------------------------------------------------------------------------
func setupLocalStorageLevelDB(t *testing.T) string {
t.Helper()
return createTestLevelDB(t, map[string]string{
localStorageVersionKey: "1",
localStorageMetaPrefix + "https://example.com": string([]byte{0x08, 0x96, 0x01}),
localStorageMetaAccessKey + "https://example.com": string([]byte{0x10, 0x20}),
string(append([]byte("_https://example.com\x00"), testEncodeLatin1("token")...)): string(testEncodeLatin1("abc123")),
string(append([]byte("_https://example.com\x00"), testEncodeUTF16("テスト")...)): string(testEncodeUTF16("データ")),
})
}
func setupSessionStorageLevelDB(t *testing.T) string {
t.Helper()
return createTestLevelDB(t, map[string]string{
"namespace-abcd1234_5678_9abc_def0_111111111111-https://github.com/": "100",
"namespace-abcd1234_5678_9abc_def0_111111111111-https://example.com/": "101",
"map-100-__darkreader__wasEnabledForHost": string(testEncodeUTF16Raw("false")),
"map-101-token": string(testEncodeUTF16Raw("abc123")),
"next-map-id": "200",
"version": "1",
})
}
func TestExtractLocalStorage(t *testing.T) {
dir := setupLocalStorageLevelDB(t)
got, err := extractLocalStorage(dir)
require.NoError(t, err)
require.Len(t, got, 4, "VERSION filtered, META kept, data kept")
metaCount := 0
byKey := map[string]string{}
for _, e := range got {
assert.Equal(t, "https://example.com", e.URL)
if e.IsMeta {
metaCount++
assert.Contains(t, e.Value, "meta data, value bytes is")
continue
}
byKey[e.Key] = e.Value
}
assert.Equal(t, 2, metaCount)
assert.Equal(t, "abc123", byKey["token"])
assert.Equal(t, "データ", byKey["テスト"])
}
// ---------------------------------------------------------------------------
// extractSessionStorage
// ---------------------------------------------------------------------------
func TestExtractSessionStorage(t *testing.T) {
dir := setupSessionStorageLevelDB(t)
got, err := extractSessionStorage(dir)
require.NoError(t, err)
require.Len(t, got, 2)
byKey := map[string]string{}
for _, entry := range got {
byKey[entry.URL+"/"+entry.Key] = entry.Value
}
assert.Equal(t, "false", byKey["https://github.com//__darkreader__wasEnabledForHost"])
assert.Equal(t, "abc123", byKey["https://example.com//token"])
}
// ---------------------------------------------------------------------------
// countLocalStorage
// ---------------------------------------------------------------------------
func TestCountLocalStorage(t *testing.T) {
dir := setupLocalStorageLevelDB(t)
count, err := countLocalStorage(dir)
require.NoError(t, err)
assert.Equal(t, 4, count) // VERSION filtered, 2 META + 2 data entries kept
}
// ---------------------------------------------------------------------------
// countSessionStorage
// ---------------------------------------------------------------------------
func TestCountSessionStorage(t *testing.T) {
dir := setupSessionStorageLevelDB(t)
count, err := countSessionStorage(dir)
require.NoError(t, err)
assert.Equal(t, 2, count) // only map- entries with key separator
}
func TestCountSessionStorage_Empty(t *testing.T) {
dir := createTestLevelDB(t, map[string]string{
"next-map-id": "1",
"version": "1",
})
count, err := countSessionStorage(dir)
require.NoError(t, err)
assert.Equal(t, 0, count)
}
// ---------------------------------------------------------------------------
// Test helpers
// ---------------------------------------------------------------------------
func testEncodeLatin1(s string) []byte {
return append([]byte{chromiumStringLatin1Format}, []byte(s)...)
}
func testEncodeUTF16(s string) []byte {
encoded := utf16.Encode([]rune(s))
result := make([]byte, 1, 1+len(encoded)*2)
result[0] = chromiumStringUTF16Format
for _, r := range encoded {
var raw [2]byte
binary.LittleEndian.PutUint16(raw[:], r)
result = append(result, raw[:]...)
}
return result
}
// testEncodeUTF16Raw encodes as raw UTF-16 LE without format byte prefix
// (used by session storage values).
func testEncodeUTF16Raw(s string) []byte {
encoded := utf16.Encode([]rune(s))
result := make([]byte, 0, len(encoded)*2)
for _, r := range encoded {
var raw [2]byte
binary.LittleEndian.PutUint16(raw[:], r)
result = append(result, raw[:]...)
}
return result
}