UNPKG

@mdfriday/foundry

Version:

The core engine of MDFriday. Convert Markdown and shortcodes into fully themed static sites – Hugo-style, powered by TypeScript.

281 lines 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const overlayfs_1 = require("../entity/overlayfs"); const overlayoptions_1 = require("../vo/overlayoptions"); const type_1 = require("../type"); // Mock dependencies jest.mock('../vo/overlayoptions'); jest.mock('../entity/overlaydir'); jest.mock('@pkg/log'); describe('OverlayFs Entity', () => { let mockFs1; let mockFs2; let mockOptions; let mockDirsMerger; let overlayFs; beforeEach(() => { mockFs1 = { name: jest.fn().mockReturnValue('MockFs1'), stat: jest.fn(), open: jest.fn(), create: jest.fn(), mkdir: jest.fn(), mkdirAll: jest.fn(), openFile: jest.fn(), remove: jest.fn(), removeAll: jest.fn(), rename: jest.fn(), chmod: jest.fn(), chown: jest.fn(), chtimes: jest.fn(), }; mockFs2 = { name: jest.fn().mockReturnValue('MockFs2'), stat: jest.fn(), open: jest.fn(), create: jest.fn(), mkdir: jest.fn(), mkdirAll: jest.fn(), openFile: jest.fn(), remove: jest.fn(), removeAll: jest.fn(), rename: jest.fn(), chmod: jest.fn(), chown: jest.fn(), chtimes: jest.fn(), }; mockDirsMerger = jest.fn().mockImplementation((a, b) => [...a, ...b]); mockOptions = { fss: [mockFs1, mockFs2], firstWritable: true, dirsMerger: mockDirsMerger, }; overlayFs = new overlayfs_1.OverlayFs(mockOptions); }); afterEach(() => { jest.clearAllMocks(); }); describe('constructor', () => { it('should initialize with overlay options', () => { expect(overlayFs.fss).toEqual([mockFs1, mockFs2]); expect(overlayFs.firstWritable).toBe(true); expect(overlayFs.mergeDirs).toBe(mockDirsMerger); }); it('should create copy of filesystems array', () => { const originalFss = mockOptions.fss; expect(overlayFs.fss).not.toBe(originalFss); expect(overlayFs.fss).toEqual(originalFss); }); }); describe('name', () => { it('should return overlayfs name', () => { expect(overlayFs.name()).toBe('overlayfs'); }); }); describe('FilesystemIterator interface', () => { it('should return filesystem by index', () => { expect(overlayFs.filesystem(0)).toBe(mockFs1); expect(overlayFs.filesystem(1)).toBe(mockFs2); }); it('should return null for out-of-bounds index', () => { expect(overlayFs.filesystem(2)).toBeNull(); expect(overlayFs.filesystem(-1)).toBeNull(); }); it('should return correct number of filesystems', () => { expect(overlayFs.numFilesystems()).toBe(2); }); }); describe('append', () => { it('should append filesystems and return new instance', () => { const mockFs3 = {}; overlayoptions_1.OverlayOptions.mockImplementation((opts) => opts); const result = overlayFs.append(mockFs3); expect(overlayoptions_1.OverlayOptions).toHaveBeenCalledWith({ fss: [mockFs1, mockFs2, mockFs3], firstWritable: true, dirsMerger: mockDirsMerger, }); expect(result).toBeInstanceOf(overlayfs_1.OverlayFs); expect(result).not.toBe(overlayFs); }); it('should append multiple filesystems', () => { const mockFs3 = {}; const mockFs4 = {}; overlayoptions_1.OverlayOptions.mockImplementation((opts) => opts); overlayFs.append(mockFs3, mockFs4); expect(overlayoptions_1.OverlayOptions).toHaveBeenCalledWith({ fss: [mockFs1, mockFs2, mockFs3, mockFs4], firstWritable: true, dirsMerger: mockDirsMerger, }); }); }); describe('withDirsMerger', () => { it('should set new directory merger and return new instance', () => { const newMerger = jest.fn(); overlayoptions_1.OverlayOptions.mockImplementation((opts) => opts); const result = overlayFs.withDirsMerger(newMerger); expect(overlayoptions_1.OverlayOptions).toHaveBeenCalledWith({ fss: [mockFs1, mockFs2], firstWritable: true, dirsMerger: newMerger, }); expect(result).toBeInstanceOf(overlayfs_1.OverlayFs); expect(result).not.toBe(overlayFs); }); }); describe('writeFs', () => { it('should return first filesystem when available', () => { const result = overlayFs.writeFs(); expect(result).toBe(mockFs1); }); it('should throw error when no filesystems available', () => { const emptyOverlayFs = new overlayfs_1.OverlayFs({ fss: [], firstWritable: false, dirsMerger: mockDirsMerger }); expect(() => emptyOverlayFs.writeFs()).toThrow(type_1.ErrNoFilesystems); }); }); describe('stat', () => { it('should stat file from first available filesystem', async () => { const mockFileInfo = { name: jest.fn().mockReturnValue('test.txt'), isDir: jest.fn().mockReturnValue(false), }; mockFs1.stat.mockResolvedValue(mockFileInfo); const result = await overlayFs.stat('test.txt'); expect(mockFs1.stat).toHaveBeenCalledWith('test.txt'); expect(result).toBe(mockFileInfo); }); it('should try next filesystem when first fails', async () => { const mockFileInfo = { name: jest.fn().mockReturnValue('test.txt'), isDir: jest.fn().mockReturnValue(false), }; mockFs1.stat.mockRejectedValue(new Error('ENOENT: no such file or directory')); mockFs2.stat.mockResolvedValue(mockFileInfo); const result = await overlayFs.stat('test.txt'); expect(mockFs1.stat).toHaveBeenCalledWith('test.txt'); expect(mockFs2.stat).toHaveBeenCalledWith('test.txt'); expect(result).toBe(mockFileInfo); }); it('should throw ErrFileNotFound when file not found in any filesystem', async () => { mockFs1.stat.mockRejectedValue(new Error('ENOENT: no such file or directory')); mockFs2.stat.mockRejectedValue(new Error('ENOENT: no such file or directory')); await expect(overlayFs.stat('nonexistent.txt')).rejects.toThrow(type_1.ErrFileNotFound); }); }); describe('open', () => { it('should open file from first available filesystem', async () => { const mockFile = {}; const mockFileInfo = { isDir: jest.fn().mockReturnValue(false), }; mockFs1.stat.mockResolvedValue(mockFileInfo); mockFs1.open.mockResolvedValue(mockFile); const result = await overlayFs.open('test.txt'); expect(mockFs1.stat).toHaveBeenCalledWith('test.txt'); expect(mockFs1.open).toHaveBeenCalledWith('test.txt'); expect(result).toBe(mockFile); }); it('should open directory using OverlayDir when file is directory', async () => { const mockFileInfo = { isDir: jest.fn().mockReturnValue(true), }; const mockOverlayDir = {}; // Mock both filesystems to have the directory so openDir is called mockFs1.stat.mockResolvedValue(mockFileInfo); mockFs2.stat.mockResolvedValue(mockFileInfo); const { openDir } = require('../entity/overlaydir'); openDir.mockResolvedValue(mockOverlayDir); const result = await overlayFs.open('testdir'); expect(mockFs1.stat).toHaveBeenCalledWith('testdir'); expect(openDir).toHaveBeenCalledWith('testdir', mockDirsMerger, expect.any(Function), expect.any(Array)); expect(result).toBe(mockOverlayDir); }); }); describe('write operations', () => { it('should delegate create to first filesystem', async () => { const mockFile = {}; mockFs1.create.mockResolvedValue(mockFile); const result = await overlayFs.create('newfile.txt'); expect(mockFs1.create).toHaveBeenCalledWith('newfile.txt'); expect(result).toBe(mockFile); }); it('should delegate mkdir to first filesystem', async () => { await overlayFs.mkdir('newdir', 0o755); expect(mockFs1.mkdir).toHaveBeenCalledWith('newdir', 0o755); }); it('should delegate mkdirAll to first filesystem', async () => { await overlayFs.mkdirAll('deep/nested/dir', 0o755); expect(mockFs1.mkdirAll).toHaveBeenCalledWith('deep/nested/dir', 0o755); }); it('should delegate openFile to first filesystem', async () => { const mockFile = {}; mockFs1.openFile.mockResolvedValue(mockFile); const result = await overlayFs.openFile('test.txt', 1, 0o644); expect(mockFs1.openFile).toHaveBeenCalledWith('test.txt', 1, 0o644); expect(result).toBe(mockFile); }); it('should delegate remove to first filesystem', async () => { await overlayFs.remove('test.txt'); expect(mockFs1.remove).toHaveBeenCalledWith('test.txt'); }); it('should delegate removeAll to first filesystem', async () => { await overlayFs.removeAll('testdir'); expect(mockFs1.removeAll).toHaveBeenCalledWith('testdir'); }); it('should delegate rename to first filesystem', async () => { await overlayFs.rename('old.txt', 'new.txt'); expect(mockFs1.rename).toHaveBeenCalledWith('old.txt', 'new.txt'); }); it('should delegate chmod to first filesystem', async () => { await overlayFs.chmod('test.txt', 0o644); expect(mockFs1.chmod).toHaveBeenCalledWith('test.txt', 0o644); }); it('should delegate chown to first filesystem', async () => { await overlayFs.chown('test.txt', 1000, 1000); expect(mockFs1.chown).toHaveBeenCalledWith('test.txt', 1000, 1000); }); it('should delegate chtimes to first filesystem', async () => { const atime = new Date('2023-01-01'); const mtime = new Date('2023-01-02'); await overlayFs.chtimes('test.txt', atime, mtime); expect(mockFs1.chtimes).toHaveBeenCalledWith('test.txt', atime, mtime); }); it('should throw error when no filesystems for write operations', async () => { const emptyOverlayFs = new overlayfs_1.OverlayFs({ fss: [], firstWritable: false, dirsMerger: mockDirsMerger }); await expect(emptyOverlayFs.create('test.txt')).rejects.toThrow('filesystem is read-only'); }); }); describe('isNotExistError', () => { it('should identify ENOENT errors', () => { const enoentError = new Error('ENOENT: no such file or directory'); const result = overlayFs.isNotExistError(enoentError); expect(result).toBe(true); }); it('should identify no such file errors', () => { const noSuchFileError = new Error('no such file or directory'); const result = overlayFs.isNotExistError(noSuchFileError); expect(result).toBe(true); }); it('should identify ErrFileNotFound error', () => { const result = overlayFs.isNotExistError(type_1.ErrFileNotFound); expect(result).toBe(true); }); it('should not identify other errors as not exist', () => { const otherError = new Error('Permission denied'); const result = overlayFs.isNotExistError(otherError); expect(result).toBe(false); }); }); describe('factory functions', () => { it('should create OverlayFs with newOverlayFs', () => { const result = (0, overlayfs_1.newOverlayFs)(mockOptions); expect(result).toBeInstanceOf(overlayfs_1.OverlayFs); }); it('should create OverlayFs with createOverlayFs', () => { const result = (0, overlayfs_1.createOverlayFs)(mockOptions); expect(result).toBeInstanceOf(overlayfs_1.OverlayFs); }); }); }); //# sourceMappingURL=entity-overlayfs.test.js.map