diff --git a/background/package.json b/background/package.json index 8385fba..e2edf4e 100644 --- a/background/package.json +++ b/background/package.json @@ -30,6 +30,7 @@ "@vue/test-utils": "^1.3.0", "eslint": "^7.32.0", "eslint-plugin-vue": "^8.7.1", + "fast-check": "^3.19.0", "typescript": "^5.4.5", "vite": "^5.2.8", "vitest": "^1.6.0", diff --git a/background/test/storage/backend/memory.ts b/background/test/storage/backend/memory.ts new file mode 100644 index 0000000..04b4718 --- /dev/null +++ b/background/test/storage/backend/memory.ts @@ -0,0 +1,17 @@ +import { IStorageBackend } from "@/storage/backend/index.js"; + +export class InMemoryStorageBackend implements IStorageBackend { + private readonly map: Map = new Map(); + + constructor() { + this.map = new Map(); + } + + get(key: string): Promise { + return this.map.get(key); + } + + async set(key: string, value: T): Promise { + this.map.set(key, value); + } +} diff --git a/background/test/storage/storage.test.js b/background/test/storage/storage.test.js deleted file mode 100644 index ab6892b..0000000 --- a/background/test/storage/storage.test.js +++ /dev/null @@ -1,75 +0,0 @@ -import Storage from "@/storage/storage" -import Mock from "../mock" -const passphrase = 'Storage Passphrase' - -Mock.mockStorage() - -describe("storage.js", () => { - let storage - let key, numValue, strValue, objValue, arrValue - beforeAll(() => { - storage = new Storage(passphrase) - }) - beforeEach(() => { - key = 'key' + Math.random() - numValue = Math.random() - strValue = Math.random().toString() - objValue = {n: Math.random(), s: Math.random().toString()} - arrValue = [Math.random(), Math.random()] - }) - - test('Basic behavior test of set, get', async () => { - storage.clearAll() - - expect(await storage.has(key)).toBeFalsy() - storage.set(key, numValue) - expect(await storage.has(key)).toBeTruthy() - expect(await storage.rawGet(key)).not.toBe(numValue) - expect(await storage.get(key)).toBe(numValue) - - storage.set(key, strValue) - expect(await storage.rawGet(key)).not.toBe(strValue) - expect(await storage.get(key)).toBe(strValue) - - storage.set(key, objValue) - expect(JSON.stringify(await storage.rawGet(key))).not.toBe(JSON.stringify(objValue)) - expect(JSON.stringify(await storage.get(key))).toBe(JSON.stringify(objValue)) - - storage.set(key, arrValue) - expect(JSON.stringify(await storage.rawGet(key))).not.toBe(JSON.stringify(arrValue)) - expect(JSON.stringify(await storage.get(key))).toBe(JSON.stringify(arrValue)) - await expect(storage.secureGet(key)).rejects.toEqual('SecureGet has accessed to not secured data') - - expect(await storage.get(key + 'INVALID')).toBe(null) - }) - - - test('Basic behavior test of secureSet, secureGet', async () => { - storage.clearAll() - - expect(storage.canCallExternal('secureSet')).toBeTruthy() - expect(storage.canCallExternal('secureGet')).toBeFalsy() - - expect(await storage.has(key)).toBeFalsy() - storage.secureSet(key, numValue) - expect(await storage.has(key)).toBeTruthy() - expect(await storage.secureGet(key)).toBe(numValue) - await expect(storage.get(key)).rejects.toEqual('Can not access secure data') - storage.remove(key) - expect(await storage.has(key)).toBeFalsy() - - storage.secureSet(key, strValue) - expect(await storage.secureGet(key)).toBe(strValue) - await expect(storage.get(key)).rejects.toEqual('Can not access secure data') - - storage.secureSet(key, objValue) - expect(JSON.stringify(await storage.secureGet(key))).toBe(JSON.stringify(objValue)) - await expect(storage.get(key)).rejects.toEqual('Can not access secure data') - - storage.secureSet(key, arrValue) - expect(JSON.stringify(await storage.secureGet(key))).toBe(JSON.stringify(arrValue)) - await expect(storage.get(key)).rejects.toEqual('Can not access secure data') - - expect(await storage.secureGet(key + 'INVALID')).toBe(null) - }) -}) \ No newline at end of file diff --git a/background/test/storage/storage.test.ts b/background/test/storage/storage.test.ts new file mode 100644 index 0000000..87b788a --- /dev/null +++ b/background/test/storage/storage.test.ts @@ -0,0 +1,83 @@ +import { Storage } from "@/storage/index.js" +import { InMemoryStorageBackend } from "./backend/memory.js" +import { describe, expect, beforeEach, test } from "vitest"; +import { IStorageBackend } from "@/storage/backend/common.js"; +import fc from "fast-check"; +import aes256 from "@/utils/aes256.js"; + +// TODO: Check lazy passpharse. +const passphrase = 'Storage Passphrase' + +describe("Storage", () => { + let backend!: IStorageBackend; + let storage!: Storage; + + beforeEach(() => { + backend = new InMemoryStorageBackend(); + storage = new Storage(passphrase, backend); + }); + + describe('secureGet', () => { + test('should fail if the data is not secured', async () => { + fc.assert(fc.asyncProperty(fc.string(), fc.string(), async (key, value) => { + const v = await aes256.encrypt(JSON.stringify(value), passphrase); + await backend.set(`${key}-no-secure`, JSON.stringify({ v: v, })) + await backend.set(`${key}-secure-false`, JSON.stringify({ secure: false, v: v })) + + await expect(async () => await storage.secureGet(`${key}-no-secure`)).rejects.toThrowError("SecureGet has accessed to not secured data"); + await expect(async () => await storage.secureGet(`${key}-secure-false`)).rejects.toThrowError("SecureGet has accessed to not secured data"); + })); + }); + + test('should success if the data is secured', async () => { + fc.assert(fc.asyncProperty(fc.string(), fc.string(), async (key, value) => { + const v = await aes256.encrypt(JSON.stringify(value), passphrase); + await backend.set(key, JSON.stringify({ secure: true, v: v, })) + + await expect(storage.secureGet(key)).resolves.toEqual(value); + })); + }); + }); + + describe('get', () => { + test('should fail if the data is secured', async () => { + fc.assert(fc.asyncProperty(fc.string(), fc.string(), async (key, value) => { + const v = await aes256.encrypt(JSON.stringify(value), passphrase); + await backend.set(key, JSON.stringify({ secure: true, v: v })) + + await expect(async () => await storage.secureGet(key)).rejects.toThrowError("Can not access secure data"); + })); + }); + + test('should success if the data is not secured', async () => { + fc.assert(fc.asyncProperty(fc.string(), fc.string(), async (key, value) => { + await backend.set(key, JSON.stringify({ v: value, })); + await expect(storage.get(key)).resolves.toEqual(value); + })); + }); + }); + + describe('set', () => { + test('should store non-secured data', async () => { + fc.assert(fc.asyncProperty(fc.string(), fc.string(), async (key, value) => { + await storage.set(key, value); + + const raw = JSON.parse(await backend.get(key)); + expect(!!raw.secure).toEqual(false); + expect(raw.v).toEqual(value); + })); + }); + }); + + describe('secureSet', () => { + test('should store secured data', async () => { + fc.assert(fc.asyncProperty(fc.string(), fc.string(), async (key, value) => { + await storage.secureSet(key, value); + + const raw = JSON.parse(await backend.get(key)); + expect(raw.secure).toEqual(true); + expect(raw.v).not.toEqual(value); + })); + }); + }); +}) diff --git a/background/tsconfig.json b/background/tsconfig.json index b4c6ec0..86b98b3 100644 --- a/background/tsconfig.json +++ b/background/tsconfig.json @@ -9,5 +9,5 @@ "@/*": ["./src/*"] } }, - "include": ["src"] + "include": ["src", "test"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da7b67e..8e19947 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -70,6 +70,9 @@ importers: eslint-plugin-vue: specifier: ^8.7.1 version: 8.7.1(eslint@7.32.0) + fast-check: + specifier: ^3.19.0 + version: 3.19.0 typescript: specifier: ^5.4.5 version: 5.4.5 @@ -3351,6 +3354,10 @@ packages: resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} engines: {'0': node >=0.6.0} + fast-check@3.19.0: + resolution: {integrity: sha512-CO2JX/8/PT9bDGO1iXa5h5ey1skaKI1dvecERyhH4pp3PGjwd3KIjMAXEg79Ps9nclsdt4oPbfqiAnLU0EwrAQ==} + engines: {node: '>=8.0.0'} + fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} @@ -4957,6 +4964,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + pvtsutils@1.3.5: resolution: {integrity: sha512-ARvb14YB9Nm2Xi6nBq1ZX6dAM0FsJnuk+31aUp4TrcZEdKUlSqOqsxJHUPJDNE3qiIp+iUPEIeR6Je/tgV7zsA==} @@ -10340,6 +10350,10 @@ snapshots: extsprintf@1.3.0: {} + fast-check@3.19.0: + dependencies: + pure-rand: 6.1.0 + fast-decode-uri-component@1.0.1: {} fast-deep-equal@3.1.3: {} @@ -12167,6 +12181,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + pvtsutils@1.3.5: dependencies: tslib: 2.6.2