@mdfriday/foundry
Version:
The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.
356 lines • 15.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const cache_1 = require("./cache");
describe('ConcurrentCache', () => {
let cache;
beforeEach(() => {
cache = new cache_1.ConcurrentCache();
});
afterEach(() => {
cache.clear();
});
describe('基本功能', () => {
test('应该能够设置和获取值', () => {
cache.set('key1', 'value1');
expect(cache.get('key1')).toBe('value1');
expect(cache.has('key1')).toBe(true);
expect(cache.size()).toBe(1);
});
test('应该能够删除值', () => {
cache.set('key1', 'value1');
expect(cache.delete('key1')).toBe(true);
expect(cache.has('key1')).toBe(false);
expect(cache.size()).toBe(0);
});
test('应该能够清空缓存', () => {
cache.set('key1', 'value1');
cache.set('key2', 'value2');
cache.clear();
expect(cache.size()).toBe(0);
expect(cache.pendingCount()).toBe(0);
});
test('应该返回正确的缓存统计信息', () => {
cache.set('key1', 'value1');
cache.set('key2', 'value2');
const stats = cache.getStats();
expect(stats.size).toBe(2);
expect(stats.pendingCount).toBe(0);
expect(stats.keys).toEqual(['key1', 'key2']);
expect(stats.pendingKeys).toEqual([]);
});
});
describe('getOrCreate 功能', () => {
test('应该创建新值并缓存', async () => {
const creator = jest.fn().mockResolvedValue('created-value');
const result = await cache.getOrCreate('key1', creator);
expect(result).toBe('created-value');
expect(creator).toHaveBeenCalledTimes(1);
expect(creator).toHaveBeenCalledWith('key1');
expect(cache.get('key1')).toBe('created-value');
});
test('应该返回已缓存的值而不重新创建', async () => {
const creator = jest.fn().mockResolvedValue('created-value');
cache.set('key1', 'existing-value');
const result = await cache.getOrCreate('key1', creator);
expect(result).toBe('existing-value');
expect(creator).not.toHaveBeenCalled();
});
test('应该处理创建函数抛出的错误', async () => {
const error = new Error('Creation failed');
const creator = jest.fn().mockRejectedValue(error);
await expect(cache.getOrCreate('key1', creator)).rejects.toThrow('Cache creation failed for key key1: Creation failed');
expect(cache.get('key1')).toBeUndefined();
expect(cache.pendingCount()).toBe(0);
});
});
describe('并发安全性', () => {
test('多个并发请求应该只创建一次', async () => {
const creator = jest.fn().mockImplementation((key) => {
return new Promise(resolve => {
setTimeout(() => resolve(`created-${key}`), 100);
});
});
// 同时发起多个相同key的请求
const promises = [
cache.getOrCreate('key1', creator),
cache.getOrCreate('key1', creator),
cache.getOrCreate('key1', creator),
cache.getOrCreate('key1', creator),
cache.getOrCreate('key1', creator)
];
const results = await Promise.all(promises);
// 所有结果应该相同
results.forEach(result => {
expect(result).toBe('created-key1');
});
// 创建函数只应该被调用一次
expect(creator).toHaveBeenCalledTimes(1);
expect(cache.get('key1')).toBe('created-key1');
expect(cache.pendingCount()).toBe(0);
});
test('不同key的并发请求应该独立处理', async () => {
const creator = jest.fn().mockImplementation((key) => {
return new Promise(resolve => {
setTimeout(() => resolve(`created-${key}`), 50);
});
});
const promises = [
cache.getOrCreate('key1', creator),
cache.getOrCreate('key2', creator),
cache.getOrCreate('key3', creator)
];
const results = await Promise.all(promises);
expect(results).toEqual(['created-key1', 'created-key2', 'created-key3']);
expect(creator).toHaveBeenCalledTimes(3);
expect(cache.size()).toBe(3);
});
test('应该正确跟踪pending状态', async () => {
const creator = jest.fn().mockImplementation(() => {
return new Promise(resolve => {
setTimeout(() => resolve('created-value'), 100);
});
});
const promise = cache.getOrCreate('key1', creator);
// 在创建过程中检查pending状态
expect(cache.isPending('key1')).toBe(true);
expect(cache.pendingCount()).toBe(1);
expect(cache.getStats().pendingKeys).toEqual(['key1']);
await promise;
// 创建完成后pending状态应该清除
expect(cache.isPending('key1')).toBe(false);
expect(cache.pendingCount()).toBe(0);
});
});
describe('错误处理', () => {
test('应该处理创建函数抛出的错误', async () => {
const error = new Error('Creation failed');
const creator = jest.fn().mockRejectedValue(error);
await expect(cache.getOrCreate('key1', creator)).rejects.toThrow('Cache creation failed for key key1: Creation failed');
expect(cache.get('key1')).toBeUndefined();
expect(cache.pendingCount()).toBe(0);
});
test('应该正确清理失败的pending promises', async () => {
const creator = jest.fn().mockRejectedValue(new Error('Creation failed'));
try {
await cache.getOrCreate('key1', creator);
}
catch (error) {
// 预期的错误
}
expect(cache.pendingCount()).toBe(0);
expect(cache.isPending('key1')).toBe(false);
expect(cache.get('key1')).toBeUndefined();
});
test('多个并发请求在创建失败时都应该收到错误', async () => {
const creator = jest.fn().mockRejectedValue(new Error('Creation failed'));
const promises = [
cache.getOrCreate('key1', creator),
cache.getOrCreate('key1', creator),
cache.getOrCreate('key1', creator)
];
// 所有请求都应该失败
await expect(Promise.allSettled(promises)).resolves.toEqual([
{ status: 'rejected', reason: expect.any(Error) },
{ status: 'rejected', reason: expect.any(Error) },
{ status: 'rejected', reason: expect.any(Error) }
]);
// 创建函数只应该被调用一次
expect(creator).toHaveBeenCalledTimes(1);
expect(cache.pendingCount()).toBe(0);
});
});
describe('批量预加载', () => {
test('应该能够批量预加载数据', async () => {
const creator1 = jest.fn().mockResolvedValue('value1');
const creator2 = jest.fn().mockResolvedValue('value2');
const creator3 = jest.fn().mockResolvedValue('value3');
await cache.preload([
{ key: 'key1', create: creator1 },
{ key: 'key2', create: creator2 },
{ key: 'key3', create: creator3 }
]);
expect(cache.get('key1')).toBe('value1');
expect(cache.get('key2')).toBe('value2');
expect(cache.get('key3')).toBe('value3');
expect(cache.size()).toBe(3);
});
test('应该跳过已存在的缓存项', async () => {
cache.set('key1', 'existing-value');
const creator1 = jest.fn().mockResolvedValue('new-value1');
const creator2 = jest.fn().mockResolvedValue('new-value2');
await cache.preload([
{ key: 'key1', create: creator1 },
{ key: 'key2', create: creator2 }
]);
expect(cache.get('key1')).toBe('existing-value');
expect(cache.get('key2')).toBe('new-value2');
expect(creator1).not.toHaveBeenCalled();
expect(creator2).toHaveBeenCalledTimes(1);
});
test('应该处理预加载过程中的部分失败', async () => {
const creator1 = jest.fn().mockResolvedValue('value1');
const creator2 = jest.fn().mockRejectedValue(new Error('Failed'));
const creator3 = jest.fn().mockResolvedValue('value3');
await cache.preload([
{ key: 'key1', create: creator1 },
{ key: 'key2', create: creator2 },
{ key: 'key3', create: creator3 }
]);
expect(cache.get('key1')).toBe('value1');
expect(cache.get('key2')).toBeUndefined(); // 失败的项不会被缓存
expect(cache.get('key3')).toBe('value3');
expect(cache.size()).toBe(2);
});
});
describe('性能和内存管理', () => {
test('应该正确处理大量并发请求', async () => {
const creator = jest.fn().mockImplementation((key) => {
return new Promise(resolve => {
setTimeout(() => resolve(`created-${key}`), Math.random() * 50);
});
});
const keys = Array.from({ length: 100 }, (_, i) => `key-${i}`);
const promises = keys.flatMap(key => [
cache.getOrCreate(key, creator),
cache.getOrCreate(key, creator),
cache.getOrCreate(key, creator)
]);
const results = await Promise.all(promises);
// 验证结果正确性
for (let i = 0; i < keys.length; i++) {
const expectedValue = `created-key-${i}`;
expect(results[i * 3]).toBe(expectedValue);
expect(results[i * 3 + 1]).toBe(expectedValue);
expect(results[i * 3 + 2]).toBe(expectedValue);
}
// 每个key的creator只应该被调用一次
expect(creator).toHaveBeenCalledTimes(100);
expect(cache.size()).toBe(100);
expect(cache.pendingCount()).toBe(0);
}, 10000);
test('delete操作应该同时清理pending promises', async () => {
const creator = jest.fn().mockImplementation(() => {
return new Promise(resolve => {
setTimeout(() => resolve('created-value'), 100);
});
});
const promise = cache.getOrCreate('key1', creator);
expect(cache.isPending('key1')).toBe(true);
cache.delete('key1');
expect(cache.isPending('key1')).toBe(false);
expect(cache.pendingCount()).toBe(0);
// 原始promise仍然会完成,但不会影响缓存
await promise;
expect(cache.get('key1')).toBeUndefined();
});
});
});
describe('createCache 工厂函数', () => {
test('应该创建带有默认选项的缓存实例', () => {
const cache = (0, cache_1.createCache)();
expect(cache).toBeInstanceOf(cache_1.ConcurrentCache);
});
});
describe('globalBuildCache', () => {
afterEach(() => {
cache_1.globalBuildCache.clear();
});
test('应该是一个可用的全局缓存实例', async () => {
const creator = jest.fn().mockResolvedValue('global-value');
const result = await cache_1.globalBuildCache.getOrCreate('global-key', creator);
expect(result).toBe('global-value');
expect(cache_1.globalBuildCache.get('global-key')).toBe('global-value');
});
test('应该在多个测试间保持独立', async () => {
cache_1.globalBuildCache.set('test-key', 'test-value');
expect(cache_1.globalBuildCache.get('test-key')).toBe('test-value');
});
});
describe('真实场景测试', () => {
test('站点构建场景:多个页面共享数据', async () => {
const cache = new cache_1.ConcurrentCache();
// 模拟配置文件读取
const loadConfig = jest.fn().mockImplementation((configPath) => {
return new Promise(resolve => {
setTimeout(() => resolve({
title: 'My Site',
baseUrl: 'https://example.com',
path: configPath
}), 50);
});
});
// 模拟模板编译
const compileTemplate = jest.fn().mockImplementation((templatePath) => {
return new Promise(resolve => {
setTimeout(() => resolve({
render: (data) => `<html><title>${data.title}</title></html>`,
path: templatePath
}), 30);
});
});
// 模拟多个页面并发构建
const buildPage = async (pageId) => {
const [config, template] = await Promise.all([
cache.getOrCreate('config.toml', loadConfig),
cache.getOrCreate('layout.html', compileTemplate)
]);
return {
pageId,
html: template.render(config),
config,
template
};
};
// 并发构建多个页面
const pages = await Promise.all([
buildPage('page1'),
buildPage('page2'),
buildPage('page3'),
buildPage('page4'),
buildPage('page5')
]);
// 验证结果
expect(pages).toHaveLength(5);
pages.forEach(page => {
expect(page.html).toBe('<html><title>My Site</title></html>');
expect(page.config.title).toBe('My Site');
});
// 验证共享资源只被创建一次
expect(loadConfig).toHaveBeenCalledTimes(1);
expect(compileTemplate).toHaveBeenCalledTimes(1);
expect(cache.size()).toBe(2);
});
test('错误恢复场景:网络请求失败后重试', async () => {
const cache = new cache_1.ConcurrentCache();
let attempts = 0;
const unreliableCreator = jest.fn().mockImplementation(() => {
attempts++;
if (attempts <= 2) {
return Promise.reject(new Error(`Network error (attempt ${attempts})`));
}
return Promise.resolve('success-data');
});
// 第一个请求失败
try {
await cache.getOrCreate('api-data', unreliableCreator);
fail('Expected first request to fail');
}
catch (error) {
// 预期的错误
}
// 第二个请求也失败
try {
await cache.getOrCreate('api-data', unreliableCreator);
fail('Expected second request to fail');
}
catch (error) {
// 预期的错误
}
// 第三个请求成功
const result = await cache.getOrCreate('api-data', unreliableCreator);
expect(result).toBe('success-data');
expect(cache.get('api-data')).toBe('success-data');
expect(attempts).toBe(3);
});
});
//# sourceMappingURL=cache.test.js.map