From fb1dc2e0ba7c47d70f9ca53bcb60c32d6919b346 Mon Sep 17 00:00:00 2001 From: Stefano Magni Date: Tue, 28 Jun 2022 07:39:33 +0200 Subject: [PATCH] console: improve actions E2E tests with rest connectors PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4520 GitOrigin-RevId: 24d9176dc5f93747e4b3a12ed08171064e75900e --- .../helpers/webhookTransformHelpers.ts | 105 ----- .../mutation/mutationAction.e2e.test.ts | 18 +- .../actions/query/queryAction.e2e.test.ts | 11 +- console/cypress/integration/actions/spec.ts | 371 ------------------ console/cypress/integration/actions/test.ts | 91 +++-- .../actionWithTransform.e2e.test.ts | 36 +- .../utils/services/deleteV1LoginAction.ts | 13 + .../utils/testState/v1LoginActionMustExist.ts | 122 ++++++ .../testState/v1LoginActionMustNotExist.ts | 21 + .../v1ActionWithTransform.e2e.test.ts | 100 +++++ console/cypress/support/commands.ts | 4 - console/cypress/support/index.d.ts | 10 +- 12 files changed, 319 insertions(+), 583 deletions(-) delete mode 100644 console/cypress/integration/actions/spec.ts create mode 100644 console/cypress/integration/actions/withTransform/utils/services/deleteV1LoginAction.ts create mode 100644 console/cypress/integration/actions/withTransform/utils/testState/v1LoginActionMustExist.ts create mode 100644 console/cypress/integration/actions/withTransform/utils/testState/v1LoginActionMustNotExist.ts create mode 100644 console/cypress/integration/actions/withTransform/v1ActionWithTransform.e2e.test.ts diff --git a/console/cypress/helpers/webhookTransformHelpers.ts b/console/cypress/helpers/webhookTransformHelpers.ts index 07e97f40427..040e793a5d8 100644 --- a/console/cypress/helpers/webhookTransformHelpers.ts +++ b/console/cypress/helpers/webhookTransformHelpers.ts @@ -6,12 +6,6 @@ export const togglePayloadTransformSection = () => { }); }; -export const toggleContextArea = () => { - cy.getBySel('toggle-context-area').click({ - force: true, - }); -}; - export const toggleRequestTransformSection = () => { cy.getBySel('toggle-request-transform').click({ force: true, @@ -47,19 +41,6 @@ export const checkTransformRequestUrlError = ( } }; -export const typeIntoContextAreaEnvVars = ( - envVars: { key: string; value: string }[] -) => { - envVars.forEach((q, i) => { - cy.getBySel(`transform-env-vars-kv-key-${i}`).type(q.key, { - parseSpecialCharSequences: false, - }); - cy.getBySel(`transform-env-vars-kv-value-${i}`).type(q.value, { - parseSpecialCharSequences: false, - }); - }); -}; - export const typeIntoRequestQueryParams = ( queryParams: { key: string; value: string }[] ) => { @@ -99,89 +80,3 @@ export const checkTransformRequestBodyError = (exists: boolean) => { cy.getBySel('transform-requestBody-error').should('not.exist'); } }; - -export const getActionTransfromV1RequestBody = (actionName: string) => ({ - type: 'bulk', - source: 'default', - args: [ - { - type: 'set_custom_types', - args: { - scalars: [], - input_objects: [ - { - name: 'SampleInput', - description: null, - fields: [ - { - name: 'username', - type: 'String!', - description: null, - }, - { - name: 'password', - type: 'String!', - description: null, - }, - ], - }, - ], - objects: [ - { - name: 'SampleOutput', - description: null, - fields: [ - { - name: 'accessToken', - type: 'String!', - description: null, - }, - ], - }, - { - name: 'LoginResponse', - fields: [ - { - name: 'accessToken', - type: 'String!', - }, - ], - }, - ], - enums: [], - }, - }, - { - type: 'create_action', - args: { - name: actionName, - definition: { - arguments: [ - { - name: 'arg1', - type: 'SampleInput!', - description: null, - }, - ], - kind: 'synchronous', - output_type: 'SampleOutput', - handler: 'http://host.docker.internal:3000', - type: 'mutation', - headers: [], - timeout: null, - request_transform: { - version: 1, - template_engine: 'Kriti', - method: 'GET', - url: '{{$base_url}}/users', - query_params: {}, - body: - '{\n "users": {\n "name": {{$body.input.arg1.username}}\n }\n}', - content_type: 'application/json', - }, - }, - comment: null, - }, - }, - ], -}); diff --git a/console/cypress/integration/actions/mutation/mutationAction.e2e.test.ts b/console/cypress/integration/actions/mutation/mutationAction.e2e.test.ts index ce2a2e67a6c..6fe23bfff14 100644 --- a/console/cypress/integration/actions/mutation/mutationAction.e2e.test.ts +++ b/console/cypress/integration/actions/mutation/mutationAction.e2e.test.ts @@ -68,21 +68,13 @@ if (testMode !== 'cli') { parseSpecialCharSequences: false, }); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `test_webhook_transform` - - // -------------------- - // Pleas note that the custom timeout is not checked explicitly checked, but asserting on - // the payload (see the next cy.intercept) means asserting on it too - cy.log('**--- Type in the Custom Timeout field**'); - cy.getBySel('action-timeout-seconds').clear().type('25'); + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Click the Create button**'); cy.getBySel('create-action-btn').click(); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `create_action` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check if the success notification is visible**'); @@ -123,8 +115,7 @@ if (testMode !== 'cli') { cy.log('**--- Click Save Permissions**'); cy.getBySel('save-permissions-for-action').click(); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `create_action_permission` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check if the success notification is visible**'); @@ -163,8 +154,7 @@ if (testMode !== 'cli') { cy.log('**--- Check the prompt has been called**'); cy.window().its('prompt').should('be.called'); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `drop_action` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check if the success notification is visible**'); diff --git a/console/cypress/integration/actions/query/queryAction.e2e.test.ts b/console/cypress/integration/actions/query/queryAction.e2e.test.ts index 56215706160..4fdf35a0763 100644 --- a/console/cypress/integration/actions/query/queryAction.e2e.test.ts +++ b/console/cypress/integration/actions/query/queryAction.e2e.test.ts @@ -70,15 +70,13 @@ if (testMode !== 'cli') { parseSpecialCharSequences: false, }); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `test_webhook_transform` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Click the Create button**'); cy.getBySel('create-action-btn').click(); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `create_action` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check if the success notification is visible**'); @@ -133,8 +131,6 @@ if (testMode !== 'cli') { cy.log('**--- Click Save Permissions**'); cy.getBySel('save-permissions-for-action').click(); - // sure the Console locally works as the one in CI. The request was `create_action_permission` - // -------------------- cy.log('**--- Check if the success notification is visible**'); cy.get( @@ -168,8 +164,7 @@ if (testMode !== 'cli') { cy.log('**--- Click the Delete button**'); cy.getBySel('delete-action').click(); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `drop_action` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check if the success notification is visible**'); diff --git a/console/cypress/integration/actions/spec.ts b/console/cypress/integration/actions/spec.ts deleted file mode 100644 index 788eac1dc68..00000000000 --- a/console/cypress/integration/actions/spec.ts +++ /dev/null @@ -1,371 +0,0 @@ -import { baseUrl, getElementFromClassName } from '../../helpers/dataHelpers'; -import { setPromptValue } from '../../helpers/common'; -import { AWAIT_LONG, AWAIT_SHORT } from '../../helpers/constants'; -import { getTimeoutSeconds } from '../../helpers/eventHelpers'; -import { - toggleRequestTransformSection, - togglePayloadTransformSection, - typeIntoRequestQueryParams, - typeIntoRequestUrl, - typeIntoTransformBody, - checkTransformRequestUrlError, - checkTransformRequestBodyError, - checkTransformRequestUrlPreview, - clearPayloadTransformBody, - clearRequestUrl, - toggleContextArea, - typeIntoContextAreaEnvVars, - getActionTransfromV1RequestBody, -} from '../../helpers/webhookTransformHelpers'; - -const ACTION_REQUEST_BODY_TRANSFORM_TEXTAREA = 4; - -const statements = { - createMutationGQLQuery: `mutation getAccessToken ($username: String!, $password: String!) { - login (username: $username, password: $password) { - accessToken - `, - createMutationQueryVars: `{"username": "john", "password": "p"`, - createQueryActionText: `type Query { - addNumbers (numbers: [Int]): AddResult - }`, - createQueryActionCustomType: `type AddResult { - sum: Int - }`, - createQueryHandler: 'https://hasura-actions-demo.glitch.me/addNumbers', - createQueryGQLQuery: `query { - addNumbers(numbers: [1, 2, 3, 4]) { - sum - `, - changeHandlerText: 'http://host.docker.internal:3000', - createActionTransform: `type Mutation { - login (username: String!, password: String!): LoginResponse - } - `, - createTransformCustomType: `type LoginResponse { - accessToken: String! - } - `, - createTransformEnvHandler: '{{MY_WEBHOOK}}', - createTransformHandler: 'https://hasura-actions-demo.glitch.me', - createTransformIncorrectPayloadBody: ` - { - "userInfo": { - "name": {{$input.username}} - `, - createTransformPayloadBody: ` - { - "userInfo": { - "name": {{$body.input.username}}, - "password": {{$body.input.password}}, - "type": {{$body.action.name}} - `, -}; - -// NOTE: This test suite does not include cases for relationships, headers and -// the codegen part - -const clearActionDef = () => { - cy.get('textarea').first().type('{selectall}', { force: true }); - cy.get('textarea').first().trigger('keydown', { - keyCode: 46, - which: 46, - force: true, - }); -}; - -const clearActionTypes = () => { - cy.get('textarea').eq(1).type('{selectall}', { force: true }); - cy.get('textarea').eq(1).trigger('keydown', { - keyCode: 46, - which: 46, - force: true, - }); -}; - -const clearHandler = () => { - cy.getBySel('action-create-handler-input').type('{selectall}{backspace}', { - force: true, - }); -}; - -const typeIntoActionDef = (content: string) => { - cy.get('textarea').first().type(content, { force: true }); -}; - -const typeIntoActionTypes = (content: string) => { - cy.get('textarea').eq(1).type(content, { force: true }); -}; - -const typeIntoHandler = (content: string) => { - cy.getBySel('action-create-handler-input').type(content, { - force: true, - parseSpecialCharSequences: false, - }); -}; - -const clickOnCreateAction = () => { - cy.getBySel('create-action-btn').scrollIntoView(); - // hard await before accessing the element - cy.wait(AWAIT_SHORT); - - cy.getBySel('create-action-btn').click({ force: true }); - cy.wait(AWAIT_SHORT); - cy.get('.notification', { timeout: AWAIT_LONG }) - .should('be.visible') - .and('contain', 'Created action successfully'); -}; - -export const routeToGraphiql = () => { - cy.visit('/api/api-explorer'); - cy.url({ timeout: AWAIT_LONG }).should('eq', `${baseUrl}/api/api-explorer`); -}; - -export const verifyMutation = () => { - routeToGraphiql(); - // Type the query - cy.on('uncaught:exception', () => { - // NOTE: doing this since, there was some exception thrown by the - // graphiql editor even though the query was good. - // Docs: https://docs.cypress.io/api/events/catalog-of-events.html#To-turn-off-all-uncaught-exception-handling - return false; - }); - cy.get('textarea') - .eq(0) - .type(`{enter}{uparrow}${statements.createMutationGQLQuery}`, { - force: true, - }); - cy.get('textarea') - .eq(1) - .type(`{enter}{uparrow}${statements.createMutationQueryVars}`, { - force: true, - }); - cy.get(getElementFromClassName('execute-button')).click(); - // FIXME: NOT GOOD! - cy.get('.cm-property').contains('login'); - cy.get('.cm-property').contains('accessToken'); - cy.get('.cm-string').contains('Ew8jkGCNDGAo7p35RV72e0Lk3RGJoJKB'); -}; - -const deleteAction = (promptValue: string) => { - setPromptValue(promptValue); - cy.getBySel('delete-action').click(); - cy.window().its('prompt').should('be.called'); -}; - -export const createQueryAction = () => { - // Routing to the index page - cy.visit('/actions/manage/actions'); - cy.intercept('*', req => { - // send all other requests to the destination server - req.reply(); - }); - cy.url({ timeout: AWAIT_LONG }).should( - 'eq', - `${baseUrl}/actions/manage/actions` - ); - // Click on create - cy.getBySel('data-create-actions').click(); - // Clear default text on - clearActionDef(); - // type statement - typeIntoActionDef(statements.createQueryActionText); - // clear defaults on action types - clearActionTypes(); - // type the action type text - typeIntoActionTypes(statements.createQueryActionCustomType); - // clear handler - clearHandler(); - // type into handler - typeIntoHandler(statements.createQueryHandler); - // click to create action - clickOnCreateAction(); -}; - -export const verifyQuery = () => { - cy.on('uncaught:exception', () => { - // NOTE: doing this since, there was some exception thrown by the - // graphiql editor even though the query was good. - // Docs: https://docs.cypress.io/api/events/catalog-of-events.html#To-turn-off-all-uncaught-exception-handling - return false; - }); - routeToGraphiql(); - cy.get('textarea') - .eq(0) - .type(`{enter}{uparrow}${statements.createQueryGQLQuery}`, { force: true }) - .wait(4000); - cy.get(getElementFromClassName('execute-button')).click(); - cy.get('.cm-property').contains('addNumbers'); - cy.get('.cm-property').contains('sum'); - cy.get('.cm-number').contains('10'); -}; - -export const modifyQueryAction = () => { - cy.visit('/actions/manage/addNumbers/modify'); - cy.url({ timeout: AWAIT_LONG }).should( - 'eq', - `${baseUrl}/actions/manage/addNumbers/modify` - ); - - clearHandler(); - typeIntoHandler(statements.changeHandlerText); - - cy.getBySel('save-modify-action-changes').click(); - - // permissions part - cy.getBySel('actions-permissions').click(); - - cy.getBySel('role-textbox').type('MANAGER'); - - cy.getBySel('MANAGER-Permission').click(); - cy.getBySel('save-permissions-for-action').click(); - - cy.getBySel('actions-modify').click(); -}; - -export const deleteQueryAction = () => deleteAction('addNumbers'); - -export const createActionTransform = () => { - // Click on create - cy.getBySel('actions-sidebar-add-table').click(); - // Clear default text on - clearActionDef(); - // type statement - typeIntoActionDef(statements.createActionTransform); - // clear defaults on action types - clearActionTypes(); - // type the action type text - typeIntoActionTypes(statements.createTransformCustomType); - cy.getBySel('action-timeout-seconds').clear().type(getTimeoutSeconds()); - // open request transform section - toggleRequestTransformSection(); - cy.wait(AWAIT_SHORT); - cy.getBySel('transform-POST').click(); - - // give correct body without webhook handler - clearHandler(); - typeIntoRequestUrl('users'); - cy.wait(AWAIT_SHORT); - // check for error - checkTransformRequestUrlError( - true, - 'Please configure your webhook handler to generate request url transform' - ); - - // give correct body with env var - clearHandler(); - typeIntoHandler(statements.createTransformEnvHandler); - // give body without specifying env var - clearRequestUrl(); - typeIntoRequestUrl('/users'); - cy.wait(AWAIT_SHORT); - // check for error - checkTransformRequestUrlError(true); - - // add env var in context area - toggleContextArea(); - typeIntoContextAreaEnvVars([ - { key: 'MY_WEBHOOK', value: 'https://handler.com' }, - ]); - // check there is no error and preview works fine - checkTransformRequestUrlError(false); - checkTransformRequestUrlPreview('https://handler.com/users'); - - // clear handler - clearHandler(); - // type into handler - typeIntoHandler(statements.createTransformHandler); - - // give incorrect body - clearRequestUrl(); - typeIntoRequestUrl('{{$url}}/users'); - cy.wait(AWAIT_SHORT); - // check for error - checkTransformRequestUrlError(true); - - // give correct body - clearRequestUrl(); - typeIntoRequestUrl('/{{$body.action.name}}'); - cy.wait(AWAIT_SHORT); - typeIntoRequestQueryParams([ - { key: 'id', value: '5' }, - { key: 'name', value: '{{$body.action.name}}' }, - ]); - cy.wait(AWAIT_SHORT); - // check there is no error - checkTransformRequestUrlError(false); - // check the preview is correctly shown - checkTransformRequestUrlPreview( - 'https://hasura-actions-demo.glitch.me/login?name=login&id=5' - ); - - // open payload transform section - togglePayloadTransformSection(); - - // give incorrect body - clearPayloadTransformBody(ACTION_REQUEST_BODY_TRANSFORM_TEXTAREA); - typeIntoTransformBody( - statements.createTransformIncorrectPayloadBody, - ACTION_REQUEST_BODY_TRANSFORM_TEXTAREA - ); - cy.wait(AWAIT_SHORT); - checkTransformRequestBodyError(true); - - // give correct body - clearPayloadTransformBody(ACTION_REQUEST_BODY_TRANSFORM_TEXTAREA); - typeIntoTransformBody( - statements.createTransformPayloadBody, - ACTION_REQUEST_BODY_TRANSFORM_TEXTAREA - ); - cy.wait(AWAIT_SHORT); - checkTransformRequestBodyError(false); - - // click to create action - cy.intercept('*', req => { - // send all other requests to the destination server - req.reply(); - }); - clickOnCreateAction(); - cy.getBySel('action-timeout-seconds').should('have.value', '25'); -}; - -export const deleteActionTransform = () => deleteAction('login'); - -const createV1ActionTransform = (actionName: string) => { - cy.request( - 'POST', - 'http://localhost:8080/v1/metadata', - getActionTransfromV1RequestBody(actionName) - ).then(response => { - expect(response.body).to.not.be.null; - expect(response.body).to.be.a('array'); - expect(response.body[0]).to.have.property('message', 'success'); // true - }); -}; - -export const modifyV1ActionTransform = () => { - // Creates an action with v1 transform - createV1ActionTransform('login'); - - cy.wait(AWAIT_SHORT); - // modify and save the action, the action should be converted into v2 - cy.visit('/actions/manage/login/modify'); - cy.url({ timeout: AWAIT_LONG }).should( - 'eq', - `${baseUrl}/actions/manage/login/modify` - ); - cy.getBySel('transform-POST').click(); - cy.getBySel('transform-requestUrl') - .clear() - .type('/{{$body.action.name}}/actions', { - parseSpecialCharSequences: false, - }); - - cy.getBySel('save-modify-action-changes').click(); - cy.get('.notification', { timeout: AWAIT_LONG }) - .should('be.visible') - .and('contain', 'Action saved successfully'); - - // delete the action - deleteActionTransform(); -}; diff --git a/console/cypress/integration/actions/test.ts b/console/cypress/integration/actions/test.ts index 371aa857c70..6a21de70acf 100644 --- a/console/cypress/integration/actions/test.ts +++ b/console/cypress/integration/actions/test.ts @@ -1,62 +1,59 @@ -import { modifyV1ActionTransform } from './spec'; -import { testMode } from '../../helpers/common'; -import { setMetaData } from '../validators/validators'; - -const setup = () => { - describe.skip('Setup route', () => { - it('Visit the index route', () => { - cy.visit('/actions/manage/actions'); - // Get and set validation metadata - setMetaData(); - }); - }); -}; +// const setup = () => { +// describe.skip('Setup route', () => { +// it('Visit the index route', () => { +// cy.visit('/actions/manage/actions'); +// // Get and set validation metadata +// setMetaData(); +// }); +// }); +// }; // TODO: what about the codegen part? Why is it not tested? -export const runActionsTests = () => { - describe.skip('Actions', () => { - // The test has been moved to mutationAction.e2e.test - // it('Create Mutation Action', createMutationAction); +// export const runActionsTests = () => { +// describe.skip('Actions', () => { +// The test has been moved to mutationAction.e2e.test +// it('Create Mutation Action', createMutationAction); - // The test was commented before moving the other ones to mutationAction.e2e.test - // it('Verify Mutation Actions on GraphiQL', verifyMutation); +// The test was commented before moving the other ones to mutationAction.e2e.test +// it('Verify Mutation Actions on GraphiQL', verifyMutation); - // The test has been moved to mutationAction.e2e.test - // it('Modify Mutation Action', modifyMutationAction); +// The test has been moved to mutationAction.e2e.test +// it('Modify Mutation Action', modifyMutationAction); - // The test has been moved to mutationAction.e2e.test - // it('Delete Mutation Action', deleteMutationAction); +// The test has been moved to mutationAction.e2e.test +// it('Delete Mutation Action', deleteMutationAction); - // The test has been moved to queryAction.e2e.test.e2e.test - // it('Create Query Action', createQueryAction); +// The test has been moved to queryAction.e2e.test.e2e.test +// it('Create Query Action', createQueryAction); - // The test was commented before moving the other ones to queryAction.e2e.test - // it('Verify Query Actions on GraphiQL', verifyQuery); +// The test was commented before moving the other ones to queryAction.e2e.test +// it('Verify Query Actions on GraphiQL', verifyQuery); - // The test has been moved to queryAction.e2e.test.e2e.test - // it('Modify Query Action', modifyQueryAction); +// The test has been moved to queryAction.e2e.test.e2e.test +// it('Modify Query Action', modifyQueryAction); - // The test has been moved to queryAction.e2e.test.e2e.test - // it('Delete Query Action', deleteQueryAction); +// The test has been moved to queryAction.e2e.test.e2e.test +// it('Delete Query Action', deleteQueryAction); - // The test has been moved to actionWithTransform.e2e.test.ts - // it('Create Action With Transform', createActionTransform); +// The test has been moved to actionWithTransform.e2e.test.ts +// it('Create Action With Transform', createActionTransform); - // The test has been moved to actionWithTransform.e2e.test.ts - // it('Update Action With Transform', modifyActionTransform); +// The test has been moved to actionWithTransform.e2e.test.ts +// it('Update Action With Transform', modifyActionTransform); - // The test has been moved to actionWithTransform.e2e.test.ts - // it('Delete Action With Transform', deleteActionTransform); +// The test has been moved to actionWithTransform.e2e.test.ts +// it('Delete Action With Transform', deleteActionTransform); - it( - 'Create an action with V1 Transform and edit it through console, which will lead to the action being saved as V2', - modifyV1ActionTransform - ); - }); -}; +// The test has been moved to v1ActionWithTransform.e2e.test.ts +// it( +// 'Create an action with V1 Transform and edit it through console, which will lead to the action being saved as V2', +// modifyV1ActionTransform +// ); +// }); +// }; -if (testMode !== 'cli') { - setup(); - runActionsTests(); -} +// if (testMode !== 'cli') { +// setup(); +// runActionsTests(); +// } diff --git a/console/cypress/integration/actions/withTransform/actionWithTransform.e2e.test.ts b/console/cypress/integration/actions/withTransform/actionWithTransform.e2e.test.ts index 23c8530a96a..4302cfb906b 100644 --- a/console/cypress/integration/actions/withTransform/actionWithTransform.e2e.test.ts +++ b/console/cypress/integration/actions/withTransform/actionWithTransform.e2e.test.ts @@ -56,12 +56,6 @@ if (testMode !== 'cli') { { force: true, delay: 0 } ); - // -------------------- - // Pleas note that the custom timeout is not checked explicitly checked, but asserting on - // the payload (see the next cy.intercept) means asserting on it too - cy.log('**--- Type in the Custom Timeout field**'); - cy.getBySel('action-timeout-seconds').clearConsoleTextarea().type('25'); - // -------------------- cy.log('**--- Click the Add Request Options Transform button**'); cy.contains('Add Request Options Transform').click(); @@ -79,8 +73,7 @@ if (testMode !== 'cli') { cy.log('**--- Type in the Request URL Template field**'); cy.get('[placeholder="URL Template (Optional)..."]').type('users'); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Look for the "Invalid URL" error**'); @@ -112,8 +105,7 @@ if (testMode !== 'cli') { .clearConsoleTextarea() .type('/users'); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Look for "Missing Env Var" error**'); @@ -141,8 +133,7 @@ if (testMode !== 'cli') { delay: 1, }); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check the error disappeared**'); @@ -183,8 +174,7 @@ if (testMode !== 'cli') { .type('{{$url}}/users', { parseSpecialCharSequences: false }); }); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Look for "Invalid Path" error**'); @@ -220,8 +210,7 @@ if (testMode !== 'cli') { } ); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check the error disappeared**'); @@ -270,8 +259,7 @@ if (testMode !== 'cli') { { force: true, delay: 1, parseSpecialCharSequences: false } ); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Look for the "Invalid path" error**'); @@ -304,8 +292,7 @@ if (testMode !== 'cli') { { force: true, delay: 1, parseSpecialCharSequences: false } ); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check the error disappeared**'); @@ -321,8 +308,7 @@ if (testMode !== 'cli') { cy.log('**--- Click the Create button**'); cy.getBySel('create-action-btn').click(); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `create_action` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check if the success notification is visible**'); @@ -376,8 +362,7 @@ if (testMode !== 'cli') { cy.log('**--- Click on the Save button**'); cy.getBySel('save-modify-action-changes').click(); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `create_action_permission` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check if the success notification is visible**'); @@ -427,8 +412,7 @@ if (testMode !== 'cli') { cy.log('**--- Check the prompt has been called**'); cy.window().its('prompt').should('be.called'); - // Please note: we should wait for the outgoing request but at the moment of writing, I'm not - // sure the Console locally works as the one in CI. The request was `drop_action` + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here // -------------------- cy.log('**--- Check if the success notification is visible**'); diff --git a/console/cypress/integration/actions/withTransform/utils/services/deleteV1LoginAction.ts b/console/cypress/integration/actions/withTransform/utils/services/deleteV1LoginAction.ts new file mode 100644 index 00000000000..1a55fa63962 --- /dev/null +++ b/console/cypress/integration/actions/withTransform/utils/services/deleteV1LoginAction.ts @@ -0,0 +1,13 @@ +/** + * Delete the Action straight from the server. + */ +export function deleteV1LoginAction() { + Cypress.log({ message: '**--- Action delete: start**' }); + + return cy + .request('POST', 'http://localhost:8080/v1/metadata', { + type: 'drop_action', + args: { name: 'v1Login' }, + }) + .then(() => Cypress.log({ message: '**--- Action delete: end**' })); +} diff --git a/console/cypress/integration/actions/withTransform/utils/testState/v1LoginActionMustExist.ts b/console/cypress/integration/actions/withTransform/utils/testState/v1LoginActionMustExist.ts new file mode 100644 index 00000000000..86fcf775d26 --- /dev/null +++ b/console/cypress/integration/actions/withTransform/utils/testState/v1LoginActionMustExist.ts @@ -0,0 +1,122 @@ +import { readMetadata } from '../services/readMetadata'; +import { deleteLoginAction } from '../services/deleteLoginAction'; + +/** + * Ensure the V1 Action exists. + */ +export function v1LoginActionMustExist() { + Cypress.log({ message: '**--- Action check: start**' }); + + readMetadata().then(response => { + const actionExists = !!response.body.actions?.find( + // TODO: properly type it + action => action.name === 'TODO:' + ); + + if (actionExists) { + Cypress.log({ message: '**--- The action exists**' }); + Cypress.log({ message: '**--- Action check: end**' }); + return; + } + + Cypress.log({ message: '**--- The action does not exist**' }); + + cy.request('POST', 'http://localhost:8080/v1/metadata', { + type: 'bulk', + source: 'default', + args: [ + { + type: 'set_custom_types', + args: { + scalars: [], + input_objects: [ + { + name: 'SampleInput', + description: null, + fields: [ + { + name: 'username', + type: 'String!', + description: null, + }, + { + name: 'password', + type: 'String!', + description: null, + }, + ], + }, + ], + objects: [ + { + name: 'SampleOutput', + description: null, + fields: [ + { + name: 'accessToken', + type: 'String!', + description: null, + }, + ], + }, + { + name: 'LoginResponse', + fields: [ + { + name: 'accessToken', + type: 'String!', + }, + ], + }, + ], + enums: [], + }, + }, + { + type: 'create_action', + args: { + name: 'v1Login', + definition: { + arguments: [ + { + name: 'arg1', + type: 'SampleInput!', + description: null, + }, + ], + kind: 'synchronous', + output_type: 'SampleOutput', + handler: 'http://host.docker.internal:3000', + type: 'mutation', + headers: [], + timeout: null, + request_transform: { + version: 1, + template_engine: 'Kriti', + method: 'GET', + url: '{{$base_url}}/users', + query_params: {}, + body: + '{\n "users": {\n "name": {{$body.input.arg1.username}}\n }\n}', + content_type: 'application/json', + }, + }, + comment: null, + }, + }, + ], + }) + .then(response => { + expect(response.body, 'Response body exists').to.not.be.null; + expect(response.body, 'Response body is an array').to.be.a('array'); + expect( + response.body[0], + 'Response body contains success' + ).to.have.property('message', 'success'); + }) + .then(() => { + Cypress.log({ message: '**--- The action has been created**' }); + Cypress.log({ message: '**--- Action check: end**' }); + }); + }); +} diff --git a/console/cypress/integration/actions/withTransform/utils/testState/v1LoginActionMustNotExist.ts b/console/cypress/integration/actions/withTransform/utils/testState/v1LoginActionMustNotExist.ts new file mode 100644 index 00000000000..9f0b9bef45e --- /dev/null +++ b/console/cypress/integration/actions/withTransform/utils/testState/v1LoginActionMustNotExist.ts @@ -0,0 +1,21 @@ +import { readMetadata } from '../services/readMetadata'; +import { deleteV1LoginAction } from '../services/deleteV1LoginAction'; + +/** + * Ensure the Action does not exist. + */ +export function v1LoginActionMustNotExist() { + Cypress.log({ message: '**--- Action check: start**' }); + + readMetadata().then(response => { + const actionExists = !!response.body.actions?.find( + // TODO: properly type it + action => action.name === 'v1Login' + ); + + if (actionExists) { + Cypress.log({ message: '**--- The Action must be deleted**' }), + deleteV1LoginAction(); + } + }); +} diff --git a/console/cypress/integration/actions/withTransform/v1ActionWithTransform.e2e.test.ts b/console/cypress/integration/actions/withTransform/v1ActionWithTransform.e2e.test.ts new file mode 100644 index 00000000000..7d97821303f --- /dev/null +++ b/console/cypress/integration/actions/withTransform/v1ActionWithTransform.e2e.test.ts @@ -0,0 +1,100 @@ +import { testMode } from '../../../helpers/common'; + +import { logMetadataRequests } from './utils/requests/logMetadataRequests'; +import { v1LoginActionMustExist } from './utils/testState/v1LoginActionMustExist'; +import { v1LoginActionMustNotExist } from './utils/testState/v1LoginActionMustNotExist'; + +if (testMode !== 'cli') { + describe('V1 Actions with Transform', () => { + before(() => { + v1LoginActionMustExist(); + logMetadataRequests(); + + cy.visit('/actions/manage/v1Login/modify'); + }); + + after(() => { + // Delete the created action, if any + v1LoginActionMustNotExist(); + }); + + it('When the users edit a V1 Action with Transform, everything should work', () => { + cy.log('**------------------------------**'); + cy.log('**------------------------------**'); + cy.log('**------------------------------**'); + cy.log('**--- Step 3: Mutation Action delete**'); + cy.log('**------------------------------**'); + cy.log('**------------------------------**'); + cy.log('**------------------------------**'); + + cy.get('[data-cy="Change Request Options"]').within(() => { + // -------------------- + cy.log('**--- Choose POST**'); + cy.contains('POST').click(); + + // -------------------- + cy.log('**--- Type in the Request URL Template field**'); + cy.get('[placeholder="URL Template (Optional)..."]').type( + '/{{$body.action.name}}/actions', + { + delay: 0, + parseSpecialCharSequences: false, + } + ); + }); + + // -------------------- + cy.log('**--- Click on the Save button**'); + cy.getBySel('save-modify-action-changes').click(); + + // -------------------- + cy.log('**--- Check if the success notification is visible**'); + cy.get( + '.notification', + // The custom timeout aims to replace the lack of waiting for the outgoing request + { timeout: 10000 } + ) + .should('be.visible') + .and('contain', 'Action saved successfully'); + + cy.log('**------------------------------**'); + cy.log('**------------------------------**'); + cy.log('**------------------------------**'); + cy.log('**--- Step 2: Action delete**'); + cy.log('**------------------------------**'); + cy.log('**------------------------------**'); + cy.log('**------------------------------**'); + + // -------------------- + cy.log('**--- Go the the action page**'); + cy.getBySel('actions-table-links').within(() => { + cy.getBySel('v1Login').click(); + }); + + // -------------------- + cy.log('**--- Set the prompt value**'); + cy.window().then(win => cy.stub(win, 'prompt').returns('v1Login')); + + cy.log('**--- Click the Delete button**'); + cy.getBySel('delete-action').click(); + + // -------------------- + cy.log('**--- Check the prompt has been called**'); + cy.window().its('prompt').should('be.called'); + + // Due to the double server/cli mode behavior, we do not assert about the XHR request payload here + + // -------------------- + cy.log('**--- Check if the success notification is visible**'); + cy.get( + '.notification', + // The custom timeout aims to replace the lack of waiting for the outgoing request + { timeout: 10000 } + ) + .should('be.visible') + .and('contain', 'Action deleted successfully'); + + // TODO: check if it does not exist in the database? Other tests do that + }); + }); +} diff --git a/console/cypress/support/commands.ts b/console/cypress/support/commands.ts index 9f1f69c0550..e597e20fab5 100644 --- a/console/cypress/support/commands.ts +++ b/console/cypress/support/commands.ts @@ -32,7 +32,3 @@ import './clearConsoleTextarea'; Cypress.Commands.add('getBySel', (selector, ...args) => { return cy.get(`[data-test=${selector}]`, ...args); }); - -Cypress.Commands.add('getBySelLike', (selector, ...args) => { - return cy.get(`[data-test*=${selector}]`, ...args); -}); diff --git a/console/cypress/support/index.d.ts b/console/cypress/support/index.d.ts index 90aab891572..d7064dbbff6 100644 --- a/console/cypress/support/index.d.ts +++ b/console/cypress/support/index.d.ts @@ -7,19 +7,13 @@ declare namespace Cypress { * * @example cy.getBySel('greeting') */ - getBySel(value: string): Chainable; - /** - * Custom command to select DOM element by data-test* attribute. - * - * @example cy.getBySelLike('save_me') - */ - getBySelLike(value: string): Chainable; + getBySel(value: string): Chainable>; /** * Custom command to work around the fact that cy.clear sometimes fails at clearing the * Console's textarea * @example cy.get('textarea').clearConsoleTextarea() */ - clearConsoleTextarea(): Chainable; + clearConsoleTextarea(): Chainable>; /** * Visit the initial empty page. * Console's textarea