Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d31309a92d |
4
.github/workflows/playwright.yml
vendored
4
.github/workflows/playwright.yml
vendored
@@ -12,6 +12,9 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
BULLMQ_DASHBOARD_USERNAME: root
|
||||||
|
BULLMQ_DASHBOARD_PASSWORD: sample
|
||||||
|
ENABLE_BULLMQ_DASHBOARD: true
|
||||||
ENCRYPTION_KEY: sample_encryption_key
|
ENCRYPTION_KEY: sample_encryption_key
|
||||||
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
WEBHOOK_SECRET_KEY: sample_webhook_secret_key
|
||||||
APP_SECRET_KEY: sample_app_secret_key
|
APP_SECRET_KEY: sample_app_secret_key
|
||||||
@@ -22,6 +25,7 @@ env:
|
|||||||
POSTGRES_PASSWORD: automatisch_password
|
POSTGRES_PASSWORD: automatisch_password
|
||||||
REDIS_HOST: localhost
|
REDIS_HOST: localhost
|
||||||
APP_ENV: production
|
APP_ENV: production
|
||||||
|
PORT: 3000
|
||||||
LICENSE_KEY: dummy_license_key
|
LICENSE_KEY: dummy_license_key
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@@ -2,4 +2,6 @@ POSTGRES_DB=automatisch
|
|||||||
POSTGRES_USER=automatisch_user
|
POSTGRES_USER=automatisch_user
|
||||||
POSTGRES_PASSWORD=automatisch_password
|
POSTGRES_PASSWORD=automatisch_password
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
POSTGRES_HOST=localhost
|
POSTGRES_HOST=localhost
|
||||||
|
BULLMQ_DASHBOARD_PASSWORD=sample
|
||||||
|
BULLMQ_DASHBOARD_USERNAME=root
|
35
packages/e2e-tests/fixtures/flows-page.js
Normal file
35
packages/e2e-tests/fixtures/flows-page.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const { AuthenticatedPage } = require('./authenticated-page');
|
||||||
|
const { expect } = require('@playwright/test');
|
||||||
|
|
||||||
|
export class FlowsPage extends AuthenticatedPage {
|
||||||
|
constructor(page) {
|
||||||
|
super(page);
|
||||||
|
|
||||||
|
this.flowRow = this.page.getByTestId('flow-row');
|
||||||
|
this.flowCard = this.page.getByTestId('card-action-area');
|
||||||
|
this.deleteFlowMenuItem = this.page.getByRole('menuitem', {
|
||||||
|
name: 'Delete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickOnDeleteFlowMenuItem() {
|
||||||
|
await this.deleteFlowMenuItem.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFlow(flowId) {
|
||||||
|
const desiredFlow = await this.flowRow.filter({
|
||||||
|
has: this.page.locator(`a[href="/editor/${flowId}"]`),
|
||||||
|
});
|
||||||
|
await desiredFlow.locator('button').click();
|
||||||
|
await this.clickOnDeleteFlowMenuItem();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
await this.flowRow.filter({
|
||||||
|
has: this.page.locator(`a[href="/editor/${flowId}"]`),
|
||||||
|
})
|
||||||
|
).toHaveCount(0);
|
||||||
|
|
||||||
|
const snackbar = await this.getSnackbarData();
|
||||||
|
await expect(snackbar.variant).toBe('success');
|
||||||
|
}
|
||||||
|
}
|
@@ -9,6 +9,7 @@ const { AcceptInvitation } = require('./accept-invitation-page');
|
|||||||
const { adminFixtures } = require('./admin');
|
const { adminFixtures } = require('./admin');
|
||||||
const { AdminSetupPage } = require('./admin-setup-page');
|
const { AdminSetupPage } = require('./admin-setup-page');
|
||||||
const { AdminCreateUserPage } = require('./admin/create-user-page');
|
const { AdminCreateUserPage } = require('./admin/create-user-page');
|
||||||
|
const { FlowsPage } = require('./flows-page');
|
||||||
|
|
||||||
exports.test = test.extend({
|
exports.test = test.extend({
|
||||||
page: async ({ page }, use) => {
|
page: async ({ page }, use) => {
|
||||||
@@ -35,6 +36,9 @@ exports.test = test.extend({
|
|||||||
userInterfacePage: async ({ page }, use) => {
|
userInterfacePage: async ({ page }, use) => {
|
||||||
await use(new UserInterfacePage(page));
|
await use(new UserInterfacePage(page));
|
||||||
},
|
},
|
||||||
|
flowsPage: async ({ page }, use) => {
|
||||||
|
await use(new FlowsPage(page));
|
||||||
|
},
|
||||||
...adminFixtures,
|
...adminFixtures,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
20
packages/e2e-tests/helpers/bullmq-helper.js
Normal file
20
packages/e2e-tests/helpers/bullmq-helper.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const { expect } = require('../fixtures/index');
|
||||||
|
|
||||||
|
export const expectNoDelayedJobForFlow = async (request, flowId) => {
|
||||||
|
const token = btoa(
|
||||||
|
`${process.env.BULLMQ_DASHBOARD_USERNAME}:${process.env.BULLMQ_DASHBOARD_PASSWORD}`
|
||||||
|
);
|
||||||
|
const queues = await request.get(
|
||||||
|
`http://localhost:${process.env.PORT}/admin/queues/api/queues?activeQueue=flow&status=delayed&page=1`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: `Basic ${token}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const queuesJsonResponse = await queues.json();
|
||||||
|
const flowQueue = queuesJsonResponse.queues.find(
|
||||||
|
(queue) => queue.name === 'flow'
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
flowQueue.jobs.find((job) => job.name === `flow-${flowId}`)
|
||||||
|
).toBeUndefined();
|
||||||
|
};
|
54
packages/e2e-tests/helpers/flow-api-helper.js
Normal file
54
packages/e2e-tests/helpers/flow-api-helper.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
const { expect } = require('../fixtures/index');
|
||||||
|
|
||||||
|
export const createFlow = async (request, token) => {
|
||||||
|
const response = await request.post(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/flows`,
|
||||||
|
{ headers: { Authorization: token } }
|
||||||
|
);
|
||||||
|
await expect(response.status()).toBe(201);
|
||||||
|
return await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFlowName = async (request, token, flowId) => {
|
||||||
|
const updateFlowNameResponse = await request.patch(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/flows/${flowId}`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
data: { name: flowId },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(updateFlowNameResponse.status()).toBe(200);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateFlowStep = async (request, token, stepId, requestBody) => {
|
||||||
|
const updateTriggerStepResponse = await request.patch(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/steps/${stepId}`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
data: requestBody,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(updateTriggerStepResponse.status()).toBe(200);
|
||||||
|
return await updateTriggerStepResponse.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testStep = async (request, token, stepId) => {
|
||||||
|
const testTriggerStepResponse = await request.post(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/steps/${stepId}/test`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(testTriggerStepResponse.status()).toBe(200);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const publishFlow = async (request, token, flowId) => {
|
||||||
|
const publishFlowResponse = await request.patch(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/flows/${flowId}/status`,
|
||||||
|
{
|
||||||
|
headers: { Authorization: token },
|
||||||
|
data: { active: true },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(publishFlowResponse.status()).toBe(200);
|
||||||
|
};
|
213
packages/e2e-tests/tests/flow/delete-flow.spec.js
Normal file
213
packages/e2e-tests/tests/flow/delete-flow.spec.js
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
const { test, expect } = require('../../fixtures/index');
|
||||||
|
const { expectNoDelayedJobForFlow } = require('../../helpers/bullmq-helper');
|
||||||
|
const {
|
||||||
|
createFlow,
|
||||||
|
updateFlowName,
|
||||||
|
updateFlowStep,
|
||||||
|
testStep,
|
||||||
|
publishFlow,
|
||||||
|
} = require('../../helpers/flow-api-helper');
|
||||||
|
|
||||||
|
let tokenJsonResponse;
|
||||||
|
|
||||||
|
test.beforeAll(async ({ request }) => {
|
||||||
|
const tokenResponse = await request.post(
|
||||||
|
`http://localhost:${process.env.PORT}/api/v1/access-tokens`,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
email: process.env.LOGIN_EMAIL,
|
||||||
|
password: process.env.LOGIN_PASSWORD,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await expect(tokenResponse.status()).toBe(200);
|
||||||
|
tokenJsonResponse = await tokenResponse.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Empty flow can be deleted', async ({ page, request, flowsPage }) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Completed webhook flow can be deleted', async ({
|
||||||
|
page,
|
||||||
|
request,
|
||||||
|
flowsPage,
|
||||||
|
}) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
const flowSteps = flow.data.steps;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||||
|
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||||
|
|
||||||
|
const triggerStep = await updateFlowStep(
|
||||||
|
request,
|
||||||
|
tokenJsonResponse.data.token,
|
||||||
|
triggerStepId,
|
||||||
|
{
|
||||||
|
appKey: 'webhook',
|
||||||
|
key: 'catchRawWebhook',
|
||||||
|
parameters: {
|
||||||
|
workSynchronously: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, triggerStepId);
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, actionStepId, {
|
||||||
|
appKey: 'webhook',
|
||||||
|
key: 'respondWith',
|
||||||
|
parameters: {
|
||||||
|
statusCode: '200',
|
||||||
|
body: 'ok',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, actionStepId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
const triggerWebhookResponse = await request.get(triggerStep.data.webhookUrl);
|
||||||
|
await expect(triggerWebhookResponse.status()).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Completed poll flow can be deleted', async ({
|
||||||
|
page,
|
||||||
|
request,
|
||||||
|
flowsPage,
|
||||||
|
}) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
const flowSteps = flow.data.steps;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||||
|
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, triggerStepId, {
|
||||||
|
appKey: 'rss',
|
||||||
|
key: 'newItemsInFeed',
|
||||||
|
parameters: { feedUrl: 'https://feeds.bbci.co.uk/news/rss.xml' },
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, triggerStepId);
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, actionStepId, {
|
||||||
|
appKey: 'datastore',
|
||||||
|
key: 'setValue',
|
||||||
|
parameters: {
|
||||||
|
key: 'newsTitle',
|
||||||
|
value: '{{step.' + triggerStepId + '.title}}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, actionStepId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
|
||||||
|
await expectNoDelayedJobForFlow(request, flowId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Published webhook flow can be deleted', async ({
|
||||||
|
page,
|
||||||
|
request,
|
||||||
|
flowsPage,
|
||||||
|
}) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
const flowSteps = flow.data.steps;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||||
|
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||||
|
|
||||||
|
const triggerStep = await updateFlowStep(
|
||||||
|
request,
|
||||||
|
tokenJsonResponse.data.token,
|
||||||
|
triggerStepId,
|
||||||
|
{
|
||||||
|
appKey: 'webhook',
|
||||||
|
key: 'catchRawWebhook',
|
||||||
|
parameters: {
|
||||||
|
workSynchronously: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, triggerStepId);
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, actionStepId, {
|
||||||
|
appKey: 'webhook',
|
||||||
|
key: 'respondWith',
|
||||||
|
parameters: {
|
||||||
|
statusCode: '200',
|
||||||
|
body: 'ok',
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: '',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, actionStepId);
|
||||||
|
|
||||||
|
await publishFlow(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
const triggerWebhookResponse = await request.get(triggerStep.data.webhookUrl);
|
||||||
|
await expect(triggerWebhookResponse.status()).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Published poll flow can be deleted', async ({
|
||||||
|
page,
|
||||||
|
request,
|
||||||
|
flowsPage,
|
||||||
|
}) => {
|
||||||
|
const flow = await createFlow(request, tokenJsonResponse.data.token);
|
||||||
|
const flowId = flow.data.id;
|
||||||
|
const flowSteps = flow.data.steps;
|
||||||
|
await updateFlowName(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id;
|
||||||
|
const actionStepId = flowSteps.find((step) => step.type === 'action').id;
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, triggerStepId, {
|
||||||
|
appKey: 'rss',
|
||||||
|
key: 'newItemsInFeed',
|
||||||
|
parameters: { feedUrl: 'https://feeds.bbci.co.uk/news/rss.xml' },
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, triggerStepId);
|
||||||
|
|
||||||
|
await updateFlowStep(request, tokenJsonResponse.data.token, actionStepId, {
|
||||||
|
appKey: 'datastore',
|
||||||
|
key: 'setValue',
|
||||||
|
parameters: {
|
||||||
|
key: 'newsTitle',
|
||||||
|
value: '{{step.' + triggerStepId + '.title}}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await testStep(request, tokenJsonResponse.data.token, actionStepId);
|
||||||
|
|
||||||
|
await publishFlow(request, tokenJsonResponse.data.token, flowId);
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
|
await flowsPage.deleteFlow(flowId);
|
||||||
|
|
||||||
|
await expectNoDelayedJobForFlow(request, flowId);
|
||||||
|
});
|
Reference in New Issue
Block a user