Skip to content

Commit

Permalink
Add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
adhityamamallan committed Jan 14, 2025
1 parent 450b92b commit 99b9a1e
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 0 deletions.
116 changes: 116 additions & 0 deletions src/route-handlers/cancel-workflow/__tests__/cancel-workflow.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { NextRequest } from 'next/server';

import { GRPCError } from '@/utils/grpc/grpc-error';
import { mockGrpcClusterMethods } from '@/utils/route-handlers-middleware/middlewares/__fixtures__/grpc-cluster-methods';

import { cancelWorkflow } from '../cancel-workflow';
import {
type CancelWorkflowResponse,
type Context,
} from '../cancel-workflow.types';

describe(cancelWorkflow.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('calls requestCancelWorkflow and returns valid response', async () => {
const { res, mockRequestCancelWorkflow } = await setup({});

expect(mockRequestCancelWorkflow).toHaveBeenCalledWith({
domain: 'mock-domain',
workflowExecution: {
workflowId: 'mock-wfid',
runId: 'mock-runid',
},
cause: 'Requesting workflow cancellation from cadence-web UI',
});

const responseJson = await res.json();
expect(responseJson).toEqual({});
});

it('calls requestCancelWorkflow with cancellation reason', async () => {
const { mockRequestCancelWorkflow } = await setup({
requestBody: JSON.stringify({
cause: 'This workflow needs to be cancelled for various reasons',
}),
});

expect(mockRequestCancelWorkflow).toHaveBeenCalledWith(
expect.objectContaining({
cause: 'This workflow needs to be cancelled for various reasons',
})
);
});

it('returns an error if something went wrong in the backend', async () => {
const { res, mockRequestCancelWorkflow } = await setup({
error: true,
});

expect(mockRequestCancelWorkflow).toHaveBeenCalled();

expect(res.status).toEqual(500);
const responseJson = await res.json();
expect(responseJson).toEqual(
expect.objectContaining({
message: 'Could not cancel workflow',
})
);
});

it('returns an error if the request body is not in an expected format', async () => {
const { res, mockRequestCancelWorkflow } = await setup({
requestBody: JSON.stringify({
cause: 5,
}),
});

expect(mockRequestCancelWorkflow).not.toHaveBeenCalled();

const responseJson = await res.json();
expect(responseJson).toEqual(
expect.objectContaining({
message: 'Invalid values provided for workflow cancellation',
})
);
});
});

async function setup({
requestBody,
error,
}: {
requestBody?: string;
error?: true;
}) {
const mockRequestCancelWorkflow = jest
.spyOn(mockGrpcClusterMethods, 'requestCancelWorkflow')
.mockImplementationOnce(async () => {
if (error) {
throw new GRPCError('Could not cancel workflow');
}
return {} satisfies CancelWorkflowResponse;
});

const res = await cancelWorkflow(
new NextRequest('http://localhost', {
method: 'POST',
body: requestBody ?? '{}',
}),
{
params: {
domain: 'mock-domain',
cluster: 'mock-cluster',
workflowId: 'mock-wfid',
runId: 'mock-runid',
},
},
{
grpcClusterMethods: mockGrpcClusterMethods,
} as Context
);

return { res, mockRequestCancelWorkflow };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { NextRequest } from 'next/server';

import { GRPCError } from '@/utils/grpc/grpc-error';
import { mockGrpcClusterMethods } from '@/utils/route-handlers-middleware/middlewares/__fixtures__/grpc-cluster-methods';

import { terminateWorkflow } from '../terminate-workflow';
import {
type TerminateWorkflowResponse,
type Context,
} from '../terminate-workflow.types';

describe(terminateWorkflow.name, () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('calls terminateWorkflow and returns valid response', async () => {
const { res, mockTerminateWorkflow } = await setup({});

expect(mockTerminateWorkflow).toHaveBeenCalledWith({
domain: 'mock-domain',
workflowExecution: {
workflowId: 'mock-wfid',
runId: 'mock-runid',
},
reason: 'Terminating workflow from cadence-web UI',
});

const responseJson = await res.json();
expect(responseJson).toEqual({});
});

it('calls terminateWorkflow with termination reason', async () => {
const { mockTerminateWorkflow } = await setup({
requestBody: JSON.stringify({
reason: 'This workflow needs to be terminated for various reasons',
}),
});

expect(mockTerminateWorkflow).toHaveBeenCalledWith(
expect.objectContaining({
reason: 'This workflow needs to be terminated for various reasons',
})
);
});

it('returns an error if something went wrong in the backend', async () => {
const { res, mockTerminateWorkflow } = await setup({
error: true,
});

expect(mockTerminateWorkflow).toHaveBeenCalled();

expect(res.status).toEqual(500);
const responseJson = await res.json();
expect(responseJson).toEqual(
expect.objectContaining({
message: 'Could not terminate workflow',
})
);
});

it('returns an error if the request body is not in an expected format', async () => {
const { res, mockTerminateWorkflow } = await setup({
requestBody: JSON.stringify({
reason: 5,
}),
});

expect(mockTerminateWorkflow).not.toHaveBeenCalled();

const responseJson = await res.json();
expect(responseJson).toEqual(
expect.objectContaining({
message: 'Invalid values provided for workflow termination',
})
);
});
});

