1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

feat: Introduce Feature for file-system operations

Co-authored-by: Janne Savolainen <janne.savolainen@live.fi>
Signed-off-by: Iku-turso <mikko.aspiala@gmail.com>
This commit is contained in:
Iku-turso 2023-05-16 15:43:53 +03:00
parent 5101101da2
commit d56bd1bd69
30 changed files with 974 additions and 0 deletions

View File

@ -0,0 +1,6 @@
{
"extends": "@k8slens/eslint-config/eslint",
"parserOptions": {
"project": "./tsconfig.json"
}
}

View File

@ -0,0 +1 @@
"@k8slens/eslint-config/prettier"

View File

@ -0,0 +1,21 @@
export type { DeleteFile } from "./src/delete-file/delete-file.injectable";
export type { PathExists } from "./src/path-exists/path-exists.injectable";
export type { ReadFile } from "./src/read-file/read-file.injectable";
export type { ReadJsonFile } from "./src/read-json-file/read-json-file.injectable";
export type { ReadYamlFile } from "./src/read-yaml-file/read-yaml-file.injectable";
export type { WriteFile } from "./src/write-file/write-file.injectable";
export type { WriteJsonFile } from "./src/write-json-file/write-json-file.injectable";
export type { WriteYamlFile } from "./src/write-yaml-file/write-yaml-file.injectable";
export { deleteFileInjectionToken } from "./src/delete-file/delete-file.injectable";
export { pathExistsInjectionToken } from "./src/path-exists/path-exists.injectable";
export { readFileInjectionToken } from "./src/read-file/read-file.injectable";
export { readJsonFileInjectionToken } from "./src/read-json-file/read-json-file.injectable";
export { readYamlFileInjectionToken } from "./src/read-yaml-file/read-yaml-file.injectable";
export { writeFileInjectionToken } from "./src/write-file/write-file.injectable";
export { writeJsonFileInjectionToken } from "./src/write-json-file/write-json-file.injectable";
export { writeYamlFileInjectionToken } from "./src/write-yaml-file/write-yaml-file.injectable";
export { fileSystemFeature } from "./src/feature";
export { testUtils } from "./src/test-utils";

View File

@ -0,0 +1 @@
module.exports = require("@k8slens/jest").monorepoPackageConfig(__dirname).configForNode;

View File

@ -0,0 +1,43 @@
{
"name": "@k8slens/file-system",
"private": false,
"version": "0.1.0",
"description": "TBD",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/lensapp/lens.git"
},
"type": "commonjs",
"author": {
"name": "OpenLens Authors",
"email": "info@k8slens.dev"
},
"license": "MIT",
"homepage": "https://github.com/lensapp/lens",
"scripts": {
"build": "lens-webpack-build",
"clean": "rimraf dist/",
"test:unit": "jest --coverage --runInBand",
"lint": "lens-lint",
"lint:fix": "lens-lint --fix"
},
"peerDependencies": {
"@k8slens/feature-core": "^6.5.0-alpha.5",
"@ogre-tools/fp": "^16.1.0",
"@ogre-tools/injectable": "^16.1.0",
"@ogre-tools/injectable-extension-for-auto-registration": "^16.1.0",
"fs-extra": "^10.1.0",
"js-yaml": "^4.1.0",
"lodash": "^4.17.15"
},
"devDependencies": {
"@k8slens/eslint-config": "^6.5.0-alpha.3",
"@k8slens/react-testing-library-discovery": "^1.0.0-alpha.4",
"@k8slens/webpack": "^6.5.0-alpha.6"
}
}

View File

@ -0,0 +1,28 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import fsInjectable from "../fs/fs.injectable";
import type { AsyncCallSuccess } from "@lensapp/utils";
import { getSuccess } from "@lensapp/utils";
export type DeleteFile = (filePath: string) => AsyncCallSuccess<void>;
export const deleteFileInjectionToken = getInjectionToken<DeleteFile>({
id: "delete-file-injection-token",
});
const deleteFileInjectable = getInjectable({
id: "delete-file",
instantiate: (di): DeleteFile => {
const unlink = di.inject(fsInjectable).unlink;
return async (filePath) => {
await unlink(filePath);
return getSuccess(undefined);
};
},
injectionToken: deleteFileInjectionToken,
});
export default deleteFileInjectable;

