@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
JavaScript
;
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