test(editor): Test workflow moving with used credentials

This commit is contained in:
Csaba Tuncsik 2025-01-10 15:14:30 +01:00
parent 2e3503fa10
commit d0f3a10f43
No known key found for this signature in database
2 changed files with 111 additions and 21 deletions

View file

@ -1,12 +1,18 @@
import { createTestingPinia } from '@pinia/testing';
import userEvent from '@testing-library/user-event';
import { createComponentRenderer } from '@/__tests__/render';
import { createTestWorkflow } from '@/__tests__/mocks';
import { createProjectListItem } from '@/__tests__/data/projects';
import { getDropdownItems, mockedStore } from '@/__tests__/utils';
import type { MockedStore } from '@/__tests__/utils';
import { PROJECT_MOVE_RESOURCE_MODAL } from '@/constants';
import ProjectMoveResourceModal from '@/components/Projects/ProjectMoveResourceModal.vue';
import { useTelemetry } from '@/composables/useTelemetry';
import { mockedStore } from '@/__tests__/utils';
import { useProjectsStore } from '@/stores/projects.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
const renderComponent = createComponentRenderer(ProjectMoveResourceModal, {
pinia: createTestingPinia(),
global: {
stubs: {
Modal: {
@ -18,28 +24,22 @@ const renderComponent = createComponentRenderer(ProjectMoveResourceModal, {
});
let telemetry: ReturnType<typeof useTelemetry>;
let projectsStore: MockedStore<typeof useProjectsStore>;
let workflowsStore: MockedStore<typeof useWorkflowsStore>;
describe('ProjectMoveResourceModal', () => {
beforeEach(() => {
vi.clearAllMocks();
telemetry = useTelemetry();
projectsStore = mockedStore(useProjectsStore);
workflowsStore = mockedStore(useWorkflowsStore);
});
it('should send telemetry when mounted', async () => {
const pinia = createTestingPinia();
const telemetryTrackSpy = vi.spyOn(telemetry, 'track');
const projectsStore = mockedStore(useProjectsStore);
projectsStore.availableProjects = [
{
id: '1',
name: 'My Project',
icon: { type: 'icon', value: 'folder' },
type: 'personal',
role: 'project:personalOwner',
createdAt: '2021-01-01T00:00:00.000Z',
updatedAt: '2021-01-01T00:00:00.000Z',
},
];
projectsStore.availableProjects = [createProjectListItem()];
workflowsStore.fetchWorkflow.mockResolvedValueOnce(createTestWorkflow());
const props = {
modalName: PROJECT_MOVE_RESOURCE_MODAL,
@ -55,7 +55,7 @@ describe('ProjectMoveResourceModal', () => {
},
},
};
renderComponent({ props, pinia });
renderComponent({ props });
expect(telemetryTrackSpy).toHaveBeenCalledWith(
'User clicked to move a workflow',
expect.objectContaining({ workflow_id: '1' }),
@ -63,10 +63,8 @@ describe('ProjectMoveResourceModal', () => {
});
it('should show no available projects message', async () => {
const pinia = createTestingPinia();
const projectsStore = mockedStore(useProjectsStore);
projectsStore.availableProjects = [];
workflowsStore.fetchWorkflow.mockResolvedValueOnce(createTestWorkflow());
const props = {
modalName: PROJECT_MOVE_RESOURCE_MODAL,
@ -82,7 +80,89 @@ describe('ProjectMoveResourceModal', () => {
},
},
};
const { getByText } = renderComponent({ props, pinia });
const { getByText } = renderComponent({ props });
expect(getByText(/Currently there are not any projects or users available/)).toBeVisible();
});
it('should not load workflow if the resource is a credential', async () => {
const telemetryTrackSpy = vi.spyOn(telemetry, 'track');
projectsStore.availableProjects = [createProjectListItem()];
const props = {
modalName: PROJECT_MOVE_RESOURCE_MODAL,
data: {
resourceType: 'credential',
resourceTypeLabel: 'Credential',
resource: {
id: '1',
homeProject: {
id: '2',
name: 'My Project',
},
},
},
};
renderComponent({ props });
expect(telemetryTrackSpy).toHaveBeenCalledWith(
'User clicked to move a credential',
expect.objectContaining({ credential_id: '1' }),
);
expect(workflowsStore.fetchWorkflow).not.toHaveBeenCalled();
});
it('should send credential IDs when workflow moved with used credentials and checkbox checked', async () => {
const destinationProject = createProjectListItem();
const currentProjectId = '123';
const movedWorkflow = {
...createTestWorkflow(),
usedCredentials: [
{
id: '1',
name: 'PG Credential',
credentialType: 'postgres',
currentUserHasAccess: true,
},
{
id: '2',
name: 'Notion Credential',
credentialType: 'notion',
currentUserHasAccess: true,
},
],
};
projectsStore.currentProjectId = currentProjectId;
projectsStore.availableProjects = [destinationProject];
workflowsStore.fetchWorkflow.mockResolvedValueOnce(movedWorkflow);
const props = {
modalName: PROJECT_MOVE_RESOURCE_MODAL,
data: {
resourceType: 'workflow',
resourceTypeLabel: 'Workflow',
resource: movedWorkflow,
},
};
const { getByTestId } = renderComponent({ props });
expect(getByTestId('project-move-resource-modal-button')).toBeDisabled();
const projectSelect = getByTestId('project-move-resource-modal-select');
expect(projectSelect).toBeVisible();
const projectSelectDropdownItems = await getDropdownItems(projectSelect);
await userEvent.click(projectSelectDropdownItems[0]);
expect(getByTestId('project-move-resource-modal-button')).toBeEnabled();
await userEvent.click(getByTestId('project-move-resource-modal-checkbox-all'));
await userEvent.click(getByTestId('project-move-resource-modal-button'));
expect(projectsStore.moveResourceToProject).toHaveBeenCalledWith(
'workflow',
movedWorkflow.id,
destinationProject.id,
['1', '2'],
);
});
});

View file

@ -54,6 +54,7 @@ const selectedProject = computed(() =>
);
const isResourceInTeamProject = computed(() => isHomeProjectTeam(props.data.resource));
const isResourceWorkflow = computed(() => props.data.resourceType === ResourceType.Workflow);
const isResourceCredential = computed(() => props.data.resourceType === ResourceType.Credential);
const isHomeProjectTeam = (resource: IWorkflowDb | ICredentialsResponse) =>
resource.homeProject?.type === ProjectTypes.Team;
@ -184,7 +185,10 @@ onMounted(async () => {
></N8nOption>
</N8nSelect>
<N8nText>
<i18n-t keypath="projects.move.resource.modal.message.sharingNote">
<i18n-t
v-if="isResourceCredential"
keypath="projects.move.resource.modal.message.sharingNote"
>
<template #note
><strong>{{
i18n.baseText('projects.move.resource.modal.message.note')
@ -209,6 +213,7 @@ onMounted(async () => {
v-if="usedCredentials.length"
v-model="shareUsedCredentials"
:class="$style.textBlock"
data-test-id="project-move-resource-modal-checkbox-all"
>
<i18n-t keypath="projects.move.resource.modal.message.usedCredentials">
<template #usedCredentials>
@ -255,7 +260,12 @@ onMounted(async () => {
<N8nButton type="secondary" text class="mr-2xs" @click="closeModal">
{{ i18n.baseText('generic.cancel') }}
</N8nButton>
<N8nButton :disabled="!projectId" type="primary" @click="moveResource">
<N8nButton
:disabled="!projectId"
type="primary"
data-test-id="project-move-resource-modal-button"
@click="moveResource"
>
{{
i18n.baseText('projects.move.resource.modal.button', {
interpolate: { resourceTypeLabel: props.data.resourceTypeLabel },