Adds tests for workflow save error handling. Removes unnecessary code that was attempting to remove credentials from a new node.

This commit is contained in:
mabashian 2021-03-02 14:27:48 -05:00
parent 4cdec9c297
commit 1f93d3ad69
2 changed files with 664 additions and 16 deletions

View File

@ -64,10 +64,10 @@ const getAggregatedCredentials = (
templateDefaultCred.credential_type === overrideCred.credential_type
) {
if (
(!templateDefaultCred.vault_id && !overrideCred.inputs.vault_id) ||
(!templateDefaultCred.vault_id && !overrideCred.inputs?.vault_id) ||
(templateDefaultCred.vault_id &&
overrideCred.inputs.vault_id &&
templateDefaultCred.vault_id === overrideCred.inputs.vault_id)
overrideCred.inputs?.vault_id &&
templateDefaultCred.vault_id === overrideCred.inputs?.vault_id)
) {
credentialHasOverride = true;
}
@ -405,16 +405,7 @@ function Visualizer({ template, i18n }) {
failure_nodes: [],
always_nodes: [],
};
if (node.promptValues?.removedCredentials?.length > 0) {
node.promptValues.removedCredentials.forEach(cred => {
disassociateCredentialRequests.push(
WorkflowJobTemplateNodesAPI.disassociateCredentials(
data.id,
cred.id
)
);
});
}
if (node.promptValues?.addedCredentials?.length > 0) {
node.promptValues.addedCredentials.forEach(cred => {
associateCredentialRequests.push(
@ -583,8 +574,9 @@ function Visualizer({ template, i18n }) {
<AlertModal
isOpen
variant="error"
title={i18n._(t`Error!`)}
title={i18n._(t`Error saving the workflow!`)}
onClose={dismissNodeRequestError}
aria-label={i18n._(t`Error saving the workflow!`)}
>
{i18n._(t`There was an error saving the workflow.`)}
<ErrorDetail error={nodeRequestError} />

View File

@ -2,13 +2,37 @@ import React from 'react';
import { act } from 'react-dom/test-utils';
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
import {
WorkflowApprovalTemplatesAPI,
WorkflowJobTemplateNodesAPI,
WorkflowJobTemplatesAPI,
} from '../../../api';
import Visualizer from './Visualizer';
import workflowReducer from '../../../components/Workflow/workflowReducer';
jest.mock('../../../components/Workflow/workflowReducer');
const realWorkflowReducer = jest.requireActual(
'../../../components/Workflow/workflowReducer'
).default;
jest.mock('../../../api');
const startNode = {
id: 1,
fullUnifiedJobTemplate: {
name: 'START',
},
};
const defaultLinks = [
{
linkType: 'always',
source: { id: 1 },
target: { id: 2 },
},
];
const template = {
id: 1,
name: 'Foo WFJT',
@ -117,7 +141,6 @@ describe('Visualizer', () => {
});
afterAll(() => {
jest.clearAllMocks();
wrapper.unmount();
delete window.SVGElement.prototype.getBBox;
delete window.SVGElement.prototype.getBoundingClientRect;
@ -125,6 +148,12 @@ describe('Visualizer', () => {
delete window.SVGElement.prototype.width;
});
beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();
workflowReducer.mockImplementation(realWorkflowReducer);
});
test('Renders successfully', async () => {
await act(async () => {
wrapper = mountWithContexts(
@ -185,7 +214,7 @@ describe('Visualizer', () => {
wrapper.find('button#link-confirm').simulate('click');
expect(wrapper.find('LinkEditModal').length).toBe(0);
await act(async () => {
wrapper.find('button[aria-label="Save"]').simulate('click');
wrapper.find('Button#visualizer-save').simulate('click');
});
expect(
WorkflowJobTemplateNodesAPI.disassociateAlwaysNode
@ -219,6 +248,633 @@ describe('Visualizer', () => {
).toBe(true);
});
test('Error shown when saving fails due to node add error', async () => {
workflowReducer.mockImplementation(state => {
const newState = {
...state,
isLoading: false,
};
if (newState.nodes.length === 0) {
newState.nodes = [
startNode,
{
id: 2,
fullUnifiedJobTemplate: {
id: 3,
name: 'PING',
type: 'job_template',
},
},
];
newState.links = defaultLinks;
}
return newState;
});
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
data: {
count: 0,
results: [],
},
});
WorkflowJobTemplatesAPI.createNode.mockRejectedValue(new Error());
await act(async () => {
wrapper = mountWithContexts(
<svg>
<Visualizer template={template} />
</svg>
);
});
wrapper.update();
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(0);
await act(async () => {
wrapper.find('Button#visualizer-save').simulate('click');
});
wrapper.update();
expect(WorkflowJobTemplatesAPI.createNode).toHaveBeenCalledTimes(1);
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(1);
});
test('Error shown when saving fails due to node edit error', async () => {
workflowReducer.mockImplementation(state => {
const newState = {
...state,
isLoading: false,
};
if (newState.nodes.length === 0) {
newState.nodes = [
startNode,
{
id: 2,
isEdited: true,
fullUnifiedJobTemplate: {
id: 3,
name: 'PING',
type: 'job_template',
},
originalNodeObject: {
id: 9000,
},
},
];
newState.links = defaultLinks;
}
return newState;
});
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
data: {
count: 0,
results: [],
},
});
WorkflowJobTemplateNodesAPI.replace.mockRejectedValue(new Error());
await act(async () => {
wrapper = mountWithContexts(
<svg>
<Visualizer template={template} />
</svg>
);
});
wrapper.update();
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(0);
await act(async () => {
wrapper.find('Button#visualizer-save').simulate('click');
});
wrapper.update();
expect(WorkflowJobTemplateNodesAPI.replace).toHaveBeenCalledTimes(1);
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(1);
});
test('Error shown when saving fails due to approval template add error', async () => {
workflowReducer.mockImplementation(state => {
const newState = {
...state,
isLoading: false,
};
if (newState.nodes.length === 0) {
newState.nodes = [
startNode,
{
id: 2,
fullUnifiedJobTemplate: {
id: 3,
name: 'Approval',
timeout: 1000,
type: 'workflow_approval_template',
},
},
];
newState.links = defaultLinks;
}
return newState;
});
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
data: {
count: 0,
results: [],
},
});
WorkflowJobTemplatesAPI.createNode.mockResolvedValue({
data: {
id: 9001,
},
});
WorkflowJobTemplateNodesAPI.createApprovalTemplate.mockRejectedValue(
new Error()
);
await act(async () => {
wrapper = mountWithContexts(
<svg>
<Visualizer template={template} />
</svg>
);
});
wrapper.update();
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(0);
await act(async () => {
wrapper.find('Button#visualizer-save').simulate('click');
});
wrapper.update();
expect(WorkflowJobTemplatesAPI.createNode).toHaveBeenCalledTimes(1);
expect(
WorkflowJobTemplateNodesAPI.createApprovalTemplate
).toHaveBeenCalledTimes(1);
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(1);
});
test('Error shown when saving fails due to approval template edit error', async () => {
workflowReducer.mockImplementation(state => {
const newState = {
...state,
isLoading: false,
};
if (newState.nodes.length === 0) {
newState.nodes = [
startNode,
{
id: 2,
isEdited: true,
fullUnifiedJobTemplate: {
id: 3,
name: 'Approval',
timeout: 1000,
type: 'workflow_approval_template',
},
originalNodeObject: {
id: 9000,
summary_fields: {
unified_job_template: {
unified_job_type: 'workflow_approval',
},
},
},
},
];
newState.links = defaultLinks;
}
return newState;
});
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
data: {
count: 0,
results: [],
},
});
WorkflowApprovalTemplatesAPI.update.mockRejectedValue(new Error());
await act(async () => {
wrapper = mountWithContexts(
<svg>
<Visualizer template={template} />
</svg>
);
});
wrapper.update();
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(0);
await act(async () => {
wrapper.find('Button#visualizer-save').simulate('click');
});
wrapper.update();
expect(WorkflowApprovalTemplatesAPI.update).toHaveBeenCalledTimes(1);
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(1);
});
test('Error shown when saving fails due to node disassociate failure', async () => {
workflowReducer.mockImplementation(state => {
const newState = {
...state,
isLoading: false,
};
if (newState.nodes.length === 0) {
newState.nodes = [
startNode,
{
id: 2,
fullUnifiedJobTemplate: {
id: 3,
name: 'Approval',
timeout: 1000,
type: 'workflow_approval_template',
},
originalNodeObject: {
id: 9000,
summary_fields: {
unified_job_template: {
unified_job_type: 'workflow_approval',
},
},
success_nodes: [],
failure_nodes: [3],
always_nodes: [],
},
success_nodes: [3],
failure_nodes: [],
always_nodes: [],
},
{
id: 3,
fullUnifiedJobTemplate: {
id: 4,
name: 'Approval 2',
timeout: 1000,
type: 'workflow_approval_template',
},
originalNodeObject: {
id: 9001,
summary_fields: {
unified_job_template: {
unified_job_type: 'workflow_approval',
},
},
success_nodes: [],
failure_nodes: [],
always_nodes: [],
},
success_nodes: [],
failure_nodes: [],
always_nodes: [],
},
];
newState.links = [
{
linkType: 'always',
source: { id: 1 },
target: { id: 2 },
},
{
linkType: 'success',
source: { id: 2 },
target: { id: 3 },
},
];
}
return newState;
});
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
data: {
count: 0,
results: [],
},
});
WorkflowJobTemplateNodesAPI.disassociateFailuresNode.mockRejectedValue(
new Error()
);
await act(async () => {
wrapper = mountWithContexts(
<svg>
<Visualizer template={template} />
</svg>
);
});
wrapper.update();
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(0);
await act(async () => {
wrapper.find('Button#visualizer-save').simulate('click');
});
wrapper.update();
expect(
WorkflowJobTemplateNodesAPI.disassociateFailuresNode
).toHaveBeenCalledTimes(1);
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(1);
});
test('Error shown when saving fails due to node associate failure', async () => {
workflowReducer.mockImplementation(state => {
const newState = {
...state,
isLoading: false,
};
if (newState.nodes.length === 0) {
newState.nodes = [
startNode,
{
id: 2,
fullUnifiedJobTemplate: {
id: 3,
name: 'Approval',
timeout: 1000,
type: 'workflow_approval_template',
},
originalNodeObject: {
id: 9000,
summary_fields: {
unified_job_template: {
unified_job_type: 'workflow_approval',
},
},
success_nodes: [],
failure_nodes: [3],
always_nodes: [],
},
success_nodes: [3],
failure_nodes: [],
always_nodes: [],
},
{
id: 3,
fullUnifiedJobTemplate: {
id: 4,
name: 'Approval 2',
timeout: 1000,
type: 'workflow_approval_template',
},
originalNodeObject: {
id: 9001,
summary_fields: {
unified_job_template: {
unified_job_type: 'workflow_approval',
},
},
success_nodes: [],
failure_nodes: [],
always_nodes: [],
},
success_nodes: [],
failure_nodes: [],
always_nodes: [],
},
];
newState.links = [
{
linkType: 'always',
source: { id: 1 },
target: { id: 2 },
},
{
linkType: 'success',
source: { id: 2 },
target: { id: 3 },
},
];
}
return newState;
});
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
data: {
count: 0,
results: [],
},
});
WorkflowJobTemplateNodesAPI.disassociateFailuresNode.mockResolvedValue();
WorkflowJobTemplateNodesAPI.associateSuccessNode.mockRejectedValue(
new Error()
);
await act(async () => {
wrapper = mountWithContexts(
<svg>
<Visualizer template={template} />
</svg>
);
});
wrapper.update();
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(0);
await act(async () => {
wrapper.find('Button#visualizer-save').simulate('click');
});
wrapper.update();
expect(
WorkflowJobTemplateNodesAPI.associateSuccessNode
).toHaveBeenCalledTimes(1);
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(1);
});
test('Error shown when saving fails due to credential disassociate failure', async () => {
workflowReducer.mockImplementation(state => {
const newState = {
...state,
isLoading: false,
};
if (newState.nodes.length === 0) {
newState.nodes = [
startNode,
{
id: 2,
isEdited: true,
fullUnifiedJobTemplate: {
id: 3,
name: 'Ping',
type: 'job_template',
},
originalNodeObject: {
id: 9000,
success_nodes: [],
failure_nodes: [],
always_nodes: [],
},
originalNodeCredentials: [
{
id: 456,
credential_type: 1,
},
],
promptValues: {
credentials: [
{
id: 123,
credential_type: 1,
},
],
},
launchConfig: {
defaults: {
credentials: [
{
id: 456,
credential_type: 1,
},
],
},
},
success_nodes: [],
failure_nodes: [],
always_nodes: [],
},
];
newState.links = defaultLinks;
}
return newState;
});
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
data: {
count: 0,
results: [],
},
});
WorkflowJobTemplateNodesAPI.replace.mockResolvedValue();
WorkflowJobTemplateNodesAPI.disassociateCredentials.mockRejectedValue(
new Error()
);
await act(async () => {
wrapper = mountWithContexts(
<svg>
<Visualizer template={template} />
</svg>
);
});
wrapper.update();
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(0);
await act(async () => {
wrapper.find('Button#visualizer-save').simulate('click');
});
wrapper.update();
expect(
WorkflowJobTemplateNodesAPI.disassociateCredentials
).toHaveBeenCalledTimes(1);
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(1);
});
test('Error shown when saving fails due to credential associate failure', async () => {
workflowReducer.mockImplementation(state => {
const newState = {
...state,
isLoading: false,
};
if (newState.nodes.length === 0) {
newState.nodes = [
startNode,
{
id: 2,
isEdited: true,
fullUnifiedJobTemplate: {
id: 3,
name: 'Ping',
type: 'job_template',
},
originalNodeObject: {
id: 9000,
success_nodes: [],
failure_nodes: [],
always_nodes: [],
},
originalNodeCredentials: [
{
id: 456,
credential_type: 1,
},
],
promptValues: {
credentials: [
{
id: 123,
credential_type: 1,
},
],
},
launchConfig: {
defaults: {
credentials: [
{
id: 456,
credential_type: 1,
},
],
},
},
success_nodes: [],
failure_nodes: [],
always_nodes: [],
},
];
newState.links = defaultLinks;
}
return newState;
});
WorkflowJobTemplatesAPI.readNodes.mockResolvedValue({
data: {
count: 0,
results: [],
},
});
WorkflowJobTemplateNodesAPI.replace.mockResolvedValue();
WorkflowJobTemplateNodesAPI.disassociateCredentials.mockResolvedValue();
WorkflowJobTemplateNodesAPI.associateCredentials.mockRejectedValue(
new Error()
);
await act(async () => {
wrapper = mountWithContexts(
<svg>
<Visualizer template={template} />
</svg>
);
});
wrapper.update();
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(0);
await act(async () => {
wrapper.find('Button#visualizer-save').simulate('click');
});
wrapper.update();
expect(
WorkflowJobTemplateNodesAPI.associateCredentials
).toHaveBeenCalledTimes(1);
expect(
wrapper.find('AlertModal[title="Error saving the workflow!"]').length
).toBe(1);
});
test('Error shown to user when error thrown fetching workflow nodes', async () => {
WorkflowJobTemplatesAPI.readNodes.mockRejectedValue(new Error());
await act(async () => {