@ordojs/core
Version:
Core compiler and runtime for OrdoJS framework
290 lines • 9.17 kB
JavaScript
/**
* @fileoverview Server Function Mocking Utilities
*/
/**
* Utilities for mocking server functions in tests
*/
export class ServerMockUtils {
mocks = new Map();
originalFetch;
rpcCallHistory = [];
constructor() {
this.setupFetchMock();
}
/**
* Mock a server function
*/
mockServerFunction(functionName, implementation, config = {}) {
// Ensure function name is in config
const configWithName = { ...config, functionName };
const mockFn = this.createMockFunction(implementation, configWithName);
const mockServerFunction = {
name: functionName,
mock: mockFn,
originalImplementation: implementation,
callHistory: []
};
this.mocks.set(functionName, mockServerFunction);
return mockServerFunction;
}
/**
* Create a mock function with configuration
*/
createMockFunction(implementation, config) {
let callCount = 0;
const functionName = config.functionName || 'unknown';
return async (...args) => {
callCount++;
const timestamp = Date.now();
// Check call count limit
if (config.callCount && callCount > config.callCount) {
throw new Error(`Mock function called more than ${config.callCount} times`);
}
// Add delay if specified
if (config.delay) {
await new Promise(resolve => setTimeout(resolve, config.delay));
}
// Simulate failure if configured
if (config.shouldFail) {
const error = new Error(config.failureMessage || 'Mock server function failed');
this.recordCall(functionName, args, timestamp, undefined, error);
throw error;
}
try {
const result = await implementation(...args);
this.recordCall(functionName, args, timestamp, result);
return result;
}
catch (error) {
this.recordCall(functionName, args, timestamp, undefined, error);
throw error;
}
};
}
/**
* Record RPC call for testing
*/
recordCall(functionName, args, timestamp, result, error) {
this.rpcCallHistory.push({
functionName,
args,
timestamp,
result,
error
});
// Also record in the specific mock's history
const mock = this.mocks.get(functionName);
if (mock) {
mock.callHistory.push({
args,
timestamp,
result,
error
});
}
}
/**
* Set up fetch mock to intercept RPC calls
*/
setupFetchMock() {
if (typeof global !== 'undefined' && global.fetch) {
this.originalFetch = global.fetch;
}
const mockFetch = async (url, options) => {
const urlString = url.toString();
// Check if this is an OrdoJS RPC call
const rpcMatch = urlString.match(/\/ordojs-rpc\/(.+)$/);
if (rpcMatch && options?.method === 'POST') {
const functionName = rpcMatch[1];
const mock = this.mocks.get(functionName);
if (mock) {
try {
const body = options.body ? JSON.parse(options.body) : [];
const result = await mock.mock(...body);
return new Response(JSON.stringify(result), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
}
catch (error) {
return new Response(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
}
// Fall back to original fetch if not an RPC call or no mock found
if (this.originalFetch) {
return this.originalFetch(url, options);
}
throw new Error('Fetch not available and no mock found');
};
global.fetch = mockFetch;
}
/**
* Get mock for a specific function
*/
getMock(functionName) {
return this.mocks.get(functionName);
}
/**
* Check if a function was called
*/
wasCalled(functionName) {
const mock = this.mocks.get(functionName);
return mock ? mock.callHistory.length > 0 : false;
}
/**
* Get call count for a function
*/
getCallCount(functionName) {
const mock = this.mocks.get(functionName);
return mock ? mock.callHistory.length : 0;
}
/**
* Get call arguments for a specific call
*/
getCallArgs(functionName, callIndex = 0) {
const mock = this.mocks.get(functionName);
return mock?.callHistory[callIndex]?.args;
}
/**
* Get last call arguments
*/
getLastCallArgs(functionName) {
const mock = this.mocks.get(functionName);
if (!mock || mock.callHistory.length === 0)
return undefined;
return mock.callHistory[mock.callHistory.length - 1].args;
}
/**
* Get all call history for a function
*/
getCallHistory(functionName) {
const mock = this.mocks.get(functionName);
return mock ? [...mock.callHistory] : [];
}
/**
* Get complete RPC call history
*/
getRPCCallHistory() {
return [...this.rpcCallHistory];
}
/**
* Clear mock for a specific function
*/
clearMock(functionName) {
const mock = this.mocks.get(functionName);
if (mock) {
mock.callHistory = [];
}
}
/**
* Clear all mocks
*/
clearAllMocks() {
for (const mock of this.mocks.values()) {
mock.callHistory = [];
}
this.rpcCallHistory = [];
}
/**
* Remove mock for a specific function
*/
removeMock(functionName) {
this.mocks.delete(functionName);
}
/**
* Remove all mocks
*/
removeAllMocks() {
this.mocks.clear();
this.rpcCallHistory = [];
}
/**
* Restore original fetch
*/
restoreFetch() {
if (this.originalFetch) {
global.fetch = this.originalFetch;
this.originalFetch = undefined;
}
}
/**
* Create a mock server function with specific behavior
*/
createMockWithBehavior(config) {
return this.mockServerFunction(config.functionName, config.implementation, config);
}
/**
* Verify function was called with specific arguments
*/
verifyCalledWith(functionName, expectedArgs) {
const mock = this.mocks.get(functionName);
if (!mock)
return false;
return mock.callHistory.some(call => this.deepEqual(call.args, expectedArgs));
}
/**
* Verify function was called exactly n times
*/
verifyCallCount(functionName, expectedCount) {
return this.getCallCount(functionName) === expectedCount;
}
/**
* Verify function was never called
*/
verifyNeverCalled(functionName) {
return this.getCallCount(functionName) === 0;
}
/**
* Deep equality check for arguments
*/
deepEqual(a, b) {
if (a === b)
return true;
if (a == null || b == null)
return a === b;
if (typeof a !== typeof b)
return false;
if (typeof a === 'object') {
if (Array.isArray(a) !== Array.isArray(b))
return false;
if (Array.isArray(a)) {
if (a.length !== b.length)
return false;
for (let i = 0; i < a.length; i++) {
if (!this.deepEqual(a[i], b[i]))
return false;
}
return true;
}
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length)
return false;
for (const key of keysA) {
if (!keysB.includes(key))
return false;
if (!this.deepEqual(a[key], b[key]))
return false;
}
return true;
}
return false;
}
/**
* Create a spy that tracks calls without changing behavior
*/
spyOnServerFunction(functionName, originalImplementation) {
return this.mockServerFunction(functionName, originalImplementation);
}
/**
* Mock multiple server functions at once
*/
mockMultipleServerFunctions(configs) {
return configs.map(config => this.createMockWithBehavior(config));
}
}
//# sourceMappingURL=server-mock.js.map