Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jobs): ValidateAction #1421

Merged
merged 19 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Thank you for your interest in contributing to our project!
1. **Running the unit tests:** `npm run test`
2. **Running the e2e(api) tests:**

- First of all run `npm run prepare:local` to prepare the local environment for starting
- First of all run `npm run prepare:local` to prepare the local docker environment for starting
- After that run `npm run test:api` which will start the backend locally and run both `jest` and `mocha` e2e(api) tests.
- [Optional] If you want to run only the mocha tests you will need to start the backend locally with `npm run start` and then use `npm run test:api:mocha`
- [Optional] If you want to run only the jest tests you can use `npm run test:api:jest`
Expand Down
60 changes: 60 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"dotenv": "^16.0.3",
"express-session": "^1.17.3",
"handlebars": "^4.7.7",
"jsonpath-plus": "^9.0.0",
"lodash": "^4.17.21",
"luxon": "^3.2.1",
"mathjs": "^13.0.0",
Expand Down Expand Up @@ -87,6 +88,7 @@
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.4",
"@types/jest": "^27.0.2",
"@types/jsonpath-plus": "^5.0.5",
"@types/lodash": "^4.14.180",
"@types/luxon": "^3.1.0",
"@types/mocha": "^10.0.0",
Expand Down
3 changes: 3 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as fs from "fs";
import { merge } from "lodash";
import localconfiguration from "./localconfiguration";
import { boolean } from "mathjs";
import { ValidateAction } from "src/jobs/actions/validateaction";

