backend-test-utils: added directoryMocker.addContent

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2023-09-20 19:12:19 +02:00
parent c6e5eec85c
commit dbad03ac47
2 changed files with 103 additions and 23 deletions
@@ -59,6 +59,66 @@ describe('DirectoryMocker', () => {
});
});
it('should be able to add content', async () => {
await mocker.setContent({
'a.txt': 'a',
b: {},
});
await expect(mocker.getContent()).resolves.toEqual({
'a.txt': 'a',
b: {},
});
await mocker.addContent({
'b.txt': 'b',
b: {
'c.txt': 'c',
},
});
await expect(mocker.getContent()).resolves.toEqual({
'a.txt': 'a',
'b.txt': 'b',
b: {
'c.txt': 'c',
},
});
});
it('should replace existing files', async () => {
await mocker.setContent({
'a.txt': 'a',
});
await mocker.addContent({
'a.txt': 'a2',
});
await expect(mocker.getContent()).resolves.toEqual({
'a.txt': 'a2',
});
});
it('should not override directories', async () => {
await mocker.setContent({
'a.txt': 'a',
b: {},
});
await expect(
mocker.addContent({
'a.txt': {},
}),
).rejects.toThrow('EEXIST');
await expect(
mocker.addContent({
b: 'b',
}),
).rejects.toThrow('EISDIR');
});
describe('cleanup', () => {
let cleanupMocker: DirectoryMocker;
@@ -25,10 +25,16 @@ import {
resolve as resolvePath,
} from 'path';
type MockEntry = {
path: string;
content: Buffer;
};
type MockEntry =
| {
type: 'file';
path: string;
content: Buffer;
}
| {
type: 'dir';
path: string;
};
export type MockDirectory = {
[name in string]: MockDirectory | string | Buffer;
@@ -65,7 +71,7 @@ export class DirectoryMocker {
process.on('beforeExit', mocker.#cleanupSync);
try {
afterAll(mocker.cleanup);
afterAll(mocker.removeContent);
} catch {
/* ignore */
}
@@ -85,14 +91,23 @@ export class DirectoryMocker {
}
async setContent(root: MockDirectory) {
await this.removeContent();
return this.addContent(root);
}
async addContent(root: MockDirectory) {
const entries = this.#transformInput(root);
await this.cleanup();
for (const entry of entries) {
const fullPath = resolveSafeChildPath(this.#root, entry.path.slice(1)); // trim leading slash
for (const { path, content } of entries) {
const fullPath = resolveSafeChildPath(this.#root, path.slice(1)); // trim leading slash
await fs.ensureDir(dirname(fullPath));
await fs.writeFile(fullPath, content);
if (entry.type === 'dir') {
await fs.ensureDir(fullPath);
} else if (entry.type === 'file') {
await fs.ensureDir(dirname(fullPath));
await fs.writeFile(fullPath, entry.content);
}
}
}
@@ -104,27 +119,27 @@ export class DirectoryMocker {
? () => options?.shouldReadAsText
: options?.shouldReadAsText) ??
((path: string) => textextensions.includes(extname(path).slice(1)));
async function read(path: string): Promise<MockDirectory | undefined> {
if (!(await fs.pathExists(path))) {
return undefined;
}
const entries = await fs.readdir(path);
const entries = await fs.readdir(path, { withFileTypes: true });
return Object.fromEntries(
await Promise.all(
entries.map(async entry => {
const fullPath = resolvePath(path, entry);
const stat = await fs.stat(fullPath);
const fullPath = resolvePath(path, entry.name);
if (stat.isDirectory()) {
return [entry, await read(fullPath)];
if (entry.isDirectory()) {
return [entry.name, await read(fullPath)];
}
const content = await fs.readFile(fullPath);
if (shouldReadAsText(fullPath, content)) {
return [entry, content.toString('utf8')];
return [entry.name, content.toString('utf8')];
}
return [entry, content];
return [entry.name, content];
}),
),
);
@@ -133,15 +148,24 @@ export class DirectoryMocker {
return read(this.#root);
}
removeContent = async () => {
await fs.rm(this.#root, { recursive: true, force: true });
};
#transformInput(input: MockDirectory[string]): MockEntry[] {
const entries: MockEntry[] = [];
function traverse(node: MockDirectory[string], path: string) {
if (typeof node === 'string') {
entries.push({ path, content: Buffer.from(node, 'utf8') });
entries.push({
type: 'file',
path,
content: Buffer.from(node, 'utf8'),
});
} else if (node instanceof Buffer) {
entries.push({ path, content: node });
entries.push({ type: 'file', path, content: node });
} else {
entries.push({ type: 'dir', path });
for (const [name, child] of Object.entries(node)) {
traverse(child, `${path}/${name}`);
}
@@ -153,10 +177,6 @@ export class DirectoryMocker {
return entries;
}
cleanup = async () => {
await fs.rm(this.#root, { recursive: true, force: true });
};
#cleanupSync = () => {
fs.rmSync(this.#root, { recursive: true, force: true });
};