Skip to content

Commit

Permalink
Merge pull request #4 from imabp/github-action
Browse files Browse the repository at this point in the history
feat: add methods: `toJson`, `isOfType`, `update`,  `tests` and workflow setup file
  • Loading branch information
imabp authored Oct 25, 2022
2 parents c99b63a + a990052 commit 60ba624
Show file tree
Hide file tree
Showing 10 changed files with 257 additions and 64 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: PR testing

on:
pull_request:
types: [opened, reopened, synchronize, ready_for_review]

jobs:
test-pr:
name: Test PR - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3

- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: '12.x'

- name: Install Dependencies
run: yarn install

- name: Run tests
run: yarn test

- name: Build Problem Library
run: yarn build
21 changes: 21 additions & 0 deletions __tests__/_helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Problem } from "../src"
import { ProblemInterface } from "../src/types"

export default class ProblemContextHelper {

_problemInstance: Problem;

_instanceOptions: ProblemInterface = {
type: "null-or-falsey-document",
title: "The AsyncAPI document is null or a JS falsey value.",
detail: "The AsyncAPI document is null or a JS falsey value.",
leaveThisWhenCopy:"This is used to test copy function: LEAVE PROPS. This will not be undefined in new copy. ",
skipThisWhenCopy:"This is used to test copy functionL SKIP PROPS. This should be undefined in new copy.",
}


constructor() {
this._problemInstance = new Problem(this._instanceOptions);
}

}
79 changes: 79 additions & 0 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Problem } from "../src";
import { COPY_MODE } from "../src/constants";
import ProblemContextHelper from "./_helper";

const _testContext = new ProblemContextHelper();

describe("Class Methods Test Suite", () => {
test("Create Class with Custom Keys", () => {
const customKey = "RCA";
_testContext._problemInstance[customKey]="Root Cause Analysis"
const testProblem = new Problem(_testContext._problemInstance);
expect(testProblem).toHaveProperty(customKey);
});

test("Method: Copy, mode: LEAVE_PROPS", () => {
const _problemCopy = _testContext._problemInstance.copy(
COPY_MODE.LEAVE_PROPS,
["leaveThisWhenCopy"]
);
expect(_problemCopy).toBeInstanceOf(Problem);
expect(_problemCopy.type).toBe(_testContext._problemInstance.type);
expect(_problemCopy.title).toBe(_testContext._problemInstance.title);
// check if skipthiswhencopy is omitted.
expect(_problemCopy.skipThisWhenCopy).toBe(undefined);
// check if leavethiswhencopy is not omitted
expect(_problemCopy.leaveThisWhenCopy).toBe(_testContext._problemInstance.leaveThisWhenCopy);
});

test("Method: Copy, mode: SKIP_PROPS", () => {
const _copiedProblem = _testContext._problemInstance.copy(
COPY_MODE.SKIP_PROPS,
["skipThisWhenCopy"]
);
expect(_copiedProblem).toBeInstanceOf(Problem);
// check if leavethiswhencopy is not emitted.
expect(_copiedProblem.leaveThisWhenCopy).toBe(_testContext._problemInstance.leaveThisWhenCopy);
// check if skipthis is omitted
expect(_copiedProblem.skipThisWhenCopy).toBeUndefined();
});

test("Method: isOfType", () => {
const _TYPE_TRUTH_CHECK = "null-or-falsey-document";
const _TYPE_FALSE_CHECK = "undefined-document";
expect(
_testContext._problemInstance.isOfType(_TYPE_TRUTH_CHECK)
).toBeTruthy();
expect(
_testContext._problemInstance.isOfType(_TYPE_FALSE_CHECK)
).toBeFalsy();
});

test("Method: Update", async () => {
const LINK: string = "test-link/";
const updates = {
link: LINK,
};
await _testContext._problemInstance.update({ updates });
expect(_testContext._problemInstance).toHaveProperty("link", LINK);
});

test("Method: toJSON", () => {
const _problem = _testContext._problemInstance;

// Run updates first to add stack property, required for testing isJSON
const updates = {
stack: "./filepath",
};
_problem.update({ updates });

// non-stringified stack
expect(_problem.toJSON({ includeStack: true })).toHaveProperty(
"stack",
updates.stack
);
expect(_problem.toJSON({ includeStack: false })).not.toHaveProperty(
"stack"
);
});
});
3 changes: 3 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ const config: Config.InitialOptions = {
collectCoverageFrom: [
'src/**'
],
testPathIgnorePatterns: [
"__tests__/_helper.ts"
]
};

export default config;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint:fix": "eslint --no-error-on-unmatched-pattern --max-warnings 0 --config \".eslintrc\" \".\" --fix",
"generate:readme:toc": "markdown-toc -i \"README.md\""
},

"bugs": {
"url": "https://github.com/imabp/asyncapi_problem/issues"
},
Expand Down
9 changes: 9 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const DEFAULT_KEYS = [
"type",
"title",
];

export enum COPY_MODE {
SKIP_PROPS = "skipProps",
LEAVE_PROPS = "leaveProps",
}
129 changes: 77 additions & 52 deletions src/problem.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,87 @@
import { httpObject, ProblemInterface } from "types";

