feat(tests): add robust mock implementations and expand test coverage for S3 and OPFS storage
- 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.
2025-07-21 13:42:10 -07:00
/ * *
* S3 Compatible Storage Mock for Testing
*
* This module provides a mock implementation of the AWS S3 client
* for testing S3 - based storage in a Node . js environment without
* requiring actual S3 credentials .
* /
import { vi } from 'vitest'
// In-memory storage to simulate S3 bucket
interface S3MockObject {
key : string
body : string
metadata? : Record < string , string >
lastModified : Date
contentLength : number
contentType? : string
}
interface S3MockBucket {
name : string
objects : Map < string , S3MockObject >
}
// Mock S3 storage - use a global variable to ensure persistence between operations
// This is important because the mock client is recreated for each command
const mockS3Storage = new Map < string , S3MockBucket > ( )
/ * *
* Create a mock S3 client
*
* This function creates a mock S3 client that simulates the behavior of the AWS S3 client .
* It ' s important that all operations use the same instance of mockS3Storage to ensure
* that objects are correctly persisted between operations .
* /
export function createMockS3Client() {
// Log the current state of the mock storage
console . log ( ` [MOCK S3] Creating mock S3 client with current storage state: ` )
for ( const [ bucketName , bucket ] of mockS3Storage . entries ( ) ) {
console . log ( ` [MOCK S3] Bucket ${ bucketName } : ${ bucket . objects . size } objects ` )
if ( bucket . objects . size > 0 ) {
console . log ( '[MOCK S3] Objects in bucket:' )
for ( const key of bucket . objects . keys ( ) ) {
console . log ( ` [MOCK S3] - ${ key } ` )
}
}
}
return {
send : vi.fn ( ) . mockImplementation ( ( command ) = > {
// Log the command for debugging
console . log ( ` [MOCK S3] Received S3 command: ${ command . constructor . name } ` , command . input )
// Log the current state of the mock storage before processing the command
console . log ( ` [MOCK S3] Current storage state before processing command: ` )
for ( const [ bucketName , bucket ] of mockS3Storage . entries ( ) ) {
console . log ( ` [MOCK S3] Bucket ${ bucketName } : ${ bucket . objects . size } objects ` )
if ( bucket . objects . size > 0 ) {
console . log ( '[MOCK S3] Objects in bucket:' )
for ( const key of bucket . objects . keys ( ) ) {
console . log ( ` [MOCK S3] - ${ key } ` )
}
}
}
// Handle different command types
let result
if ( command . constructor . name === 'CreateBucketCommand' ) {
result = handleCreateBucket ( command )
} else if ( command . constructor . name === 'HeadBucketCommand' ) {
result = handleHeadBucket ( command )
} else if ( command . constructor . name === 'PutObjectCommand' ) {
result = handlePutObject ( command )
} else if ( command . constructor . name === 'GetObjectCommand' ) {
result = handleGetObject ( command )
} else if ( command . constructor . name === 'DeleteObjectCommand' ) {
result = handleDeleteObject ( command )
} else if ( command . constructor . name === 'ListObjectsV2Command' ) {
result = handleListObjectsV2 ( command )
} else {
console . warn ( ` [MOCK S3] Unhandled S3 command: ${ command . constructor . name } ` )
result = Promise . resolve ( { } )
}
// Log the current state of the mock storage after processing the command
result . then ( ( ) = > {
console . log ( ` [MOCK S3] Storage state after processing command: ` )
for ( const [ bucketName , bucket ] of mockS3Storage . entries ( ) ) {
console . log ( ` [MOCK S3] Bucket ${ bucketName } : ${ bucket . objects . size } objects ` )
if ( bucket . objects . size > 0 ) {
console . log ( '[MOCK S3] Objects in bucket:' )
for ( const key of bucket . objects . keys ( ) ) {
console . log ( ` [MOCK S3] - ${ key } ` )
}
}
}
} ) . catch ( error = > {
console . error ( ` [MOCK S3] Error processing command: ` , error )
} )
return result
} )
}
}
/ * *
* Handle CreateBucketCommand
* /
function handleCreateBucket ( command : any ) {
const { Bucket } = command . input
if ( ! mockS3Storage . has ( Bucket ) ) {
mockS3Storage . set ( Bucket , {
name : Bucket ,
objects : new Map ( )
} )
}
return Promise . resolve ( {
Location : ` / ${ Bucket } `
} )
}
/ * *
* Handle HeadBucketCommand
* /
function handleHeadBucket ( command : any ) {
const { Bucket } = command . input
if ( ! mockS3Storage . has ( Bucket ) ) {
return Promise . reject ( new Error ( ` Bucket not found: ${ Bucket } ` ) )
}
return Promise . resolve ( { } )
}
/ * *
* Handle PutObjectCommand
* /
function handlePutObject ( command : any ) {
const { Bucket , Key , Body , Metadata , ContentType } = command . input
console . log ( ` PutObjectCommand for bucket: ${ Bucket } , key: ${ Key } ` )
// Create bucket if it doesn't exist
if ( ! mockS3Storage . has ( Bucket ) ) {
console . log ( ` Creating bucket: ${ Bucket } ` )
mockS3Storage . set ( Bucket , {
name : Bucket ,
objects : new Map ( )
} )
}
const bucket = mockS3Storage . get ( Bucket ) !
let bodyContent : string
if ( typeof Body === 'string' ) {
bodyContent = Body
} else if ( Body instanceof Uint8Array || Body instanceof Buffer ) {
bodyContent = new TextDecoder ( ) . decode ( Body )
} else if ( Body && typeof Body . toString === 'function' ) {
bodyContent = Body . toString ( )
} else {
bodyContent = JSON . stringify ( Body )
}
// Log the key and body content for debugging
console . log ( ` Storing object with key: ${ Key } ` )
console . log ( ` Body content: ${ bodyContent . substring ( 0 , 50 ) } ${ bodyContent . length > 50 ? '...' : '' } ` )
// Parse the body content if it's JSON to ensure it's valid
try {
if ( ContentType === 'application/json' ) {
const parsedBody = JSON . parse ( bodyContent )
console . log ( ` Parsed JSON body: ` , parsedBody )
2025-08-07 06:09:00 -07:00
// If this is a noun or verb (but NOT metadata), ensure it has an id property
if ( ( Key . includes ( '/nouns/' ) || Key . includes ( '/verbs/' ) ) && ! Key . includes ( '/metadata/' ) ) {
feat(tests): add robust mock implementations and expand test coverage for S3 and OPFS storage
- 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.
2025-07-21 13:42:10 -07:00
if ( ! parsedBody . id ) {
console . error ( ` Warning: Object ${ Key } does not have an id property ` )
// Add id property based on the key name
const id = Key . split ( '/' ) . pop ( ) ? . replace ( '.json' , '' ) || 'unknown'
parsedBody . id = id
console . log ( ` Added id property: ${ id } ` )
bodyContent = JSON . stringify ( parsedBody )
}
}
}
} catch ( error ) {
console . error ( ` Error parsing JSON body for ${ Key } : ` , error )
// Continue with the original body content
}
// Store the object in the bucket
bucket . objects . set ( Key , {
key : Key ,
body : bodyContent ,
metadata : Metadata ,
lastModified : new Date ( ) ,
contentLength : bodyContent.length ,
contentType : ContentType
} )
// Debug: Log all objects in the bucket after adding the new one
console . log ( ` All objects in bucket ${ Bucket } after adding ${ Key } : ` )
for ( const [ key , obj ] of bucket . objects . entries ( ) ) {
console . log ( ` - ${ key } : ${ obj . body . substring ( 0 , 30 ) } ... ` )
}
// Return a success response
const response = {
ETag : ` " ${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) } " `
}
console . log ( ` PutObjectCommand successful for ${ Key } ` )
return Promise . resolve ( response )
}
/ * *
* Handle GetObjectCommand
* /
function handleGetObject ( command : any ) {
const { Bucket , Key } = command . input
console . log ( ` GetObjectCommand for bucket: ${ Bucket } , key: ${ Key } ` )
if ( ! mockS3Storage . has ( Bucket ) ) {
console . log ( ` Bucket ${ Bucket } not found ` )
return Promise . reject ( new Error ( ` Bucket not found: ${ Bucket } ` ) )
}
const bucket = mockS3Storage . get ( Bucket ) !
// Debug: Log all objects in the bucket
console . log ( ` All objects in bucket ${ Bucket } : ` )
for ( const [ key , obj ] of bucket . objects . entries ( ) ) {
console . log ( ` - ${ key } : ${ obj . body . substring ( 0 , 30 ) } ... ` )
}
if ( ! bucket . objects . has ( Key ) ) {
console . log ( ` Object ${ Key } not found in bucket ${ Bucket } ` )
2025-08-09 14:36:54 -07:00
// Create proper NoSuchKey error that matches AWS SDK structure
const error = new Error ( ` NoSuchKey: The specified key does not exist. ` )
error . name = 'NoSuchKey'
return Promise . reject ( error )
feat(tests): add robust mock implementations and expand test coverage for S3 and OPFS storage
- 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.
2025-07-21 13:42:10 -07:00
}
const object = bucket . objects . get ( Key ) !
console . log ( ` Found object ${ Key } in bucket ${ Bucket } ` )
console . log ( ` Object body: ${ object . body . substring ( 0 , 50 ) } ${ object . body . length > 50 ? '...' : '' } ` )
// If this is a JSON object, ensure it has the required properties
let bodyContent = object . body
if ( object . contentType === 'application/json' ) {
try {
const parsedBody = JSON . parse ( bodyContent )
console . log ( ` Parsed JSON body for ${ Key } : ` , parsedBody )
2025-08-07 06:09:00 -07:00
// If this is a noun or verb (but NOT metadata), ensure it has an id property
if ( ( Key . includes ( '/nouns/' ) || Key . includes ( '/verbs/' ) ) && ! Key . includes ( '/metadata/' ) ) {
feat(tests): add robust mock implementations and expand test coverage for S3 and OPFS storage
- 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.
2025-07-21 13:42:10 -07:00
if ( ! parsedBody . id ) {
console . error ( ` Warning: Object ${ Key } does not have an id property ` )
// Add id property based on the key name
const id = Key . split ( '/' ) . pop ( ) ? . replace ( '.json' , '' ) || 'unknown'
parsedBody . id = id
console . log ( ` Added id property: ${ id } ` )
bodyContent = JSON . stringify ( parsedBody )
}
}
} catch ( error ) {
console . error ( ` Error parsing JSON body for ${ Key } : ` , error )
// Continue with the original body
}
}
// Create a response object that matches what the S3 SDK would return
const response = {
Body : {
transformToString : ( ) = > Promise . resolve ( bodyContent ) ,
transformToByteArray : ( ) = > Promise . resolve ( new TextEncoder ( ) . encode ( bodyContent ) )
} ,
Metadata : object.metadata || { } ,
LastModified : object.lastModified ,
ContentLength : bodyContent.length ,
ContentType : object.contentType
}
console . log ( ` Returning response for ${ Key } ` )
return Promise . resolve ( response )
}
/ * *
* Handle DeleteObjectCommand
* /
function handleDeleteObject ( command : any ) {
const { Bucket , Key } = command . input
if ( ! mockS3Storage . has ( Bucket ) ) {
return Promise . reject ( new Error ( ` Bucket not found: ${ Bucket } ` ) )
}
const bucket = mockS3Storage . get ( Bucket ) !
if ( ! bucket . objects . has ( Key ) ) {
return Promise . reject ( new Error ( ` Object not found: ${ Key } ` ) )
}
bucket . objects . delete ( Key )
return Promise . resolve ( { } )
}
/ * *
* Handle ListObjectsV2Command
* /
function handleListObjectsV2 ( command : any ) {
const { Bucket , Prefix , MaxKeys = 1000 , ContinuationToken } = command . input
console . log ( ` ListObjectsV2Command for bucket: ${ Bucket } , prefix: ${ Prefix || 'none' } ` )
if ( ! mockS3Storage . has ( Bucket ) ) {
console . log ( ` Bucket ${ Bucket } not found, returning empty result ` )
// Return empty result instead of rejecting
return Promise . resolve ( {
Contents : [ ] ,
IsTruncated : false ,
KeyCount : 0
} )
}
const bucket = mockS3Storage . get ( Bucket ) !
// Debug: Log all objects in the bucket
console . log ( ` All objects in bucket ${ Bucket } before filtering: ` )
for ( const [ key , obj ] of bucket . objects . entries ( ) ) {
console . log ( ` - ${ key } : ${ obj . body . substring ( 0 , 30 ) } ... ` )
}
// Filter objects by prefix if provided
console . log ( ` [MOCK S3] Filtering objects by prefix: " ${ Prefix || 'none' } " ` )
console . log ( ` [MOCK S3] All keys in bucket before filtering: ` )
for ( const key of bucket . objects . keys ( ) ) {
console . log ( ` [MOCK S3] - ${ key } ` )
}
const filteredObjects = Array . from ( bucket . objects . values ( ) ) . filter ( obj = > {
if ( ! Prefix ) return true
const matches = obj . key . startsWith ( Prefix )
console . log ( ` [MOCK S3] Key: ${ obj . key } , Matches prefix " ${ Prefix } ": ${ matches } ` )
return matches
} )
console . log ( ` Found ${ filteredObjects . length } objects with prefix: ${ Prefix || 'none' } ` )
// Debug: Log filtered objects
console . log ( ` Filtered objects: ` )
for ( const obj of filteredObjects ) {
console . log ( ` - ${ obj . key } : ${ obj . body . substring ( 0 , 30 ) } ... ` )
// Ensure each object has a valid body
try {
if ( obj . contentType === 'application/json' ) {
const parsedBody = JSON . parse ( obj . body )
console . log ( ` Parsed JSON body for ${ obj . key } : ` , parsedBody )
// If this is a noun or verb, ensure it has an id property
if ( obj . key . includes ( '/nouns/' ) || obj . key . includes ( '/verbs/' ) ) {
if ( ! parsedBody . id ) {
console . error ( ` Warning: Object ${ obj . key } does not have an id property ` )
// Add id property based on the key name
const id = obj . key . split ( '/' ) . pop ( ) ? . replace ( '.json' , '' ) || 'unknown'
parsedBody . id = id
console . log ( ` Added id property: ${ id } ` )
obj . body = JSON . stringify ( parsedBody )
obj . contentLength = obj . body . length
}
}
}
} catch ( error ) {
console . error ( ` Error parsing JSON body for ${ obj . key } : ` , error )
// Continue with the original body
}
}
// Handle pagination
const startIndex = ContinuationToken ? parseInt ( ContinuationToken , 10 ) : 0
const endIndex = Math . min ( startIndex + MaxKeys , filteredObjects . length )
const objects = filteredObjects . slice ( startIndex , endIndex )
// Check if there are more objects
const isTruncated = endIndex < filteredObjects . length
const nextContinuationToken = isTruncated ? endIndex . toString ( ) : undefined
// Map objects to the expected format
const contents = objects . map ( obj = > ( {
Key : obj.key ,
LastModified : obj.lastModified ,
Size : obj.contentLength || obj . body . length , // Ensure Size is always set
ETag : ` " ${ Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) } " `
} ) )
console . log ( ` Returning ${ contents . length } objects in response ` )
// Debug: Log the contents being returned
if ( contents . length > 0 ) {
console . log ( ` Contents being returned: ` )
for ( const obj of contents ) {
console . log ( ` - ${ obj . Key } , Size: ${ obj . Size } ` )
}
}
// Always return Contents array, even if empty
return Promise . resolve ( {
Contents : contents ,
IsTruncated : isTruncated ,
NextContinuationToken : nextContinuationToken ,
KeyCount : objects.length
} )
}
/ * *
* Setup S3 mock environment
* /
export function setupS3Mock() {
console . log ( 'Setting up S3 mock environment' )
// Clear the mock S3 storage
mockS3Storage . clear ( )
// Create mock S3 client with enhanced logging
const mockS3Client = {
send : async ( command : any ) = > {
console . log ( ` [MOCK S3] Received command: ${ command . constructor . name } ` )
console . log ( ` [MOCK S3] Command input: ` , command . input )
// Log the current state of the mock storage before processing the command
console . log ( ` [MOCK S3] Current storage state before command: ` )
for ( const [ bucketName , bucket ] of mockS3Storage . entries ( ) ) {
console . log ( ` [MOCK S3] Bucket ${ bucketName } : ${ bucket . objects . size } objects ` )
if ( bucket . objects . size > 0 ) {
console . log ( ` [MOCK S3] Objects in bucket ${ bucketName } : ` )
for ( const [ key , obj ] of bucket . objects . entries ( ) ) {
console . log ( ` [MOCK S3] - ${ key } : ${ obj . body . substring ( 0 , 30 ) } ... ` )
}
}
}
// Process the command using the original implementation
const result = await createMockS3Client ( ) . send ( command )
// Log the result and the state of the mock storage after processing the command
console . log ( ` [MOCK S3] Command result: ` , result )
console . log ( ` [MOCK S3] Storage state after command: ` )
for ( const [ bucketName , bucket ] of mockS3Storage . entries ( ) ) {
console . log ( ` [MOCK S3] Bucket ${ bucketName } : ${ bucket . objects . size } objects ` )
if ( bucket . objects . size > 0 ) {
console . log ( ` [MOCK S3] Objects in bucket ${ bucketName } : ` )
for ( const [ key , obj ] of bucket . objects . entries ( ) ) {
console . log ( ` [MOCK S3] - ${ key } : ${ obj . body . substring ( 0 , 30 ) } ... ` )
}
}
}
return result
}
}
// Create a test bucket to ensure it exists
const testBucket = 'test-bucket'
if ( ! mockS3Storage . has ( testBucket ) ) {
console . log ( ` Creating test bucket: ${ testBucket } ` )
mockS3Storage . set ( testBucket , {
name : testBucket ,
objects : new Map ( )
} )
}
console . log ( 'S3 mock environment setup complete' )
return {
mockS3Client ,
mockS3Storage ,
reset : ( ) = > {
console . log ( '[MOCK S3] Resetting S3 mock storage' )
// Log the state of the mock storage before reset
console . log ( '[MOCK S3] Mock storage before reset:' )
for ( const [ bucketName , bucket ] of mockS3Storage . entries ( ) ) {
console . log ( ` [MOCK S3] Bucket ${ bucketName } : ${ bucket . objects . size } objects ` )
if ( bucket . objects . size > 0 ) {
console . log ( '[MOCK S3] Objects in bucket:' )
for ( const key of bucket . objects . keys ( ) ) {
console . log ( ` [MOCK S3] - ${ key } ` )
}
}
}
// Clear the mock S3 storage completely
mockS3Storage . clear ( )
// Re-create the test bucket with an empty objects map
console . log ( ` [MOCK S3] Re-creating test bucket: ${ testBucket } ` )
mockS3Storage . set ( testBucket , {
name : testBucket ,
objects : new Map ( )
} )
// Log the state of the mock storage after reset
console . log ( ` [MOCK S3] Mock storage after reset: ${ mockS3Storage . size } buckets ` )
for ( const [ bucketName , bucket ] of mockS3Storage . entries ( ) ) {
console . log ( ` [MOCK S3] Bucket ${ bucketName } : ${ bucket . objects . size } objects ` )
}
// Ensure the mock client is using the latest storage state
console . log ( '[MOCK S3] Ensuring mock client is using the latest storage state' )
}
}
}
/ * *
* Cleanup S3 mock environment
* /
export function cleanupS3Mock() {
console . log ( 'Cleaning up S3 mock environment' )
// Reset mocks
vi . restoreAllMocks ( )
// Clear the mock S3 storage
mockS3Storage . clear ( )
console . log ( 'S3 mock environment cleanup complete' )
}
/ * *
* Create mock S3 command classes
* /
export const S3Commands = {
CreateBucketCommand : class CreateBucketCommand {
input : any
constructor ( input : any ) {
this . input = input
}
} ,
HeadBucketCommand : class HeadBucketCommand {
input : any
constructor ( input : any ) {
this . input = input
}
} ,
PutObjectCommand : class PutObjectCommand {
input : any
constructor ( input : any ) {
this . input = input
}
} ,
GetObjectCommand : class GetObjectCommand {
input : any
constructor ( input : any ) {
this . input = input
}
} ,
DeleteObjectCommand : class DeleteObjectCommand {
input : any
constructor ( input : any ) {
this . input = input
}
} ,
ListObjectsV2Command : class ListObjectsV2Command {
input : any
constructor ( input : any ) {
this . input = input
}
}
}