- Introduced comprehensive mock for the Origin Private File System (OPFS) in `tests/mocks/opfs-mock.ts`, simulating environment for detailed storage testing. - Added `tests/opfs-storage.test.ts`, containing extensive test cases for `OPFSStorage` operations including metadata, nouns, verbs, and storage status. - Improved S3 mock implementation in `tests/mocks/s3-mock.ts` with better object persistence, validation, and logging to emulate real S3 behavior. - Resolved issues related to metadata, nouns, verbs, and storage usage inconsistencies in mock storage adapters. - Enhanced logging and error handling to aid in debugging and test reliability. Purpose: Improve test completeness and reliability by introducing detailed mocks and extended test cases for S3 and OPFS storage systems.
223 lines
No EOL
7 KiB
TypeScript
223 lines
No EOL
7 KiB
TypeScript
/**
|
|
* OPFS (Origin Private File System) Mock for Testing
|
|
*
|
|
* This module provides a comprehensive mock implementation of the OPFS API
|
|
* for testing OPFS-based storage in a Node.js environment.
|
|
*/
|
|
|
|
import { vi } from 'vitest'
|
|
|
|
// In-memory storage to simulate file system
|
|
const mockFileSystem: Map<string, Map<string, any>> = new Map()
|
|
|
|
// Mock file data
|
|
interface MockFileData {
|
|
content: string
|
|
type: string
|
|
}
|
|
|
|
/**
|
|
* Create a mock FileSystemFileHandle
|
|
*/
|
|
export function createMockFileHandle(fileName: string, content: string = '{}') {
|
|
return {
|
|
kind: 'file',
|
|
name: fileName,
|
|
getFile: vi.fn().mockResolvedValue({
|
|
text: vi.fn().mockResolvedValue(content),
|
|
arrayBuffer: vi.fn().mockResolvedValue(new TextEncoder().encode(content).buffer),
|
|
size: content.length
|
|
}),
|
|
createWritable: vi.fn().mockImplementation(() => {
|
|
const writable = {
|
|
write: vi.fn().mockImplementation((data: string | ArrayBuffer) => {
|
|
// Store the data in our mock file system
|
|
const path = mockFileSystem.get('currentPath') || '/'
|
|
const dirMap = mockFileSystem.get(path) || new Map()
|
|
|
|
let content: string
|
|
if (typeof data === 'string') {
|
|
content = data
|
|
} else if (data instanceof ArrayBuffer) {
|
|
content = new TextDecoder().decode(data)
|
|
} else if (data && typeof data === 'object' && 'type' in data && data.type === 'write') {
|
|
// Handle FileSystemWriteChunkType
|
|
const chunk = data as { type: 'write', position?: number, data: string | ArrayBuffer }
|
|
if (typeof chunk.data === 'string') {
|
|
content = chunk.data
|
|
} else {
|
|
content = new TextDecoder().decode(chunk.data)
|
|
}
|
|
} else {
|
|
content = JSON.stringify(data)
|
|
}
|
|
|
|
dirMap.set(fileName, { content, type: 'file' })
|
|
mockFileSystem.set(path, dirMap)
|
|
return Promise.resolve()
|
|
}),
|
|
close: vi.fn().mockResolvedValue(undefined)
|
|
}
|
|
return Promise.resolve(writable)
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a mock FileSystemDirectoryHandle
|
|
*/
|
|
export function createMockDirectoryHandle(dirName: string, entries: Map<string, any> = new Map()) {
|
|
const dirPath = mockFileSystem.get('currentPath') || '/'
|
|
const fullPath = dirPath === '/' ? `/${dirName}` : `${dirPath}/${dirName}`
|
|
|
|
// Store the directory in our mock file system
|
|
mockFileSystem.set(fullPath, entries)
|
|
|
|
return {
|
|
kind: 'directory',
|
|
name: dirName,
|
|
getDirectoryHandle: vi.fn().mockImplementation((name: string, options: { create?: boolean } = {}) => {
|
|
mockFileSystem.set('currentPath', fullPath)
|
|
|
|
const dirEntries = mockFileSystem.get(fullPath) || new Map()
|
|
const entry = dirEntries.get(name)
|
|
|
|
if (entry && entry.type === 'directory') {
|
|
return Promise.resolve(createMockDirectoryHandle(name, entry.content))
|
|
}
|
|
|
|
if (!entry && options.create) {
|
|
const newDir = new Map()
|
|
dirEntries.set(name, { content: newDir, type: 'directory' })
|
|
mockFileSystem.set(fullPath, dirEntries)
|
|
return Promise.resolve(createMockDirectoryHandle(name, newDir))
|
|
}
|
|
|
|
return Promise.reject(new Error(`Directory not found: ${name}`))
|
|
}),
|
|
getFileHandle: vi.fn().mockImplementation((name: string, options: { create?: boolean } = {}) => {
|
|
mockFileSystem.set('currentPath', fullPath)
|
|
|
|
const dirEntries = mockFileSystem.get(fullPath) || new Map()
|
|
const entry = dirEntries.get(name)
|
|
|
|
if (entry && entry.type === 'file') {
|
|
return Promise.resolve(createMockFileHandle(name, entry.content))
|
|
}
|
|
|
|
if (!entry && options.create) {
|
|
return Promise.resolve(createMockFileHandle(name))
|
|
}
|
|
|
|
return Promise.reject(new Error(`File not found: ${name}`))
|
|
}),
|
|
removeEntry: vi.fn().mockImplementation((name: string, options: { recursive?: boolean } = {}) => {
|
|
const dirEntries = mockFileSystem.get(fullPath) || new Map()
|
|
|
|
if (!dirEntries.has(name)) {
|
|
return Promise.reject(new Error(`Entry not found: ${name}`))
|
|
}
|
|
|
|
const entry = dirEntries.get(name)
|
|
|
|
if (entry.type === 'directory' && !options.recursive) {
|
|
const subDirPath = fullPath === '/' ? `/${name}` : `${fullPath}/${name}`
|
|
const subDirEntries = mockFileSystem.get(subDirPath) || new Map()
|
|
|
|
if (subDirEntries.size > 0) {
|
|
return Promise.reject(new Error(`Directory not empty: ${name}`))
|
|
}
|
|
}
|
|
|
|
dirEntries.delete(name)
|
|
|
|
if (entry.type === 'directory') {
|
|
const subDirPath = fullPath === '/' ? `/${name}` : `${fullPath}/${name}`
|
|
mockFileSystem.delete(subDirPath)
|
|
}
|
|
|
|
return Promise.resolve()
|
|
}),
|
|
entries: vi.fn().mockImplementation(function* () {
|
|
const dirEntries = mockFileSystem.get(fullPath) || new Map()
|
|
|
|
for (const [name, entry] of dirEntries.entries()) {
|
|
if (entry.type === 'file') {
|
|
yield [name, createMockFileHandle(name, entry.content)]
|
|
} else {
|
|
yield [name, createMockDirectoryHandle(name, entry.content)]
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Setup OPFS mock environment
|
|
*/
|
|
export function setupOPFSMock() {
|
|
// Clear the mock file system
|
|
mockFileSystem.clear()
|
|
mockFileSystem.set('/', new Map())
|
|
mockFileSystem.set('currentPath', '/')
|
|
|
|
// Create root directory handle
|
|
const rootDirectoryHandle = createMockDirectoryHandle('root')
|
|
|
|
// Mock navigator.storage if it doesn't exist
|
|
if (typeof global.navigator === 'undefined') {
|
|
// @ts-expect-error - Mocking global
|
|
global.navigator = {}
|
|
}
|
|
|
|
// Define storage if it doesn't exist
|
|
if (typeof global.navigator.storage === 'undefined') {
|
|
Object.defineProperty(global.navigator, 'storage', {
|
|
value: {},
|
|
writable: true,
|
|
configurable: true
|
|
})
|
|
}
|
|
|
|
// Mock storage methods
|
|
global.navigator.storage.getDirectory = vi.fn().mockResolvedValue(rootDirectoryHandle)
|
|
global.navigator.storage.persisted = vi.fn().mockResolvedValue(true)
|
|
global.navigator.storage.persist = vi.fn().mockResolvedValue(true)
|
|
global.navigator.storage.estimate = vi.fn().mockImplementation(() => {
|
|
// Calculate total size of all files in the mock file system
|
|
let totalSize = 0
|
|
|
|
for (const [path, entries] of mockFileSystem.entries()) {
|
|
if (path === 'currentPath') continue
|
|
|
|
for (const [_, entry] of entries.entries()) {
|
|
if (entry.type === 'file') {
|
|
totalSize += entry.content.length
|
|
}
|
|
}
|
|
}
|
|
|
|
return Promise.resolve({ usage: totalSize, quota: 10 * 1024 * 1024 }) // 10MB quota
|
|
})
|
|
|
|
return {
|
|
rootDirectoryHandle,
|
|
mockFileSystem,
|
|
reset: () => {
|
|
mockFileSystem.clear()
|
|
mockFileSystem.set('/', new Map())
|
|
mockFileSystem.set('currentPath', '/')
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup OPFS mock environment
|
|
*/
|
|
export function cleanupOPFSMock() {
|
|
// Reset mocks
|
|
vi.restoreAllMocks()
|
|
|
|
// Clear the mock file system
|
|
mockFileSystem.clear()
|
|
} |