enum COPY_MODE {
SKIP_PROPS = 'skipProps',
LEAVE_PROPS = 'leaveProps'
}
import { DEFAULT_KEYS } from "./constants";
import {
HttpObject,
ProblemInterface,
ToJsonParamType,
UpdateProblemParamType,
} from "./types";

import { COPY_MODE } from "./constants";
import { objectToProblemMap } from "./util";
export class Problem extends Error implements ProblemInterface {
public type: string;
public title: string;
public instance?: string;
public detail?: string;
public http?: httpObject
[key: string]: any;


constructor(problem: ProblemInterface, customKeys?: string[]) {
super(problem.detail || problem.title);
this.http = problem.http
this.type = problem.type
this.title = problem.title;
this.detail = problem.detail;
this.instance = problem.instance;
this.stack = problem.stack;
customKeys?.map((customKey) => {
this[customKey] = problem[customKey];
})
}
public type: string;
public title: string;
public instance?: string;
public detail?: string;
public http?: HttpObject;
[key: string]: any;

copy(problem: ProblemInterface, mode: COPY_MODE, props: string[]): Problem {
switch (mode) {

case COPY_MODE.LEAVE_PROPS:
return new Problem(problem, props);

case COPY_MODE.SKIP_PROPS:
default:
let keysToBeCopied: string[] = [];
for (let key in problem) {
if (props.includes(key))
continue;
keysToBeCopied.push(key)
}
return new Problem(problem, keysToBeCopied)
}
};

toJSON(problem: Problem, includeStack = false): ProblemInterface {
constructor(protected readonly problem: ProblemInterface) {
super(problem.detail || problem.title);
this.http = problem.http;
this.type = problem.type;
this.title = problem.title;
this.detail = problem.detail;
this.instance = problem.instance;
this.stack = problem.stack;

const { name, message, stack, ...rest } = problem;
// add extra keys
Object.keys(problem)
.filter((el) => !DEFAULT_KEYS.includes(el))
.forEach((k) => (this[k] = problem[k]));
}

const jsonObject = {
...rest
copy(mode: COPY_MODE = COPY_MODE.LEAVE_PROPS, props: string[] = []): Problem {
switch (mode) {
// returns a new problem object with preserved keys passed as props
case COPY_MODE.LEAVE_PROPS:{
let newProblemKeyValuePairs:Record<string,any> = {
type: this.problem.type,
title:this.problem.title,
}
props.forEach((key)=>{
newProblemKeyValuePairs={...newProblemKeyValuePairs, [key]:this.problem[key]}
})
const newProblem = new Problem(objectToProblemMap(newProblemKeyValuePairs));
return newProblem;
}
// skip the copy of keys
case COPY_MODE.SKIP_PROPS:
default: {
let newProblemKeyValuePairs:Record<string,any>={};

if (includeStack)
jsonObject.stack = stack;
// loop to copy only the required keys
for (let key in this.problem) {
// Skip only those keys, which are given in props and NOT a default key.
if (props.includes(key) && !DEFAULT_KEYS.includes(key)) continue;
newProblemKeyValuePairs[key] = this.problem[key];
}
const newProblem = new Problem(objectToProblemMap(newProblemKeyValuePairs))
return newProblem;
}
}
}

return jsonObject;
toJSON({ includeStack = false }: ToJsonParamType) {
const { stack, ...rest } = this;

if (includeStack) {
return {
...this,
stack: this.stack,
};
}
};

return { ...rest };
}

isOfType(type: string) {
return this.type === type;
}

update({ updates }: UpdateProblemParamType) {
Object.keys(updates).forEach((i) => {
this[i] = updates[i];
});
}
}
34 changes: 22 additions & 12 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { Problem } from "../problem";

export type ProblemInterface = {
http?: httpObject,
type: string,
title: string, // Title should be description of http status, if type is not present.
detail?: string,
instance?: string, // Details to reproduce the error.
stack?: string;
[key: string]: any, // Custom Field of Problem
}

export type httpObject= {
status: number, // Status Code
[key: string]: any,
type: string;
title: string;
http?: HttpObject;
detail?: string;
instance?: string;
stack?: string;
[key: string]: any;
};

export type HttpObject = {
status: number; // Status Code
[key: string]: any;
};

export type UpdateProblemParamType = {
updates: { [key: string]: any };
};

export type ToJsonParamType = {
includeStack?:boolean
}
10 changes: 10 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ProblemInterface } from "types";

export const objectToProblemMap = (obj:Record<string,any>) =>{
const type: string = obj.type;
const title: string = obj.title;
const problemObject:ProblemInterface = {
type, title, ...obj
}
return problemObject
}
6 changes: 6 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"lib": [
"esnext"
],
"composite": true,
"declaration": true,
"allowJs": true,
"skipLibCheck": true,
Expand All @@ -19,7 +20,12 @@
"resolveJsonModule": true,
"isolatedModules": true,
},

"include": [
"src"
],
"exclude": [
"__tests__",
"./lib"
]
}

0 comments on commit 60ba624

Please sign in to comment.