UNPKG

@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
"use strict"; 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