filecoin-pin
Version:
Bridge IPFS content to Filecoin Onchain Cloud using familiar tools
269 lines (227 loc) • 7.84 kB
text/typescript
/**
* Unit tests for calculateActualStorage
*
* Tests abort handling, timeout behavior, and basic calculation correctness.
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { calculateActualStorage } from '../../core/data-set/calculate-actual-storage.js'
import type { DataSetSummary } from '../../core/data-set/types.js'
// Mock the dependencies
const {
mockSynapse,
mockCreateStorageContext,
mockGetDataSetPieces,
defaultCreateStorageContext,
defaultGetDataSetPieces,
state,
} = vi.hoisted(() => {
const state = {
pieces: [] as Array<{ pieceId: number; pieceCid: string; size?: number }>,
}
const mockGetAddress = vi.fn(async () => '0xtest-address')
const defaultGetDataSetPieces = async (_synapse: any, _context: any, _options?: any) => {
if (_options?.signal?.aborted) {
const error = new Error('This operation was aborted')
error.name = 'AbortError'
throw error
}
const pieces = state.pieces.map((p) => ({
pieceId: p.pieceId,
pieceCid: p.pieceCid,
size: p.size ?? undefined,
}))
const totalSizeBytes = pieces.reduce((sum, p) => sum + BigInt(p.size ?? 0), 0n)
return {
pieces,
dataSetId: _context?.dataSetId ?? 1,
totalSizeBytes,
warnings: [],
}
}
const mockGetDataSetPieces = vi.fn(defaultGetDataSetPieces)
const defaultCreateStorageContext = async (_synapse: any, dataSetId: number) => ({
storage: { dataSetId },
providerInfo: { id: 1 },
})
const mockCreateStorageContext = vi.fn(defaultCreateStorageContext)
const mockSynapse = {
getClient: () => ({ getAddress: mockGetAddress }),
}
return {
mockSynapse,
mockCreateStorageContext,
mockGetDataSetPieces,
defaultCreateStorageContext,
defaultGetDataSetPieces,
state,
}
})
// Mock the imports
vi.mock('../../core/synapse/storage-context-helper.js', () => ({
createStorageContextFromDataSetId: mockCreateStorageContext,
}))
vi.mock('../../core/data-set/get-data-set-pieces.js', () => ({
getDataSetPieces: mockGetDataSetPieces,
}))
describe('calculateActualStorage', () => {
beforeEach(() => {
vi.resetAllMocks()
state.pieces = []
mockCreateStorageContext.mockImplementation(defaultCreateStorageContext)
mockGetDataSetPieces.mockImplementation(defaultGetDataSetPieces)
})
describe('basic calculation', () => {
it('should calculate total storage from multiple data sets', async () => {
// Setup: 2 data sets with different piece sizes
const dataSets: DataSetSummary[] = [
{
dataSetId: 1,
providerId: 1,
serviceProvider: '0xprovider1',
isLive: true,
} as DataSetSummary,
{
dataSetId: 2,
providerId: 1,
serviceProvider: '0xprovider1',
isLive: true,
} as DataSetSummary,
]
const oneGiB = 1024n * 1024n * 1024n
// pieces apply to both data sets
state.pieces = [
{ pieceId: 1, pieceCid: 'bafy1', size: Number(oneGiB) },
{ pieceId: 2, pieceCid: 'bafy2', size: Number(oneGiB) },
]
const result = await calculateActualStorage(mockSynapse as any, dataSets)
expect(result.dataSetCount).toBe(2)
expect(result.dataSetsProcessed).toBe(2)
expect(result.totalBytes).toBe(oneGiB * 2n * 2n) // 2 pieces × 2 datasets
expect(result.pieceCount).toBe(4)
expect(result.timedOut).toBeFalsy()
expect(result.warnings).toHaveLength(0)
})
it('should handle empty data sets', async () => {
const result = await calculateActualStorage(mockSynapse as any, [])
expect(result.dataSetCount).toBe(0)
expect(result.dataSetsProcessed).toBe(0)
expect(result.totalBytes).toBe(0n)
expect(result.pieceCount).toBe(0)
expect(result.timedOut).toBeFalsy()
})
it('should handle data sets with no pieces', async () => {
const dataSets: DataSetSummary[] = [
{
dataSetId: 1,
providerId: 1,
serviceProvider: '0xprovider1',
isLive: true,
} as DataSetSummary,
]
state.pieces = []
const result = await calculateActualStorage(mockSynapse as any, dataSets)
expect(result.dataSetCount).toBe(1)
expect(result.dataSetsProcessed).toBe(1)
expect(result.totalBytes).toBe(0n)
expect(result.pieceCount).toBe(0)
})
})
describe('abort handling', () => {
it('should handle immediate abort', async () => {
const dataSets: DataSetSummary[] = [
{
dataSetId: 1,
providerId: 1,
serviceProvider: '0xprovider1',
isLive: true,
} as DataSetSummary,
]
// Create already-aborted signal
const controller = new AbortController()
controller.abort()
const result = await calculateActualStorage(mockSynapse as any, dataSets, {
signal: controller.signal,
})
expect(result.timedOut).toBe(true)
expect(result.dataSetsProcessed).toBe(0)
expect(result.warnings.some((w) => w.code === 'CALCULATION_ABORTED')).toBe(true)
})
it('should return partial results on abort', async () => {
const dataSets: DataSetSummary[] = [
{
dataSetId: 1,
providerId: 1,
serviceProvider: '0xprovider1',
isLive: true,
} as DataSetSummary,
{
dataSetId: 2,
providerId: 2,
serviceProvider: '0xprovider2',
isLive: true,
} as DataSetSummary,
]
const controller = new AbortController()
// Allow first dataset to complete
let callCount = 0
mockGetDataSetPieces.mockImplementation(async (_synapse: any, _context: any, _options?: any) => {
callCount++
if (callCount === 1) {
return {
pieces: [{ pieceId: 1, pieceCid: 'bafy1', size: 1024 }],
dataSetId: 1,
totalSizeBytes: 1024n,
warnings: [],
}
}
controller.abort()
const error = new Error('This operation was aborted')
error.name = 'AbortError'
throw error
})
const result = await calculateActualStorage(mockSynapse as any, dataSets, {
signal: controller.signal,
})
expect(result.timedOut).toBe(true)
expect(result.totalBytes).toBe(1024n) // Partial result from first dataset
expect(result.dataSetsProcessed).toBe(1)
expect(result.pieceCount).toBe(1)
expect(result.warnings.some((w) => w.code === 'CALCULATION_ABORTED')).toBe(true)
})
})
describe('error handling', () => {
it('should continue processing other datasets when one fails', async () => {
const dataSets: DataSetSummary[] = [
{
dataSetId: 1,
providerId: 1,
serviceProvider: '0xprovider1',
isLive: true,
} as DataSetSummary,
{
dataSetId: 2,
providerId: 2,
serviceProvider: '0xprovider2',
isLive: true,
} as DataSetSummary,
]
let callCount = 0
mockGetDataSetPieces.mockImplementation(async (_synapse: any, _context: any, _options?: any) => {
callCount++
if (callCount === 1) {
throw new Error('Dataset query failed')
}
return {
pieces: [{ pieceId: 1, pieceCid: 'bafy1', size: 1024 }],
dataSetId: 2,
totalSizeBytes: 1024n,
warnings: [],
}
})
const result = await calculateActualStorage(mockSynapse as any, dataSets)
expect(result.dataSetsProcessed).toBe(1) // Only second dataset succeeded
expect(result.totalBytes).toBe(1024n)
expect(result.warnings.some((w) => w.code === 'DATA_SET_QUERY_FAILED')).toBe(true)
})
})
})