const configuration = () => {
const accessGroupsStaticValues =
Expand Down Expand Up @@ -241,10 +242,12 @@ export function registerDefaultActions() {
registerCreateAction(EmailJobAction);
registerCreateAction(URLAction);
registerCreateAction(RabbitMQJobAction);
registerCreateAction(ValidateAction);
// Status Update
registerStatusUpdateAction(LogJobAction);
registerStatusUpdateAction(EmailJobAction);
registerStatusUpdateAction(RabbitMQJobAction);
registerStatusUpdateAction(ValidateAction);
}

export type OidcConfig = ReturnType<typeof configuration>["oidc"];
Expand Down
5 changes: 2 additions & 3 deletions src/jobs/actions/emailaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
*/
import { readFileSync } from "fs";
import { compile, TemplateDelegate } from "handlebars";
import { createTransport, Transporter } from "nodemailer";

Check warning on line 8 in src/jobs/actions/emailaction.ts

View workflow job for this annotation

GitHub Actions / eslint

'createTransport' is defined but never used
import { Logger, NotFoundException } from "@nestjs/common";
import { JobAction } from "../config/jobconfig";
import { JobAction, JobDto } from "../config/jobconfig";
import { JobClass } from "../schemas/job.schema";
import configuration from "src/config/configuration";

// Handlebar options for JobClass templates
const jobTemplateOptions = {
Expand Down Expand Up @@ -40,7 +39,7 @@
/**
* Send an email following a job
*/
export class EmailJobAction<T> implements JobAction<T> {
export class EmailJobAction<T extends JobDto> implements JobAction<T> {
public static readonly actionType = "email";

private mailService: Transporter;
Expand Down
8 changes: 6 additions & 2 deletions src/jobs/actions/logaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@
*
*/
import { Logger } from "@nestjs/common";
import { JobAction } from "../config/jobconfig";
import { JobAction, JobDto } from "../config/jobconfig";
import { JobClass } from "../schemas/job.schema";

export class LogJobAction<T> implements JobAction<T> {
export class LogJobAction<T extends JobDto> implements JobAction<T> {
public static readonly actionType = "log";

getActionType(): string {
return LogJobAction.actionType;
}

async validate(dto: T) {
Logger.log("Validating job dto: " + JSON.stringify(dto), "LogJobAction");
}

async performJob(job: JobClass) {
Logger.log("Performing job: " + JSON.stringify(job), "LogJobAction");
}
Expand Down
4 changes: 2 additions & 2 deletions src/jobs/actions/rabbitmqaction.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Logger, NotFoundException } from "@nestjs/common";
import amqp, { Connection } from "amqplib/callback_api";
import { JobAction } from "../config/jobconfig";
import { JobAction, JobDto } from "../config/jobconfig";
import { JobClass } from "../schemas/job.schema";

/**
* Publish a message in a RabbitMQ queue
*/
export class RabbitMQJobAction<T> implements JobAction<T> {
export class RabbitMQJobAction<T extends JobDto> implements JobAction<T> {
public static readonly actionType = "rabbitmq";
private connection;
private binding;
Expand Down
17 changes: 4 additions & 13 deletions src/jobs/actions/urlaction.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Logger, NotFoundException, HttpException } from "@nestjs/common";
import { JobAction } from "../config/jobconfig";
import { JobAction, JobDto } from "../config/jobconfig";
import { JobClass } from "../schemas/job.schema";
import * as Handlebars from "handlebars";

Expand Down Expand Up @@ -37,7 +37,7 @@ function isStringRecord(obj: any): obj is Record<string, string> {
/**
* Respond to Job events by making an HTTP call.
*/
export class URLAction<T> implements JobAction<T> {
export class URLAction<T extends JobDto> implements JobAction<T> {
public static readonly actionType = "url";

private urlTemplate: Handlebars.TemplateDelegate<JobClass>;
Expand All @@ -54,7 +54,7 @@ export class URLAction<T> implements JobAction<T> {

async performJob(job: JobClass) {
const url = encodeURI(this.urlTemplate(job, jobTemplateOptions));
Logger.log(`Requesting ${url}`, "UrlJobAction");
Logger.log(`Requesting ${url}`, "URLAction");

const response = await fetch(url, {
method: this.method,
Expand All @@ -71,11 +71,7 @@ export class URLAction<T> implements JobAction<T> {
: undefined,
});

Logger.log(
`Request for ${url} returned ${response.status}`,
"UrlJobAction",
);

Logger.log(`Request for ${url} returned ${response.status}`, "URLAction");
if (!response.ok) {
throw new HttpException(
{
Expand All @@ -102,11 +98,6 @@ export class URLAction<T> implements JobAction<T> {
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor(data: Record<string, any>) {
Logger.log(
"Initializing UrlJobAction. Params: " + JSON.stringify(data),
"UrlJobAction",
);

if (!data["url"]) {
throw new NotFoundException("Param 'url' is undefined in url action");
}
Expand Down
134 changes: 134 additions & 0 deletions src/jobs/actions/validateaction.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { ValidateAction } from "./validateaction";
import { CreateJobDto } from "../dto/create-job.dto";

const createJobBase = {
type: "validate",
ownerUser: "owner",
ownerGroup: "group",
contactEmail: "[email protected]",
};

describe("ValiateAction", () => {
const config = {
actionType: "validate",
request: {
"jobParams.stringVal": { type: "string" },
"jobParams.requiredArray[*]": { type: "string" },
"jobParams.numberVal": { type: "number" },
jobParams: { required: ["nonNull"] },
},
};
const action = new ValidateAction<CreateJobDto>(config);
it("should be configured successfully", async () => {
expect(action).toBeDefined();
});

it("should pass if required params are present", async () => {
const dto: CreateJobDto = {
...createJobBase,
jobParams: {
stringVal: "ok",
numberVal: 1,
nonNull: "value1",
requiredArray: ["ok"],
},
};

await expect(action.validate(dto)).resolves.toBeUndefined();
});

it("should fail if nonNull is missing", async () => {
const dto: CreateJobDto = {
...createJobBase,
jobParams: {
stringVal: "ok",
numberVal: 1,
//nonNull: "value1",
requiredArray: ["ok"],
},
};

await expect(action.validate(dto)).rejects.toThrow(
"Invalid request. Invalid value for 'jobParams'",
);
});

it("should fail if string type is wrong", async () => {
const dto: CreateJobDto = {
...createJobBase,
jobParams: {
stringVal: 0xdeadbeef, // wrong type
numberVal: 1,
nonNull: "value1",
requiredArray: ["ok"],
},
};

await expect(action.validate(dto)).rejects.toThrow(
"Invalid request. Invalid value for 'jobParams.stringVal",
);
});

it("should fail if number type is wrong", async () => {
const dto: CreateJobDto = {
...createJobBase,
jobParams: {
stringVal: "ok",
numberVal: "error",
nonNull: "value1",
requiredArray: ["ok"],
},
};

await expect(action.validate(dto)).rejects.toThrow(
"Invalid request. Invalid value for 'jobParams.numberVal'",
);
});

it("should fail if requiredArray is ommitted", async () => {
const dto: CreateJobDto = {
...createJobBase,
jobParams: {
stringVal: "ok",
numberVal: 1,
nonNull: "value1",
//requiredArray: ["ok"],
},
};

await expect(action.validate(dto)).rejects.toThrow(
"Invalid request. Requires 'jobParams.requiredArray[*]'",
);
});

it("should fail if requiredArray is empty", async () => {
const dto: CreateJobDto = {
...createJobBase,
jobParams: {
stringVal: "ok",
numberVal: 1,
nonNull: "value1",
requiredArray: [],
},
};
await expect(action.validate(dto)).rejects.toThrow(
"Invalid request. Requires 'jobParams.requiredArray[*]'",
);
});

it("should fail if requiredArray has the wrong type", async () => {
const dto: CreateJobDto = {
...createJobBase,
jobParams: {
stringVal: "ok",
numberVal: "error",
nonNull: "value1",
requiredArray: [0xdeadbeef],
},
};

await expect(action.validate(dto)).rejects.toThrow(
"Invalid request. Invalid value for 'jobParams.requiredArray[*]'",
);
});
});
Loading
Loading