actionhero
Version:
The reusable, scalable, and quick node.js API server for stateless and stateful applications
183 lines (162 loc) • 5.75 kB
text/typescript
import * as uuid from "uuid";
import { Worker } from "node-resque";
import { api, config, task, Task, Action, Connection } from "./../index";
import { WebServer } from "../servers/web";
import { AsyncReturnType } from "type-fest";
import { TaskInputs } from "../classes/task";
export type SpecHelperConnection = Connection & {
actionCallbacks?: { [key: string]: Function };
};
export namespace specHelper {
/**
* Generate a connection to use in your tests
*/
export async function buildConnection() {
return api.specHelper.Connection.createAsync() as SpecHelperConnection;
}
/**
* Run an action via the specHelper server.
*/
export async function runAction<A extends Action | void = void>(
actionName: string,
input: Partial<SpecHelperConnection> | Record<string, any> = {},
) {
let connection: SpecHelperConnection;
if (input.id && input.type === "testServer") {
connection = input as SpecHelperConnection;
} else {
connection = await specHelper.buildConnection();
connection.params = input;
}
connection.params.action = actionName;
connection.messageId = connection.params.messageId || uuid.v4();
const response: (A extends Action
? AsyncReturnType<A["run"]>
: { [key: string]: any }) & {
messageId?: string;
error?: NodeJS.ErrnoException | string | any;
requesterInformation?: ReturnType<WebServer["buildRequesterInformation"]>;
serverInformation?: ReturnType<WebServer["buildServerInformation"]>;
} = await new Promise((resolve) => {
api.servers.servers.testServer.processAction(connection);
connection.actionCallbacks[connection.messageId] = resolve;
});
return response;
}
/**
* Mock a specHelper connection requesting a file from the server.
*/
export async function getStaticFile(file: string): Promise<any> {
const connection = await specHelper.buildConnection();
connection.params.file = file;
const response = await new Promise((resolve) => {
api.servers.servers.testServer.processFile(connection);
connection.actionCallbacks[connection.messageId] = resolve;
});
return response;
}
/**
* Use the specHelper to run a task.
* Note: this only runs the task's `run()` method, and no middleware. This is faster than api.specHelper.runFullTask.
*/
export async function runTask<T extends Task | void = void>(
taskName: string,
params: object | Array<any>,
) {
if (!api.tasks.tasks[taskName]) {
throw new Error(`task ${taskName} not found`);
}
const result: (T extends Task
? AsyncReturnType<T["run"]>
: { [key: string]: any }) & {
error?: NodeJS.ErrnoException | string;
} = await api.tasks.tasks[taskName].run(params, undefined);
return result;
}
/**
* Use the specHelper to run a task.
* Note: this will run a full Task worker, and will also include any middleware. This is slower than api.specHelper.runTask.
*/
export async function runFullTask<T extends Task | void = void>(
taskName: string,
params: object | Array<any>,
) {
const worker = new Worker(
{
connection: {
redis: api.redis.clients.tasks,
pkg:
api.redis.clients.tasks?.constructor?.name === "RedisMock"
? "ioredis-mock"
: "ioredis",
},
queues: (Array.isArray(config.tasks.queues)
? config.tasks.queues
: await config.tasks.queues()) || ["default"],
},
api.tasks.jobs,
);
try {
await worker.connect();
const result: (T extends Task
? AsyncReturnType<T["run"]>
: { [key: string]: any }) & {
error?: string;
} = await worker.performInline(
taskName,
Array.isArray(params) ? params : [params],
);
await worker.end();
return result;
} catch (error) {
try {
worker.end();
} catch (error) {}
throw error;
}
}
/**
* Use the specHelper to find enqueued instances of a task
* This will return an array of instances of the task which have been enqueued either in the normal queues or delayed queues
* If a task is enqueued in a delayed queue, it will have a 'timestamp' property
* i.e. [ { class: 'regularTask', queue: 'testQueue', args: [ [Object] ] } ]
*/
export async function findEnqueuedTasks(taskName: string) {
let found: TaskInputs[] = [];
// normal queues
const queues = await api.resque.queue.queues();
for (const i in queues) {
const q = queues[i];
const length = await api.resque.queue.length(q);
const batchFound = await task.queued(q, 0, length + 1);
let matches = batchFound.filter((t) => t.class === taskName);
matches = matches.map((m) => {
m.timestamp = null;
return m;
});
found = found.concat(matches);
}
// delayed queues
const allDelayed = await api.resque.queue.allDelayed();
for (const timestamp in allDelayed) {
let matches = allDelayed[timestamp].filter((t) => t.class === taskName);
matches = matches.map((m) => {
m.timestamp = parseInt(timestamp);
return m;
});
found = found.concat(matches);
}
return found;
}
/**
* Delete all enqueued instances of a task, both in all the normal queues and all of the delayed queues
*/
export async function deleteEnqueuedTasks(taskName: string, params: {}) {
const queues = await api.resque.queue.queues();
for (const i in queues) {
const q = queues[i];
await api.resque.queue.del(q, taskName, [params]);
await api.resque.queue.delDelayed(q, taskName, [params]);
}
}
}