View File

@ -0,0 +1,56 @@
import { createContainer } from "@ogre-tools/injectable";
import { registerFeature } from "@lensapp/feature-core";
import { fileSystemFeature } from "../feature";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import fsInjectable from "../fs/fs.injectable";
import deleteFileInjectable, { DeleteFile } from "./delete-file.injectable";
import { AsyncCallResult, getSuccess } from "@lensapp/utils";
describe("delete-file", () => {
let fsUnlinkMock: AsyncFnMock<(filePath: string) => Promise<void>>;
let deleteFile: DeleteFile;
beforeEach(() => {
const di = createContainer("irrelevant");
registerFeature(di, fileSystemFeature);
fsUnlinkMock = asyncFn();
const fsStub = {
unlink: fsUnlinkMock,
};
di.override(fsInjectable, () => fsStub as any);
deleteFile = di.inject(deleteFileInjectable);
});
describe("when called", () => {
let actualPromise: AsyncCallResult<void>;
beforeEach(() => {
actualPromise = deleteFile("./some-directory/some-file.js");
});
it("calls for unlink from file system", () => {
expect(fsUnlinkMock).toHaveBeenCalledWith("./some-directory/some-file.js");
});
it("when unlink resolves, resolves with success", async () => {
await fsUnlinkMock.resolve();
const actual = await actualPromise;
expect(actual).toEqual(getSuccess(undefined));
});
it("when unlink rejects, rejects with the original error", () => {
const someError = new Error("some-error");
fsUnlinkMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
});

View File

@ -0,0 +1,15 @@
import { autoRegister } from "@ogre-tools/injectable-extension-for-auto-registration";
import { getFeature } from "@k8slens/feature-core";
export const fileSystemFeature = getFeature({
id: "fs",
register: (di) => {
autoRegister({
di,
targetModule: module,
getRequireContexts: () => [require.context("./", true, /\.injectable\.(ts|tsx)$/)],
});
},
});

View File

@ -0,0 +1,10 @@
import { getInjectable } from "@ogre-tools/injectable";
import fse from "fs-extra";
const fsInjectable = getInjectable({
id: "fs",
instantiate: () => fse,
causesSideEffects: true,
});
export default fsInjectable;

View File

@ -0,0 +1,15 @@
import fsInjectable from "./fs.injectable";
import { createContainer } from "@ogre-tools/injectable";
import fse from "fs-extra";
describe("fs", () => {
it("is fs-extra", () => {
const di = createContainer("irrelevant");
di.register(fsInjectable);
const fs = di.inject(fsInjectable);
expect(fs).toBe(fse);
});
});

View File

@ -0,0 +1,27 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import fsInjectable from "../fs/fs.injectable";
import type { AsyncCallSuccess } from "@lensapp/utils";
import { getSuccess } from "@lensapp/utils";
export type PathExists = (path: string) => AsyncCallSuccess<boolean>;
export const pathExistsInjectionToken = getInjectionToken<PathExists>({
id: "path-exists-injection-token",
});
const pathExistsInjectable = getInjectable({
id: "path-exists",
instantiate: (di): PathExists => {
const pathExists = di.inject(fsInjectable).pathExists;
return async (filePath: string) => {
const result = await pathExists(filePath);
return getSuccess(result);
};
},
injectionToken: pathExistsInjectionToken,
});
export default pathExistsInjectable;

View File

@ -0,0 +1,58 @@
import { createContainer } from "@ogre-tools/injectable";
import { registerFeature } from "@lensapp/feature-core";
import { fileSystemFeature } from "../feature";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import fsInjectable from "../fs/fs.injectable";
import type { PathExists } from "./path-exists.injectable";
import pathExistsInjectable from "./path-exists.injectable";
import type { AsyncCallResult } from "@lensapp/utils";
import { getSuccess } from "@lensapp/utils";
describe("path-exists", () => {
let fsPathExistsMock: AsyncFnMock<(filePath: string) => Promise<boolean>>;
let pathExists: PathExists;
beforeEach(() => {
const di = createContainer("irrelevant");
registerFeature(di, fileSystemFeature);
fsPathExistsMock = asyncFn();
const fsStub = {
pathExists: fsPathExistsMock,
};
di.override(fsInjectable, () => fsStub as any);
pathExists = di.inject(pathExistsInjectable);
});
describe("when called", () => {
let actualPromise: AsyncCallResult<boolean>;
beforeEach(() => {
actualPromise = pathExists("./some-directory/some-file.js");
});
it("calls for filesystem", () => {
expect(fsPathExistsMock).toHaveBeenCalledWith("./some-directory/some-file.js");
});
it("when call resolves with true, resolves with true", async () => {
await fsPathExistsMock.resolve(true);
const actual = await actualPromise;
expect(actual).toEqual(getSuccess(true));
});
it("when call resolves with false, resolves with false", async () => {
await fsPathExistsMock.resolve(false);
const actual = await actualPromise;
expect(actual).toEqual(getSuccess(false));
});
});
});

View File

@ -0,0 +1,12 @@
import { getInjectable } from "@ogre-tools/injectable";
import path from "path";
export type GetDirnameOfPath = (path: string) => string;
const getDirnameOfPathInjectable = getInjectable({
id: "get-dirname-of-path",
instantiate: (): GetDirnameOfPath => path.dirname,
causesSideEffects: true,
});
export default getDirnameOfPathInjectable;

View File

@ -0,0 +1,15 @@
import { createContainer } from "@ogre-tools/injectable";
import path from "path";
import getDirnameOfPathInjectable from "./get-dirname.injectable";
describe("get-dirname", () => {
it("is exactly dirname from path module", () => {
const di = createContainer("irrelevant");
di.register(getDirnameOfPathInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
expect(getDirnameOfPath).toBe(path.dirname);
});
});

View File

@ -0,0 +1,28 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import fsInjectable from "../fs/fs.injectable";
import type { AsyncCallSuccess } from "@lensapp/utils";
import { getSuccess } from "@lensapp/utils";
export type ReadFile = (filePath: string) => AsyncCallSuccess<string>;
export const readFileInjectionToken = getInjectionToken<ReadFile>({
id: "read-file-injection-token",
});
const readFileInjectable = getInjectable({
id: "read-file",
instantiate: (di): ReadFile => {
const { readFile } = di.inject(fsInjectable);
return async (filePath) => {
const response = await readFile(filePath, "utf-8");
return getSuccess(response);
};
},
injectionToken: readFileInjectionToken,
});
export default readFileInjectable;

View File

@ -0,0 +1,59 @@
import { createContainer } from "@ogre-tools/injectable";
import { registerFeature } from "@lensapp/feature-core";
import { fileSystemFeature } from "../feature";
import fsInjectable from "../fs/fs.injectable";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import type { ReadFile } from "./read-file.injectable";
import readFileInjectable from "./read-file.injectable";
import type { CallResult } from "@lensapp/utils";
import { getSuccess } from "@lensapp/utils";
describe("read-file", () => {
let fsReadFileMock: AsyncFnMock<(fileName: string, encoding: string) => Promise<string>>;
let readFile: ReadFile;
beforeEach(() => {
const di = createContainer("irrelevant");
registerFeature(di, fileSystemFeature);
fsReadFileMock = asyncFn();
const fsStub = {
readFile: fsReadFileMock,
};
di.override(fsInjectable, () => fsStub as any);
readFile = di.inject(readFileInjectable);
});
describe("when called", () => {
let actualPromise: Promise<CallResult<string>>;
beforeEach(() => {
actualPromise = readFile("some-file.js");
});
it("calls for file from file system", () => {
expect(fsReadFileMock).toHaveBeenCalledWith("some-file.js", "utf-8");
});
it("when reading file resolves, resolves with success", async () => {
await fsReadFileMock.resolve("some-content");
const actual = await actualPromise;
expect(actual).toEqual(getSuccess("some-content"));
});
it("when reading file rejects, throws", async () => {
const someError = new Error("some-error");
fsReadFileMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
});

View File

@ -0,0 +1,28 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { AsyncCallSuccess } from "@lensapp/utils";
import { getSuccess } from "@lensapp/utils";
import readFileInjectable from "../read-file/read-file.injectable";
export type ReadJsonFile = (filePath: string) => AsyncCallSuccess<any>;
export const readJsonFileInjectionToken = getInjectionToken<ReadJsonFile>({
id: "read-json-file-injection-token",
});
const readJsonFileInjectable = getInjectable({
id: "read-json-file",
instantiate: (di): ReadJsonFile => {
const readFile = di.inject(readFileInjectable);
return async (filePath) => {
const call = await readFile(filePath);
return getSuccess(JSON.parse(call.response));
};
},
injectionToken: readJsonFileInjectionToken,
});
export default readJsonFileInjectable;

View File

@ -0,0 +1,57 @@
import { createContainer } from "@ogre-tools/injectable";
import readJsonFileInjectable, { ReadJsonFile } from "./read-json-file.injectable";
import { registerFeature } from "@lensapp/feature-core";
import { fileSystemFeature } from "../feature";
import { CallResult, getSuccess } from "@lensapp/utils";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import fsInjectable from "../fs/fs.injectable";
describe("read-json-file", () => {
let fsReadFileMock: AsyncFnMock<(fileName: string, encoding: string) => Promise<string>>;
let readJsonFile: ReadJsonFile;
beforeEach(() => {
const di = createContainer("irrelevant");
registerFeature(di, fileSystemFeature);
fsReadFileMock = asyncFn();
const fsStub = {
readFile: fsReadFileMock,
};
di.override(fsInjectable, () => fsStub as any);
readJsonFile = di.inject(readJsonFileInjectable);
});
describe("when called", () => {
let actualPromise: Promise<CallResult<any>>;
beforeEach(() => {
actualPromise = readJsonFile("some-file.js");
});
it("calls for file from file system", () => {
expect(fsReadFileMock).toHaveBeenCalledWith("some-file.js", "utf-8");
});
it("when reading file resolves, resolves with success", async () => {
await fsReadFileMock.resolve(JSON.stringify({ some: "content" }));
const actual = await actualPromise;
expect(actual).toEqual(getSuccess({ some: "content" }));
});
it("when reading file rejects, throws", async () => {
const someError = new Error("some-error");
fsReadFileMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
});

View File

@ -0,0 +1,31 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { AsyncCallSuccess } from "@lensapp/utils";
import { getSuccess } from "@lensapp/utils";
import readFileInjectable from "../read-file/read-file.injectable";
import yaml from "js-yaml";
export type ReadYamlFile = (filePath: string) => AsyncCallSuccess<object>;
export const readYamlFileInjectionToken = getInjectionToken<ReadYamlFile>({
id: "read-yaml-file-injection-token",
});
const readYamlFileInjectable = getInjectable({
id: "read-yaml-file",
instantiate: (di): ReadYamlFile => {
const readFile = di.inject(readFileInjectable);
return async (filePath: string) => {
const call = await readFile(filePath);
const parsedResponse = yaml.load(call.response) as object;
return getSuccess(parsedResponse);
};
},
injectionToken: readYamlFileInjectionToken,
});
export default readYamlFileInjectable;

View File

@ -0,0 +1,57 @@
import { createContainer } from "@ogre-tools/injectable";
import readYamlFileInjectable, { ReadYamlFile } from "./read-yaml-file.injectable";
import { registerFeature } from "@lensapp/feature-core";
import { fileSystemFeature } from "../feature";
import { CallResult, getSuccess } from "@lensapp/utils";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import fsInjectable from "../fs/fs.injectable";
describe("read-yaml-file", () => {
let fsReadFileMock: AsyncFnMock<(fileName: string, encoding: string) => Promise<string>>;
let readYamlFile: ReadYamlFile;
beforeEach(() => {
const di = createContainer("irrelevant");
registerFeature(di, fileSystemFeature);
fsReadFileMock = asyncFn();
const fsStub = {
readFile: fsReadFileMock,
};
di.override(fsInjectable, () => fsStub as any);
readYamlFile = di.inject(readYamlFileInjectable);
});
describe("when called", () => {
let actualPromise: Promise<CallResult<object>>;
beforeEach(() => {
actualPromise = readYamlFile("some-file.js");
});
it("calls for file from file system", () => {
expect(fsReadFileMock).toHaveBeenCalledWith("some-file.js", "utf-8");
});
it("when reading file resolves, resolves with success", async () => {
await fsReadFileMock.resolve("some: content");
const actual = await actualPromise;
expect(actual).toEqual(getSuccess({ some: "content" }));
});
it("when reading file rejects, throws", async () => {
const someError = new Error("some-error");
fsReadFileMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
});

View File

@ -0,0 +1,3 @@
import { overrideFsWithFakes } from "./override-fs-with-fakes";
export const testUtils = { overrideFsWithFakes };

View File

@ -0,0 +1,47 @@
import type { DiContainer } from "@ogre-tools/injectable";
import readFileInjectable from "../read-file/read-file.injectable";
import pathExistsInjectable from "../path-exists/path-exists.injectable";
import deleteFileInjectable from "../delete-file/delete-file.injectable";
import { getSuccess } from "@lensapp/utils";
import writeFileInjectable from "../write-file/write-file.injectable";
export const overrideFsWithFakes = (di: DiContainer, state = new Map()) => {
const readFile = readFileFor(state);
di.override(readFileInjectable, () => readFile);
di.override(writeFileInjectable, () => async (filePath, contents) => {
state.set(filePath, contents);
return getSuccess(undefined);
});
di.override(
pathExistsInjectable,
() => (filePath: string) => Promise.resolve(getSuccess(state.has(filePath)))
);
di.override(deleteFileInjectable, () => async (filePath: string) => {
state.delete(filePath);
return getSuccess(undefined);
});
};
const readFileFor = (state: Map<string, string>) => (filePath: string) => {
const fileContent = state.get(filePath);
if (!fileContent) {
const existingFilePaths = [...state.keys()].join('", "');
const error = new Error(
`Tried to access file ${filePath} which does not exist. Existing file paths are: "${existingFilePaths}"`
);
(error as any).code = "ENOENT";
throw error;
}
return Promise.resolve(getSuccess(fileContent));
};

View File

@ -0,0 +1,40 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { WriteFileOptions } from "fs-extra";
import fsInjectable from "../fs/fs.injectable";
import getDirnameOfPathInjectable from "../path/get-dirname.injectable";
import { AsyncCallSuccess, getSuccess } from "@lensapp/utils";
export type WriteFile = (
filePath: string,
content: string | Buffer,
opts?: WriteFileOptions
) => AsyncCallSuccess<void>;
export const writeFileInjectionToken = getInjectionToken<WriteFile>({
id: "write-file-injection-token",
});
const writeFileInjectable = getInjectable({
id: "write-file",
instantiate: (di): WriteFile => {
const { writeFile, ensureDir } = di.inject(fsInjectable);
const getDirnameOfPath = di.inject(getDirnameOfPathInjectable);
return async (filePath, content) => {
await ensureDir(getDirnameOfPath(filePath), {
mode: 0o755,
});
await writeFile(filePath, content, {
encoding: "utf-8",
});
return getSuccess(undefined);
};
},
injectionToken: writeFileInjectionToken,
});
export default writeFileInjectable;

View File

@ -0,0 +1,85 @@
import { createContainer } from "@ogre-tools/injectable";
import { registerFeature } from "@lensapp/feature-core";
import { fileSystemFeature } from "../feature";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import fsInjectable from "../fs/fs.injectable";
import writeFileInjectable, { WriteFile } from "./write-file.injectable";
import { AsyncCallResult, getSuccess } from "@lensapp/utils";
describe("write-file", () => {
let fsWriteFileMock: AsyncFnMock<(filePath: string) => Promise<void>>;
let fsEnsureDirMock: AsyncFnMock<(directoryPath: string) => Promise<void>>;
let writeFile: WriteFile;
beforeEach(() => {
const di = createContainer("irrelevant");
registerFeature(di, fileSystemFeature);
fsWriteFileMock = asyncFn();
fsEnsureDirMock = asyncFn();
const fsStub = {
writeFile: fsWriteFileMock,
ensureDir: fsEnsureDirMock,
};
di.override(fsInjectable, () => fsStub as any);
writeFile = di.inject(writeFileInjectable);
});
describe("when called", () => {
let actualPromise: AsyncCallResult<void>;
beforeEach(() => {
actualPromise = writeFile("./some-directory/some-other-directory/some-file.js", "some-content");
});
it("makes sure that directory exists", () => {
expect(fsEnsureDirMock).toHaveBeenCalledWith("./some-directory/some-other-directory", { mode: 493 });
});
it("does not write file yet", () => {
expect(fsWriteFileMock).not.toHaveBeenCalled();
});
describe("when ensuring existence of directory is resolves with success", () => {
beforeEach(async () => {
await fsEnsureDirMock.resolve();
});
it("writes file to filesystem", () => {
expect(fsWriteFileMock).toHaveBeenCalledWith(
"./some-directory/some-other-directory/some-file.js",
"some-content",
{ encoding: "utf-8" },
);
});
it("when writing resolves with success, resolves with success", async () => {
await fsWriteFileMock.resolve();
const actual = await actualPromise;
expect(actual).toEqual(getSuccess(undefined));
});
it("when writing rejects with failure, resolves with failure", () => {
const someError = new Error("some-error");
fsWriteFileMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
it("when ensuring existence of directory rejects, rejects with the original error", () => {
const someError = new Error("some-error");
fsEnsureDirMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
});

View File

@ -0,0 +1,28 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { JsonValue } from "type-fest";
import writeFileInjectable from "../write-file/write-file.injectable";
import type { AsyncCallSuccess } from "@lensapp/utils";
export type WriteJsonFile = (
filePath: string,
contents: JsonValue
) => AsyncCallSuccess<void>;
export const writeJsonFileInjectionToken = getInjectionToken<WriteJsonFile>({
id: "write-json-file-injection-token",
});
const writeJsonFileInjectable = getInjectable({
id: "write-json-file",
instantiate: (di): WriteJsonFile => {
const writeFile = di.inject(writeFileInjectable);
return async (filePath, content) =>
writeFile(filePath, JSON.stringify(content, null, 2));
},
injectionToken: writeJsonFileInjectionToken,
});
export default writeJsonFileInjectable;

View File

@ -0,0 +1,86 @@
import { createContainer } from "@ogre-tools/injectable";
import { registerFeature } from "@lensapp/feature-core";
import { fileSystemFeature } from "../feature";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import fsInjectable from "../fs/fs.injectable";
import { AsyncCallResult, getSuccess } from "@lensapp/utils";
import type { WriteJsonFile } from "./write-json-file.injectable";
import writeJsonFileInjectable from "./write-json-file.injectable";
describe("write-json-file", () => {
let fsWriteFileMock: AsyncFnMock<(filePath: string) => Promise<void>>;
let fsEnsureDirMock: AsyncFnMock<(directoryPath: string) => Promise<void>>;
let writeJsonFile: WriteJsonFile;
beforeEach(() => {
const di = createContainer("irrelevant");
registerFeature(di, fileSystemFeature);
fsWriteFileMock = asyncFn();
fsEnsureDirMock = asyncFn();
const fsStub = {
writeFile: fsWriteFileMock,
ensureDir: fsEnsureDirMock,
};
di.override(fsInjectable, () => fsStub as any);
writeJsonFile = di.inject(writeJsonFileInjectable);
});
describe("when called", () => {
let actualPromise: AsyncCallResult<void>;
beforeEach(() => {
actualPromise = writeJsonFile("./some-directory/some-other-directory/some-file.json", { some: "content" });
});
it("makes sure that directory exists", () => {
expect(fsEnsureDirMock).toHaveBeenCalledWith("./some-directory/some-other-directory", { mode: 493 });
});
it("does not write file yet", () => {
expect(fsWriteFileMock).not.toHaveBeenCalled();
});
describe("when ensuring existence of directory is resolves with success", () => {
beforeEach(async () => {
await fsEnsureDirMock.resolve();
});
it("writes file to filesystem", () => {
expect(fsWriteFileMock).toHaveBeenCalledWith(
"./some-directory/some-other-directory/some-file.json",
JSON.stringify({ some: "content" }, null, 2),
{ encoding: "utf-8" },
);
});
it("when writing resolves with success, resolves with success", async () => {
await fsWriteFileMock.resolve();
const actual = await actualPromise;
expect(actual).toEqual(getSuccess(undefined));
});
it("when writing rejects with failure, resolves with failure", () => {
const someError = new Error("some-error");
fsWriteFileMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
it("when ensuring existence of directory rejects, rejects with the original error", () => {
const someError = new Error("some-error");
fsEnsureDirMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
});

View File

@ -0,0 +1,27 @@
import { getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import writeFileInjectable from "../write-file/write-file.injectable";
import type { AsyncCallSuccess } from "@lensapp/utils";
import yaml from "js-yaml";
export type WriteYamlFile = (
filePath: string,
contents: object
) => AsyncCallSuccess<void>;
export const writeYamlFileInjectionToken = getInjectionToken<WriteYamlFile>({
id: "write-yaml-file-injection-token",
});
const writeYamlFileInjectable = getInjectable({
id: "write-yaml-file",
instantiate: (di): WriteYamlFile => {
const writeFile = di.inject(writeFileInjectable);
return async (filePath, content) => writeFile(filePath, yaml.dump(content));
},
injectionToken: writeYamlFileInjectionToken,
});
export default writeYamlFileInjectable;

View File

@ -0,0 +1,85 @@
import { createContainer } from "@ogre-tools/injectable";
import { registerFeature } from "@lensapp/feature-core";
import { fileSystemFeature } from "../feature";
import asyncFn, { AsyncFnMock } from "@async-fn/jest";
import fsInjectable from "../fs/fs.injectable";
import { AsyncCallResult, getSuccess } from "@lensapp/utils";
import writeYamlFileInjectable, { WriteYamlFile } from "./write-yaml-file.injectable";
describe("write-yaml-file", () => {
let fsWriteFileMock: AsyncFnMock<(filePath: string) => Promise<void>>;
let fsEnsureDirMock: AsyncFnMock<(directoryPath: string) => Promise<void>>;
let writeYamlFile: WriteYamlFile;
beforeEach(() => {
const di = createContainer("irrelevant");
registerFeature(di, fileSystemFeature);
fsWriteFileMock = asyncFn();
fsEnsureDirMock = asyncFn();
const fsStub = {
writeFile: fsWriteFileMock,
ensureDir: fsEnsureDirMock,
};
di.override(fsInjectable, () => fsStub as any);
writeYamlFile = di.inject(writeYamlFileInjectable);
});
describe("when called", () => {
let actualPromise: AsyncCallResult<void>;
beforeEach(() => {
actualPromise = writeYamlFile("./some-directory/some-other-directory/some-file.yml", { some: "content" });
});
it("makes sure that directory exists", () => {
expect(fsEnsureDirMock).toHaveBeenCalledWith("./some-directory/some-other-directory", { mode: 493 });
});
it("does not write file yet", () => {
expect(fsWriteFileMock).not.toHaveBeenCalled();
});
describe("when ensuring existence of directory is resolves with success", () => {
beforeEach(async () => {
await fsEnsureDirMock.resolve();
});
it("writes file to filesystem", () => {
expect(fsWriteFileMock).toHaveBeenCalledWith(
"./some-directory/some-other-directory/some-file.yml",
"some: content\n",
{ encoding: "utf-8" },
);
});
it("when writing resolves with success, resolves with success", async () => {
await fsWriteFileMock.resolve();
const actual = await actualPromise;
expect(actual).toEqual(getSuccess(undefined));
});
it("when writing rejects with failure, resolves with failure", () => {
const someError = new Error("some-error");
fsWriteFileMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
it("when ensuring existence of directory rejects, rejects with the original error", () => {
const someError = new Error("some-error");
fsEnsureDirMock.reject(someError);
return expect(actualPromise).rejects.toBe(someError);
});
});
});

View File

@ -0,0 +1,4 @@
{
"extends": "@k8slens/typescript/config/base.json",
"include": ["**/*.ts"]
}

View File

@ -0,0 +1 @@
module.exports = require("@k8slens/webpack").configForNode;