Skip to content

Commit

Permalink
refactor: improve process fail safety (#10)
Browse files Browse the repository at this point in the history
* refactor: improve process fail safety

* 1.4.0
  • Loading branch information
sapachev authored Feb 7, 2023
1 parent 5cefa64 commit 835e0bd
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 29 deletions.
2 changes: 1 addition & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { PathRegistry } from "./lib/classes/PathRegistry";
import { DEFAULTS } from "./lib/constants";
import { DI_TOKENS } from "./lib/tokens";

// Resigstry of resolved paths used during the process
// Resigstry of resolved paths to usage during the process
const pr = new PathRegistry({
assetsDist: `${DEFAULTS.DIST}/${DEFAULTS.ASSETS}`,
config: DEFAULTS.CONFIG,
Expand Down
15 changes: 14 additions & 1 deletion lib/classes/Compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ export class Compiler implements ICompiler {

async initTemplateVariables(): Promise<TemplateVariables> {
const pkg = await this.fsHelper.readJson<PackageId>(this.pr.get("package"));

if (!this.config.locale || !pkg.version) {
throw new Error(Messages.text(MessagesEnum.NO_DEFAULT_VARS));
}

return {
locale: this.config.locale,
version: pkg.version,
Expand All @@ -59,7 +64,11 @@ export class Compiler implements ICompiler {
if (list.size > 0) {
const initVars = await this.initTemplateVariables();
const commonVars = await this.fsHelper.readJson<TemplateVariables>(this.pr.join("src", "common.json"));

const template = await this.fsHelper.readFile(this.pr.join("theme", "template.html"));
if (!template) {
throw new Error(Messages.text(MessagesEnum.NO_TEMPLATE_CONTENT));
}

await Promise.all(
Array.from(list).map(async (code) => {
Expand All @@ -84,11 +93,15 @@ export class Compiler implements ICompiler {

await Promise.all(
snippets.map(async (snippet) => {
const template = await this.fsHelper.readFile(this.pr.join("snippets", snippet));
if (!template) {
throw new Error(Messages.text(MessagesEnum.NO_TEMPLATE_CONTENT));
}

const path = this.pr.join("dist", snippet);

this.logger.print(Messages.list(path));

const template = await this.fsHelper.readFile(this.pr.join("snippets", snippet));
await this.fsHelper.writeFile(path, Renderer.renderTemplate(template, { codes: Array.from(list) }));
})
);
Expand Down
8 changes: 6 additions & 2 deletions lib/classes/FileSystemHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ export class FileSystemHelper implements IFileSystemHelper {
}

async readFile(path: string): Promise<string> {
return await this.fs.readFile(path).then(String);
if (await this.ensure(path)) {
return await this.fs.readFile(path).then(String);
} else {
throw new Error(Messages.text(MessagesEnum.NO_FILE, { path }));
}
}

async readJson<T>(path: string): Promise<T> {
Expand Down Expand Up @@ -107,7 +111,7 @@ export class MockFileSystemHelper implements IFileSystemHelper {
}

async readDir() {
return [""];
return [];
}

async readFile() {
Expand Down
2 changes: 1 addition & 1 deletion lib/classes/FileSystemWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class MockFS implements IFileSystemWrapper {
}

async readDir() {
return [""];
return [];
}

async rm() {
Expand Down
5 changes: 4 additions & 1 deletion messages.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
export enum MessagesEnum {
// Errors
NO_CONFIG_PROPERTY = "Please set '{{ prop }}' property in your configuration: {{ &path }}",
NO_FILE = "No file to read: {{ &path }}",
NO_DEFAULT_VARS = "Not enough data to init default template variables",
NO_DIRECTORY = "No directory to read: {{ &path }}",
NO_PATH = "No path in registry with '{{ key }}' key",
NO_SOURCE_DATA = "No source data to compile",
NO_TEMPLATE_CONTENT = "No template content to render",
OVERALL = "An error occurred during compilation. Please, check 'README.md' to get more details about calling this process.\n\nDetails:\n{{ &stack }}",

// Info
Expand All @@ -14,7 +17,7 @@ export enum MessagesEnum {
FLUSH_DESTINATION = "Flush build directory '{{ &path }}'",
START = "Start building process",
TAILWIND_CMD = "Run '{{ &cmd }}' command",
TAILWIND_DONE = "Tailwind CSS styles were built",
TAILWIND_DONE = "Tailwind CSS styles were successfully built",
TAILWIND_START = "Build Tailwind CSS styles",

// Skip
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "error-pages",
"version": "1.3.0",
"version": "1.4.0",
"description": "Lightweight tool to create static HTTP Error Pages in minimalistic adaptive and accessible design with customization and localization support",
"main": "index.ts",
"scripts": {
Expand Down
128 changes: 108 additions & 20 deletions test/Compiler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ describe("class Compiler", async () => {
});

describe("initTemplateVariables()", async () => {
it("should be resolved to default template variables", async () => {
beforeEach(() => {
pr.update({ package: "package" });
});

it("should be resolved to default template variables", async () => {
const fsHelper = testContainer.get<IFileSystemHelper>(DI_TOKENS.FS_HELPER);
fsHelper.readJson = async <T>() => ({ version } as T);

Expand All @@ -76,6 +78,10 @@ describe("class Compiler", async () => {
assert.deepEqual(await compiler.getStatusList(), mockStatusCodes);
});

it("should be resolved to empty Set", async () => {
assert.deepEqual(await compiler.getStatusList(), new Set());
});

it("should be executed with one readDir() call", async () => {
const fsHelper = testContainer.get<IFileSystemHelper>(DI_TOKENS.FS_HELPER);
fsHelper.readDir = async () => mockStatusFiles;
Expand Down Expand Up @@ -177,30 +183,112 @@ describe("class Compiler", async () => {

describe("failure scenarios", async () => {
beforeEach(() => {
const fsHelper = testContainer.get<IFileSystemHelper>(DI_TOKENS.FS_HELPER);
fsHelper.readDir = async () => [];

pr.update({ src: "src" });
});

it("makePages() should be rejected", async () => {
await compiler.makePages().then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_SOURCE_DATA));
}
);
sinon.assert.calledWithExactly(printSpy, Messages.info(MessagesEnum.COMPILE_PAGES));
describe("initTemplateVariables()", async () => {
beforeEach(() => {
pr.update({ package: "package" });
});

it("should be rejected without config locale", async () => {
testContainer.rebind<Config>(DI_TOKENS.CONFIG).toConstantValue({} as Config);
compiler = testContainer.resolve(Compiler);

const fsHelper = testContainer.get<IFileSystemHelper>(DI_TOKENS.FS_HELPER);
fsHelper.readJson = async <T>() => ({ version } as T);

await compiler.initTemplateVariables().then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_DEFAULT_VARS));
}
);
});

it("should be rejected without package version", async () => {
await compiler.initTemplateVariables().then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_DEFAULT_VARS));
}
);
});
});

it("makeConfigs() should be rejected", async () => {
await compiler.makeConfigs().then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_SOURCE_DATA));
}
);
sinon.assert.calledWithExactly(printSpy, Messages.info(MessagesEnum.COMPILE_CONFIGS));
describe("makePages()", () => {
it("should be rejected without source data", async () => {
await compiler.makePages().then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_SOURCE_DATA));
}
);
});

it("should be rejected without template file", async () => {
pr.update({
package: "package.json",
theme: "theme",
});

const fsHelper = testContainer.get<IFileSystemHelper>(DI_TOKENS.FS_HELPER);
fsHelper.readDir = async () => mockStatusFiles;
fsHelper.readJson = async <T>() => ({ version } as T);
fsHelper.readFile = () => Promise.reject();

await compiler.makePages().then(
() => assert.ok(false),
(err) => {
assert.ok(true);
}
);
});

it("should be rejected on empty template", async () => {
pr.update({
package: "package.json",
theme: "theme",
});

const fsHelper = testContainer.get<IFileSystemHelper>(DI_TOKENS.FS_HELPER);
fsHelper.readDir = async () => mockStatusFiles;
fsHelper.readJson = async <T>() => ({ version } as T);

await compiler.makePages().then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_TEMPLATE_CONTENT));
}
);
});
});

describe("makePages()", () => {
it("should be rejected without source data", async () => {
await compiler.makeConfigs().then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_SOURCE_DATA));
}
);
});

it("should be rejected on empty template", async () => {
pr.update({
snippets: "snippets",
});

const fsHelper = testContainer.get<IFileSystemHelper>(DI_TOKENS.FS_HELPER);
fsHelper.readDir = async () => mockStatusFiles;

await compiler.makeConfigs().then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_TEMPLATE_CONTENT));
}
);
});
});
});
});
20 changes: 20 additions & 0 deletions test/FileSystemHelper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ describe("class FileSystemHelper", async () => {
assert.deepEqual(await fsHelper.readDir(path), mockStrArr);
});

it("should be resolved to empty list", async () => {
assert.deepEqual(await fsHelper.readDir(path), []);
});

it("should be rejected with Error", async () => {
const fsWrapper = testContainer.get<IFileSystemWrapper>(DI_TOKENS.FS);
fsWrapper.access = () => Promise.reject();
Expand All @@ -119,6 +123,22 @@ describe("class FileSystemHelper", async () => {

assert.equal(await fsHelper.readFile(path), mockStr);
});

it("should be resolved to empty string", async () => {
assert.equal(await fsHelper.readFile(path), "");
});

it("should be rejected with Error", async () => {
const fsWrapper = testContainer.get<IFileSystemWrapper>(DI_TOKENS.FS);
fsWrapper.access = () => Promise.reject();

await fsHelper.readFile(path).then(
() => assert.ok(false),
(err) => {
assert.equal(err.message, Messages.text(MessagesEnum.NO_FILE, { path }));
}
);
});
});

describe("readJson()", async () => {
Expand Down

0 comments on commit 835e0bd

Please sign in to comment.