Skip to content

Commit 8de4095

Browse files
committed
feat(secret): add test cases and improve decompress logic
- Add comprehensive test cases for crypto & compress utilities - Improve `decompress` function to handle empty/invalid input gracefully - Update error logging in `decompress` from error to warn level - Refactor path utility tests with real filesystem operations - Add `resolvePath` utility and enhance `mkdirp`/`rmrf` tests
1 parent 1eafca2 commit 8de4095

4 files changed

Lines changed: 94 additions & 90 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ pnpm build
9191
# docs
9292
cd docs
9393
pnpm run predocs:dev
94+
95+
# add test cases
9496
```
9597

9698
## License

src/common/secret.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,16 @@ export function compress(str: string): string {
6464
* @returns The original decompressed string.
6565
*/
6666
export function decompress(str: string): string {
67+
if (!str)
68+
return '' // 空字符串直接返回
6769
try {
6870
const decodedBase64 = decodeURIComponent(str)
6971
const binary = atob(decodedBase64)
7072
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0))
7173
return new TextDecoder().decode(bytes)
7274
}
7375
catch (err) {
74-
console.error('Decompress error:', err)
76+
console.warn('Decompress error: Invalid input', err)
7577
return ''
7678
}
7779
}

test/common/secret.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { compress, decompress, decrypt, encrypt, xor } from '../../src/common/secret'
3+
4+
describe('crypto & Compress Utils', () => {
5+
const plainText = 'Hello World! 123'
6+
const key = 'mysecret'
7+
8+
it('xor should be reversible', () => {
9+
const encrypted = xor(plainText, key)
10+
const decrypted = xor(encrypted, key)
11+
expect(decrypted).toBe(plainText)
12+
})
13+
14+
it('encrypt and decrypt should be reversible', () => {
15+
const enc = encrypt(plainText, key)
16+
const dec = decrypt(enc, key)
17+
expect(dec).toBe(plainText)
18+
})
19+
20+
it('encrypt produces URI-safe string', () => {
21+
const enc = encrypt(plainText, key)
22+
expect(enc).not.toContain(' ')
23+
expect(enc).not.toContain('+')
24+
expect(enc).not.toContain('=')
25+
})
26+
27+
it('compress and decompress should be reversible', () => {
28+
const compressed = compress(plainText)
29+
const decompressed = decompress(compressed)
30+
expect(decompressed).toBe(plainText)
31+
})
32+
33+
it('decompress should return empty string on invalid input', () => {
34+
// 非合法 URI
35+
expect(decompress('%%%')).toBe('')
36+
// 空字符串
37+
expect(decompress('')).toBe('')
38+
// undefined 需要先断言类型
39+
})
40+
41+
it('decrypt should return empty string on invalid input', () => {
42+
const result = decrypt('invalid%%%')
43+
expect(result).toBe('')
44+
})
45+
46+
it('xor with default key works', () => {
47+
const encrypted = xor(plainText)
48+
const decrypted = xor(encrypted)
49+
expect(decrypted).toBe(plainText)
50+
})
51+
52+
it('encrypt and decrypt with default key works', () => {
53+
const enc = encrypt(plainText)
54+
const dec = decrypt(enc)
55+
expect(dec).toBe(plainText)
56+
})
57+
})

test/node/path.test.ts

Lines changed: 32 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,48 @@
1-
import { access, mkdir, readdir, rm, stat } from 'node:fs/promises' // 根据实际情况调整导入
2-
import { dirname, join } from 'node:path'
3-
import { beforeEach, describe, expect, it, vi } from 'vitest'
4-
import { mkdirp, rmrf } from '../../src/node/path'
1+
import { existsSync } from 'node:fs'
2+
import { join } from 'node:path'
3+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
4+
import { mkdirp, projectRoot, resolvePath, rmrf } from '../../src/node/path'
55

6-
// 模拟 fs/promises 和 path 模块
7-
vi.mock('fs/promises', async () => {
8-
const actual = await vi.importActual('fs/promises')
9-
return {
10-
...actual,
11-
access: vi.fn(),
12-
mkdir: vi.fn(),
13-
stat: vi.fn(),
14-
readdir: vi.fn(),
15-
rm: vi.fn(),
16-
}
17-
})
18-
19-
// 修改:合并所有对 path 模块的模拟,避免重复定义
20-
vi.mock('path', () => ({
21-
dirname: vi.fn(),
22-
join: vi.fn(),
23-
normalize: vi.fn(),
24-
}))
25-
26-
describe('mkdirp', () => {
27-
let accessMock: any
28-
let mkdirMock: any
29-
let dirnameMock: any
30-
31-
beforeEach(() => {
32-
accessMock = vi.mocked(access)
33-
mkdirMock = vi.mocked(mkdir)
34-
dirnameMock = vi.mocked(dirname)
35-
})
36-
37-
it('should not create directory if it already exists', async () => {
38-
accessMock.mockResolvedValueOnce()
39-
40-
await mkdirp('/mock/path')
6+
describe('path Utils', () => {
7+
const testDir = join(projectRoot, 'test-temp')
418

42-
expect(accessMock).toHaveBeenCalledWith('/mock/path')
43-
expect(mkdirMock).not.toHaveBeenCalled()
9+
// 测试前确保目录不存在
10+
beforeEach(async () => {
11+
await rmrf(testDir).catch(() => {})
4412
})
4513

46-
it('should recursively create directories', async () => {
47-
accessMock.mockRejectedValueOnce(new Error('ENOENT')) // 第一次访问失败
48-
accessMock.mockResolvedValueOnce() // 父目录存在
49-
dirnameMock.mockReturnValueOnce('/mock') // 第一次 dirname 调用
50-
51-
await mkdirp('/mock/path')
52-
53-
expect(accessMock).toHaveBeenNthCalledWith(1, '/mock/path')
54-
expect(dirnameMock).toHaveBeenCalledWith('/mock/path')
55-
expect(mkdirMock).toHaveBeenCalledWith('/mock/path')
14+
// 测试后清理
15+
afterEach(async () => {
16+
await rmrf(testDir).catch(() => {})
5617
})
57-
})
58-
59-
describe('rmrf', () => {
60-
let statMock: any
61-
let readdirMock: any
62-
let rmMock: any
63-
let joinMock: any
6418

65-
beforeEach(() => {
66-
statMock = vi.mocked(stat)
67-
readdirMock = vi.mocked(readdir)
68-
rmMock = vi.mocked(rm)
69-
joinMock = vi.mocked(join)
19+
it('should resolvePath correctly', () => {
20+
const path = resolvePath('foo/bar')
21+
expect(path).toBe(join(projectRoot, 'foo/bar'))
7022
})
7123

72-
beforeEach(() => {
73-
statMock.mockClear()
74-
readdirMock.mockClear()
75-
rmMock.mockClear()
76-
joinMock.mockClear()
24+
it('should create directory recursively', async () => {
25+
const nestedDir = join(testDir, 'a/b/c')
26+
await mkdirp(nestedDir)
27+
expect(existsSync(nestedDir)).toBe(true)
7728
})
7829

79-
it('should delete a file', async () => {
80-
statMock.mockResolvedValueOnce({ isDirectory: () => false })
30+
it('should remove directory recursively', async () => {
31+
const nestedDir = join(testDir, 'x/y/z')
32+
await mkdirp(nestedDir)
33+
expect(existsSync(nestedDir)).toBe(true)
8134

82-
await rmrf('/mock/path/to/file')
83-
84-
expect(statMock).toHaveBeenCalledWith('/mock/path/to/file')
85-
expect(rmMock).toHaveBeenCalledWith('/mock/path/to/file')
86-
expect(joinMock).not.toHaveBeenCalled()
87-
expect(readdirMock).not.toHaveBeenCalled()
35+
await rmrf(testDir)
36+
expect(existsSync(testDir)).toBe(false)
8837
})
8938

90-
it('should handle errors gracefully', async () => {
91-
const testError = new Error('Test error')
92-
statMock.mockRejectedValue(testError)
93-
94-
try {
95-
await rmrf('/mock/path/to/error')
96-
}
97-
catch (error) {
98-
expect(error).toBe(testError)
99-
}
39+
it('rmrf should not throw if path does not exist', async () => {
40+
await expect(rmrf(join(testDir, 'nonexistent'))).resolves.not.toThrow()
41+
})
10042

101-
expect(statMock).toHaveBeenCalledWith('/mock/path/to/error')
102-
expect(readdirMock).not.toHaveBeenCalled()
103-
expect(rmMock).not.toHaveBeenCalled()
43+
it('mkdirp should not throw if directory already exists', async () => {
44+
const dir = join(testDir, 'already')
45+
await mkdirp(dir)
46+
await expect(mkdirp(dir)).resolves.not.toThrow()
10447
})
10548
})

0 commit comments

Comments
 (0)