async function setup({
requestBody,
error,
}: {
requestBody?: string;
error?: true;
}) {
const mockTerminateWorkflow = jest
.spyOn(mockGrpcClusterMethods, 'terminateWorkflow')
.mockImplementationOnce(async () => {
if (error) {
throw new GRPCError('Could not terminate workflow');
}
return {} satisfies TerminateWorkflowResponse;
});

const res = await terminateWorkflow(
new NextRequest('http://localhost', {
method: 'POST',
body: requestBody ?? '{}',
}),
{
params: {
domain: 'mock-domain',
cluster: 'mock-cluster',
workflowId: 'mock-wfid',
runId: 'mock-runid',
},
},
{
grpcClusterMethods: mockGrpcClusterMethods,
} as Context
);

return { res, mockTerminateWorkflow };
}
12 changes: 12 additions & 0 deletions src/utils/grpc/grpc-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import { type ListWorkflowExecutionsRequest__Input } from '@/__generated__/proto
import { type ListWorkflowExecutionsResponse } from '@/__generated__/proto-ts/uber/cadence/api/v1/ListWorkflowExecutionsResponse';
import { type QueryWorkflowRequest__Input } from '@/__generated__/proto-ts/uber/cadence/api/v1/QueryWorkflowRequest';
import { type QueryWorkflowResponse } from '@/__generated__/proto-ts/uber/cadence/api/v1/QueryWorkflowResponse';
import { type RequestCancelWorkflowExecutionRequest__Input } from '@/__generated__/proto-ts/uber/cadence/api/v1/RequestCancelWorkflowExecutionRequest';
import { type RequestCancelWorkflowExecutionResponse } from '@/__generated__/proto-ts/uber/cadence/api/v1/RequestCancelWorkflowExecutionResponse';
import { type SignalWorkflowExecutionRequest__Input } from '@/__generated__/proto-ts/uber/cadence/api/v1/SignalWorkflowExecutionRequest';
import { type SignalWorkflowExecutionResponse } from '@/__generated__/proto-ts/uber/cadence/api/v1/SignalWorkflowExecutionResponse';
import { type TerminateWorkflowExecutionRequest__Input } from '@/__generated__/proto-ts/uber/cadence/api/v1/TerminateWorkflowExecutionRequest';
Expand Down Expand Up @@ -93,6 +95,9 @@ export type GRPCClusterMethods = {
terminateWorkflow: (
payload: TerminateWorkflowExecutionRequest__Input
) => Promise<TerminateWorkflowExecutionResponse>;
requestCancelWorkflow: (
payload: RequestCancelWorkflowExecutionRequest__Input
) => Promise<RequestCancelWorkflowExecutionResponse>;
};

const clusterServices = CLUSTERS_CONFIGS.reduce((result, c) => {
Expand Down Expand Up @@ -248,6 +253,13 @@ const getClusterServicesMethods = (
method: 'TerminateWorkflowExecution',
metadata: metadata,
}),
requestCancelWorkflow: workflowService.request<
RequestCancelWorkflowExecutionRequest__Input,
RequestCancelWorkflowExecutionResponse
>({
method: 'RequestCancelWorkflowExecution',
metadata: metadata,
}),
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { type GRPCClusterMethods } from '@/utils/grpc/grpc-client';

export const mockGrpcClusterMethods: GRPCClusterMethods = {
archivedWorkflows: jest.fn(),
closedWorkflows: jest.fn(),
describeCluster: jest.fn(),
describeDomain: jest.fn(),
updateDomain: jest.fn(),
describeTaskList: jest.fn(),
describeWorkflow: jest.fn(),
exportHistory: jest.fn(),
getHistory: jest.fn(),
listDomains: jest.fn(),
listTaskListPartitions: jest.fn(),
listWorkflows: jest.fn(),
openWorkflows: jest.fn(),
queryWorkflow: jest.fn(),
signalWorkflow: jest.fn(),
terminateWorkflow: jest.fn(),
requestCancelWorkflow: jest.fn(),
};

0 comments on commit 99b9a1e

Please sign in to comment.