diff --git a/jest.config.js b/jest.config.js index e3b34b8..0524f52 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,7 @@ module.exports = { + setupFilesAfterEnv: [ + './src/__test__/setup_jest.js' + ], moduleNameMapper: { '@/(.*)': '/src/$1' } diff --git a/package-lock.json b/package-lock.json index baed5be..4fed9de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@babel/preset-typescript": "^7.16.5", "@jixun/qmc2-crypto": "^0.0.5-R4", - "@unlock-music/joox-crypto": "^0.0.1-R4", + "@unlock-music/joox-crypto": "^0.0.1-R5", "base64-js": "^1.5.1", "browser-id3-writer": "^4.4.0", "core-js": "^3.16.0", @@ -3487,9 +3487,9 @@ "dev": true }, "node_modules/@unlock-music/joox-crypto": { - "version": "0.0.1-R4", - "resolved": "https://registry.npmjs.org/@unlock-music/joox-crypto/-/joox-crypto-0.0.1-R4.tgz", - "integrity": "sha512-5UScjXtH9J3mAy9sRBjNn5kkEuT7dHvH3YQYKRyOfF3EKxLkWsJP0Fuw/tZtylLPL5beI3qDBzyHf5mVrpdH4A==", + "version": "0.0.1-R5", + "resolved": "https://registry.npmjs.org/@unlock-music/joox-crypto/-/joox-crypto-0.0.1-R5.tgz", + "integrity": "sha512-+FhGT4bjzfb1Q7dAwHps/XqbqXrRA6Qg7pkDPzyXfeRmQocAySQ/dekojxkaFBf7ZX5ToIAopwxkKZ5NFt5bFw==", "dependencies": { "crypto-js": "^4.1.1" }, @@ -23635,9 +23635,9 @@ "dev": true }, "@unlock-music/joox-crypto": { - "version": "0.0.1-R4", - "resolved": "https://registry.npmjs.org/@unlock-music/joox-crypto/-/joox-crypto-0.0.1-R4.tgz", - "integrity": "sha512-5UScjXtH9J3mAy9sRBjNn5kkEuT7dHvH3YQYKRyOfF3EKxLkWsJP0Fuw/tZtylLPL5beI3qDBzyHf5mVrpdH4A==", + "version": "0.0.1-R5", + "resolved": "https://registry.npmjs.org/@unlock-music/joox-crypto/-/joox-crypto-0.0.1-R5.tgz", + "integrity": "sha512-+FhGT4bjzfb1Q7dAwHps/XqbqXrRA6Qg7pkDPzyXfeRmQocAySQ/dekojxkaFBf7ZX5ToIAopwxkKZ5NFt5bFw==", "requires": { "crypto-js": "^4.1.1" } diff --git a/package.json b/package.json index f6295bb..12644ef 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "dependencies": { "@babel/preset-typescript": "^7.16.5", "@jixun/qmc2-crypto": "^0.0.5-R4", - "@unlock-music/joox-crypto": "^0.0.1-R4", + "@unlock-music/joox-crypto": "^0.0.1-R5", "base64-js": "^1.5.1", "browser-id3-writer": "^4.4.0", "core-js": "^3.16.0", diff --git a/src/__test__/setup_jest.js b/src/__test__/setup_jest.js new file mode 100644 index 0000000..c7b47bf --- /dev/null +++ b/src/__test__/setup_jest.js @@ -0,0 +1,2 @@ +// Polyfill for node. +global.Blob = global.Blob || require("node:buffer").Blob; diff --git a/src/decrypt/__test__/fixture/joox_1.bin b/src/decrypt/__test__/fixture/joox_1.bin new file mode 100644 index 0000000..5ffe376 Binary files /dev/null and b/src/decrypt/__test__/fixture/joox_1.bin differ diff --git a/src/decrypt/__test__/joox.test.ts b/src/decrypt/__test__/joox.test.ts new file mode 100644 index 0000000..dd0af1d --- /dev/null +++ b/src/decrypt/__test__/joox.test.ts @@ -0,0 +1,52 @@ +import fs from 'fs'; +import { storage } from '@/utils/storage'; + +import { Decrypt as decryptJoox } from '../joox'; +import { extractQQMusicMeta as extractQQMusicMetaOrig } from '@/utils/qm_meta'; + +jest.mock('@/utils/storage'); +jest.mock('@/utils/qm_meta'); + +const loadJooxUUID = storage.loadJooxUUID as jest.MockedFunction; +const extractQQMusicMeta = extractQQMusicMetaOrig as jest.MockedFunction; + +const TEST_UUID_ZEROS = ''.padStart(32, '0'); +const encryptedFile1 = fs.readFileSync(__dirname + '/fixture/joox_1.bin'); + +describe('decrypt/joox', () => { + it('should be able to decrypt sample file (v4)', async () => { + loadJooxUUID.mockResolvedValue(TEST_UUID_ZEROS); + extractQQMusicMeta.mockImplementationOnce(async (blob: Blob) => { + return { + title: 'unused', + album: 'unused', + blob: blob, + artist: 'unused', + imgUrl: 'https://github.com/unlock-music', + }; + }); + + const result = await decryptJoox(new Blob([encryptedFile1]), 'test.bin', 'bin'); + const resultBuf = await result.blob.arrayBuffer(); + expect(resultBuf).toEqual(Buffer.from('Hello World', 'utf-8').buffer); + }); + + it('should reject E!99 files', async () => { + loadJooxUUID.mockResolvedValue(TEST_UUID_ZEROS); + + const input = new Blob([Buffer.from('E!99....')]); + await expect(decryptJoox(input, 'test.bin', 'bin')).rejects.toThrow('不支持的 joox 加密格式'); + }); + + it('should reject empty uuid', async () => { + loadJooxUUID.mockResolvedValue(''); + const input = new Blob([encryptedFile1]); + await expect(decryptJoox(input, 'test.bin', 'bin')).rejects.toThrow('UUID'); + }); + + it('should reject invalid uuid', async () => { + loadJooxUUID.mockResolvedValue('hello!'); + const input = new Blob([encryptedFile1]); + await expect(decryptJoox(input, 'test.bin', 'bin')).rejects.toThrow('UUID'); + }); +}); diff --git a/src/utils/__mocks__/qm_meta.ts b/src/utils/__mocks__/qm_meta.ts new file mode 100644 index 0000000..99c3cec --- /dev/null +++ b/src/utils/__mocks__/qm_meta.ts @@ -0,0 +1 @@ +export const extractQQMusicMeta = jest.fn(); diff --git a/src/utils/__mocks__/storage.ts b/src/utils/__mocks__/storage.ts new file mode 100644 index 0000000..af9fd67 --- /dev/null +++ b/src/utils/__mocks__/storage.ts @@ -0,0 +1,4 @@ +export const storage = { + loadJooxUUID: jest.fn(), + saveJooxUUID: jest.fn(), +};