mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
console: Setup cypress on the Nx monorepo (close #5463)
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5675 GitOrigin-RevId: b320b6f5bb092d20a4de8e51f6711323dd2b0a05
This commit is contained in:
parent
5764174087
commit
d864bed4f4
@ -13,7 +13,8 @@ import {
|
||||
TableFields,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
import { AWAIT_SHORT } from '../../../helpers/constants';
|
||||
|
||||
const AWAIT_SHORT = 2000;
|
||||
|
||||
const delRel = (table: string, relname: string) => {
|
||||
cy.get(getElementFromAlias(table)).click();
|
||||
|
@ -32,7 +32,9 @@ import {
|
||||
clearPayloadTransformBody,
|
||||
clearRequestUrl,
|
||||
} from '../../../helpers/webhookTransformHelpers';
|
||||
import { AWAIT_LONG, AWAIT_SHORT } from '../../../helpers/constants';
|
||||
|
||||
const AWAIT_SHORT = 2000;
|
||||
const AWAIT_LONG = 7000;
|
||||
|
||||
const EVENT_REQUEST_BODY_TRANSFORM_TEXTAREA = 1;
|
||||
|
||||
|
@ -1,7 +1 @@
|
||||
export const ADMIN_SECRET_HEADER_KEY = 'x-hasura-admin-secret';
|
||||
|
||||
// TODO cypress default timeout is 4000, we can remove this `AWAIT_SHORT` after verifying that this is followed by a test command that works with timeout
|
||||
// https://docs.cypress.io/guides/references/configuration#Timeouts
|
||||
export const AWAIT_SHORT = 2000;
|
||||
export const AWAIT_MODERATE = 5000;
|
||||
export const AWAIT_LONG = 7000;
|
||||
|
@ -1,10 +1,27 @@
|
||||
{
|
||||
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
|
||||
"extends": [
|
||||
"plugin:cypress/recommended",
|
||||
"../../.eslintrc.json",
|
||||
"plugin:chai-friendly/recommended"
|
||||
],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
"rules": {
|
||||
// TODO: restore it to "error". At the beginning of the Nx migration, we must keep it as is because
|
||||
// of the huge number of cy.wait() around the legacy tests
|
||||
"cypress/no-unnecessary-waiting": "warn",
|
||||
|
||||
"no-underscore-dangle": "off",
|
||||
"@typescript-eslint/no-unused-expressions": "off",
|
||||
"no-plusplus": [
|
||||
"error",
|
||||
{
|
||||
"allowForLoopAfterthoughts": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
57
frontend/apps/console-ce-e2e/README.md
Normal file
57
frontend/apps/console-ce-e2e/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Test
|
||||
|
||||
## Useful resources
|
||||
- [Cypress Dashboard for the Console project](https://dashboard.cypress.io/projects/5yiuic)
|
||||
|
||||
## Running all tests to generate coverage
|
||||
|
||||
1. Set the `TEST_MODE` field in `cypress.json` to `cli`
|
||||
2. Run the command `npm run test` from the `console` directory to run all the tests.
|
||||
|
||||
## Running tests individually
|
||||
|
||||
Tests are modularized into following modules:
|
||||
|
||||
- API-Explorer
|
||||
- Data
|
||||
- Migration Mode
|
||||
- Create Table
|
||||
- Insert Browse
|
||||
- Modify Table
|
||||
- Table Relationships
|
||||
- Table and View Permissions
|
||||
- Views
|
||||
|
||||
To run the tests for the modules individually (say for create table),
|
||||
|
||||
- Go to the `cypress.json` and set the `env > TEST_MODE` variable to `ui`.
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"TEST_MODE": "ui"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Run the command `npm run cy:open` and click on `create-table > test.js`
|
||||
|
||||
## Writing Tests
|
||||
|
||||
- Read ups
|
||||
|
||||
- If this is your first time with cypress, check out this getting started [guide](https://docs.cypress.io/guides/getting-started/writing-your-first-test.html)
|
||||
|
||||
- Read cypress [best practices](https://docs.cypress.io/guides/references/best-practices.html)
|
||||
|
||||
- File Structure
|
||||
|
||||
The top-level directories in [console/cypress](../../console/cypress) are auto-generated by cypress except [helpers](../../console/cypress/helpers). To understand the use of each directory check out [Folder Structure](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests.html#Folder-Structure)
|
||||
|
||||
[helpers](../../console/cypress/helpers) directory is used for sharing reusable functions/constants across tests. Before adding a resubale function in this directory, consider if it will be better as a custom cypress command, if so, then add it to [Support](../../console/cypress/support) directory following this [guide](https://docs.cypress.io/api/cypress-api/custom-commands.html), preferrably to [command.ts](../../console/cypress/support/commands.ts) file.
|
||||
|
||||
- Adding a Test
|
||||
|
||||
Tests go to [integration](../../console/cypress/integration) directory, where there are folders corresponding to Components in [Services](../../console/src/components/Services) directory (The top-level routes on the console).
|
||||
|
||||
Each of these folders contains different test folders, named after the particular feature they are testing. For example [create-table](../../console/cypress/integration/data/create-table) folder tests the functionality of creating a table from the console UI.
|
@ -1,6 +1,37 @@
|
||||
import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset';
|
||||
|
||||
import * as customTasks from './src/support/tasks';
|
||||
|
||||
const nxConfig = nxE2EPreset(__dirname);
|
||||
|
||||
export default defineConfig({
|
||||
e2e: nxE2EPreset(__dirname),
|
||||
env: {
|
||||
TEST_MODE: 'parallel',
|
||||
MIGRATE_URL: 'http://localhost:9693/apis/migrate',
|
||||
},
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
chromeWebSecurity: false,
|
||||
video: false,
|
||||
projectId: '5yiuic',
|
||||
numTestsKeptInMemory: 10,
|
||||
e2e: {
|
||||
...nxConfig,
|
||||
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
on('task', {
|
||||
...customTasks,
|
||||
});
|
||||
|
||||
return config;
|
||||
},
|
||||
baseUrl: 'http://localhost:3000',
|
||||
specPattern: [
|
||||
'src/e2e/**/*test.{js,jsx,ts,tsx}',
|
||||
'src/support/**/*unit.test.{js,ts}',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
25
frontend/apps/console-ce-e2e/src/e2e/_onboarding/spec.ts
Normal file
25
frontend/apps/console-ce-e2e/src/e2e/_onboarding/spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { getElementFromAlias } from '../../helpers/dataHelpers';
|
||||
|
||||
export const viewOnboarding = () => {
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('onboarding-popup'))
|
||||
.should('be.visible')
|
||||
.should('contain.text', `Hi there, let's get started with Hasura!`);
|
||||
// cy.get(getElementFromAlias('btn-hide-for-now')).click();
|
||||
};
|
||||
export const hideNow = () => {
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('btn-hide-for-now')).click();
|
||||
cy.get(getElementFromAlias('onboarding-popup')).should('not.exist');
|
||||
};
|
||||
|
||||
export const dontShowAgain = () => {
|
||||
// Click on create
|
||||
cy.reload();
|
||||
cy.get(getElementFromAlias('onboarding-popup')).should('be.visible');
|
||||
|
||||
cy.get(getElementFromAlias('btn-ob-dont-show-again')).click();
|
||||
cy.get(getElementFromAlias('onboarding-popup')).should('not.exist');
|
||||
cy.reload();
|
||||
cy.get(getElementFromAlias('onboarding-popup')).should('not.exist');
|
||||
};
|
39
frontend/apps/console-ce-e2e/src/e2e/_onboarding/test.ts
Normal file
39
frontend/apps/console-ce-e2e/src/e2e/_onboarding/test.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { viewOnboarding, hideNow, dontShowAgain } from './spec';
|
||||
import { testMode } from '../../helpers/common';
|
||||
import { setMetaData } from '../validators/validators';
|
||||
import { getIndexRoute } from '../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
cy.visit(getIndexRoute());
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runActionsTests = () => {
|
||||
describe('onboarding', () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
it.skip('should show onboarding guide', viewOnboarding);
|
||||
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
it.skip('should hide when user click on Hide Now', hideNow);
|
||||
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
it.skip(
|
||||
'should hide forever when user click on Dont Show again',
|
||||
dontShowAgain
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runActionsTests();
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
import { testMode } from '../../../helpers/common';
|
||||
|
||||
import { logMetadataRequests } from './utils/requests/logMetadataRequests';
|
||||
import { addNumbersActionMustNotExist } from './utils/testState/addNumbersActionMustNotExist';
|
||||
|
||||
// NOTE: This test suite does not include cases for relationships, headers and the codegen part
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Query Actions', () => {
|
||||
before(() => {
|
||||
addNumbersActionMustNotExist();
|
||||
logMetadataRequests();
|
||||
|
||||
cy.visit('/actions/manage/actions');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// Cleanup after the whole test file run
|
||||
|
||||
// Ensure the application is not there when manually deleting the created action to avoid any
|
||||
// potential client-side error that makes the test fail
|
||||
cy.visitEmptyPage();
|
||||
|
||||
// Delete the created action, if any
|
||||
addNumbersActionMustNotExist();
|
||||
});
|
||||
|
||||
it('When the users create, edit, and delete a Query Action, everything should work', () => {
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 1: Query Action creation**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click on the Create button of the Actions panel**');
|
||||
cy.getBySel('data-create-actions').click();
|
||||
|
||||
// --------------------
|
||||
// Assign an alias to the most unclear selectors for future references
|
||||
cy.get('textarea').eq(0).as('actionDefinitionTextarea');
|
||||
cy.get('textarea').eq(1).as('typeConfigurationTextarea');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Action Definition textarea**');
|
||||
cy.get('@actionDefinitionTextarea')
|
||||
.clearConsoleTextarea()
|
||||
.type(
|
||||
`type Query {
|
||||
addNumbers (numbers: [Int]): AddResult
|
||||
}`,
|
||||
{ force: true, delay: 0 }
|
||||
);
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Type Configuration textarea**');
|
||||
cy.get('@typeConfigurationTextarea')
|
||||
.clearConsoleTextarea()
|
||||
.type(
|
||||
`type AddResult {
|
||||
sum: Int
|
||||
}`,
|
||||
{ force: true, delay: 0 }
|
||||
);
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Webhook Handler field**');
|
||||
cy.getBySel('action-create-handler-input')
|
||||
.clearConsoleTextarea()
|
||||
.type('https://hasura-actions-demo.glitch.me/addNumbers', {
|
||||
delay: 0,
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
|
||||
// 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();
|
||||
|
||||
// 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.expectSuccessNotificationWithTitle('Created action successfully');
|
||||
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 2: Permission add and Handler change**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
// --------------------
|
||||
|
||||
cy.log('**--- Go the the action page**');
|
||||
cy.getBySel('actions-table-links').within(() => {
|
||||
cy.getBySel('addNumbers').click();
|
||||
});
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Webhook Handler field**');
|
||||
cy.getBySel('action-create-handler-input')
|
||||
.clearConsoleTextarea()
|
||||
.type('http://host.docker.internal:3000', {
|
||||
delay: 0,
|
||||
// parseSpecialCharSequences: false,
|
||||
});
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click on the Save button**');
|
||||
cy.getBySel('save-modify-action-changes').click();
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click the Permissions tab**');
|
||||
cy.getBySel('actions-permissions').click();
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Enter a new role**');
|
||||
cy.getBySel('role-textbox').type('manager');
|
||||
cy.getBySel('manager-Permission').click();
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click Save Permissions**');
|
||||
cy.getBySel('save-permissions-for-action').click();
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Check if the success notification is visible**');
|
||||
cy.expectSuccessNotificationWithTitle('Permission saved successfully');
|
||||
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 3: Query Action delete**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Go the the action page**');
|
||||
cy.getBySel('actions-table-links').within(() => {
|
||||
cy.getBySel('addNumbers').click();
|
||||
});
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Set the prompt value**');
|
||||
cy.window().then(win => cy.stub(win, 'prompt').returns('addNumbers'));
|
||||
|
||||
cy.log('**--- Click the Delete button**');
|
||||
cy.getBySel('delete-action').click();
|
||||
|
||||
// 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.expectSuccessNotificationWithTitle('Action deleted successfully');
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
interface SingleMetadataRequest {
|
||||
type: string;
|
||||
// There are a lot of other fields, but tracking them is not important for the purpose of this module
|
||||
}
|
||||
|
||||
interface BulkMetadataRequest {
|
||||
type: 'bulk';
|
||||
args: SingleMetadataRequest[];
|
||||
}
|
||||
|
||||
type MetadataRequest = SingleMetadataRequest | BulkMetadataRequest;
|
||||
|
||||
/*
|
||||
* Log all the requests outgoing to the Metadata endpoint.
|
||||
* This is useful to have a glance of the requests that are going to the server.
|
||||
*/
|
||||
export function logMetadataRequests() {
|
||||
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
|
||||
const noArgs = !req.body.args;
|
||||
|
||||
if (noArgs) return;
|
||||
|
||||
const requestBody = req.body as MetadataRequest;
|
||||
|
||||
if (requestBody.type === 'bulk') {
|
||||
const request = requestBody as BulkMetadataRequest;
|
||||
Cypress.log({ message: '*--- Bulk request*' });
|
||||
|
||||
request.args.forEach(arg =>
|
||||
Cypress.log({ message: `*--- Request: ${arg.type}*` })
|
||||
);
|
||||
} else {
|
||||
Cypress.log({ message: `*--- Request: ${requestBody.type}*` });
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Delete the Action straight from the server.
|
||||
*/
|
||||
export function deleteAddNumbersAction() {
|
||||
Cypress.log({ message: '**--- Action delete: start**' });
|
||||
|
||||
return cy
|
||||
.request('POST', 'http://localhost:8080/v1/metadata', {
|
||||
type: 'drop_action',
|
||||
args: { name: 'addNumbers' },
|
||||
})
|
||||
.then(() => Cypress.log({ message: '**--- Action delete: end**' }));
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Read the Metadata straight from the server.
|
||||
*/
|
||||
export function readMetadata() {
|
||||
Cypress.log({ message: '**--- Metadata read: start**' });
|
||||
|
||||
return cy
|
||||
.request('POST', 'http://localhost:8080/v1/metadata', {
|
||||
args: {},
|
||||
type: 'export_metadata',
|
||||
})
|
||||
.then(_response => {
|
||||
Cypress.log({ message: '**--- Metadata read: end**' });
|
||||
});
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { readMetadata } from '../services/readMetadata';
|
||||
import { deleteAddNumbersAction } from '../services/deleteAddNumbersAction';
|
||||
|
||||
/**
|
||||
* Ensure the Action does not exist.
|
||||
*/
|
||||
export function addNumbersActionMustNotExist() {
|
||||
Cypress.log({ message: '**--- Action check: start**' });
|
||||
|
||||
readMetadata().then(response => {
|
||||
const actionExists = !!response.body.actions?.find(
|
||||
// TODO: properly type it
|
||||
action => action.name === 'addNumbers'
|
||||
);
|
||||
|
||||
if (actionExists) {
|
||||
Cypress.log({ message: '**--- The Action must be deleted**' });
|
||||
deleteAddNumbersAction();
|
||||
}
|
||||
});
|
||||
}
|
59
frontend/apps/console-ce-e2e/src/e2e/actions/test.ts
Normal file
59
frontend/apps/console-ce-e2e/src/e2e/actions/test.ts
Normal file
@ -0,0 +1,59 @@
|
||||
// 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);
|
||||
|
||||
// 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('Delete Mutation Action', deleteMutationAction);
|
||||
|
||||
// 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 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 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('Delete Action With Transform', deleteActionTransform);
|
||||
|
||||
// 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();
|
||||
// }
|
@ -0,0 +1,280 @@
|
||||
import { testMode } from '../../../helpers/common';
|
||||
|
||||
import { logMetadataRequests } from './utils/requests/logMetadataRequests';
|
||||
import { loginActionMustNotExist } from './utils/testState/loginActionMustNotExist';
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
describe('Actions with Transform', () => {
|
||||
before(() => {
|
||||
loginActionMustNotExist();
|
||||
logMetadataRequests();
|
||||
|
||||
cy.visit('/actions/manage/actions');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// Delete the created action, if any
|
||||
loginActionMustNotExist();
|
||||
});
|
||||
|
||||
it('When the users create, and delete a Action with Transform, everything should work', () => {
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 1: Action with Transform creation**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click on the Create button of the Actions panel**');
|
||||
cy.getBySel('data-create-actions').click();
|
||||
|
||||
// Assign an alias to the most unclear selectors for future references
|
||||
cy.get('textarea').eq(0).as('actionDefinitionTextarea');
|
||||
cy.get('textarea').eq(1).as('typeConfigurationTextarea');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Action Definition textarea**');
|
||||
cy.get('@actionDefinitionTextarea')
|
||||
.clearConsoleTextarea()
|
||||
.type(
|
||||
`type Mutation {
|
||||
login (username: String!, password: String!): LoginResponse
|
||||
}`,
|
||||
{ force: true, delay: 0 }
|
||||
);
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Type Configuration textarea**');
|
||||
cy.get('@typeConfigurationTextarea')
|
||||
.clearConsoleTextarea()
|
||||
.type(
|
||||
`type LoginResponse {
|
||||
accessToken: String!
|
||||
}`,
|
||||
{ force: true, delay: 0 }
|
||||
);
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click the Add Request Options Transform button**');
|
||||
cy.contains('Add Request Options Transform').click();
|
||||
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 1.1: Add URL**');
|
||||
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('/users');
|
||||
});
|
||||
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 1.2: Add Env Var**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Webhook Handler field**');
|
||||
cy.getBySel('action-create-handler-input')
|
||||
.clearConsoleTextarea()
|
||||
.type('{{MY_WEBHOOK}}', {
|
||||
delay: 0,
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click the Show Sample Context button**');
|
||||
cy.contains('Show Sample Context').click();
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Env Variables Key field**');
|
||||
cy.getBySel('transform-env-vars-kv-key-0').type('MY_WEBHOOK', {
|
||||
delay: 1,
|
||||
});
|
||||
cy.log('**--- Type in the Env Variables Value field**');
|
||||
cy.getBySel('transform-env-vars-kv-value-0').type('https://handler.com', {
|
||||
delay: 1,
|
||||
});
|
||||
|
||||
// --------------------
|
||||
cy.get('[data-cy="Change Request Options"]').within(() => {
|
||||
cy.log('**--- Check the Preview of the Request URL Template**');
|
||||
cy.getBySel('transform-requestUrl-preview').should(
|
||||
'have.value',
|
||||
'https://handler.com/users'
|
||||
);
|
||||
});
|
||||
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 1.3: Add path**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the Webhook Handler field**');
|
||||
cy.getBySel('action-create-handler-input')
|
||||
.clearConsoleTextarea()
|
||||
.type('https://hasura-actions-demo.glitch.me', {
|
||||
delay: 0,
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 1.4: Query Params add**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
cy.get('[placeholder="URL Template (Optional)..."]')
|
||||
.clearConsoleTextarea()
|
||||
.type('/{{$body.action.name}}', { parseSpecialCharSequences: false });
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the first Query Params Key field**');
|
||||
cy.getBySel('transform-query-params-kv-key-0').type('id');
|
||||
cy.log('**--- Type in the first Query Params Value field**');
|
||||
cy.getBySel('transform-query-params-kv-value-0').type('5');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Type in the second Query Params Key field**');
|
||||
cy.getBySel('transform-query-params-kv-key-1').type('name');
|
||||
cy.log('**--- Type in the second Query Params Value field**');
|
||||
cy.getBySel('transform-query-params-kv-value-1').type(
|
||||
'{{$body.action.name}}',
|
||||
{
|
||||
parseSpecialCharSequences: false,
|
||||
delay: 0,
|
||||
}
|
||||
);
|
||||
|
||||
// --------------------
|
||||
cy.get('[data-cy="Change Request Options"]').within(() => {
|
||||
cy.log('**--- Check the Preview of the Request URL Template**');
|
||||
cy.findByLabelText('Preview').should(
|
||||
'have.value',
|
||||
'https://hasura-actions-demo.glitch.me/login?name=login&id=5'
|
||||
);
|
||||
});
|
||||
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 1.5: Add Payload Transform**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click the Add Payload Transform button**');
|
||||
cy.contains('Add Payload Transform').click();
|
||||
|
||||
// --------------------
|
||||
cy.get('[data-cy="Change Payload"]').within(() => {
|
||||
// Assign an alias to the most unclear selectors for future references
|
||||
cy.get('textarea').eq(1).as('payloadTransformRequestBody');
|
||||
|
||||
cy.log('**--- Type in the Payload Transform Request Body textarea**');
|
||||
cy.get('@payloadTransformRequestBody')
|
||||
.clearConsoleTextarea()
|
||||
.type(
|
||||
`{
|
||||
"userInfo": {
|
||||
"name": {{$body.input.username}},
|
||||
"password": {{$body.input.password}},
|
||||
"type": {{$body.action.name}}
|
||||
`,
|
||||
// delay is set to 1 because setting it to 0 causes the test to fail because writes
|
||||
// something like
|
||||
// "name": {{$body.input.username}}name
|
||||
// in the textarea (the closing "name" is a mistake)
|
||||
{ force: true, delay: 1, parseSpecialCharSequences: false }
|
||||
);
|
||||
});
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Click the Create button**');
|
||||
cy.getBySel('create-action-btn').click();
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Check if the success notification is visible**');
|
||||
cy.expectSuccessNotificationWithTitle('Created action successfully');
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// The "Action change" part has been removed since it caused Cypress to crash
|
||||
// TODO: identify the crashing reason
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
// cy.log('**------------------------------**');
|
||||
// cy.log('**------------------------------**');
|
||||
// cy.log('**------------------------------**');
|
||||
// cy.log('**--- Step 2: Action change**');
|
||||
// cy.log('**------------------------------**');
|
||||
// cy.log('**------------------------------**');
|
||||
// cy.log('**------------------------------**');
|
||||
|
||||
// // --------------------
|
||||
// cy.log('**--- Wait all the requests to be settled**');
|
||||
// waitForPostCreationRequests();
|
||||
|
||||
// cy.get('[data-cy="Change Request Options"]').within(() => {
|
||||
// // --------------------
|
||||
// cy.log('**--- Choose GET**');
|
||||
// cy.contains('GET').click();
|
||||
|
||||
// // --------------------
|
||||
// cy.log('**--- Type in the Request URL Template field**');
|
||||
|
||||
// cy.get('[placeholder="URL Template (Optional)..."]')
|
||||
// .clearConsoleTextarea()
|
||||
// .type('/{{$body.action.name}}/actions', {
|
||||
// delay: 0,
|
||||
// parseSpecialCharSequences: false,
|
||||
// });
|
||||
|
||||
// // --------------------
|
||||
// cy.log('**--- Click on the first Remove Query Param button**');
|
||||
// cy.getBySel('transform-query-params-kv-remove-button-0').click();
|
||||
// });
|
||||
|
||||
// // --------------------
|
||||
// cy.log('**--- Click the Remove Payload Transform button**');
|
||||
// cy.contains('Remove Payload Transform').click();
|
||||
|
||||
// // --------------------
|
||||
// cy.log('**--- Click on the Save button**');
|
||||
// cy.getBySel('save-modify-action-changes').click();
|
||||
|
||||
// // --------------------
|
||||
// cy.log('**--- Check if the success notification is visible**');
|
||||
// cy.expectSuccessNotificationWithTitle('Action saved successfully');
|
||||
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Step 3: Action delete**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Go the the action page**');
|
||||
cy.getBySel('actions-table-links').within(() => {
|
||||
cy.getBySel('login').click();
|
||||
});
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Set the prompt value**');
|
||||
cy.window().then(win => cy.stub(win, 'prompt').returns('login'));
|
||||
|
||||
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');
|
||||
|
||||
// --------------------
|
||||
cy.log('**--- Check if the success notification is visible**');
|
||||
cy.expectSuccessNotificationWithTitle('Action deleted successfully');
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
interface SingleMetadataRequest {
|
||||
type: string;
|
||||
// There are a lot of other fields, but tracking them is not important for the purpose of this module
|
||||
}
|
||||
|
||||
interface BulkMetadataRequest {
|
||||
type: 'bulk';
|
||||
args: SingleMetadataRequest[];
|
||||
}
|
||||
|
||||
type MetadataRequest = SingleMetadataRequest | BulkMetadataRequest;
|
||||
|
||||
/*
|
||||
* Log all the requests outgoing to the Metadata endpoint.
|
||||
* This is useful to have a glance of the requests that are going to the server.
|
||||
*/
|
||||
export function logMetadataRequests() {
|
||||
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
|
||||
const noArgs = !req.body.args;
|
||||
|
||||
if (noArgs) return;
|
||||
|
||||
const requestBody = req.body as MetadataRequest;
|
||||
|
||||
if (requestBody.type === 'bulk') {
|
||||
const request = requestBody as BulkMetadataRequest;
|
||||
Cypress.log({ message: '*--- Bulk request*' });
|
||||
|
||||
request.args.forEach(arg =>
|
||||
Cypress.log({ message: `*--- Request: ${arg.type}*` })
|
||||
);
|
||||
} else {
|
||||
Cypress.log({ message: `*--- Request: ${requestBody.type}*` });
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Delete the Action straight from the server.
|
||||
*/
|
||||
export function deleteLoginAction() {
|
||||
Cypress.log({ message: '**--- Action delete: start**' });
|
||||
|
||||
return cy
|
||||
.request('POST', 'http://localhost:8080/v1/metadata', {
|
||||
type: 'drop_action',
|
||||
args: { name: 'login' },
|
||||
})
|
||||
.then(() => Cypress.log({ message: '**--- Action delete: end**' }));
|
||||
}
|
@ -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**' }));
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Read the Metadata straight from the server.
|
||||
*/
|
||||
export function readMetadata() {
|
||||
Cypress.log({ message: '**--- Metadata read: start**' });
|
||||
|
||||
return cy
|
||||
.request('POST', 'http://localhost:8080/v1/metadata', {
|
||||
args: {},
|
||||
type: 'export_metadata',
|
||||
})
|
||||
.then(_response => {
|
||||
Cypress.log({ message: '**--- Metadata read: end**' });
|
||||
});
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
import { readMetadata } from '../services/readMetadata';
|
||||
import { deleteLoginAction } from '../services/deleteLoginAction';
|
||||
|
||||
/**
|
||||
* Ensure the Action does not exist.
|
||||
*/
|
||||
export function loginActionMustNotExist() {
|
||||
Cypress.log({ message: '**--- Action check: start**' });
|
||||
|
||||
readMetadata().then(response => {
|
||||
const actionExists = !!response.body.actions?.find(
|
||||
// TODO: properly type it
|
||||
action => action.name === 'login'
|
||||
);
|
||||
|
||||
if (actionExists) {
|
||||
Cypress.log({ message: '**--- The Action must be deleted**' });
|
||||
deleteLoginAction();
|
||||
}
|
||||
});
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
import {
|
||||
getElementFromAlias,
|
||||
baseUrl,
|
||||
tableColumnTypeSelector,
|
||||
makeDataAPIOptions,
|
||||
getIndexRoute,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
import { validateCT, ResultType } from '../../validators/validators';
|
||||
import { toggleOnMigrationMode } from '../../data/migration-mode/utils';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
// ***************** UTIL FUNCTIONS **************************
|
||||
|
||||
let adminSecret: string;
|
||||
let dataApiUrl: string;
|
||||
|
||||
export const createTestTable = () => {
|
||||
cy.window().then(win => {
|
||||
adminSecret = win.__env.adminSecret;
|
||||
dataApiUrl = win.__env.dataApiUrl;
|
||||
const { consoleMode } = win.__env;
|
||||
if (consoleMode === 'cli') {
|
||||
toggleOnMigrationMode();
|
||||
}
|
||||
});
|
||||
|
||||
// Click on the create table button
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(15000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
// Enter the table name
|
||||
cy.get(getElementFromAlias('tableName')).type('users');
|
||||
// Set first column
|
||||
cy.get(getElementFromAlias('column-0')).clear().type('id');
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
|
||||
.first()
|
||||
.click();
|
||||
cy.get(getElementFromAlias('column-1')).clear().type('name');
|
||||
tableColumnTypeSelector('col-type-1');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_text'))
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(10000);
|
||||
// Check if the table got created and navigatied to modify table
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/users/modify`
|
||||
);
|
||||
// Validate
|
||||
validateCT('users', ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const insertValue = () => {
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
// Insert a row
|
||||
cy.get(getElementFromAlias('typed-input-1')).type('someName');
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
};
|
||||
|
||||
export const openAPIExplorer = () => {
|
||||
// Open API Explorer
|
||||
cy.get(getElementFromAlias('api')).click();
|
||||
cy.wait(3000);
|
||||
};
|
||||
|
||||
export const checkQuery = () => {
|
||||
if (adminSecret) {
|
||||
cy.get(getElementFromAlias('header-key-2')).type('someKey');
|
||||
cy.get(getElementFromAlias('header-value-2')).type('someValue');
|
||||
} else {
|
||||
cy.get(getElementFromAlias('header-key-1')).type('someKey');
|
||||
cy.get(getElementFromAlias('header-value-1')).type('someValue');
|
||||
}
|
||||
|
||||
cy.get('textarea')
|
||||
.first()
|
||||
.type('{enter}{uparrow}query{{}users{{}id}}', { force: true });
|
||||
cy.wait(1000);
|
||||
cy.get('.execute-button').click();
|
||||
cy.get('.cm-property').contains('id');
|
||||
cy.get('.cm-number').contains('1');
|
||||
};
|
||||
|
||||
export const checkMutation = () => {
|
||||
cy.get('textarea')
|
||||
.first()
|
||||
.type(
|
||||
'{enter}{uparrow}#{leftarrow}{enter}{uparrow}mutation insert_user{{}insert_users(objects:[{{}name:"someName"}]){{}returning{{}id}}}',
|
||||
{ force: true }
|
||||
);
|
||||
cy.wait(1000);
|
||||
cy.get('.execute-button').click();
|
||||
cy.wait(5000);
|
||||
cy.get('.cm-property').contains('id');
|
||||
cy.get('.cm-number').contains('2');
|
||||
};
|
||||
|
||||
export const checkSub = () => {
|
||||
// Make a subscription
|
||||
cy.get('textarea')
|
||||
.first()
|
||||
.type(
|
||||
'{enter}{uparrow}#{leftarrow}{enter}{uparrow}subscription{{}users{{}name}}',
|
||||
{ force: true }
|
||||
);
|
||||
cy.wait(1000);
|
||||
cy.get('.execute-button').click();
|
||||
cy.wait(5000);
|
||||
cy.get('.cm-property').contains('name');
|
||||
cy.get('.cm-string').contains('someName');
|
||||
// Update the user with id 1
|
||||
const reqBody = {
|
||||
type: 'update',
|
||||
args: {
|
||||
table: {
|
||||
name: 'users',
|
||||
},
|
||||
where: {
|
||||
id: '1',
|
||||
},
|
||||
$set: {
|
||||
name: 'someOtherName',
|
||||
},
|
||||
},
|
||||
};
|
||||
// Make the request
|
||||
const requestOptions = makeDataAPIOptions(dataApiUrl, adminSecret, reqBody);
|
||||
cy.request(requestOptions).then(res => {
|
||||
cy.log(JSON.stringify(res));
|
||||
cy.wait(3000);
|
||||
cy.get('.cm-string').contains('someOtherName');
|
||||
});
|
||||
};
|
||||
|
||||
export const delTestTable = () => {
|
||||
cy.get(getElementFromAlias('data-tab-link')).click();
|
||||
// Go to the modify section of the table
|
||||
cy.get(getElementFromAlias('users')).click();
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
setPromptValue('users');
|
||||
// Click on delete
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
// Confirm
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(5000);
|
||||
|
||||
// Temporarily disabled, until it's fixed on the main branch
|
||||
// Match the URL
|
||||
// cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
|
||||
|
||||
// Validate
|
||||
validateCT('users', ResultType.FAILURE);
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
import {
|
||||
openAPIExplorer,
|
||||
checkQuery,
|
||||
checkMutation,
|
||||
createTestTable,
|
||||
insertValue,
|
||||
checkSub,
|
||||
delTestTable,
|
||||
} from './spec';
|
||||
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit('/');
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runApiExplorerTests = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('API Explorer', () => {
|
||||
it('Create test table', createTestTable);
|
||||
it('Insert row into test table', insertValue);
|
||||
it('Open API Explorer', openAPIExplorer);
|
||||
it('Check query result', checkQuery);
|
||||
it('Check mutation result', checkMutation);
|
||||
it('Check subscription result', checkSub);
|
||||
it('Delete test table', delTestTable);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runApiExplorerTests();
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
import { getGreeting } from '../support/app.po';
|
||||
|
||||
describe('console-ce', () => {
|
||||
beforeEach(() => cy.visit('/'));
|
||||
|
||||
it('should display welcome message', () => {
|
||||
// Custom command example, see `../support/commands.ts` file
|
||||
cy.login('my-email@something.com', 'myPassword');
|
||||
|
||||
// Function helper example, see `../support/app.po.ts` file
|
||||
getGreeting().contains('Welcome console-ce');
|
||||
});
|
||||
});
|
28
frontend/apps/console-ce-e2e/src/e2e/data/404/test.ts
Normal file
28
frontend/apps/console-ce-e2e/src/e2e/data/404/test.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
describe('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const run404Test = () => {
|
||||
describe('404', () => {
|
||||
it('Open random page', () => {
|
||||
cy.visit('/someRandomPage');
|
||||
cy.get('h1').contains('404');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
run404Test();
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
import {
|
||||
baseUrl,
|
||||
getElementFromAlias,
|
||||
getElementFromClassName,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
const statements = {
|
||||
createTableSql:
|
||||
'CREATE TABLE a_test_test_author (id serial PRIMARY KEY, first_name text, last_name text);',
|
||||
createCustomFuncSql: `CREATE OR REPLACE FUNCTION test_get_author_full_name(a_test_test_author_row a_test_test_author)
|
||||
RETURNS TEXT AS $function$
|
||||
SELECT a_test_test_author_row.first_name || ' ' || a_test_test_author_row.last_name
|
||||
$function$
|
||||
LANGUAGE sql STABLE;`,
|
||||
insertData_a1: `INSERT INTO a_test_test_author(first_name, last_name) VALUES ('ruskin', 'bond');`,
|
||||
insertData_a2: `INSERT INTO a_test_test_author(first_name, last_name) VALUES ('enid', 'blyton');`,
|
||||
cleanUpSql: 'DROP TABLE a_test_test_author CASCADE;',
|
||||
graphql: {
|
||||
query: `{
|
||||
a_test_test_author {
|
||||
full_name # this is the computed field`,
|
||||
},
|
||||
};
|
||||
|
||||
export const openRawSQL = () => {
|
||||
cy.get('a').contains('Data').click();
|
||||
cy.wait(3000);
|
||||
cy.get(getElementFromAlias('sql-link')).click();
|
||||
cy.wait(3000);
|
||||
cy.url().should('eq', `${baseUrl}/data/sql`);
|
||||
};
|
||||
|
||||
const clearText = () => {
|
||||
cy.get('textarea').type('{selectall}', { force: true });
|
||||
cy.get('textarea').trigger('keydown', {
|
||||
keyCode: 46,
|
||||
which: 46,
|
||||
force: true,
|
||||
});
|
||||
cy.wait(2000);
|
||||
};
|
||||
|
||||
// helper to type into the SQL textarea on rawsql page
|
||||
const typeStatement = (
|
||||
statement: string,
|
||||
shouldClearText = false,
|
||||
waitTimeUponType = 2000,
|
||||
endWaitTime = 5000,
|
||||
uncheckTrackCheckbox = false
|
||||
) => {
|
||||
if (shouldClearText) {
|
||||
clearText();
|
||||
}
|
||||
cy.get('textarea').type(statement, { force: true });
|
||||
cy.wait(waitTimeUponType);
|
||||
if (uncheckTrackCheckbox) {
|
||||
cy.get(getElementFromAlias('raw-sql-track-check')).uncheck();
|
||||
}
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
// FIXME: maybe necessary for CLI mode
|
||||
// cy.get(getElementFromAlias('raw-sql-statement-timeout')).should('be.disabled');
|
||||
cy.wait(endWaitTime);
|
||||
};
|
||||
|
||||
export const createTableAuthor = () => typeStatement(statements.createTableSql);
|
||||
|
||||
export const createCustomFunction = () =>
|
||||
typeStatement(statements.createCustomFuncSql, true, 2000, 5000, true);
|
||||
|
||||
export const insertAuthorsIntoTable = () => {
|
||||
typeStatement(statements.insertData_a1, true);
|
||||
typeStatement(statements.insertData_a2, true);
|
||||
clearText();
|
||||
};
|
||||
|
||||
export const searchForTable = () => {
|
||||
// ADD LATER: after search functionality is implemented
|
||||
// cy.get(getElementFromAlias('search-tables')).type('a_test_test_author');
|
||||
// cy.get(getElementFromAlias('table-links')).should(
|
||||
// 'contain',
|
||||
// 'a_test_test_author'
|
||||
// );
|
||||
cy.get(getElementFromAlias('a_test_test_author')).click();
|
||||
};
|
||||
|
||||
export const openModifySection = () => {
|
||||
// open modify section
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
// click on computed field section
|
||||
cy.get(getElementFromAlias('modify-table-edit-computed-field-0')).click();
|
||||
// type name
|
||||
cy.get(getElementFromAlias('computed-field-name-input')).type('{selectall}', {
|
||||
force: true,
|
||||
});
|
||||
cy.get(getElementFromAlias('computed-field-name-input')).trigger('keydown', {
|
||||
keyCode: 46,
|
||||
which: 46,
|
||||
force: true,
|
||||
});
|
||||
cy.get(getElementFromAlias('computed-field-name-input')).type('full_name', {
|
||||
force: true,
|
||||
});
|
||||
cy.wait(2000);
|
||||
// type & select function name
|
||||
cy.get(getElementFromClassName('function-name-select__control'))
|
||||
.children('div')
|
||||
.click({ multiple: true })
|
||||
.find('input')
|
||||
.focus()
|
||||
.type('test_get_author_full_name', { force: true })
|
||||
.get(getElementFromClassName('function-name-select__menu'))
|
||||
.first()
|
||||
.click();
|
||||
// enter comment
|
||||
cy.get(
|
||||
getElementFromAlias('computed-field-comment-input')
|
||||
).type('this is a test comment', { force: true });
|
||||
// saving the computed field
|
||||
cy.get(getElementFromAlias('modify-table-computed-field-0-save')).click();
|
||||
// verify that a computed field exists
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('computed-field-full_name')).contains('full_name');
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const routeToGraphiql = () => {
|
||||
cy.visit('/api/api-explorer');
|
||||
cy.wait(7000);
|
||||
cy.url().should('eq', `${baseUrl}/api/api-explorer`);
|
||||
};
|
||||
|
||||
export const verifyComputedFieldsResult = () => {
|
||||
// type the query
|
||||
cy.get('textarea')
|
||||
.first()
|
||||
.type(`{enter}{uparrow}${statements.graphql.query}`, { force: true });
|
||||
cy.wait(2000);
|
||||
// execute the query
|
||||
cy.get('.execute-button').click();
|
||||
// verify if full_name is present
|
||||
cy.get('.cm-property').contains('full_name');
|
||||
cy.get('.cm-string').contains('ruskin bond');
|
||||
cy.wait(2000);
|
||||
};
|
||||
|
||||
export const cleanUpSql = () => typeStatement(statements.cleanUpSql, true);
|
||||
|
||||
export const routeToSQLPage = () => {
|
||||
cy.visit('/data/sql');
|
||||
cy.wait(7000);
|
||||
cy.url().should('eq', `${baseUrl}/data/sql`);
|
||||
};
|
@ -0,0 +1,45 @@
|
||||
import {
|
||||
openRawSQL,
|
||||
createTableAuthor,
|
||||
createCustomFunction,
|
||||
insertAuthorsIntoTable,
|
||||
searchForTable,
|
||||
cleanUpSql,
|
||||
openModifySection,
|
||||
routeToGraphiql,
|
||||
verifyComputedFieldsResult,
|
||||
routeToSQLPage,
|
||||
} from './spec';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
describe('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runComputedFieldTests = () => {
|
||||
describe('Computed Fields', () => {
|
||||
it('Open Raw SQL page', openRawSQL);
|
||||
it('Create test table', createTableAuthor);
|
||||
it('Run SQL for custom function', createCustomFunction);
|
||||
it('Insert authors into table', insertAuthorsIntoTable);
|
||||
it('Search for table', searchForTable);
|
||||
it('Open Modify page and add computed field', openModifySection);
|
||||
it('Route to GraphiQL page', routeToGraphiql);
|
||||
it('Check computed field results on GraphiQL', verifyComputedFieldsResult);
|
||||
it('Route to Raw SQL page', routeToSQLPage);
|
||||
it('Test cleanup', cleanUpSql);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runComputedFieldTests();
|
||||
}
|
246
frontend/apps/console-ce-e2e/src/e2e/data/create-table/spec.ts
Normal file
246
frontend/apps/console-ce-e2e/src/e2e/data/create-table/spec.ts
Normal file
@ -0,0 +1,246 @@
|
||||
import {
|
||||
tableColumnTypeSelector,
|
||||
getElementFromAlias,
|
||||
getTableName,
|
||||
getColName,
|
||||
baseUrl,
|
||||
getIndexRoute,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
import {
|
||||
setMetaData,
|
||||
validateCT,
|
||||
ResultType,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
const testName = 'ct';
|
||||
|
||||
export const checkCreateTableRoute = () => {
|
||||
// Click on the create table button
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(15000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
// Match the URL
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
};
|
||||
|
||||
export const failCTWithoutColumns = () => {
|
||||
// Type table name
|
||||
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
// Check if the route didn't change
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
// Validate
|
||||
validateCT(getTableName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const failCTWithoutPK = () => {
|
||||
// Set first column
|
||||
cy.get(getElementFromAlias('column-0')).type(getColName(0));
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
|
||||
.first()
|
||||
.click();
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
// Check if the route didn't change
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
// Validate
|
||||
validateCT(getTableName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const failCTDuplicateColumns = () => {
|
||||
// Set second column
|
||||
cy.get(getElementFromAlias('column-1')).type(getColName(0));
|
||||
tableColumnTypeSelector('col-type-1');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
|
||||
.first()
|
||||
.click();
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
// Check for an alert
|
||||
cy.on('window:alert', str => {
|
||||
expect(
|
||||
str === `You have the following column names repeated: [${getColName(0)}]`
|
||||
).to.be.true;
|
||||
});
|
||||
// Check if the route didn't change
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
// Validate
|
||||
validateCT(getTableName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const failCTWrongDefaultValue = () => {
|
||||
// Set second column
|
||||
cy.get(getElementFromAlias('column-1')).clear().type(getColName(1));
|
||||
tableColumnTypeSelector('col-type-1');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
|
||||
.first()
|
||||
.click();
|
||||
cy.get(getElementFromAlias('col-default-1')).type('qwerty');
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
// Check if the route didn't change
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
// Validate
|
||||
validateCT(getTableName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const passCT = () => {
|
||||
cy.get(getElementFromAlias('frequently-used-columns')).first().should('exist');
|
||||
// Set second column
|
||||
cy.get(getElementFromAlias('column-1')).clear().type(getColName(1));
|
||||
tableColumnTypeSelector('col-type-1');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_text'))
|
||||
.first()
|
||||
.click();
|
||||
cy.get(getElementFromAlias('col-default-1')).clear();
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
cy.get(getElementFromAlias('primary-key-select-1')).select('1');
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(10000);
|
||||
// Check if the table got created and navigatied to modify table
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
cy.get(getElementFromAlias(getTableName(0, testName)));
|
||||
// Validate
|
||||
validateCT(getTableName(0, testName), ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passCTWithFK = () => {
|
||||
// go to create-table
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
// cy.get(getElementFromAlias('table-create')).click();
|
||||
// Set tablename
|
||||
cy.get(getElementFromAlias('tableName'))
|
||||
.clear()
|
||||
.type(getTableName(1, testName));
|
||||
// Set first column
|
||||
cy.get(getElementFromAlias('column-0')).type(getColName(0));
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
|
||||
.first()
|
||||
.click();
|
||||
// Set second column
|
||||
cy.get(getElementFromAlias('column-1')).type(getColName(1));
|
||||
tableColumnTypeSelector('col-type-1');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_text'))
|
||||
.first()
|
||||
.click();
|
||||
// Set third column
|
||||
cy.get(getElementFromAlias('column-2')).type(getColName(2));
|
||||
tableColumnTypeSelector('col-type-2');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_text'))
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Set foreign key
|
||||
cy.get(getElementFromAlias('add-table-edit-fk-0')).click();
|
||||
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
|
||||
getTableName(0, testName)
|
||||
);
|
||||
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('0');
|
||||
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select(getColName(0));
|
||||
cy.get(getElementFromAlias('foreign-key-0-lcol-1')).select('1');
|
||||
cy.get(getElementFromAlias('foreign-key-0-rcol-1')).select(getColName(1));
|
||||
cy.get(getElementFromAlias('foreign-key-0-onUpdate-cascade')).check();
|
||||
cy.get(getElementFromAlias('foreign-key-0-onDelete-cascade')).check();
|
||||
|
||||
// set unique key 1
|
||||
cy.get(getElementFromAlias('add-table-edit-unique-key-0')).click();
|
||||
cy.get(getElementFromAlias('unique-key-0-column-0')).select('1');
|
||||
|
||||
// set unique key 2
|
||||
cy.get(getElementFromAlias('add-table-edit-unique-key-1')).click();
|
||||
cy.get(getElementFromAlias('unique-key-1-column-0')).select('1');
|
||||
cy.get(getElementFromAlias('unique-key-1-column-1')).select('2');
|
||||
cy.get(getElementFromAlias('unique-key-1-column-2')).select('0');
|
||||
cy.get(getElementFromAlias('remove-uk-1-column-1')).click();
|
||||
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(10000);
|
||||
// Check if the table got created and navigatied to modify table
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
1,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
cy.get('div').contains(
|
||||
`${getTableName(1, testName)}_${getColName(1)}_${getColName(0)}`
|
||||
);
|
||||
cy.get(getElementFromAlias(getTableName(1, testName)));
|
||||
// Validate
|
||||
validateCT(getTableName(1, testName), ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const failCTDuplicateTable = () => {
|
||||
// Visit data page
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
// Type table name
|
||||
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
|
||||
// Set column
|
||||
cy.get(getElementFromAlias('column-0')).type(getColName(1));
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
|
||||
.first()
|
||||
.click();
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
const deleteTable = (tableName: string) => {
|
||||
cy.get(getElementFromAlias(tableName)).click();
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
|
||||
setPromptValue(tableName);
|
||||
|
||||
// Click on delete
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
// Confirm
|
||||
cy.window().its('prompt').should('be.called');
|
||||
|
||||
cy.wait(5000);
|
||||
validateCT(tableName, ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const deleteCTTestTables = () => {
|
||||
// Go to the modify section of the second table
|
||||
const secondTableName = getTableName(1, testName);
|
||||
deleteTable(secondTableName);
|
||||
// Go to the modify section of the first table
|
||||
const firstTableName = getTableName(0, testName);
|
||||
deleteTable(firstTableName);
|
||||
|
||||
// Match the URL
|
||||
|
||||
// FIXME: Temporarily disabling this.
|
||||
// cy.url().should('eq', `${baseUrl}/data/schema`);
|
||||
};
|
||||
|
||||
export const setValidationMetaData = () => {
|
||||
setMetaData();
|
||||
};
|
@ -0,0 +1,48 @@
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
|
||||
import {
|
||||
checkCreateTableRoute,
|
||||
failCTWithoutColumns,
|
||||
failCTWithoutPK,
|
||||
failCTDuplicateColumns,
|
||||
failCTWrongDefaultValue,
|
||||
passCT,
|
||||
failCTDuplicateTable,
|
||||
deleteCTTestTables,
|
||||
passCTWithFK,
|
||||
} from './spec';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
describe('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runCreateTableTests = () => {
|
||||
describe('Create Table', () => {
|
||||
it('Create table button opens the correct route', checkCreateTableRoute);
|
||||
it('Fails to create table without columns', failCTWithoutColumns);
|
||||
it('Fails to create table without primary key', failCTWithoutPK);
|
||||
it('Fails to create with duplicate columns', failCTDuplicateColumns);
|
||||
it('Fails to create with wrong default value', failCTWrongDefaultValue);
|
||||
it('Successfuly creates table', passCT);
|
||||
it(
|
||||
'Successfuly creates table with composite foreign and unique key',
|
||||
passCTWithFK
|
||||
);
|
||||
it('Fails to create duplicate table', failCTDuplicateTable);
|
||||
it('Delete the test tables', deleteCTTestTables);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runCreateTableTests();
|
||||
}
|
@ -0,0 +1,125 @@
|
||||
import { baseUrl, getElementFromAlias } from '../../../helpers/dataHelpers';
|
||||
|
||||
const statements = {
|
||||
createTableSql:
|
||||
'CREATE TABLE a_test_test_article (id serial PRIMARY KEY, title text, content text);',
|
||||
createCustomFuncSql: `CREATE FUNCTION a_test_test_search_articles(search text)
|
||||
RETURNS SETOF a_test_test_article AS $function$
|
||||
SELECT *
|
||||
FROM a_test_test_article
|
||||
WHERE
|
||||
title ilike ('%' || search || '%')
|
||||
OR content ilike ('%' || search || '%')
|
||||
$function$ LANGUAGE sql STABLE;`,
|
||||
insertData_a1: `INSERT INTO a_test_test_article(title, content) VALUES ('hasura is awesome', 'I mean duh?!');`,
|
||||
insertData_a2: `INSERT INTO a_test_test_article(title, content) VALUES ('cloud lauched', 'hasura <3 the cloud');`,
|
||||
deleteFunction: 'DROP FUNCTION a_test_test_search_articles(search text);',
|
||||
cleanUpSql: 'DROP TABLE a_test_test_article CASCADE;',
|
||||
graphql: {
|
||||
query: `{
|
||||
a_test_test_search_articles
|
||||
(args: {{} search: "hasura" }) {
|
||||
id
|
||||
title
|
||||
content
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
export const openRawSQL = () => {
|
||||
cy.get('a').contains('Data').click();
|
||||
cy.wait(3000);
|
||||
cy.get(getElementFromAlias('sql-link')).click();
|
||||
cy.wait(3000);
|
||||
cy.url().should('eq', `${baseUrl}/data/sql`);
|
||||
};
|
||||
|
||||
const clearText = () => {
|
||||
cy.get('textarea').type('{selectall}', { force: true });
|
||||
cy.get('textarea').trigger('keydown', {
|
||||
keyCode: 46,
|
||||
which: 46,
|
||||
force: true,
|
||||
});
|
||||
cy.wait(2000);
|
||||
};
|
||||
|
||||
// helper to type into the SQL textarea on rawsql page
|
||||
const typeStatement = (
|
||||
statement: string,
|
||||
shouldClearText = false,
|
||||
waitTimeUponType = 2000,
|
||||
endWaitTime = 5000,
|
||||
unCheckTrackFunction = false
|
||||
) => {
|
||||
if (shouldClearText) {
|
||||
clearText();
|
||||
}
|
||||
cy.get('textarea').type(statement, { force: true });
|
||||
cy.wait(waitTimeUponType);
|
||||
if (unCheckTrackFunction) {
|
||||
cy.get(getElementFromAlias('raw-sql-track-check')).uncheck();
|
||||
}
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
// FIXME: maybe necessary for CLI mode
|
||||
// cy.get(getElementFromAlias('raw-sql-statement-timeout')).should('be.disabled');
|
||||
cy.wait(endWaitTime);
|
||||
};
|
||||
|
||||
export const createTableArticle = () =>
|
||||
typeStatement(statements.createTableSql);
|
||||
|
||||
export const createCustomFunction = () =>
|
||||
typeStatement(statements.createCustomFuncSql, true, 2000, 5000, true);
|
||||
|
||||
export const insertAuthorsIntoTable = () => {
|
||||
typeStatement(statements.insertData_a1, true);
|
||||
typeStatement(statements.insertData_a2, true);
|
||||
clearText();
|
||||
};
|
||||
|
||||
export const trackCustomFn = () => {
|
||||
cy.visit('/data/default/schema/public');
|
||||
cy.wait(7000);
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
|
||||
|
||||
// Track Function
|
||||
cy.get(
|
||||
getElementFromAlias('add-track-function-a_test_test_search_articles')
|
||||
).click();
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const routeToGraphiql = () => {
|
||||
cy.visit('/api/api-explorer');
|
||||
cy.wait(7000);
|
||||
cy.url().should('eq', `${baseUrl}/api/api-explorer`);
|
||||
};
|
||||
|
||||
export const verifyCustomFnResult = () => {
|
||||
// Type the query
|
||||
cy.get('textarea')
|
||||
.first()
|
||||
.type(`{enter}{uparrow}${statements.graphql.query}`, { force: true });
|
||||
cy.wait(2000);
|
||||
cy.get('.execute-button').click();
|
||||
// verify if article is present
|
||||
|
||||
cy.get('.cm-property').contains('title');
|
||||
cy.get('.cm-property').contains('content');
|
||||
|
||||
cy.wait(2000);
|
||||
};
|
||||
|
||||
export const cleanUpSql = () => {
|
||||
typeStatement(statements.deleteFunction, true);
|
||||
typeStatement(statements.cleanUpSql, true);
|
||||
clearText();
|
||||
cy.wait(2000);
|
||||
};
|
||||
|
||||
export const routeToSQLPage = () => {
|
||||
cy.visit('/data/sql');
|
||||
cy.wait(7000);
|
||||
cy.url().should('eq', `${baseUrl}/data/sql`);
|
||||
};
|
@ -0,0 +1,43 @@
|
||||
import {
|
||||
openRawSQL,
|
||||
createTableArticle,
|
||||
createCustomFunction,
|
||||
insertAuthorsIntoTable,
|
||||
cleanUpSql,
|
||||
trackCustomFn,
|
||||
routeToGraphiql,
|
||||
verifyCustomFnResult,
|
||||
routeToSQLPage,
|
||||
} from './spec';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
describe('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runCustomFunctionTests = () => {
|
||||
describe('Custom Functions', () => {
|
||||
it('Open Raw SQL page', openRawSQL);
|
||||
it('Create test table', createTableArticle);
|
||||
it('Run SQL for custom function', createCustomFunction);
|
||||
it('Insert articles into table', insertAuthorsIntoTable);
|
||||
it('Track custom function', trackCustomFn);
|
||||
it('Route to GraphiQL page', routeToGraphiql);
|
||||
it('Check custom function results on GraphiQL', verifyCustomFnResult);
|
||||
it('Route to Raw SQL page', routeToSQLPage);
|
||||
it('Test cleanup', cleanUpSql);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runCustomFunctionTests();
|
||||
}
|
198
frontend/apps/console-ce-e2e/src/e2e/data/functions/spec.ts
Normal file
198
frontend/apps/console-ce-e2e/src/e2e/data/functions/spec.ts
Normal file
@ -0,0 +1,198 @@
|
||||
import {
|
||||
getElementFromAlias,
|
||||
baseUrl,
|
||||
getCustomFunctionName,
|
||||
getSchema,
|
||||
dropTable,
|
||||
testCustomFunctionSQLWithSessArg,
|
||||
getTrackFnPayload,
|
||||
createFunctionTable,
|
||||
trackCreateFunctionTable,
|
||||
getCreateTestFunctionQuery,
|
||||
getTrackTestFunctionQuery,
|
||||
createSampleTable,
|
||||
getTrackSampleTableQuery,
|
||||
createVolatileFunction,
|
||||
dropTableIfExists,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
import {
|
||||
dataRequest,
|
||||
validateCFunc,
|
||||
validateUntrackedFunc,
|
||||
ResultType,
|
||||
trackFunctionRequest,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
export const createCustomFunctionSuccess = () => {
|
||||
// Round about way to create a function
|
||||
dataRequest(createFunctionTable(), ResultType.SUCCESS, 'query');
|
||||
cy.wait(5000);
|
||||
dataRequest(trackCreateFunctionTable(), ResultType.SUCCESS, 'metadata');
|
||||
cy.wait(5000);
|
||||
|
||||
dataRequest(getCreateTestFunctionQuery(1), ResultType.SUCCESS, 'query');
|
||||
cy.wait(5000);
|
||||
dataRequest(getTrackTestFunctionQuery(1), ResultType.SUCCESS, 'metadata');
|
||||
cy.wait(5000);
|
||||
|
||||
// Check if the track checkbox is clicked or not
|
||||
validateCFunc(getCustomFunctionName(1), getSchema(), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const unTrackFunction = () => {
|
||||
cy.visit(
|
||||
`data/default/schema/public/functions/${getCustomFunctionName(1)}/modify`
|
||||
);
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('custom-function-edit-untrack-btn')).click();
|
||||
cy.wait(5000);
|
||||
validateUntrackedFunc(
|
||||
getCustomFunctionName(1),
|
||||
getSchema(),
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const trackFunction = () => {
|
||||
cy.get(
|
||||
getElementFromAlias(`add-track-function-${getCustomFunctionName(1)}`)
|
||||
).should('exist');
|
||||
cy.get(
|
||||
getElementFromAlias(`add-track-function-${getCustomFunctionName(1)}`)
|
||||
).click();
|
||||
cy.get(getElementFromAlias(`track-as-mutation`)).should('exist');
|
||||
cy.get(getElementFromAlias(`track-as-mutation`)).click();
|
||||
cy.wait(5000);
|
||||
validateCFunc(getCustomFunctionName(1), getSchema(), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const testSessVariable = () => {
|
||||
const fN = 'customFunctionWithSessionArg'.toLowerCase();
|
||||
dataRequest(
|
||||
dropTableIfExists({ name: 'text_result', schema: 'public' }),
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.wait(3000);
|
||||
dataRequest(createSampleTable(), ResultType.SUCCESS, 'query');
|
||||
cy.wait(3000);
|
||||
dataRequest(getTrackSampleTableQuery(), ResultType.SUCCESS, 'metadata');
|
||||
cy.wait(3000);
|
||||
|
||||
dataRequest(testCustomFunctionSQLWithSessArg(fN), ResultType.SUCCESS);
|
||||
cy.wait(3000);
|
||||
|
||||
trackFunctionRequest(getTrackFnPayload(fN), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
|
||||
cy.visit(`data/default/schema/public/functions/${fN}/modify`);
|
||||
cy.get(getElementFromAlias(`${fN}-session-argument-btn`), {
|
||||
timeout: 5000,
|
||||
}).click();
|
||||
|
||||
// invalid data should fail
|
||||
cy.get(getElementFromAlias(`${fN}-edit-sessvar-function-field`))
|
||||
.clear()
|
||||
.type('invalid');
|
||||
cy.get(getElementFromAlias(`${fN}-session-argument-save`)).click();
|
||||
|
||||
cy.expectErrorNotificationWithTitle(
|
||||
'Updating Session argument variable failed'
|
||||
);
|
||||
|
||||
cy.get(getElementFromAlias(`${fN}-session-argument-btn`), {
|
||||
timeout: 1000,
|
||||
}).click();
|
||||
cy.get(getElementFromAlias(`${fN}-edit-sessvar-function-field`))
|
||||
.clear()
|
||||
.type('hasura_session');
|
||||
cy.get(getElementFromAlias(`${fN}-session-argument-save`)).click();
|
||||
cy.wait(3000);
|
||||
cy.get(getElementFromAlias(fN)).should('be.visible');
|
||||
cy.visit(`data/default/schema/public/functions/${fN}/modify`);
|
||||
cy.wait(3000);
|
||||
cy.get(getElementFromAlias(`${fN}-session-argument`)).should(
|
||||
'contain',
|
||||
'hasura_session'
|
||||
);
|
||||
dataRequest(dropTable('text_result', true), ResultType.SUCCESS);
|
||||
cy.wait(3000);
|
||||
cy.visit(`data/default/schema/public/`);
|
||||
};
|
||||
|
||||
export const verifyPermissionTab = () => {
|
||||
cy.get(getElementFromAlias('functions-data/default-permissions')).click();
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('custom-function-permission-link')).should(
|
||||
'exist'
|
||||
);
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const deleteCustomFunction = () => {
|
||||
cy.get(getElementFromAlias('functions-data/default-modify')).click();
|
||||
|
||||
setPromptValue(getCustomFunctionName(1));
|
||||
|
||||
cy.get(getElementFromAlias('custom-function-edit-delete-btn')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('delete-confirmation-error')).should('not.exist');
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
|
||||
cy.wait(5000);
|
||||
|
||||
dataRequest(dropTable(), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const trackVolatileFunction = () => {
|
||||
const fN = 'customVolatileFunc'.toLowerCase();
|
||||
dataRequest(
|
||||
dropTableIfExists({ name: 'text_result', schema: 'public' }),
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.wait(5000);
|
||||
dataRequest(createSampleTable(), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
dataRequest(getTrackSampleTableQuery(), ResultType.SUCCESS, 'metadata');
|
||||
dataRequest(createVolatileFunction(fN), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
cy.visit(`data/default/schema/public`);
|
||||
cy.get(getElementFromAlias(`add-track-function-${fN}`)).click();
|
||||
cy.get(getElementFromAlias('track-as-mutation')).click();
|
||||
cy.wait(2000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/functions/${fN}/modify`
|
||||
);
|
||||
dataRequest(dropTable('text_result', true), ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const trackVolatileFunctionAsQuery = () => {
|
||||
const fN = 'customVolatileFunc'.toLowerCase();
|
||||
dataRequest(
|
||||
dropTableIfExists({ name: 'text_result', schema: 'public' }),
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.wait(5000);
|
||||
dataRequest(createSampleTable(), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
dataRequest(getTrackSampleTableQuery(), ResultType.SUCCESS, 'metadata');
|
||||
dataRequest(createVolatileFunction(fN), ResultType.SUCCESS);
|
||||
cy.wait(5000);
|
||||
cy.visit(`data/default/schema/public`);
|
||||
cy.get(getElementFromAlias(`add-track-function-${fN}`)).click();
|
||||
cy.get(getElementFromAlias('track-as-query')).click();
|
||||
cy.wait(2000);
|
||||
cy.get(getElementFromAlias('track-as-query-confirm')).click();
|
||||
cy.wait(2000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/functions/${fN}/modify`
|
||||
);
|
||||
dataRequest(dropTable('text_result', true), ResultType.SUCCESS);
|
||||
};
|
43
frontend/apps/console-ce-e2e/src/e2e/data/functions/test.ts
Normal file
43
frontend/apps/console-ce-e2e/src/e2e/data/functions/test.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
|
||||
import {
|
||||
createCustomFunctionSuccess,
|
||||
deleteCustomFunction,
|
||||
unTrackFunction,
|
||||
trackFunction,
|
||||
verifyPermissionTab,
|
||||
trackVolatileFunction,
|
||||
trackVolatileFunctionAsQuery,
|
||||
} from './spec';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
describe('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runCreateCustomFunctionsTableTests = () => {
|
||||
describe('Custom Function Tests', () => {
|
||||
it('Create a custom function and track', createCustomFunctionSuccess);
|
||||
it('Untrack custom function', unTrackFunction);
|
||||
it('Track custom function', trackFunction);
|
||||
it('Verify permission tab', verifyPermissionTab);
|
||||
it('Delete custom function', deleteCustomFunction);
|
||||
// TODO it('Test custom function with Session Argument', testSessVariable);
|
||||
it('Tracks VOLATILE function as mutation', trackVolatileFunction);
|
||||
it('Tracks VOLATILE function as query', trackVolatileFunctionAsQuery);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runCreateCustomFunctionsTableTests();
|
||||
}
|
533
frontend/apps/console-ce-e2e/src/e2e/data/insert-browse/spec.ts
Normal file
533
frontend/apps/console-ce-e2e/src/e2e/data/insert-browse/spec.ts
Normal file
@ -0,0 +1,533 @@
|
||||
import {
|
||||
baseUrl,
|
||||
getColName,
|
||||
getTableName,
|
||||
dataTypes,
|
||||
getElementFromAlias,
|
||||
typeDefaults,
|
||||
tableColumnTypeSelector,
|
||||
getIndexRoute,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
import {
|
||||
validateInsert,
|
||||
setMetaData,
|
||||
validateCT,
|
||||
ResultType,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
const numOfDataTypes = dataTypes.length;
|
||||
const testName = 'ib';
|
||||
|
||||
//* ******************** Util functions ************************
|
||||
|
||||
const setColumns = () => {
|
||||
for (let i = 0; i < numOfDataTypes; i += 1) {
|
||||
// Type column name
|
||||
cy.get(getElementFromAlias(`column-${i}`)).type(getColName(i));
|
||||
// Select column type
|
||||
tableColumnTypeSelector(`col-type-${i}`);
|
||||
cy.get(getElementFromAlias(`data_test_column_type_value_${dataTypes[i]}`))
|
||||
.first()
|
||||
.click();
|
||||
|
||||
if (i === dataTypes.indexOf('text')) {
|
||||
cy.get(getElementFromAlias(`unique-${i}`)).check({ force: true });
|
||||
}
|
||||
// Set appropriate default if the type is not serial
|
||||
if (i > 1) {
|
||||
cy.get(getElementFromAlias(`col-default-${i}`)).type(
|
||||
typeDefaults[dataTypes[i]]
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const clickSaveOrInsert = (firstIndex: number, currentIndex: number) => {
|
||||
if (currentIndex === firstIndex) {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
} else {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
}
|
||||
cy.wait(2000);
|
||||
};
|
||||
|
||||
const checkQuerySuccess = () => {
|
||||
// Expect only 4 rows i.e. expect fifth element to not exist
|
||||
cy.get('[role=gridcell]').contains('filter-text');
|
||||
cy.get('[role=row]').eq(2).should('not.exist');
|
||||
};
|
||||
|
||||
const checkOrder = (order: string) => {
|
||||
// Utility function to get right element
|
||||
|
||||
if (order === 'asc') {
|
||||
cy.get('[role=row]').each(($el, index) => {
|
||||
if (index !== 0) {
|
||||
cy.wrap($el)
|
||||
.find('[role=gridcell]')
|
||||
.first()
|
||||
.next()
|
||||
.next()
|
||||
.contains(index);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cy.get('[role=row]').each(($el, index) => {
|
||||
if (index !== 0) {
|
||||
cy.wrap($el)
|
||||
.find('[role=gridcell]')
|
||||
.first()
|
||||
.next()
|
||||
.next()
|
||||
.contains(22 - index);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
//* ******************** Test functions ***************************
|
||||
|
||||
export const passBICreateTable = () => {
|
||||
cy.wait(7000);
|
||||
// Click create table button
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
// Type table name
|
||||
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
|
||||
// Set columns with all fields
|
||||
setColumns();
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
validateCT(getTableName(0, testName), ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passSearchTables = () => {
|
||||
// Click add table button
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
// Type table name
|
||||
cy.get(getElementFromAlias('tableName')).type(getTableName(1, testName));
|
||||
// Type column name
|
||||
cy.get(getElementFromAlias('column-0')).type(getColName(0));
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
|
||||
.first()
|
||||
.click();
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
validateCT(getTableName(0, testName), ResultType.SUCCESS);
|
||||
// cy.get(getElementFromAlias('search-tables')).type('0');
|
||||
// cy.get(getElementFromAlias('table-links')).should('not.contain', '1');
|
||||
// cy.get(getElementFromAlias('search-tables')).type('{home}{del}');
|
||||
};
|
||||
|
||||
export const checkInsertRoute = () => {
|
||||
// Click on Insert tab
|
||||
cy.get(getElementFromAlias(getTableName(0, testName))).click();
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
// Match URL
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/insert`
|
||||
);
|
||||
};
|
||||
|
||||
export const failBIWrongDataType = () => {
|
||||
// Check if the table creation fails for wrong inputs of each data type
|
||||
for (let i = 2; i < numOfDataTypes; i += 1) {
|
||||
// Text and Boolean always succeed, so we check only for others
|
||||
if (dataTypes[i] !== 'text' && dataTypes[i] !== 'boolean') {
|
||||
const sureFailString = 'abcd1234';
|
||||
// Type a string that fails
|
||||
cy.get(getElementFromAlias(`typed-input-${i}`)).type(sureFailString);
|
||||
// Click the Save/Insert Again button.
|
||||
clickSaveOrInsert(2, i);
|
||||
cy.get(getElementFromAlias(`typed-input-${i}`)).clear();
|
||||
// Check the default radio of curret column
|
||||
cy.get(getElementFromAlias(`typed-input-default-${i}`)).check();
|
||||
}
|
||||
|
||||
validateInsert(getTableName(0, testName), 0);
|
||||
}
|
||||
};
|
||||
|
||||
export const passBIInsert20Rows = () => {
|
||||
for (let i = 0; i < 20; i += 1) {
|
||||
// Type a string in the text type fields of some rows (to be tested in Browse rows)
|
||||
const textIndex = dataTypes.indexOf('text');
|
||||
// Click the Insert Again button.
|
||||
if (i === 0) {
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
|
||||
'{selectall}{del}'
|
||||
);
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
|
||||
'filter-text'
|
||||
);
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
} else {
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
|
||||
'{selectall}{del}'
|
||||
);
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`))
|
||||
.type('{selectall}{del}')
|
||||
.type(Math.random().toString(36).substring(7));
|
||||
cy.get(
|
||||
getElementFromAlias(`typed-input-default-${textIndex + 1}`)
|
||||
).check();
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
cy.wait(300);
|
||||
validateInsert(getTableName(0, testName), i + 1);
|
||||
}
|
||||
}
|
||||
// Wait for insert notifications to disappear
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
export const checkBrowseRoute = () => {
|
||||
// Click on Browse tab
|
||||
cy.get(getElementFromAlias(getTableName(0, testName))).click();
|
||||
cy.get(getElementFromAlias('table-browse-rows')).click();
|
||||
cy.wait(2000);
|
||||
// Match URL
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/browse`
|
||||
);
|
||||
};
|
||||
|
||||
export const passBI20RowsExist = () => {
|
||||
// Check if the 20 inserted elements reflect in the UI
|
||||
cy.get(getElementFromAlias('pagination-select')).select('20');
|
||||
// cy.get(getElementFromAlias('table-browse-rows')).contains('20');
|
||||
};
|
||||
|
||||
export const checkPagination = () => {
|
||||
// Check if the current page is 1
|
||||
cy.get('.-pageJump > input').should('have.value', '1');
|
||||
// Check if the total number of pages is 3
|
||||
cy.get('.-totalPages').contains('3');
|
||||
// Check if the default value of rows displayed is 10
|
||||
cy.get('.-pageSizeOptions > select').should('have.value', '10');
|
||||
cy.get('.-next > button').click({ force: true });
|
||||
cy.wait(3000);
|
||||
// Check if the page changed
|
||||
cy.get(
|
||||
'.rt-tbody > div:nth-child(1) > div > div:nth-child(3) > div'
|
||||
).contains('11');
|
||||
cy.get('.-pageJump > input').should('have.value', '2');
|
||||
cy.get('.-previous > button').click({ force: true });
|
||||
cy.wait(3000);
|
||||
// Check if the page changed
|
||||
cy.get('.-pageJump > input').should('have.value', '1');
|
||||
cy.get('.-pageSizeOptions > select').select('5 rows');
|
||||
cy.wait(3000);
|
||||
// Check if the total number of pages changed
|
||||
cy.get('.-totalPages').contains('5');
|
||||
};
|
||||
|
||||
export const passBISort = (order: string) => {
|
||||
cy.wait(7000);
|
||||
// Select column with type 'serial'
|
||||
const serialIndex = dataTypes.indexOf('serial');
|
||||
cy.get(getElementFromAlias('sort-column-0')).select(getColName(serialIndex), {
|
||||
force: true,
|
||||
});
|
||||
// Select order as `descending`
|
||||
cy.get(getElementFromAlias('sort-order-0')).select(
|
||||
order === 'asc' ? 'Asc' : 'Desc',
|
||||
{ force: true }
|
||||
);
|
||||
// Run query
|
||||
cy.get(getElementFromAlias('run-query')).click();
|
||||
cy.wait(5000);
|
||||
// Check order
|
||||
checkOrder(order);
|
||||
|
||||
// Clear filter
|
||||
cy.get(getElementFromAlias('clear-sorts-0')).click({ force: true });
|
||||
// Run query
|
||||
cy.get(getElementFromAlias('run-query')).click();
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passBIFilterQueryEq = () => {
|
||||
// Select column with type "text"
|
||||
const textIndex = dataTypes.indexOf('text');
|
||||
cy.get(getElementFromAlias('filter-column-0')).select(getColName(textIndex));
|
||||
// Select operator as `eq`
|
||||
cy.get(getElementFromAlias('filter-op-0')).select('$eq');
|
||||
// Type value as "filter-text"
|
||||
cy.get("input[placeholder='-- value --']").last().type('filter-text');
|
||||
// Run query
|
||||
cy.get(getElementFromAlias('run-query')).click();
|
||||
cy.wait(2000);
|
||||
// Check if the query was successful
|
||||
checkQuerySuccess();
|
||||
|
||||
// Clear filter
|
||||
cy.get(getElementFromAlias('clear-filter-0')).click();
|
||||
// Run query
|
||||
cy.get(getElementFromAlias('run-query')).click();
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const deleteBITestTable = () => {
|
||||
cy.get(getElementFromAlias(getTableName(2, testName))).click();
|
||||
// Go to the modify section of the table
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
cy.wait(2000);
|
||||
setPromptValue(getTableName(2, testName));
|
||||
// Click on delete
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
// Confirm
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(7000);
|
||||
// Match the URL
|
||||
// FIXME: change this later
|
||||
// cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
|
||||
validateCT(getTableName(2, testName), ResultType.FAILURE);
|
||||
|
||||
cy.get(getElementFromAlias(getTableName(1, testName))).click();
|
||||
// Go to the modify section of the table
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
cy.wait(2000);
|
||||
setPromptValue(getTableName(1, testName));
|
||||
// Click on delete
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
// Confirm
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(7000);
|
||||
// Match the URL
|
||||
// FIXME: change this later
|
||||
// cy.url().should('eq', `${baseUrl}/data/schema`);
|
||||
validateCT(getTableName(1, testName), ResultType.FAILURE);
|
||||
|
||||
cy.get(getElementFromAlias(getTableName(0, testName))).click();
|
||||
// Go to the modify section of the table
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
setPromptValue(getTableName(0, testName));
|
||||
cy.wait(2000);
|
||||
// Click on delete
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
// Confirm
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(7000);
|
||||
|
||||
// Match the URL
|
||||
// FIXME: change later
|
||||
// cy.url().should('eq', `${baseUrl}/data/schema`);
|
||||
validateCT(getTableName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const failBIUniqueKeys = () => {
|
||||
// Type a string in the text type fields of some rows (to be tested in Browse rows)
|
||||
const textIndex = dataTypes.indexOf('text');
|
||||
const floatIndex = dataTypes.indexOf('numeric');
|
||||
cy.get(getElementFromAlias(`typed-input-${floatIndex}`)).type(`${0.5555}`);
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`))
|
||||
.clear()
|
||||
.type('filter-text');
|
||||
|
||||
// Click the Insert Again button.
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
|
||||
'{selectall}{del}'
|
||||
);
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type('name');
|
||||
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
// Check default for next insert
|
||||
|
||||
cy.get(getElementFromAlias(`typed-input-default-${textIndex}`)).check();
|
||||
|
||||
validateInsert(getTableName(0, testName), 21);
|
||||
cy.wait(7000);
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`))
|
||||
.clear()
|
||||
.type('filter-text');
|
||||
// Click the Insert Again button.
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
|
||||
'{selectall}{del}'
|
||||
);
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type('name');
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
|
||||
cy.wait(7000);
|
||||
validateInsert(getTableName(0, testName), 21);
|
||||
};
|
||||
export const setValidationMetaData = () => {
|
||||
setMetaData();
|
||||
};
|
||||
|
||||
export const passEditButton = () => {
|
||||
cy.get(getElementFromAlias('table-browse-rows')).click();
|
||||
cy.wait(2000);
|
||||
cy.get(getElementFromAlias('row-edit-button-0')).click();
|
||||
cy.wait(2000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/edit`
|
||||
);
|
||||
const textIndex = dataTypes.indexOf('text');
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type(
|
||||
'{selectall}{del}'
|
||||
);
|
||||
cy.get(getElementFromAlias(`typed-input-${textIndex}`)).type('new-text');
|
||||
cy.get(getElementFromAlias('edit-save-button')).click();
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
export const passCloneButton = () => {
|
||||
cy.get(getElementFromAlias('table-browse-rows')).click();
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('row-clone-button-0')).click();
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/insert`
|
||||
);
|
||||
cy.get(getElementFromAlias('clear-button')).click();
|
||||
cy.get(getElementFromAlias('typed-input-0')).should('have.value', '');
|
||||
};
|
||||
|
||||
export const checkViewRelationship = () => {
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
// Type table name
|
||||
cy.get(getElementFromAlias('tableName')).type(getTableName(2, testName));
|
||||
cy.get(getElementFromAlias('column-0')).type('id');
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
|
||||
.first()
|
||||
.click();
|
||||
cy.get(getElementFromAlias('column-1')).type('someID');
|
||||
tableColumnTypeSelector('col-type-1');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
|
||||
.first()
|
||||
.click();
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
validateCT(getTableName(0, testName), ResultType.SUCCESS);
|
||||
// Add foreign key
|
||||
cy.get(getElementFromAlias('modify-table-edit-fk-0')).click();
|
||||
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
|
||||
getTableName(0, testName)
|
||||
);
|
||||
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('0');
|
||||
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select(getColName(0));
|
||||
cy.get(getElementFromAlias('modify-table-fk-0-save')).click();
|
||||
cy.wait(5000);
|
||||
// Add relationship
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('obj-rel-add-0')).click();
|
||||
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('someRel');
|
||||
cy.get(getElementFromAlias('obj-rel-save-0')).click();
|
||||
cy.wait(2000);
|
||||
// Insert a row
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
cy.get(getElementFromAlias('typed-input-1')).type('1');
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
cy.wait(1000);
|
||||
cy.get(getElementFromAlias('table-browse-rows')).click();
|
||||
cy.wait(1000);
|
||||
cy.get('.rt-table').within(() => {
|
||||
cy.get('a').contains('View').click();
|
||||
cy.wait(1000);
|
||||
});
|
||||
cy.get('a').contains('Close').first().click();
|
||||
};
|
||||
|
||||
export const passDeleteRow = () => {
|
||||
cy.get(getElementFromAlias('table-browse-rows')).click();
|
||||
cy.wait(5000);
|
||||
// cy.get(getElementFromAlias('table-browse-rows')).contains('21');
|
||||
cy.get(getElementFromAlias('row-delete-button-0')).click();
|
||||
cy.on('window:confirm', str => {
|
||||
expect(
|
||||
str.indexOf('This will permanently delete this row from this table') !==
|
||||
-1
|
||||
).to.be.true;
|
||||
});
|
||||
// cy.get(getElementFromAlias('table-browse-rows')).contains('20');
|
||||
cy.wait(14000);
|
||||
};
|
||||
|
||||
export const passBulkDeleteRows = () => {
|
||||
cy.get(getElementFromAlias('table-browse-rows')).click();
|
||||
cy.wait(5000);
|
||||
// cy.get(getElementFromAlias('table-browse-rows')).contains('20');
|
||||
cy.get(getElementFromAlias('row-checkbox-0')).click();
|
||||
cy.get(getElementFromAlias('row-checkbox-1')).click();
|
||||
cy.get(getElementFromAlias('bulk-delete')).click();
|
||||
cy.wait(1000);
|
||||
cy.on('window:confirm', str => {
|
||||
expect(
|
||||
str.indexOf('This will permanently delete rows from this table') !== -1
|
||||
).to.be.true;
|
||||
});
|
||||
// cy.get(getElementFromAlias('table-browse-rows')).contains('18');
|
||||
cy.wait(14000);
|
||||
};
|
||||
|
||||
export const passBulkDeleteAllRows = () => {
|
||||
cy.get(getElementFromAlias('table-browse-rows')).click();
|
||||
cy.wait(5000);
|
||||
// cy.get(getElementFromAlias('table-browse-rows')).contains('18');
|
||||
cy.get(getElementFromAlias('select-all-rows')).click();
|
||||
cy.get(getElementFromAlias('bulk-delete')).click();
|
||||
cy.wait(1000);
|
||||
cy.on('window:confirm', str => {
|
||||
expect(
|
||||
str.indexOf('This will permanently delete rows from this table') !== -1
|
||||
).to.be.true;
|
||||
});
|
||||
// cy.get(getElementFromAlias('table-browse-rows')).contains('(8)');
|
||||
cy.wait(14000);
|
||||
};
|
||||
|
||||
export const passArrayDataType = () => {
|
||||
// create new column
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
cy.wait(1000);
|
||||
cy.get(getElementFromAlias('modify-table-edit-add-new-column')).click();
|
||||
cy.get(getElementFromAlias('column-name')).type('array_column');
|
||||
cy.get(getElementFromAlias('col-type-0'))
|
||||
.children('div')
|
||||
.click()
|
||||
.find('input')
|
||||
.type('text[]', { force: true });
|
||||
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
|
||||
|
||||
// insert new row
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('typed-input-11')).type('["a", "b"]');
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
|
||||
// go to browse rows and check if row was added
|
||||
cy.get(getElementFromAlias('table-browse-rows')).click();
|
||||
cy.wait(5000);
|
||||
// cy.get(getElementFromAlias('table-browse-rows')).contains('(9)');
|
||||
};
|
@ -0,0 +1,68 @@
|
||||
import { testMode } from '../../../helpers/common';
|
||||
|
||||
import {
|
||||
passBICreateTable,
|
||||
deleteBITestTable,
|
||||
checkInsertRoute,
|
||||
failBIWrongDataType,
|
||||
failBIUniqueKeys,
|
||||
passBIInsert20Rows,
|
||||
checkBrowseRoute,
|
||||
passBISort,
|
||||
passBIFilterQueryEq,
|
||||
passEditButton,
|
||||
passSearchTables,
|
||||
passCloneButton,
|
||||
checkViewRelationship,
|
||||
passDeleteRow,
|
||||
passBulkDeleteRows,
|
||||
passBulkDeleteAllRows,
|
||||
passArrayDataType,
|
||||
} from './spec';
|
||||
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runInsertBrowseTests = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Table: Browse and Insert', () => {
|
||||
it('Create a table with fields of all data types', passBICreateTable);
|
||||
it('Search for tables', passSearchTables);
|
||||
it('Check Insert Route', checkInsertRoute);
|
||||
it('Fails when entered wrong data type', failBIWrongDataType);
|
||||
it('Insert 20 rows', passBIInsert20Rows);
|
||||
it('Fail for adding same data for Unique keys', failBIUniqueKeys);
|
||||
it('Check browser rows route', checkBrowseRoute);
|
||||
// it('20 Inserted rows reflect in browse rows', passBI20RowsExist);
|
||||
// it('Check pagination in Browse Rows table', checkPagination);
|
||||
it('Ascending sort works as expected', () => passBISort('asc'));
|
||||
it('Descending sort works as expected', () => passBISort('desc'));
|
||||
it('Filter query works as expected with $eq', passBIFilterQueryEq);
|
||||
it('Check edit button', passEditButton);
|
||||
it('Check for clone clear', passCloneButton);
|
||||
it('Delete the row', passDeleteRow);
|
||||
it('Bulk delete rows', passBulkDeleteRows);
|
||||
it('Bulk delete all rows', passBulkDeleteAllRows);
|
||||
it('Handle array data types', passArrayDataType);
|
||||
it('Check view relationship', checkViewRelationship);
|
||||
it('Delete test table', deleteBITestTable);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runInsertBrowseTests();
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import { setPromptWithCb } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
|
||||
export const navigateAndOpenConnectDatabasesForm = () => {
|
||||
cy.location('pathname').then(currentPage => {
|
||||
const alreadyOnThePage = currentPage.startsWith('/data/manage');
|
||||
if (!alreadyOnThePage) {
|
||||
cy.log('**--- Navigate to Connect Databases Form**');
|
||||
|
||||
cy.log('**--- visit index route and set metadata**');
|
||||
cy.visit('/data/default/schema/public').then(setMetaData);
|
||||
|
||||
cy.log('**--- Click on the manage database menu**');
|
||||
cy.findByRole('button', { name: 'Manage' }).click();
|
||||
cy.location('pathname').should('eq', '/data/manage');
|
||||
}
|
||||
});
|
||||
|
||||
cy.log('**--- Click on the Connect Database button**');
|
||||
cy.findByRole('button', { name: 'Connect Database' }).click();
|
||||
cy.location('pathname').should('eq', '/data/manage/connect');
|
||||
|
||||
cy.get('form').within(() => {
|
||||
cy.log('**--- Click on Connection Settings section**');
|
||||
cy.contains('Connection Settings').click();
|
||||
});
|
||||
cy.get('form').within(() => {
|
||||
cy.log('**--- Click on GraphQL Field Customization section**');
|
||||
cy.contains('GraphQL Field Customization').click();
|
||||
});
|
||||
};
|
||||
|
||||
export const navigateToManageDatabases = () => {
|
||||
cy.log('**--- Visit index route and set metadata**');
|
||||
cy.visit('/data/default/schema/public').then(setMetaData);
|
||||
|
||||
cy.log('**--- Visit the manage database route**');
|
||||
cy.getBySel('sidebar-manage-database').click();
|
||||
cy.location('pathname').should('eq', '/data/manage');
|
||||
};
|
||||
|
||||
export const submitConnectDBForm = () => {
|
||||
cy.log('**--- Click on the Connect Database button**');
|
||||
cy.getBySel('connect-database-btn').click();
|
||||
};
|
||||
|
||||
export const removeDBFromList = (dbName: string) => {
|
||||
setPromptWithCb(dbName, () => {
|
||||
cy.log('**--- Click on the Remove Database button**');
|
||||
cy.getBySel(dbName).find('button').contains('Remove').click();
|
||||
});
|
||||
};
|
@ -0,0 +1,160 @@
|
||||
import { baseUrl, testMode } from '../../../helpers/common';
|
||||
import {
|
||||
navigateAndOpenConnectDatabasesForm,
|
||||
navigateToManageDatabases,
|
||||
removeDBFromList,
|
||||
submitConnectDBForm,
|
||||
} from './common.spec';
|
||||
import {
|
||||
createDB,
|
||||
fillDetailsForPgConnParamsForm,
|
||||
fillDetailsForPgDbUrlForm,
|
||||
fillDetailsForPgEnvVarForm,
|
||||
removeDB,
|
||||
} from './postgres.spec';
|
||||
|
||||
const connectPgDatabaseFormTests = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Add a database via connect form', () => {
|
||||
describe('can successfully add', () => {
|
||||
describe('a postgres database', () => {
|
||||
it('using a connection string', () => {
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Add postgres DB via connection string**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
navigateAndOpenConnectDatabasesForm();
|
||||
fillDetailsForPgDbUrlForm('postgres_db_with_url');
|
||||
submitConnectDBForm();
|
||||
|
||||
cy.log('**--- Notifies that DB is being added');
|
||||
cy.expectSuccessNotificationWithTitle('Adding data source...');
|
||||
|
||||
cy.log('**--- Redirects to Data Manager page');
|
||||
cy.getBySel('manage-database-section').within(() => {
|
||||
cy.findByText('Data Manager');
|
||||
});
|
||||
cy.url().should('eq', `${baseUrl}/data/manage`);
|
||||
|
||||
cy.log('**--- has postgres_db_with_url on manage page');
|
||||
cy.findByText('postgres_db_with_url');
|
||||
|
||||
cy.log('**--- has success notification displayed');
|
||||
cy.expectSuccessNotificationWithTitle(
|
||||
'Data source added successfully!'
|
||||
);
|
||||
|
||||
cy.log('**--- Remove database**');
|
||||
removeDB('postgres_db_with_url');
|
||||
});
|
||||
|
||||
it('using connection parameters', () => {
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Add postgres DB via connection parameters**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
navigateAndOpenConnectDatabasesForm();
|
||||
fillDetailsForPgConnParamsForm('postgres_db_with_conn_param');
|
||||
submitConnectDBForm();
|
||||
|
||||
cy.log('**--- notifies that db is being added');
|
||||
cy.expectSuccessNotificationWithTitle('Adding data source...');
|
||||
|
||||
cy.log('**--- redirects to Data Manager page');
|
||||
cy.getBySel('manage-database-section').within(() => {
|
||||
cy.findByText('Data Manager');
|
||||
});
|
||||
cy.url().should('eq', `${baseUrl}/data/manage`);
|
||||
|
||||
cy.log('**--- has postgres_db_with_conn_param on manage page');
|
||||
cy.findByText('postgres_db_with_conn_param');
|
||||
|
||||
cy.log('**--- has success notification displayed');
|
||||
cy.expectSuccessNotificationWithTitle(
|
||||
'Data source added successfully!'
|
||||
);
|
||||
|
||||
cy.log('**--- Remove database**');
|
||||
removeDB('postgres_db_with_conn_param');
|
||||
});
|
||||
|
||||
it('using environment variables', () => {
|
||||
cy.log('**------------------------------**');
|
||||
cy.log('**--- Add postgres DB via env vars**');
|
||||
cy.log('**------------------------------**');
|
||||
|
||||
navigateAndOpenConnectDatabasesForm();
|
||||
fillDetailsForPgEnvVarForm('postgres_db_with_env_var');
|
||||
submitConnectDBForm();
|
||||
|
||||
cy.log('**--- notifies that db is being added');
|
||||
cy.expectSuccessNotificationWithTitle('Adding data source...');
|
||||
|
||||
cy.log('**--- redirects to Data Manager page');
|
||||
cy.getBySel('manage-database-section').within(() => {
|
||||
cy.findByText('Data Manager');
|
||||
});
|
||||
cy.url().should('eq', `${baseUrl}/data/manage`);
|
||||
|
||||
cy.log('**--- has postgres_db_with_env_var on manage page');
|
||||
cy.findByText('postgres_db_with_env_var');
|
||||
|
||||
cy.log('**--- has success notification displayed');
|
||||
cy.expectSuccessNotificationWithTitle(
|
||||
'Data source added successfully!'
|
||||
);
|
||||
|
||||
cy.log('**--- Remove database**');
|
||||
removeDB('postgres_db_with_env_var');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fails on submitting', () => {
|
||||
describe('an empty form', () => {
|
||||
it('submit with no inputs filled in', () => {
|
||||
navigateAndOpenConnectDatabasesForm();
|
||||
cy.getBySel('connect-database-btn').click();
|
||||
cy.expectErrorNotification();
|
||||
});
|
||||
});
|
||||
|
||||
it('with a duplicate database name', () => {
|
||||
navigateAndOpenConnectDatabasesForm();
|
||||
createDB('test');
|
||||
fillDetailsForPgDbUrlForm('test');
|
||||
submitConnectDBForm();
|
||||
|
||||
cy.log('**--- verify error notification');
|
||||
cy.expectErrorNotificationWithTitle('Adding data source failed');
|
||||
|
||||
cy.log('**--- Remove database**');
|
||||
removeDB('test');
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const manageDatabasesPageTests = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Connected Databases list page', () => {
|
||||
it('can successfully remove db', () => {
|
||||
cy.log('**--- Create database**');
|
||||
createDB('db_for_removal');
|
||||
navigateToManageDatabases();
|
||||
|
||||
cy.log('**--- use the remove button');
|
||||
removeDBFromList('db_for_removal');
|
||||
|
||||
cy.log('**--- verify success notification');
|
||||
cy.expectSuccessNotificationWithTitle('Data source removed successfully');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
connectPgDatabaseFormTests();
|
||||
manageDatabasesPageTests();
|
||||
}
|
@ -0,0 +1,198 @@
|
||||
const config = {
|
||||
host: 'postgres',
|
||||
port: '5432',
|
||||
dbName: 'postgres',
|
||||
username: 'postgres',
|
||||
password: 'postgrespassword',
|
||||
};
|
||||
|
||||
const dbUrl = `postgres://${config.username}:${config.password}@${config.host}:${config.port}/${config.dbName}`;
|
||||
|
||||
const graphqlCustomizationTest = () => {
|
||||
cy.log('**--- Select a graphql naming convention**');
|
||||
cy.get('[data-test="GraphQL Field Customization"]').within(() => {
|
||||
cy.get('[data-test="radio-select-hasura-default"]').should('be.visible');
|
||||
cy.get('[data-test="radio-select-graphql-default"]').should('be.visible');
|
||||
cy.get('label[for="radio-select-graphql-default"]').click();
|
||||
});
|
||||
|
||||
cy.log('**--- Fill Root Fields Customizations**');
|
||||
cy.get('form[aria-label="rootFields"]').within(() => {
|
||||
cy.findByPlaceholderText('Namespace...').type('name_space');
|
||||
cy.findByPlaceholderText('prefix_').type('prefix_');
|
||||
cy.findByPlaceholderText('_suffix').type('_suffix');
|
||||
});
|
||||
|
||||
cy.log('**--- Fill Type Names Customizations**');
|
||||
cy.get('form[aria-label="typeNames"]').within(() => {
|
||||
cy.findByPlaceholderText('prefix_').type('prefix_');
|
||||
cy.findByPlaceholderText('_suffix').type('_suffix');
|
||||
});
|
||||
};
|
||||
|
||||
export const fillDetailsForPgDbUrlForm = (dbName: string) => {
|
||||
cy.log('**--- Fill Form using db url**');
|
||||
cy.getBySel('database-display-name').type(dbName);
|
||||
cy.getBySel('database-type').select('postgres');
|
||||
cy.getBySel('database-url').type(dbUrl);
|
||||
cy.getBySel('max-connections').type('50');
|
||||
cy.getBySel('idle-timeout').type('180');
|
||||
cy.getBySel('retries').type('1');
|
||||
graphqlCustomizationTest();
|
||||
};
|
||||
|
||||
export const fillDetailsForPgConnParamsForm = (dbName: string) => {
|
||||
cy.log('**--- Fill Form using connection parameter**');
|
||||
cy.get("input[type='radio']").eq(2).click();
|
||||
cy.getBySel('database-display-name').type(dbName);
|
||||
cy.getBySel('database-type').select('postgres');
|
||||
cy.getBySel('host').type(config.host);
|
||||
cy.getBySel('port').type(config.port);
|
||||
cy.getBySel('username').type(config.username);
|
||||
cy.getBySel('password').type(config.password);
|
||||
cy.getBySel('database-name').type(config.dbName);
|
||||
graphqlCustomizationTest();
|
||||
};
|
||||
|
||||
export const fillDetailsForPgEnvVarForm = (dbName: string) => {
|
||||
cy.log('**--- Fill Form using env vars**');
|
||||
cy.get("input[type='radio']").eq(0).click();
|
||||
cy.getBySel('database-display-name').type(dbName);
|
||||
cy.getBySel('database-type').select('postgres');
|
||||
cy.getBySel('database-url-env').type('HASURA_GRAPHQL_DATABASE_URL');
|
||||
graphqlCustomizationTest();
|
||||
};
|
||||
|
||||
export const createDB = (dbName: string) => {
|
||||
const postBody = {
|
||||
type: 'pg_add_source',
|
||||
args: {
|
||||
name: dbName,
|
||||
configuration: {
|
||||
connection_info: {
|
||||
database_url: dbUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const removeDB = (dbName: string) => {
|
||||
const postBody = { type: 'pg_drop_source', args: { name: dbName } };
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
cy.reload();
|
||||
};
|
||||
|
||||
const createTable = (tableName: string) => {
|
||||
const postBody = {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
source: 'default',
|
||||
sql: `CREATE TABLE "public"."${tableName}" ("id" serial NOT NULL, "name" text NOT NULL, "countryCode" text DEFAULT 'IN', PRIMARY KEY ("id") );`,
|
||||
cascade: false,
|
||||
read_only: false,
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v2/query', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('result_type', 'CommandOk'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const trackTable = (tableName: string) => {
|
||||
const postBody = {
|
||||
type: 'pg_track_table',
|
||||
args: {
|
||||
table: {
|
||||
name: tableName,
|
||||
schema: 'public',
|
||||
},
|
||||
source: 'default',
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const untrackTable = (tableName: string) => {
|
||||
const postBody = {
|
||||
type: 'pg_untrack_table',
|
||||
args: {
|
||||
table: {
|
||||
schema: 'public',
|
||||
name: tableName,
|
||||
},
|
||||
source: 'default',
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const deleteTable = (tableName: string) => {
|
||||
const postBody = {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
source: 'default',
|
||||
sql: `DROP table "public"."${tableName}";`,
|
||||
cascade: false,
|
||||
read_only: false,
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v2/query', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('result_type', 'CommandOk'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const createRemoteSchema = (remoteSchemaName: string) => {
|
||||
const postBody = {
|
||||
type: 'add_remote_schema',
|
||||
args: {
|
||||
name: remoteSchemaName,
|
||||
definition: {
|
||||
timeout_seconds: 60,
|
||||
forward_client_headers: false,
|
||||
headers: [],
|
||||
url: 'https://countries.trevorblades.com/',
|
||||
},
|
||||
comment: '',
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const deleteRemoteSchema = (remoteSchemaName: string) => {
|
||||
const postBody = {
|
||||
type: 'remove_remote_schema',
|
||||
args: {
|
||||
name: remoteSchemaName,
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
@ -0,0 +1,424 @@
|
||||
import {
|
||||
getElementFromAlias,
|
||||
baseUrl,
|
||||
tableColumnTypeSelector,
|
||||
getIndexRoute,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
import {
|
||||
setMetaData,
|
||||
validateCT,
|
||||
createView,
|
||||
validateColumn,
|
||||
validateView,
|
||||
ResultType,
|
||||
TableFields,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
const userId = 5555;
|
||||
|
||||
export const createTable = (name: string, dict: TableFields) => {
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
cy.get(getElementFromAlias('tableName')).type(`${name}_table_vt`);
|
||||
const keys = Object.keys(dict).map(k => k);
|
||||
const values = Object.keys(dict).map(k => dict[k]);
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
cy.get(getElementFromAlias(`column-${i}`)).type(keys[i]);
|
||||
tableColumnTypeSelector(`col-type-${i}`);
|
||||
cy.get(getElementFromAlias(`data_test_column_type_value_${values[i]}`))
|
||||
.first()
|
||||
.click();
|
||||
}
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('id');
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${name}_table_vt/modify`
|
||||
);
|
||||
|
||||
validateCT(`${name}_table_vt`, ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passVCreateTables = () => {
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
createTable('author', { id: 'integer', name: 'text' });
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
createTable('article', {
|
||||
id: 'integer',
|
||||
title: 'text',
|
||||
Content: 'text',
|
||||
author_id: 'integer',
|
||||
rating: 'integer',
|
||||
});
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
createTable('comment', {
|
||||
id: 'integer',
|
||||
user_id: 'integer',
|
||||
article_id: 'integer',
|
||||
comment: 'text',
|
||||
});
|
||||
};
|
||||
|
||||
export const passVCreateMaterializedViews = () => {
|
||||
createView(`CREATE MATERIALIZED VIEW author_average_rating_vt AS
|
||||
SELECT author_table_vt.id, avg(article_table_vt.rating)
|
||||
From author_table_vt, article_table_vt
|
||||
WHERE author_table_vt.id = article_table_vt.author_id
|
||||
GROUP BY author_table_vt.id`);
|
||||
};
|
||||
|
||||
export const passTrackTable = () => {
|
||||
cy.visit('/data/default/schema/public/');
|
||||
cy.wait(7000);
|
||||
cy.get(
|
||||
getElementFromAlias('add-track-table-author_average_rating_vt')
|
||||
).click();
|
||||
cy.wait(7000);
|
||||
validateView('author_average_rating_vt', ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passMaterializedViewRoute = () => {
|
||||
cy.get(getElementFromAlias('author_average_rating_vt')).click();
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/views/author_average_rating_vt/browse`
|
||||
);
|
||||
};
|
||||
|
||||
export const passVAddDataarticle = (
|
||||
data: (string | number)[],
|
||||
index: number
|
||||
) => {
|
||||
// Click the Insert Again button.
|
||||
cy.get('label')
|
||||
.contains('id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
|
||||
cy.get('label')
|
||||
.contains('title')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('title')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[1]}`);
|
||||
cy.get('label')
|
||||
.contains('Content')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('Content')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[2]}`);
|
||||
cy.get('label')
|
||||
.contains('author_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('author_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[3]}`);
|
||||
cy.get('label')
|
||||
.contains('rating')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('rating')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[4]}`);
|
||||
if (index) {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
} else {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
}
|
||||
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passVAddDataauthor = (
|
||||
data: (string | number)[],
|
||||
index: number
|
||||
) => {
|
||||
cy.get('label')
|
||||
.contains('id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
|
||||
cy.get('label')
|
||||
.contains('name')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('name')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[1]}`);
|
||||
if (index) {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
} else {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
}
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passVAddDatacomment = (
|
||||
data: (string | number)[],
|
||||
index: number
|
||||
) => {
|
||||
cy.get('label')
|
||||
.contains('id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
|
||||
cy.get('label')
|
||||
.contains('user_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('user_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[1]}`);
|
||||
cy.get('label')
|
||||
.contains('article_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('article_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[2]}`);
|
||||
cy.get('label')
|
||||
.contains('comment')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('comment')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[3]}`);
|
||||
if (index) {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
} else {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
}
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
const checkQuerySuccess = () => {
|
||||
// Expect only 4 rows i.e. expect fifth element to not exist
|
||||
cy.get('[role=gridcell]').contains(userId);
|
||||
cy.get('[role=row]').eq(2).should('not.exist');
|
||||
};
|
||||
|
||||
export const passVAddData = () => {
|
||||
let data;
|
||||
cy.get(getElementFromAlias('article_table_vt')).click();
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
data = [1, 'A', 'Sontent', userId, 4];
|
||||
passVAddDataarticle(data, 0);
|
||||
data = [2, 'B', 'Sontenta', 2, 4];
|
||||
passVAddDataarticle(data, 1);
|
||||
data = [3, 'C', 'Sontentb', userId, 4];
|
||||
passVAddDataarticle(data, 2);
|
||||
cy.get(getElementFromAlias('author_table_vt')).click();
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
|
||||
data = [userId, 'A'];
|
||||
passVAddDataauthor(data, 0);
|
||||
data = [2, 'B'];
|
||||
passVAddDataauthor(data, 1);
|
||||
cy.get(getElementFromAlias('comment_table_vt')).click();
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
|
||||
data = [1, 1, 1, 'new comment'];
|
||||
passVAddDatacomment(data, 0);
|
||||
data = [2, 2, 2, 'new comment'];
|
||||
passVAddDatacomment(data, 1);
|
||||
data = [3, 1, 2, 'new comment'];
|
||||
passVAddDatacomment(data, 2);
|
||||
};
|
||||
|
||||
export const passVFilterQueryEq = () => {
|
||||
// Select column with type `text`
|
||||
cy.get('select')
|
||||
.find('option')
|
||||
.contains('-- column --')
|
||||
.parent()
|
||||
.first()
|
||||
.select('id');
|
||||
// Type value as `filter-text`
|
||||
cy.get("input[placeholder='-- value --']").last().type(`${userId}`);
|
||||
// Run query
|
||||
cy.get(getElementFromAlias('run-query')).click();
|
||||
cy.wait(5000);
|
||||
// Check if the query was successful
|
||||
checkQuerySuccess();
|
||||
};
|
||||
|
||||
const checkOrder = (order: string) => {
|
||||
// Utility function to get right element
|
||||
if (order === 'asc') {
|
||||
cy.get('[role=row]').each(($el, index) => {
|
||||
if (index === 1) {
|
||||
cy.wrap($el).find('[role=gridcell]').first().next().next().contains(2);
|
||||
}
|
||||
if (index === 2) {
|
||||
cy.wrap($el)
|
||||
.find('[role=gridcell]')
|
||||
.first()
|
||||
.next()
|
||||
.next()
|
||||
.contains(userId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cy.get('[role=row]').each(($el, index) => {
|
||||
if (index === 2) {
|
||||
cy.wrap($el).find('[role=gridcell]').first().next().next().contains(2);
|
||||
}
|
||||
if (index === 1) {
|
||||
cy.wrap($el)
|
||||
.find('[role=gridcell]')
|
||||
.first()
|
||||
.next()
|
||||
.next()
|
||||
.contains(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const passVAscendingSort = () => {
|
||||
cy.wait(7000);
|
||||
// cy.scrollTo('top');
|
||||
// Select column with type 'serial'
|
||||
cy.get('select')
|
||||
.find('option')
|
||||
.contains('-- column --')
|
||||
.parent()
|
||||
.last()
|
||||
.select('id');
|
||||
// Run query
|
||||
cy.get(getElementFromAlias('run-query')).click();
|
||||
// Check order
|
||||
checkOrder('asc');
|
||||
};
|
||||
|
||||
export const passModifyMaterializedView = () => {
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
cy.get('button').contains('Modify').last().click();
|
||||
cy.url().should('eq', `${baseUrl}/data/sql`);
|
||||
};
|
||||
|
||||
export const passVAddManualObjRel = () => {
|
||||
cy.get(getElementFromAlias('author_average_rating_vt')).click();
|
||||
cy.wait(2000);
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.wait(2000);
|
||||
cy.get(getElementFromAlias('create-edit-manual-rel')).click();
|
||||
cy.get(getElementFromAlias('manual-relationship-type')).select('object');
|
||||
cy.get("input[placeholder='Enter relationship name']").type('author');
|
||||
cy.get(getElementFromAlias('manual-relationship-ref-schema')).select(
|
||||
'public'
|
||||
);
|
||||
cy.get(getElementFromAlias('manual-relationship-ref-table')).select(
|
||||
'author_table_vt'
|
||||
);
|
||||
cy.get(getElementFromAlias('manual-relationship-lcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('manual-relationship-rcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('create-manual-rel-save')).click();
|
||||
cy.wait(7000);
|
||||
validateColumn(
|
||||
'author_average_rating_vt',
|
||||
['avg', { name: 'author', columns: ['name'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const passVDeleteRelationships = () => {
|
||||
cy.get(getElementFromAlias('author_average_rating_vt')).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('relationship-toggle-editor-author')).click();
|
||||
cy.get(getElementFromAlias('relationship-remove-author')).click();
|
||||
cy.on('window:alert', str => {
|
||||
return expect(str === 'Are you sure?').to.be.true;
|
||||
});
|
||||
cy.wait(7000);
|
||||
validateColumn(
|
||||
'author_average_rating_vt',
|
||||
['avg', { name: 'author', columns: ['name'] }],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
};
|
||||
|
||||
export const passVDeleteMaterializedView = () => {
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
setPromptValue('author_average_rating_vt');
|
||||
cy.get(getElementFromAlias('delete-view')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(7000);
|
||||
validateView('author_average_rating_vt', ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const deleteTable = (name: string) => {
|
||||
cy.get(getElementFromAlias(name)).click();
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
setPromptValue(name);
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(7000);
|
||||
validateCT(name, ResultType.FAILURE);
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
export const passVDeleteTables = () => {
|
||||
deleteTable('comment_table_vt');
|
||||
deleteTable('article_table_vt');
|
||||
deleteTable('author_table_vt');
|
||||
};
|
||||
|
||||
export const setValidationMetaData = () => {
|
||||
setMetaData();
|
||||
};
|
@ -0,0 +1,54 @@
|
||||
import {
|
||||
passVCreateTables,
|
||||
passVCreateMaterializedViews,
|
||||
passVAddData,
|
||||
passTrackTable,
|
||||
passVAddManualObjRel,
|
||||
passVAscendingSort,
|
||||
passModifyMaterializedView,
|
||||
passVFilterQueryEq,
|
||||
passMaterializedViewRoute,
|
||||
passVDeleteRelationships,
|
||||
passVDeleteMaterializedView,
|
||||
passVDeleteTables,
|
||||
} from './spec';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runMaterializedViewsTest = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Materialized Views', () => {
|
||||
it('Create Tables', passVCreateTables);
|
||||
it('Add data to table', passVAddData);
|
||||
it('Create MaterializedView', passVCreateMaterializedViews);
|
||||
it('Adding it to the table', passTrackTable);
|
||||
it('Check the materializedview route', passMaterializedViewRoute);
|
||||
it('Ascending order MaterializedView Table', passVAscendingSort);
|
||||
it('Filter the MaterializedView table', passVFilterQueryEq);
|
||||
it('Modify the View', passModifyMaterializedView);
|
||||
it('Adding Object Relationship to MaterializedView', passVAddManualObjRel);
|
||||
it('Deleting Relationship', passVDeleteRelationships);
|
||||
it('Deleting MaterializedView', passVDeleteMaterializedView);
|
||||
it('Deleting Tables', passVDeleteTables);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runMaterializedViewsTest();
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import { validateMigrationMode } from '../../validators/validators';
|
||||
|
||||
import { toggleOnMigrationMode, toggleOffMigrationMode } from './utils';
|
||||
|
||||
export const testToggleButton = () => {
|
||||
// Turn off migration mode
|
||||
toggleOffMigrationMode();
|
||||
cy.wait(10000);
|
||||
// Validate
|
||||
validateMigrationMode(false);
|
||||
cy.wait(7000);
|
||||
// Turn on migration mode
|
||||
toggleOnMigrationMode();
|
||||
cy.wait(10000);
|
||||
// Validate
|
||||
validateMigrationMode(true);
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
export const checkToggleButton = () => {
|
||||
cy.window().then(win => {
|
||||
const { consoleMode } = win.__env;
|
||||
if (consoleMode === 'cli') {
|
||||
testToggleButton();
|
||||
} else {
|
||||
cy.get('[class=react-toggle-track]').should('not.exist');
|
||||
}
|
||||
});
|
||||
};
|
@ -0,0 +1,27 @@
|
||||
import { checkToggleButton } from './spec';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
describe('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runMigrationModeTests = () => {
|
||||
describe('Migration mode', () => {
|
||||
it('Check the toggle button', checkToggleButton);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runMigrationModeTests();
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
import { migrateModeUrl } from '../../../helpers/common';
|
||||
|
||||
export const toggleOnMigrationMode = () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: migrateModeUrl,
|
||||
}).then(response => {
|
||||
if (response.body.migration_mode === 'false') {
|
||||
// Go to migrations section
|
||||
cy.get('a')
|
||||
.contains('Migrations')
|
||||
.click();
|
||||
cy.wait(3000);
|
||||
// Toggle Migration mode
|
||||
cy.get('[class=react-toggle-track]').click();
|
||||
cy.wait(10000);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const toggleOffMigrationMode = () => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: migrateModeUrl,
|
||||
}).then(response => {
|
||||
if (response.body.migration_mode === 'true') {
|
||||
// Go to migrations section
|
||||
cy.get('a')
|
||||
.contains('Migrations')
|
||||
.click();
|
||||
cy.wait(3000);
|
||||
// Toggle Migration mode
|
||||
cy.get('[class=react-toggle-track]').click();
|
||||
cy.wait(10000);
|
||||
}
|
||||
});
|
||||
};
|
439
frontend/apps/console-ce-e2e/src/e2e/data/modify/spec.ts
Normal file
439
frontend/apps/console-ce-e2e/src/e2e/data/modify/spec.ts
Normal file
@ -0,0 +1,439 @@
|
||||
import {
|
||||
tableColumnTypeSelector,
|
||||
baseUrl,
|
||||
getTableName,
|
||||
getColName,
|
||||
getElementFromAlias,
|
||||
createUntrackedFunctionSQL,
|
||||
dropUntrackedFunctionSQL,
|
||||
getIndexRoute,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
import {
|
||||
setMetaData,
|
||||
validateCT,
|
||||
validateColumn,
|
||||
ResultType,
|
||||
dataRequest,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
const testName = 'mod';
|
||||
|
||||
export const passMTFunctionList = () => {
|
||||
const tableName = getTableName(0, testName);
|
||||
dataRequest(
|
||||
createUntrackedFunctionSQL(`${tableName}_id_fn`, tableName),
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('modify-table-edit-computed-field-0')).click();
|
||||
|
||||
cy.get(getElementFromAlias('functions-dropdown')).click();
|
||||
|
||||
cy.get('[data-test^="data_test_column_type_value_"]').should(
|
||||
'have.length',
|
||||
1
|
||||
);
|
||||
|
||||
cy.get('[data-test^="data_test_column_type_value_"]')
|
||||
.first()
|
||||
.should('have.text', `${getTableName(0, testName)}_id_fn`.toLowerCase());
|
||||
dataRequest(
|
||||
dropUntrackedFunctionSQL(`${tableName}_id_fn`),
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const passMTCreateTable = () => {
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
|
||||
cy.get(getElementFromAlias('column-0')).type('id');
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
|
||||
.first()
|
||||
.click();
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('id');
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
validateCT(getTableName(0, testName), ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passMTCheckRoute = () => {
|
||||
// Click on the create table button
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
// Match the URL
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
};
|
||||
|
||||
export const passMTRenameTable = () => {
|
||||
cy.get(getElementFromAlias('heading-edit-table')).click();
|
||||
cy.get(getElementFromAlias('heading-edit-table-input'))
|
||||
.clear()
|
||||
.type(getTableName(3, testName));
|
||||
cy.get(getElementFromAlias('heading-edit-table-save')).click();
|
||||
cy.wait(15000);
|
||||
validateCT(getTableName(3, testName), ResultType.SUCCESS);
|
||||
cy.get(getElementFromAlias('heading-edit-table')).click();
|
||||
cy.get(getElementFromAlias('heading-edit-table-input'))
|
||||
.clear()
|
||||
.type(getTableName(0, testName));
|
||||
cy.get(getElementFromAlias('heading-edit-table-save')).click();
|
||||
cy.wait(15000);
|
||||
validateCT(getTableName(0, testName), ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passMTRenameColumn = () => {
|
||||
cy.wait(10000);
|
||||
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
|
||||
cy.get(getElementFromAlias('edit-col-name')).clear().type(getColName(3));
|
||||
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
|
||||
cy.wait(15000);
|
||||
validateColumn(
|
||||
getTableName(0, testName),
|
||||
[getColName(3)],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
|
||||
cy.get(getElementFromAlias('edit-col-name')).clear().type('id');
|
||||
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
|
||||
cy.wait(15000);
|
||||
validateColumn(getTableName(0, testName), ['id'], ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passMTChangeDefaultValueForPKey = () => {
|
||||
cy.wait(10000);
|
||||
cy.get(getElementFromAlias('modify-table-edit-column-0')).click();
|
||||
cy.get(getElementFromAlias('edit-col-default')).clear().type('1234');
|
||||
cy.get(getElementFromAlias('modify-table-column-0-save')).click();
|
||||
cy.wait(15000);
|
||||
};
|
||||
|
||||
export const passMTMoveToTable = () => {
|
||||
cy.get(getElementFromAlias(getTableName(0, testName))).click();
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/browse`
|
||||
);
|
||||
};
|
||||
|
||||
export const failMTWithoutColName = () => {
|
||||
cy.get(getElementFromAlias('modify-table-edit-add-new-column')).click();
|
||||
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
|
||||
validateColumn(
|
||||
getTableName(0, testName),
|
||||
[getColName(2)],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
};
|
||||
|
||||
export const failMTWithoutColType = () => {
|
||||
cy.get(getElementFromAlias('column-name')).type(getColName(2));
|
||||
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
validateColumn(
|
||||
getTableName(0, testName),
|
||||
[getColName(2)],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
};
|
||||
|
||||
export const Addcolumnnullable = () => {
|
||||
cy.get(getElementFromAlias('column-name')).type('{selectall}{del}');
|
||||
cy.get(getElementFromAlias('column-name')).type(getColName(3));
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_text'))
|
||||
.first()
|
||||
.click();
|
||||
cy.get(getElementFromAlias('nullable-checkbox')).uncheck({ force: true });
|
||||
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
|
||||
cy.wait(2500);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
validateColumn(
|
||||
getTableName(0, testName),
|
||||
[getColName(3)],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
};
|
||||
|
||||
export const Addcolumnname = (name: string) => {
|
||||
cy.get(getElementFromAlias('column-name')).type('{selectall}{del}');
|
||||
cy.get(getElementFromAlias('column-name')).type(name);
|
||||
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
|
||||
.first()
|
||||
.click();
|
||||
|
||||
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
|
||||
cy.wait(5000);
|
||||
validateColumn(getTableName(0, testName), [name], ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passMTAddColumn = () => {
|
||||
cy.get(getElementFromAlias('frequently-used-columns')).first().should('exist');
|
||||
cy.get(getElementFromAlias('column-name')).type('{selectall}{del}');
|
||||
cy.get(getElementFromAlias('column-name')).type(getColName(0));
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
|
||||
.first()
|
||||
.click();
|
||||
cy.get(getElementFromAlias('modify-table-add-new-column-save')).click();
|
||||
cy.wait(5000);
|
||||
validateColumn(
|
||||
getTableName(0, testName),
|
||||
[getColName(0)],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const Movetocolumn = () => {
|
||||
Addcolumnname(getColName(1));
|
||||
cy.get(getElementFromAlias('modify-table-edit-column-1')).click();
|
||||
};
|
||||
|
||||
export const failMCWithWrongDefaultValue = () => {
|
||||
cy.get(getElementFromAlias('modify-table-edit-column-1')).click();
|
||||
cy.get(getElementFromAlias('edit-col-default')).type('abcd');
|
||||
cy.get(getElementFromAlias('modify-table-column-1-save')).click();
|
||||
};
|
||||
|
||||
export const passMCWithRightDefaultValue = () => {
|
||||
cy.get(getElementFromAlias('edit-col-default')).clear().type('1234');
|
||||
cy.get(getElementFromAlias('modify-table-column-1-save')).click();
|
||||
cy.wait(10000);
|
||||
};
|
||||
|
||||
export const passCreateForeignKey = () => {
|
||||
cy.get(getElementFromAlias('modify-table-edit-fk-0')).click();
|
||||
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
|
||||
getTableName(0, testName)
|
||||
);
|
||||
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('0');
|
||||
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('modify-table-fk-0-save')).click();
|
||||
cy.wait(10000);
|
||||
};
|
||||
|
||||
export const passRemoveForeignKey = () => {
|
||||
cy.get(getElementFromAlias('modify-table-edit-fk-0')).click();
|
||||
cy.get(getElementFromAlias('modify-table-fk-0-remove')).click();
|
||||
cy.wait(10000);
|
||||
};
|
||||
|
||||
export const passModifyPkey = () => {
|
||||
cy.get(getElementFromAlias('modify-table-edit-pks')).click();
|
||||
cy.get(getElementFromAlias('primary-key-select-1')).select('1');
|
||||
cy.get(getElementFromAlias('modify-table-pks-save')).click();
|
||||
cy.get(getElementFromAlias('pk-config-text')).within(() => {
|
||||
cy.get('b').contains(getColName(0));
|
||||
cy.get('b').contains('id');
|
||||
});
|
||||
cy.wait(5000);
|
||||
|
||||
cy.get(getElementFromAlias('remove-pk-column-1')).click();
|
||||
cy.get(getElementFromAlias('modify-table-pks-save')).click();
|
||||
cy.get(getElementFromAlias('pk-config-text')).within(() => {
|
||||
cy.get('b').contains('id');
|
||||
});
|
||||
cy.get(getElementFromAlias('pk-config-text')).within(() => {
|
||||
cy.get('b').should('not.contain', getColName(0));
|
||||
});
|
||||
cy.get(getElementFromAlias('modify-table-close-pks')).click();
|
||||
cy.wait(3000);
|
||||
};
|
||||
|
||||
export const passCreateUniqueKey = () => {
|
||||
cy.get(getElementFromAlias('modify-table-edit-unique-key-0')).click();
|
||||
cy.get(getElementFromAlias('unique-key-0-column-0')).select('0');
|
||||
cy.get(getElementFromAlias('unique-key-0-column-1')).select('1');
|
||||
cy.wait(1000);
|
||||
cy.get(getElementFromAlias('modify-table-unique-key-0-save')).click();
|
||||
cy.wait(5000);
|
||||
cy.get('div').contains(
|
||||
`${getTableName(0, testName)}_id_${getColName(0)}_key`
|
||||
);
|
||||
};
|
||||
|
||||
export const passModifyUniqueKey = () => {
|
||||
cy.get(getElementFromAlias('modify-table-edit-unique-key-0')).click();
|
||||
cy.get(getElementFromAlias('remove-uk-0-column-0')).click();
|
||||
cy.get(getElementFromAlias('modify-table-unique-key-0-save')).click();
|
||||
cy.wait(5000);
|
||||
cy.get('div').contains(`${getTableName(0, testName)}_${getColName(0)}_key`);
|
||||
};
|
||||
|
||||
export const passRemoveUniqueKey = () => {
|
||||
cy.get(getElementFromAlias('modify-table-edit-unique-key-0')).click();
|
||||
cy.get(getElementFromAlias('modify-table-unique-key-0-remove')).click();
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passMTDeleteCol = () => {
|
||||
setPromptValue(getColName(0));
|
||||
cy.get(getElementFromAlias('modify-table-edit-column-1')).click();
|
||||
cy.get(getElementFromAlias('modify-table-column-1-remove')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(5000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
validateColumn(
|
||||
getTableName(0, testName),
|
||||
[getColName(0)],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
};
|
||||
|
||||
export const passMTDeleteTableCancel = () => {
|
||||
setPromptValue(null);
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
|
||||
validateCT(getTableName(0, testName), ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passMTDeleteTable = () => {
|
||||
setPromptValue(getTableName(0, testName));
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(5000);
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
|
||||
validateCT(getTableName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const setValidationMetaData = () => {
|
||||
setMetaData();
|
||||
};
|
||||
|
||||
// Views Modify /////////////////////////////////////////////////
|
||||
|
||||
export const createTable = (name: string, dict: { [key: string]: any }) => {
|
||||
cy.url().should('eq', `${baseUrl}/data/schema/public/table/add`);
|
||||
cy.get(getElementFromAlias('tableName')).type(`${name}_table_mod`);
|
||||
const keys = Object.keys(dict).map(k => k);
|
||||
const values = Object.keys(dict).map(k => dict[k]);
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
cy.get('input[placeholder="column_name"]').last().type(keys[i]);
|
||||
cy.get('select')
|
||||
.find('option')
|
||||
.contains('-- type --')
|
||||
.parent()
|
||||
.last()
|
||||
.select(values[i]);
|
||||
}
|
||||
|
||||
cy.get('select').last().select('id');
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${name}_table_mod/modify`
|
||||
);
|
||||
|
||||
validateCT(`${name}_table_mod`, ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const Createtables = () => {
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
createTable('author', { id: 'integer', name: 'Text' });
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
createTable('article', {
|
||||
id: 'integer',
|
||||
title: 'text',
|
||||
Content: 'text',
|
||||
author_id: 'integer',
|
||||
rating: 'integer',
|
||||
});
|
||||
};
|
||||
|
||||
export const Createview = () => {
|
||||
cy.get(getElementFromAlias('sql-link')).click();
|
||||
cy.get('textarea').type(`CREATE VIEW author_average_rating_mod AS
|
||||
SELECT author_table_mod.id, avg(article_table.rating)
|
||||
From author_table_mod, article_table_mod
|
||||
WHERE author_table_mod.id = article_table_mod.author_id
|
||||
GROUP BY author_table_mod.id`);
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
validateCT('author_average_rating_mod', ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const Checkviewtable = () => {
|
||||
cy.get(getElementFromAlias('author_average_rating_mod')).click();
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/views/author_average_rating_mod/browse`
|
||||
);
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
cy.get(getElementFromAlias('modify-view')).click();
|
||||
cy.url().should('eq', `${baseUrl}/data/sql`);
|
||||
};
|
||||
|
||||
export const Checkviewtabledelete = () => {
|
||||
cy.get(getElementFromAlias('author_average_rating_mod')).click();
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/views/author_average_rating_mod/browse`
|
||||
);
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
setPromptValue('author_average_rating_mod');
|
||||
cy.get(getElementFromAlias('delete-view')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
|
||||
cy.wait(7000);
|
||||
validateCT('author_average_rating_mod', ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const Issue = () => {
|
||||
cy.get('.ace_text-input').first().type('#include');
|
||||
};
|
75
frontend/apps/console-ce-e2e/src/e2e/data/modify/test.ts
Normal file
75
frontend/apps/console-ce-e2e/src/e2e/data/modify/test.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
passMTCheckRoute,
|
||||
passMTMoveToTable,
|
||||
passMTCreateTable,
|
||||
failMTWithoutColName,
|
||||
failMTWithoutColType,
|
||||
passMTAddColumn,
|
||||
passMTDeleteTableCancel,
|
||||
passMTDeleteCol,
|
||||
passMTDeleteTable,
|
||||
passMCWithRightDefaultValue,
|
||||
failMCWithWrongDefaultValue,
|
||||
passCreateForeignKey,
|
||||
passRemoveForeignKey,
|
||||
passMTRenameTable,
|
||||
passMTRenameColumn,
|
||||
passModifyPkey,
|
||||
passCreateUniqueKey,
|
||||
passModifyUniqueKey,
|
||||
passRemoveUniqueKey,
|
||||
passMTChangeDefaultValueForPKey,
|
||||
passMTFunctionList,
|
||||
} from './spec';
|
||||
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
describe('Check Data Tab', () => {
|
||||
it('Clicking on Data tab opens the correct route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runModifyTableTests = () => {
|
||||
describe('Modify Table', () => {
|
||||
it('Creating a table', passMTCreateTable);
|
||||
it('Moving to the table', passMTMoveToTable);
|
||||
it('Modify table button opens the correct route', passMTCheckRoute);
|
||||
it(
|
||||
'Can create computed field with compatible functions',
|
||||
passMTFunctionList
|
||||
);
|
||||
it('Pass renaming table', passMTRenameTable);
|
||||
it('Pass renaming column', passMTRenameColumn);
|
||||
it('Fails to add column without column name', failMTWithoutColName);
|
||||
it('Fails without type selected', failMTWithoutColType);
|
||||
it('Add a column', passMTAddColumn);
|
||||
it('Fail modify with wrong default value', failMCWithWrongDefaultValue);
|
||||
it('Pass modify with wrong default value', passMCWithRightDefaultValue);
|
||||
it('Pass create foreign-key', passCreateForeignKey);
|
||||
it('Pass remove foreign-key', passRemoveForeignKey);
|
||||
it(
|
||||
'Pass edit default value for primary key',
|
||||
passMTChangeDefaultValueForPKey
|
||||
);
|
||||
it('Pass modifying a primary key', passModifyPkey);
|
||||
it('Pass creating a unique key', passCreateUniqueKey);
|
||||
it('Pass modifying a unique key', passModifyUniqueKey);
|
||||
it('Pass removing a unique key', passRemoveUniqueKey);
|
||||
it('Delete the column', passMTDeleteCol);
|
||||
it('Delete Table Cancel', passMTDeleteTableCancel);
|
||||
it('Delete table', passMTDeleteTable);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runModifyTableTests();
|
||||
}
|
133
frontend/apps/console-ce-e2e/src/e2e/data/permissions/spec.ts
Normal file
133
frontend/apps/console-ce-e2e/src/e2e/data/permissions/spec.ts
Normal file
@ -0,0 +1,133 @@
|
||||
import {
|
||||
tableColumnTypeSelector,
|
||||
baseUrl,
|
||||
getTableName,
|
||||
getElementFromAlias,
|
||||
getColName,
|
||||
queryTypes,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
|
||||
import { testPermissions, permRemove, createView, trackView } from './utils';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
const testName = 'perm';
|
||||
|
||||
export const passPTCreateTable = () => {
|
||||
// Click on create table
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
// Match the URL
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
// Type table name
|
||||
cy.get(getElementFromAlias('tableName')).type(getTableName(0, testName));
|
||||
// Set first column
|
||||
cy.get(getElementFromAlias('column-0')).type(getColName(0));
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_serial'))
|
||||
.first()
|
||||
.click();
|
||||
// Set second column
|
||||
cy.get(getElementFromAlias('column-1')).type(getColName(1));
|
||||
tableColumnTypeSelector('col-type-1');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_integer'))
|
||||
.first()
|
||||
.click();
|
||||
|
||||
// Set third column
|
||||
cy.get(getElementFromAlias('column-2')).type(getColName(2));
|
||||
tableColumnTypeSelector('col-type-2');
|
||||
cy.get(getElementFromAlias('data_test_column_type_value_text'))
|
||||
.first()
|
||||
.click();
|
||||
// Set primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('0');
|
||||
// Create
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(0, testName)}/modify`
|
||||
);
|
||||
};
|
||||
|
||||
export const passPTCheckRoute = () => {
|
||||
// Go to permissiosn tab
|
||||
cy.get(getElementFromAlias('table-permissions')).click();
|
||||
// Match the URL
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/permissions`
|
||||
);
|
||||
};
|
||||
|
||||
export const passPTNoChecks = () => {
|
||||
// Type role
|
||||
cy.get(getElementFromAlias('role-textbox')).type('role0');
|
||||
// Set permissions
|
||||
testPermissions(getTableName(0, testName), 'none');
|
||||
};
|
||||
|
||||
export const passPTCustomChecks = () => {
|
||||
testPermissions(getTableName(0, testName), 'custom');
|
||||
};
|
||||
|
||||
export const passPTRemovePerms = () => {
|
||||
queryTypes.forEach(query => {
|
||||
permRemove(getTableName(0, testName), query);
|
||||
});
|
||||
};
|
||||
|
||||
export const passPVCreateView = () => {
|
||||
// create a view
|
||||
createView(getTableName(1, testName), getTableName(0, testName));
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passPVPermissions = () => {
|
||||
// Track the view
|
||||
trackView();
|
||||
// Type role
|
||||
cy.get(getElementFromAlias('role-textbox')).type('role0');
|
||||
// Test permissions
|
||||
testPermissions(getTableName(1, testName), 'none', true);
|
||||
testPermissions(getTableName(1, testName), 'custom', true);
|
||||
};
|
||||
|
||||
export const passPVRemovePerms = () => {
|
||||
permRemove(getTableName(1, testName), 'select');
|
||||
};
|
||||
|
||||
export const passPVDeleteView = () => {
|
||||
// Go to modify view
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
// Delete view
|
||||
setPromptValue(getTableName(1, testName));
|
||||
cy.get(getElementFromAlias('delete-view')).click();
|
||||
cy.window()
|
||||
.its('prompt')
|
||||
.should('be.called');
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
export const passPTDeleteTable = () => {
|
||||
// Go to the table
|
||||
cy.get(getElementFromAlias(getTableName(0, testName))).click();
|
||||
cy.wait(7000);
|
||||
// Go to modify table
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
// Delete table
|
||||
setPromptValue(getTableName(0, testName));
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
cy.window()
|
||||
.its('prompt')
|
||||
.should('be.called');
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
export const setValidationMetaData = () => {
|
||||
setMetaData();
|
||||
};
|
@ -0,0 +1,50 @@
|
||||
import {
|
||||
passPTCreateTable,
|
||||
passPTCheckRoute,
|
||||
passPTNoChecks,
|
||||
passPTCustomChecks,
|
||||
passPTRemovePerms,
|
||||
passPVCreateView,
|
||||
passPVPermissions,
|
||||
passPVRemovePerms,
|
||||
passPVDeleteView,
|
||||
passPTDeleteTable,
|
||||
} from './spec';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Check Data Tab', () => {
|
||||
it('Visiting the data URL opens the correct route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runPermissionsTests = () => {
|
||||
describe.skip('Permissions', () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
it('Create a table', passPTCreateTable);
|
||||
it('Create a view', passPVCreateView);
|
||||
it('Check permission route', passPTCheckRoute);
|
||||
it('Table No-check permissions work as expected', passPTNoChecks);
|
||||
it('Table Custom-check permissions work as expected', passPTCustomChecks);
|
||||
it('Table Permissions removal works as expected', passPTRemovePerms);
|
||||
it('View permissions work as expected', passPVPermissions);
|
||||
it('View Permissions removal works as expected', passPVRemovePerms);
|
||||
it('Delete the views', passPVDeleteView);
|
||||
it('Delete the test table', passPTDeleteTable);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runPermissionsTests();
|
||||
}
|
158
frontend/apps/console-ce-e2e/src/e2e/data/permissions/utils.ts
Normal file
158
frontend/apps/console-ce-e2e/src/e2e/data/permissions/utils.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import {
|
||||
getElementFromAlias,
|
||||
getTableName,
|
||||
getColName,
|
||||
queryTypes,
|
||||
makeDataAPIOptions,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
import {
|
||||
validatePermission,
|
||||
QueryType,
|
||||
ResultType,
|
||||
CheckType,
|
||||
} from '../../validators/validators';
|
||||
|
||||
const testName = 'perm';
|
||||
|
||||
export const savePermission = () => {
|
||||
cy.get(getElementFromAlias('Save-Permissions-button')).click();
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
export const permNoCheck = (tableName: string, query: QueryType) => {
|
||||
// click on the query type to edit permission
|
||||
cy.get(getElementFromAlias(`role0-${query}`)).click();
|
||||
cy.get(getElementFromAlias('without-checks')).first().click();
|
||||
// set filter { }
|
||||
// Toggle all columns in case
|
||||
if (query === 'select' || query === 'update') {
|
||||
cy.get(getElementFromAlias('toggle-all-col-btn')).click();
|
||||
}
|
||||
if (query === 'insert' || query === 'update') {
|
||||
cy.get(getElementFromAlias('toggle-presets-permission')).click();
|
||||
cy.get(getElementFromAlias('column-presets-column-0')).select(
|
||||
getColName(0)
|
||||
);
|
||||
cy.get(getElementFromAlias('column-presets-type-0')).select('static');
|
||||
cy.get(getElementFromAlias('column-presets-value-0')).type('1').blur();
|
||||
cy.get(getElementFromAlias('column-presets-column-1')).select(
|
||||
getColName(1)
|
||||
);
|
||||
cy.get(getElementFromAlias('column-presets-type-1')).select('session');
|
||||
cy.get(getElementFromAlias('column-presets-value-1')).type('user-id');
|
||||
}
|
||||
// Save
|
||||
savePermission();
|
||||
// Validate
|
||||
validatePermission(
|
||||
tableName,
|
||||
'role0',
|
||||
query,
|
||||
'none',
|
||||
ResultType.SUCCESS,
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
export const permCustomCheck = (tableName: string, query: QueryType) => {
|
||||
// click on the query type to edit permission
|
||||
cy.get(getElementFromAlias(`role0-${query}`)).click();
|
||||
// check the without checks textbox
|
||||
cy.get(getElementFromAlias('toggle-row-permission')).click();
|
||||
cy.get(getElementFromAlias('custom-check')).first().click();
|
||||
|
||||
cy.get(getElementFromAlias('qb_container'))
|
||||
.first()
|
||||
.within(() => {
|
||||
// Select column
|
||||
cy.get(getElementFromAlias('qb-select')).first().select(getColName(0));
|
||||
// Select operator
|
||||
cy.get(getElementFromAlias('qb-select'))
|
||||
.last()
|
||||
.select(`${getColName(0)}._eq`);
|
||||
});
|
||||
// Set filter to 1
|
||||
cy.get(getElementFromAlias('perm-check-textbox')).first().type('1');
|
||||
// Save
|
||||
savePermission();
|
||||
// Validate
|
||||
validatePermission(
|
||||
tableName,
|
||||
'role0',
|
||||
query,
|
||||
'custom',
|
||||
ResultType.SUCCESS,
|
||||
[0, 1, 2].map(i => getColName(i))
|
||||
);
|
||||
// Do not allow users to make upset queries in case of Insert
|
||||
};
|
||||
|
||||
export const permRemove = (tableName: string, query: QueryType) => {
|
||||
// click on the query type to edit permission
|
||||
cy.get(getElementFromAlias(`role0-${query}`)).click();
|
||||
// Remove permission
|
||||
cy.get(getElementFromAlias('Delete-Permissions-button')).click();
|
||||
cy.wait(2500);
|
||||
cy.wait(5000);
|
||||
// Validate
|
||||
validatePermission(
|
||||
tableName,
|
||||
'role0',
|
||||
query,
|
||||
'custom',
|
||||
ResultType.FAILURE,
|
||||
null
|
||||
);
|
||||
};
|
||||
|
||||
export const testPermissions = (
|
||||
tableName: string,
|
||||
check: CheckType,
|
||||
isView?: boolean
|
||||
) => {
|
||||
let allQueryTypes: QueryType[] = queryTypes;
|
||||
if (isView) {
|
||||
allQueryTypes = ['select'];
|
||||
}
|
||||
|
||||
if (check === 'none') {
|
||||
allQueryTypes.forEach(query => {
|
||||
permNoCheck(tableName, query);
|
||||
});
|
||||
} else {
|
||||
allQueryTypes.forEach(query => {
|
||||
permCustomCheck(tableName, query);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const trackView = () => {
|
||||
// track view data-tab-link
|
||||
cy.get(getElementFromAlias('data-tab-link')).click();
|
||||
cy.wait(7000);
|
||||
cy.get(
|
||||
getElementFromAlias(`add-track-table-${getTableName(1, testName)}`)
|
||||
).click();
|
||||
cy.wait(10000);
|
||||
// Move to permissions
|
||||
cy.get(getElementFromAlias('table-permissions')).click();
|
||||
};
|
||||
|
||||
export const createView = (viewName: string, tableName: string) => {
|
||||
const reqBody = {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `create view "${viewName}" as select * from "${tableName}"`,
|
||||
},
|
||||
};
|
||||
cy.window().then(win => {
|
||||
const { __env } = win;
|
||||
const requestOptions = makeDataAPIOptions(
|
||||
__env.dataApiUrl,
|
||||
__env.adminSecret,
|
||||
reqBody
|
||||
);
|
||||
cy.request(requestOptions);
|
||||
});
|
||||
};
|
96
frontend/apps/console-ce-e2e/src/e2e/data/raw-sql/spec.ts
Normal file
96
frontend/apps/console-ce-e2e/src/e2e/data/raw-sql/spec.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { baseUrl, getElementFromAlias } from '../../../helpers/dataHelpers';
|
||||
|
||||
let prevStr = '';
|
||||
|
||||
export const openRawSQL = () => {
|
||||
// Open RawSQL
|
||||
cy.get('a').contains('Data').click();
|
||||
cy.wait(3000);
|
||||
cy.get(getElementFromAlias('sql-link')).click();
|
||||
cy.wait(3000);
|
||||
// Match URL
|
||||
cy.url().should('eq', `${baseUrl}/data/sql`);
|
||||
};
|
||||
const clearText = () => {
|
||||
cy.get('textarea').type('{selectall}', { force: true });
|
||||
cy.get('textarea').trigger('keydown', {
|
||||
keyCode: 46,
|
||||
which: 46,
|
||||
force: true,
|
||||
});
|
||||
cy.wait(2000); // ace editor textarea doesn't expose the value to check, so wait
|
||||
};
|
||||
|
||||
export const passCreateTable = () => {
|
||||
prevStr = 'CREATE TABLE Apic_test_table_rsql (id serial PRIMARY KEY);';
|
||||
cy.get('textarea').type(prevStr, { force: true });
|
||||
cy.wait(1000); // debounce
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
// cy.get(getElementFromAlias('raw-sql-statement-timeout'));
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passInsertValues = () => {
|
||||
clearText();
|
||||
// eslint-disable-next-line prefer-spread
|
||||
const str = Array.apply(null, Array(15))
|
||||
.map(
|
||||
(_, ix: number) => `INSERT INTO Apic_test_table_rsql VALUES (${+ix + 1});`
|
||||
)
|
||||
.join('\n');
|
||||
cy.get('textarea').type(str, { force: true });
|
||||
cy.wait(1000);
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const readQuery = () => {
|
||||
clearText();
|
||||
prevStr = 'SELECT * FROM public.Apic_test_table_rsql;';
|
||||
cy.get('textarea').type(prevStr, { force: true });
|
||||
cy.wait(1000); // debounce
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
cy.wait(3000); // debounce
|
||||
cy.get('div.rt-tr-group').should('have.length', 10);
|
||||
cy.get('button.-btn').last().click();
|
||||
cy.wait(500);
|
||||
cy.get('div.rt-tr-group').should('have.length', 5);
|
||||
cy.get('div.rt-td').first().should('have.text', '11');
|
||||
cy.get('div.rt-td').last().should('have.text', 'NULL');
|
||||
};
|
||||
export const passAlterTable = () => {
|
||||
clearText();
|
||||
prevStr = 'ALTER TABLE Apic_test_table_rsql ADD COLUMN name text;';
|
||||
cy.get('textarea').type(prevStr, { force: true });
|
||||
// Untrack table
|
||||
cy.wait(1000);
|
||||
cy.get(getElementFromAlias('raw-sql-track-check')).uncheck();
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passCreateView = () => {
|
||||
clearText();
|
||||
prevStr = 'CREATE VIEW abcd AS SELECT * FROM Apic_test_table_rsql;';
|
||||
cy.get('textarea').type(prevStr, { force: true });
|
||||
// Track table
|
||||
cy.wait(1000);
|
||||
cy.get(getElementFromAlias('raw-sql-track-check')).check();
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const delTestTables = () => {
|
||||
clearText();
|
||||
prevStr = 'DROP TABLE Apic_test_table_rsql CASCADE;';
|
||||
cy.get('textarea').type(prevStr, { force: true });
|
||||
cy.wait(1000);
|
||||
cy.get(getElementFromAlias('raw-sql-migration-check')).uncheck();
|
||||
cy.get(getElementFromAlias('run-sql')).click();
|
||||
// NOTE: This is only visible, when the console is in CLI mode
|
||||
cy.get(getElementFromAlias('not-migration-confirm')).click();
|
||||
// cy.get(getElementFromAlias('raw-sql-statement-timeout')).type('20', {
|
||||
// force: true,
|
||||
// });
|
||||
cy.wait(5000);
|
||||
};
|
40
frontend/apps/console-ce-e2e/src/e2e/data/raw-sql/test.ts
Normal file
40
frontend/apps/console-ce-e2e/src/e2e/data/raw-sql/test.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {
|
||||
openRawSQL,
|
||||
passCreateTable,
|
||||
delTestTables,
|
||||
passCreateView,
|
||||
passInsertValues,
|
||||
passAlterTable,
|
||||
readQuery,
|
||||
} from './spec';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
describe('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runRawSQLTests = () => {
|
||||
describe('Raw SQL', () => {
|
||||
it('Open Raw SQL page', openRawSQL);
|
||||
it('Pass create table', passCreateTable);
|
||||
it('Pass insert values', passInsertValues);
|
||||
it('Pass alter table', passAlterTable);
|
||||
it('Read from table', readQuery);
|
||||
it('Pass create view', passCreateView);
|
||||
it('Delete test table', delTestTables);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runRawSQLTests();
|
||||
}
|
326
frontend/apps/console-ce-e2e/src/e2e/data/relationships/spec.ts
Normal file
326
frontend/apps/console-ce-e2e/src/e2e/data/relationships/spec.ts
Normal file
@ -0,0 +1,326 @@
|
||||
import {
|
||||
baseUrl,
|
||||
getElementFromAlias,
|
||||
tableColumnTypeSelector,
|
||||
getIndexRoute,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
import {
|
||||
setMetaData,
|
||||
validateCT,
|
||||
validateColumn,
|
||||
ResultType,
|
||||
TableFields,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
const AWAIT_SHORT = 2000;
|
||||
|
||||
const delRel = (table: string, relname: string) => {
|
||||
cy.get(getElementFromAlias(table)).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias(`relationship-toggle-editor-${relname}`)).click();
|
||||
cy.get(getElementFromAlias(`relationship-remove-${relname}`)).click();
|
||||
cy.on('window:alert', str => {
|
||||
expect(str === 'Are you sure?').to.be.true;
|
||||
});
|
||||
cy.wait(15000);
|
||||
};
|
||||
|
||||
export const createTable = (name: string, fields: TableFields) => {
|
||||
// Click on the "Add table" button and input the table name
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
cy.get(getElementFromAlias('tableName')).type(`${name}_table_rt`);
|
||||
|
||||
// Enter column info
|
||||
let i = 0;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const key in fields) {
|
||||
// eslint-disable-next-line no-prototype-builtins
|
||||
if (fields.hasOwnProperty(key)) {
|
||||
cy.get(getElementFromAlias(`column-${i}`)).type(key);
|
||||
tableColumnTypeSelector(`col-type-${i}`);
|
||||
cy.get(getElementFromAlias(`data_test_column_type_value_${fields[key]}`))
|
||||
.first()
|
||||
.click();
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Select primary key
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('id');
|
||||
|
||||
if (name === 'article') {
|
||||
cy.get(getElementFromAlias('add-table-edit-fk-0')).click();
|
||||
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
|
||||
'author_table_rt'
|
||||
);
|
||||
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('3');
|
||||
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('foreign-key-0-onUpdate-cascade')).check();
|
||||
cy.get(getElementFromAlias('foreign-key-0-onDelete-cascade')).check();
|
||||
} else if (name === 'comment') {
|
||||
cy.get(getElementFromAlias('add-table-edit-fk-0')).click();
|
||||
cy.get(getElementFromAlias('foreign-key-ref-table-0')).select(
|
||||
'author_table_rt'
|
||||
);
|
||||
cy.get(getElementFromAlias('foreign-key-0-lcol-0')).select('1');
|
||||
cy.get(getElementFromAlias('foreign-key-0-rcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('foreign-key-0-onUpdate-cascade')).check();
|
||||
cy.get(getElementFromAlias('foreign-key-0-onDelete-cascade')).check();
|
||||
cy.get(getElementFromAlias('add-table-edit-fk-1')).click();
|
||||
cy.get(getElementFromAlias('foreign-key-ref-table-1')).select(
|
||||
'article_table_rt'
|
||||
);
|
||||
cy.get(getElementFromAlias('foreign-key-1-lcol-0')).select('2');
|
||||
cy.get(getElementFromAlias('foreign-key-1-rcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('foreign-key-1-onUpdate-cascade')).check();
|
||||
cy.get(getElementFromAlias('foreign-key-1-onDelete-cascade')).check();
|
||||
}
|
||||
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(15000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${name}_table_rt/modify`
|
||||
);
|
||||
|
||||
validateCT(`${name}_table_rt`, ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passRTCreateTables = () => {
|
||||
createTable('author', { id: 'integer', name: 'text' });
|
||||
createTable('article', {
|
||||
id: 'integer',
|
||||
title: 'text',
|
||||
Content: 'text',
|
||||
author_id: 'integer',
|
||||
rating: 'integer',
|
||||
});
|
||||
createTable('comment', {
|
||||
id: 'integer',
|
||||
user_id: 'integer',
|
||||
article_id: 'integer',
|
||||
comment: 'text',
|
||||
});
|
||||
};
|
||||
|
||||
export const Deletetable = (name: string) => {
|
||||
cy.get(getElementFromAlias(name)).click();
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
setPromptValue(name);
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(15000);
|
||||
validateCT(name, ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const passRTDeleteTables = () => {
|
||||
Deletetable('comment_table_rt');
|
||||
Deletetable('article_table_rt');
|
||||
Deletetable('author_table_rt');
|
||||
};
|
||||
|
||||
export const passRTAddManualObjRel = () => {
|
||||
cy.get(getElementFromAlias('article_table_rt')).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.wait(4000);
|
||||
cy.get(getElementFromAlias('article_table_rt')).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('create-edit-manual-rel')).click();
|
||||
cy.get(getElementFromAlias('manual-relationship-type')).select('object');
|
||||
cy.get("input[placeholder='Enter relationship name']").type('author');
|
||||
cy.get(getElementFromAlias('manual-relationship-ref-schema')).select(
|
||||
'public'
|
||||
);
|
||||
cy.get(getElementFromAlias('manual-relationship-ref-table')).select(
|
||||
'author_table_rt'
|
||||
);
|
||||
cy.get(getElementFromAlias('manual-relationship-lcol-0')).select('author_id');
|
||||
cy.get(getElementFromAlias('manual-relationship-rcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('create-manual-rel-save')).click();
|
||||
cy.wait(15000);
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'author', columns: ['name'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const passRTAddManualArrayRel = () => {
|
||||
cy.get(getElementFromAlias('article_table_rt')).click();
|
||||
cy.wait(4000);
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('create-edit-manual-rel')).click();
|
||||
cy.get(getElementFromAlias('manual-relationship-type')).select('array');
|
||||
cy.get("input[placeholder='Enter relationship name']").type('comments');
|
||||
cy.get(getElementFromAlias('manual-relationship-ref-schema')).select(
|
||||
'public'
|
||||
);
|
||||
cy.get(getElementFromAlias('manual-relationship-ref-table')).select(
|
||||
'comment_table_rt'
|
||||
);
|
||||
cy.get(getElementFromAlias('manual-relationship-lcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('manual-relationship-rcol-0')).select(
|
||||
'article_id'
|
||||
);
|
||||
cy.get(getElementFromAlias('create-manual-rel-save')).click();
|
||||
cy.wait(15000);
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'comments', columns: ['comment'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const passRTDeleteRelationships = () => {
|
||||
delRel('article_table_rt', 'author');
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'author', columns: ['name'] }],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
delRel('article_table_rt', 'comments');
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'comments', columns: ['comment'] }],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
};
|
||||
|
||||
export const passRTAddSuggestedRel = () => {
|
||||
cy.get(getElementFromAlias('article_table_rt')).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('obj-rel-add-0')).click();
|
||||
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('author');
|
||||
cy.get(getElementFromAlias('obj-rel-save-0')).click();
|
||||
cy.wait(5000);
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'author', columns: ['name'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.get(getElementFromAlias('article_table_rt')).click();
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('arr-rel-add-0')).click();
|
||||
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('comments');
|
||||
cy.get(getElementFromAlias('arr-rel-save-0')).click();
|
||||
cy.wait(5000);
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'comments', columns: ['comment'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const passRTRenameRelationship = () => {
|
||||
cy.get(getElementFromAlias('relationship-toggle-editor-comments')).click();
|
||||
cy.get(getElementFromAlias('relationship-name-input-comments'))
|
||||
.clear()
|
||||
.type('comments_renamed');
|
||||
cy.get(getElementFromAlias('relationship-save-comments')).click();
|
||||
cy.wait(5000);
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'comments_renamed', columns: ['comment'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.get(
|
||||
getElementFromAlias('relationship-toggle-editor-comments_renamed')
|
||||
).click();
|
||||
cy.get(getElementFromAlias('relationship-name-input-comments_renamed'))
|
||||
.clear()
|
||||
.type('comments');
|
||||
cy.get(getElementFromAlias('relationship-save-comments_renamed')).click();
|
||||
cy.wait(5000);
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'comments', columns: ['comment'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const failRTAddSuggestedRel = () => {
|
||||
cy.get(getElementFromAlias('article_table_rt')).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('obj-rel-add-0')).click();
|
||||
cy.get(getElementFromAlias('suggested-rel-name')).clear();
|
||||
cy.get(getElementFromAlias('obj-rel-save-0')).click();
|
||||
cy.wait(15000);
|
||||
cy.get(getElementFromAlias('suggested-rel-name')).clear().type(`${123123}`);
|
||||
cy.get('button').contains('Save').click();
|
||||
cy.wait(15000);
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'author', columns: ['name'] }],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
cy.get(getElementFromAlias('article_table_rt')).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('obj-rel-add-0')).click();
|
||||
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('author');
|
||||
cy.get(getElementFromAlias('obj-rel-save-0')).click();
|
||||
cy.wait(15000);
|
||||
validateColumn(
|
||||
'article_table_rt',
|
||||
['title', { name: 'author', columns: ['name'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
cy.get(getElementFromAlias('article_table_rt')).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('arr-rel-add-0')).click();
|
||||
cy.get(getElementFromAlias('suggested-rel-name')).clear().type('comments');
|
||||
cy.get(getElementFromAlias('arr-rel-save-0')).click();
|
||||
cy.wait(15000);
|
||||
delRel('article_table_rt', 'author');
|
||||
delRel('article_table_rt', 'comments');
|
||||
};
|
||||
|
||||
export const passRSTSetup = () => {
|
||||
createTable('countries', {
|
||||
id: 'integer',
|
||||
name: 'text',
|
||||
countryCode: 'text',
|
||||
});
|
||||
};
|
||||
|
||||
export const passRSTReset = () => {
|
||||
Deletetable('countries_table_rt');
|
||||
};
|
||||
|
||||
const AWAIT_MINOR = 500;
|
||||
|
||||
export const passRSTAddRSRel = () => {
|
||||
cy.reload();
|
||||
cy.getBySel('countries_table_rt').click();
|
||||
cy.getBySel('table-relationships').click();
|
||||
cy.getBySel('table-relationship-edit-remote-relationship-add').click();
|
||||
cy.wait(AWAIT_MINOR);
|
||||
cy.getBySel('remote-rel-name-input').type('remote');
|
||||
cy.getBySel('remote-rel-schema-input').select('remote_rel_test_rs');
|
||||
cy.get('input[type="checkbox"]').eq(3).check();
|
||||
cy.wait(AWAIT_MINOR);
|
||||
cy.get('input[type="checkbox"]').eq(4).check();
|
||||
cy.get('select').eq(2).select('countryCode');
|
||||
cy.getBySel('table-relationship-remote-relationship-add-save').click();
|
||||
cy.get('.notification', { timeout: AWAIT_SHORT })
|
||||
.should('be.visible')
|
||||
.and('contain', 'Successfully created remote relationship');
|
||||
};
|
||||
|
||||
export const passRSTDeleteRSRel = () => {
|
||||
cy.reload();
|
||||
cy.getBySel('table-relationship-edit-remote-relationship-edit').click();
|
||||
cy.getBySel('table-relationship-remote-relationship-edit-remove').click();
|
||||
cy.get('.notification', { timeout: AWAIT_SHORT })
|
||||
.should('be.visible')
|
||||
.and('contain', 'Successfully deleted remote relationship');
|
||||
};
|
||||
|
||||
export const setValidationMetaData = () => {
|
||||
setMetaData();
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
import {
|
||||
passRTCreateTables,
|
||||
passRTDeleteTables,
|
||||
passRTAddManualObjRel,
|
||||
passRTAddManualArrayRel,
|
||||
passRTDeleteRelationships,
|
||||
passRTAddSuggestedRel,
|
||||
failRTAddSuggestedRel,
|
||||
passRTRenameRelationship,
|
||||
} from './spec';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Check Data Tab', () => {
|
||||
it('Clicking on Data tab opens the correct route', () => {
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runRelationshipsTests = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Relationships Tests', () => {
|
||||
it('Create testing tables', passRTCreateTables);
|
||||
it('Add Manual Relationship Object', passRTAddManualObjRel);
|
||||
it('Add Manual Relationship Array', passRTAddManualArrayRel);
|
||||
it('Delete the relationships', passRTDeleteRelationships);
|
||||
it('Add Suggested Relationships Error', failRTAddSuggestedRel);
|
||||
it('Add Suggested Relationships', passRTAddSuggestedRel);
|
||||
it('Rename relationships', passRTRenameRelationship);
|
||||
it('Delete the relationships', passRTDeleteRelationships);
|
||||
it('Delete test tables', passRTDeleteTables);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runRelationshipsTests();
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
import { getDbRoute } from '../../../helpers/dataHelpers';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { setPromptValue, testMode } from '../../../helpers/common';
|
||||
|
||||
const setup = () => {
|
||||
describe('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
cy.visit(getDbRoute());
|
||||
cy.wait(7000);
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runSchemaSharingTests = () => {
|
||||
describe('template gallery', () => {
|
||||
it('display content', () => {
|
||||
cy.get('[data-test=table-links]').contains('default').click();
|
||||
cy.get('table').contains('Relationships: One-to-One').click();
|
||||
cy.contains('Install Template').click();
|
||||
cy.wait(1000);
|
||||
cy.get('[data-test=table-links]').contains('_onetoone').click();
|
||||
setPromptValue('_onetoone');
|
||||
cy.contains('_onetoone').parent().parent().contains('owner');
|
||||
cy.contains('_onetoone').parent().parent().contains('passport_info');
|
||||
cy.get('[title="Delete current schema"]').click();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runSchemaSharingTests();
|
||||
}
|
415
frontend/apps/console-ce-e2e/src/e2e/data/views/spec.ts
Normal file
415
frontend/apps/console-ce-e2e/src/e2e/data/views/spec.ts
Normal file
@ -0,0 +1,415 @@
|
||||
import {
|
||||
getElementFromAlias,
|
||||
baseUrl,
|
||||
tableColumnTypeSelector,
|
||||
getIndexRoute,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
|
||||
import {
|
||||
setMetaData,
|
||||
validateCT,
|
||||
createView,
|
||||
validateColumn,
|
||||
validateView,
|
||||
ResultType,
|
||||
TableFields,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
const userId = 5555;
|
||||
|
||||
export const createTable = (name: string, dict: TableFields) => {
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
cy.get(getElementFromAlias('tableName')).type(`${name}_table_vt`);
|
||||
const keys = Object.keys(dict).map(k => k);
|
||||
const values = Object.keys(dict).map(k => dict[k]);
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
cy.get(getElementFromAlias(`column-${i}`)).type(keys[i]);
|
||||
tableColumnTypeSelector(`col-type-${i}`);
|
||||
cy.get(getElementFromAlias(`data_test_column_type_value_${values[i]}`))
|
||||
.first()
|
||||
.click();
|
||||
}
|
||||
cy.get(getElementFromAlias('primary-key-select-0')).select('id');
|
||||
cy.get(getElementFromAlias('table-create')).click();
|
||||
cy.wait(7000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${name}_table_vt/modify`
|
||||
);
|
||||
|
||||
validateCT(`${name}_table_vt`, ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passVCreateTables = () => {
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
createTable('author', { id: 'integer', name: 'text' });
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
createTable('article', {
|
||||
id: 'integer',
|
||||
title: 'text',
|
||||
Content: 'text',
|
||||
author_id: 'integer',
|
||||
rating: 'integer',
|
||||
});
|
||||
cy.visit(getIndexRoute());
|
||||
cy.wait(5000);
|
||||
cy.get(getElementFromAlias('data-create-table')).click();
|
||||
createTable('comment', {
|
||||
id: 'integer',
|
||||
user_id: 'integer',
|
||||
article_id: 'integer',
|
||||
comment: 'text',
|
||||
});
|
||||
};
|
||||
|
||||
export const passVCreateViews = () => {
|
||||
createView(`CREATE VIEW author_average_rating_vt AS
|
||||
SELECT author_table_vt.id, avg(article_table_vt.rating)
|
||||
From author_table_vt, article_table_vt
|
||||
WHERE author_table_vt.id = article_table_vt.author_id
|
||||
GROUP BY author_table_vt.id`);
|
||||
};
|
||||
|
||||
export const passTrackTable = () => {
|
||||
cy.visit('/data/default/schema/public/');
|
||||
cy.wait(7000);
|
||||
cy.get(
|
||||
getElementFromAlias('add-track-table-author_average_rating_vt')
|
||||
).click();
|
||||
cy.wait(7000);
|
||||
validateView('author_average_rating_vt', ResultType.SUCCESS);
|
||||
};
|
||||
|
||||
export const passViewRoute = () => {
|
||||
cy.get(getElementFromAlias('author_average_rating_vt')).click();
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/views/author_average_rating_vt/browse`
|
||||
);
|
||||
};
|
||||
|
||||
type Data = (string | number)[];
|
||||
export const passVAddDataarticle = (data: Data, index: number) => {
|
||||
// Click the Insert Again button.
|
||||
cy.get('label')
|
||||
.contains('id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
|
||||
cy.get('label')
|
||||
.contains('title')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('title')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[1]}`);
|
||||
cy.get('label')
|
||||
.contains('Content')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('Content')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[2]}`);
|
||||
cy.get('label')
|
||||
.contains('author_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('author_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[3]}`);
|
||||
cy.get('label')
|
||||
.contains('rating')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('rating')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[4]}`);
|
||||
if (index) {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
} else {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
}
|
||||
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passVAddDataauthor = (data: Data, index: number) => {
|
||||
cy.get('label')
|
||||
.contains('id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
|
||||
cy.get('label')
|
||||
.contains('name')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('name')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[1]}`);
|
||||
if (index) {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
} else {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
}
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
export const passVAddDatacomment = (data: Data, index: number) => {
|
||||
cy.get('label')
|
||||
.contains('id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label').contains('id').next().find('input').last().type(`${data[0]}`);
|
||||
cy.get('label')
|
||||
.contains('user_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('user_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[1]}`);
|
||||
cy.get('label')
|
||||
.contains('article_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('article_id')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[2]}`);
|
||||
cy.get('label')
|
||||
.contains('comment')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type('{selectall}{del}');
|
||||
cy.get('label')
|
||||
.contains('comment')
|
||||
.next()
|
||||
.find('input')
|
||||
.last()
|
||||
.type(`${data[3]}`);
|
||||
if (index) {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
} else {
|
||||
cy.get(getElementFromAlias('insert-save-button')).click();
|
||||
}
|
||||
cy.wait(5000);
|
||||
};
|
||||
|
||||
const checkQuerySuccess = () => {
|
||||
// Expect only 4 rows i.e. expect fifth element to not exist
|
||||
cy.get('[role=gridcell]').contains(userId);
|
||||
cy.get('[role=row]').eq(2).should('not.exist');
|
||||
};
|
||||
|
||||
export const passVAddData = () => {
|
||||
let data;
|
||||
cy.get(getElementFromAlias('article_table_vt')).click();
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
data = [1, 'A', 'Sontent', userId, 4];
|
||||
passVAddDataarticle(data, 0);
|
||||
data = [2, 'B', 'Sontenta', 2, 4];
|
||||
passVAddDataarticle(data, 1);
|
||||
data = [3, 'C', 'Sontentb', userId, 4];
|
||||
passVAddDataarticle(data, 2);
|
||||
cy.get(getElementFromAlias('author_table_vt')).click();
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
|
||||
data = [userId, 'A'];
|
||||
passVAddDataauthor(data, 0);
|
||||
data = [2, 'B'];
|
||||
passVAddDataauthor(data, 1);
|
||||
cy.get(getElementFromAlias('comment_table_vt')).click();
|
||||
cy.get(getElementFromAlias('table-insert-rows')).click();
|
||||
|
||||
data = [1, 1, 1, 'new comment'];
|
||||
passVAddDatacomment(data, 0);
|
||||
data = [2, 2, 2, 'new comment'];
|
||||
passVAddDatacomment(data, 1);
|
||||
data = [3, 1, 2, 'new comment'];
|
||||
passVAddDatacomment(data, 2);
|
||||
};
|
||||
|
||||
export const passVFilterQueryEq = () => {
|
||||
// Select column with type `text`
|
||||
cy.get('select')
|
||||
.find('option')
|
||||
.contains('-- column --')
|
||||
.parent()
|
||||
.first()
|
||||
.select('id');
|
||||
// Type value as `filter-text`
|
||||
cy.get("input[placeholder='-- value --']").last().type(`${userId}`);
|
||||
// Run query
|
||||
cy.get(getElementFromAlias('run-query')).click();
|
||||
cy.wait(5000);
|
||||
// Check if the query was successful
|
||||
checkQuerySuccess();
|
||||
};
|
||||
|
||||
const checkOrder = (order: string) => {
|
||||
// Utility function to get right element
|
||||
if (order === 'asc') {
|
||||
cy.get('[role=row]').each(($el, index) => {
|
||||
if (index === 1) {
|
||||
cy.wrap($el).find('[role=gridcell]').first().next().next().contains(2);
|
||||
}
|
||||
if (index === 2) {
|
||||
cy.wrap($el)
|
||||
.find('[role=gridcell]')
|
||||
.first()
|
||||
.next()
|
||||
.next()
|
||||
.contains(userId);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cy.get('[role=row]').each(($el, index) => {
|
||||
if (index === 2) {
|
||||
cy.wrap($el).find('[role=gridcell]').first().next().next().contains(2);
|
||||
}
|
||||
if (index === 1) {
|
||||
cy.wrap($el)
|
||||
.find('[role=gridcell]')
|
||||
.first()
|
||||
.next()
|
||||
.next()
|
||||
.contains(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const passVAscendingSort = () => {
|
||||
cy.wait(7000);
|
||||
// Select column with type 'serial'
|
||||
cy.get('select')
|
||||
.find('option')
|
||||
.contains('-- column --')
|
||||
.parent()
|
||||
.last()
|
||||
.select('id');
|
||||
// Run query
|
||||
cy.get(getElementFromAlias('run-query')).click();
|
||||
// Check order
|
||||
checkOrder('asc');
|
||||
};
|
||||
|
||||
export const passModifyView = () => {
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
cy.get('button').contains('Modify').last().click();
|
||||
cy.url().should('eq', `${baseUrl}/data/sql`);
|
||||
};
|
||||
|
||||
export const passVAddManualObjRel = () => {
|
||||
cy.get(getElementFromAlias('author_average_rating_vt')).click();
|
||||
cy.wait(2000);
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.wait(2000);
|
||||
cy.get(getElementFromAlias('create-edit-manual-rel')).click();
|
||||
cy.get(getElementFromAlias('manual-relationship-type')).select('object');
|
||||
cy.get("input[placeholder='Enter relationship name']").type('author');
|
||||
cy.get(getElementFromAlias('manual-relationship-ref-schema')).select(
|
||||
'public'
|
||||
);
|
||||
cy.get(getElementFromAlias('manual-relationship-ref-table')).select(
|
||||
'author_table_vt'
|
||||
);
|
||||
cy.get(getElementFromAlias('manual-relationship-lcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('manual-relationship-rcol-0')).select('id');
|
||||
cy.get(getElementFromAlias('create-manual-rel-save')).click();
|
||||
cy.wait(7000);
|
||||
validateColumn(
|
||||
'author_average_rating_vt',
|
||||
['avg', { name: 'author', columns: ['name'] }],
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const passVDeleteRelationships = () => {
|
||||
cy.get(getElementFromAlias('author_average_rating_vt')).click();
|
||||
cy.get(getElementFromAlias('table-relationships')).click();
|
||||
cy.get(getElementFromAlias('relationship-toggle-editor-author')).click();
|
||||
cy.get(getElementFromAlias('relationship-remove-author')).click();
|
||||
cy.on('window:alert', str => {
|
||||
return expect(str === 'Are you sure?').to.be.true;
|
||||
});
|
||||
cy.wait(7000);
|
||||
validateColumn(
|
||||
'author_average_rating_vt',
|
||||
['avg', { name: 'author', columns: ['name'] }],
|
||||
ResultType.FAILURE
|
||||
);
|
||||
};
|
||||
|
||||
export const passVDeleteView = () => {
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
setPromptValue('author_average_rating_vt');
|
||||
cy.get(getElementFromAlias('delete-view')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(7000);
|
||||
validateView('author_average_rating_vt', ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const deleteTable = (name: string) => {
|
||||
cy.get(getElementFromAlias(name)).click();
|
||||
cy.get(getElementFromAlias('table-modify')).click();
|
||||
setPromptValue(name);
|
||||
cy.get(getElementFromAlias('delete-table')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(7000);
|
||||
validateCT(name, ResultType.FAILURE);
|
||||
cy.wait(7000);
|
||||
};
|
||||
|
||||
export const passVDeleteTables = () => {
|
||||
deleteTable('comment_table_vt');
|
||||
deleteTable('article_table_vt');
|
||||
deleteTable('author_table_vt');
|
||||
};
|
||||
|
||||
export const setValidationMetaData = () => {
|
||||
setMetaData();
|
||||
};
|
57
frontend/apps/console-ce-e2e/src/e2e/data/views/test.ts
Normal file
57
frontend/apps/console-ce-e2e/src/e2e/data/views/test.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import {
|
||||
passVCreateTables,
|
||||
passVCreateViews,
|
||||
passVAddData,
|
||||
passTrackTable,
|
||||
passVAddManualObjRel,
|
||||
passVAscendingSort,
|
||||
passVFilterQueryEq,
|
||||
passViewRoute,
|
||||
passModifyView,
|
||||
passVDeleteRelationships,
|
||||
passVDeleteView,
|
||||
passVDeleteTables,
|
||||
} from './spec';
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runViewsTest = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Views', () => {
|
||||
// NOTE: Ideally, we should be adding "should" at the beginning of
|
||||
// the test descriptions. It will sound like this when you read it.
|
||||
// eg. it should create test tables ...and so on
|
||||
it('Create Tables', passVCreateTables);
|
||||
it('Insert test data to table(s)', passVAddData);
|
||||
it('Create View', passVCreateViews);
|
||||
it('Add View to comment table', passTrackTable);
|
||||
it('Visit the view route', passViewRoute);
|
||||
it('Order Ascending order View Table', passVAscendingSort);
|
||||
it('Apply Filters on the View', passVFilterQueryEq);
|
||||
it('Modify the View', passModifyView);
|
||||
it('Add Object Relationship to View', passVAddManualObjRel);
|
||||
it('Delete Relationship(s)', passVDeleteRelationships);
|
||||
it('Delete View', passVDeleteView);
|
||||
it('Delete Tables', passVDeleteTables);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runViewsTest();
|
||||
}
|
@ -0,0 +1,409 @@
|
||||
import {
|
||||
getElementFromAlias,
|
||||
getTableName,
|
||||
getTriggerName,
|
||||
getWebhookURL,
|
||||
getNoOfRetries,
|
||||
getIntervalSeconds,
|
||||
getTimeoutSeconds,
|
||||
baseUrl,
|
||||
} from '../../../helpers/eventHelpers';
|
||||
import {
|
||||
getColName,
|
||||
tableColumnTypeSelector,
|
||||
} from '../../../helpers/dataHelpers';
|
||||
import {
|
||||
setMetaData,
|
||||
validateCT,
|
||||
validateInsert,
|
||||
ResultType,
|
||||
validateCTrigger,
|
||||
} from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
import {
|
||||
toggleRequestTransformSection,
|
||||
togglePayloadTransformSection,
|
||||
typeIntoRequestQueryParams,
|
||||
typeIntoRequestUrl,
|
||||
typeIntoTransformBody,
|
||||
checkTransformRequestUrlError,
|
||||
checkTransformRequestBodyError,
|
||||
checkTransformRequestUrlPreview,
|
||||
clearPayloadTransformBody,
|
||||
clearRequestUrl,
|
||||
} from '../../../helpers/webhookTransformHelpers';
|
||||
|
||||
const AWAIT_SHORT = 2000;
|
||||
const AWAIT_LONG = 7000;
|
||||
|
||||
const EVENT_REQUEST_BODY_TRANSFORM_TEXTAREA = 1;
|
||||
|
||||
const EVENT_TRIGGER_INDEX_ROUTE = '/events/data';
|
||||
|
||||
const testName = 'ctr'; // create trigger
|
||||
|
||||
const statements = {
|
||||
createTransformIncorrectPayloadBody: `
|
||||
{
|
||||
"tableInfo": {
|
||||
"name": {{$table.name}}
|
||||
`,
|
||||
createTransformPayloadBody: `
|
||||
{
|
||||
"tableInfo": {
|
||||
"name": {{$body.table.name}},
|
||||
"schema": {{$body.table.schema}},
|
||||
"trigger": {{$body.trigger.name}}
|
||||
`,
|
||||
};
|
||||
|
||||
const createETForm = (allCols: boolean) => {
|
||||
// Set trigger name and select table
|
||||
cy.getBySel('trigger-name').clear().type(getTriggerName(0, testName));
|
||||
cy.getBySel('select-source').select('default');
|
||||
cy.wait(500);
|
||||
cy.getBySel('select-schema').select('public');
|
||||
cy.getBySel('select-table').select(getTableName(0, testName));
|
||||
|
||||
// operations
|
||||
cy.getBySel('insert-operation').check();
|
||||
cy.getBySel('update-operation').check();
|
||||
cy.getBySel('delete-operation').check();
|
||||
|
||||
// webhook url
|
||||
cy.getBySel('webhook-input').clear().type(getWebhookURL());
|
||||
|
||||
// advanced settings
|
||||
cy.getBySel('advanced-settings').click();
|
||||
if (!allCols) {
|
||||
cy.getBySel('choose-column').click();
|
||||
cy.getBySel('select-column').first().click();
|
||||
}
|
||||
|
||||
// retry configuration
|
||||
cy.getBySel('no-of-retries').clear().type(getNoOfRetries());
|
||||
cy.getBySel('interval-seconds').clear().type(getIntervalSeconds());
|
||||
cy.getBySel('timeout-seconds').clear().type(getTimeoutSeconds());
|
||||
};
|
||||
|
||||
export const passPTCreateTable = () => {
|
||||
// Click on create table
|
||||
cy.getBySel('data-create-table').click();
|
||||
// Match the URL
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public/table/add`);
|
||||
// Type table name
|
||||
cy.getBySel('tableName').type(getTableName(0, testName));
|
||||
// Set first column
|
||||
cy.getBySel('column-0').type(getColName(0));
|
||||
tableColumnTypeSelector('col-type-0');
|
||||
cy.getBySel('data_test_column_type_value_serial').first().click();
|
||||
// cy.getBySel('col-type-0')).select('serial');
|
||||
// Set second column
|
||||
cy.getBySel('column-1').type(getColName(1));
|
||||
tableColumnTypeSelector('col-type-1');
|
||||
cy.getBySel('data_test_column_type_value_integer').first().click();
|
||||
|
||||
// cy.getBySel('col-type-1')).select('integer');
|
||||
// Set third column
|
||||
cy.getBySel('column-2').type(getColName(2));
|
||||
tableColumnTypeSelector('col-type-2');
|
||||
cy.getBySel('data_test_column_type_value_text').first().click();
|
||||
// cy.getBySel('col-type-2')).select('text');
|
||||
// Set primary key
|
||||
cy.getBySel('primary-key-select-0').select('0');
|
||||
// Create
|
||||
cy.getBySel('table-create').click();
|
||||
cy.wait(7000);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/data/default/schema/public/tables/${getTableName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
};
|
||||
|
||||
export const checkCreateTriggerRoute = () => {
|
||||
// Click on the create trigger button
|
||||
cy.visit(`${EVENT_TRIGGER_INDEX_ROUTE}/manage`);
|
||||
cy.wait(4000);
|
||||
cy.visit(EVENT_TRIGGER_INDEX_ROUTE);
|
||||
cy.wait(15000);
|
||||
cy.getBySel('data-sidebar-add').click();
|
||||
// Match the URL
|
||||
cy.url().should('eq', `${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/add`);
|
||||
};
|
||||
|
||||
export const failCTWithoutData = () => {
|
||||
// Type trigger name
|
||||
cy.getBySel('trigger-name').type(getTriggerName(0, testName));
|
||||
// Click on create
|
||||
cy.getBySel('trigger-create').click();
|
||||
// Check if the route didn't change
|
||||
cy.url().should('eq', `${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/add`);
|
||||
// Validate
|
||||
validateCT(getTriggerName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const passCT1 = () => {
|
||||
// select choose column from the radio input
|
||||
const allCols = false;
|
||||
createETForm(allCols);
|
||||
|
||||
// Click on create
|
||||
cy.getBySel('trigger-create').click();
|
||||
cy.wait(10000);
|
||||
// Check if the trigger got created and navigated to processed events page
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/${getTriggerName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
cy.getBySel(getTriggerName(0, testName));
|
||||
// Validate
|
||||
validateCTrigger(
|
||||
getTriggerName(0, testName),
|
||||
getTableName(0, testName),
|
||||
'public',
|
||||
ResultType.SUCCESS,
|
||||
allCols
|
||||
);
|
||||
};
|
||||
|
||||
export const passCT2 = () => {
|
||||
cy.getBySel('data-sidebar-add').click();
|
||||
// select all columns from the radio input
|
||||
const allCols = true;
|
||||
createETForm(allCols);
|
||||
|
||||
// Click on create
|
||||
cy.getBySel('trigger-create').click();
|
||||
cy.wait(10000);
|
||||
// Check if the trigger got created and navigated to processed events page
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/${getTriggerName(
|
||||
0,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
cy.getBySel(getTriggerName(0, testName));
|
||||
// Validate
|
||||
validateCTrigger(
|
||||
getTriggerName(0, testName),
|
||||
getTableName(0, testName),
|
||||
'public',
|
||||
ResultType.SUCCESS,
|
||||
allCols
|
||||
);
|
||||
};
|
||||
|
||||
export const failCTDuplicateTrigger = () => {
|
||||
// Visit create trigger page
|
||||
cy.visit(`${EVENT_TRIGGER_INDEX_ROUTE}/add`);
|
||||
// trigger and table name
|
||||
cy.getBySel('trigger-name').clear().type(getTriggerName(0, testName));
|
||||
cy.getBySel('select-source').select('default');
|
||||
cy.getBySel('select-schema').select('public');
|
||||
cy.getBySel('select-table').select(getTableName(0, testName));
|
||||
|
||||
// operations
|
||||
cy.getBySel('insert-operation').check();
|
||||
cy.getBySel('update-operation').check();
|
||||
cy.getBySel('delete-operation').check();
|
||||
|
||||
// webhook url
|
||||
cy.getBySel('webhook-input').clear().type(getWebhookURL());
|
||||
|
||||
// FIXME: Commenting this for now. Uncomment once, the server issue is fixed.
|
||||
|
||||
// click on create
|
||||
// cy.getBySel('trigger-create')).click();
|
||||
// cy.wait(5000);
|
||||
// should be on the same URL
|
||||
// cy.url().should('eq', `${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/add`);
|
||||
cy.visit(`${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/add`);
|
||||
cy.wait(4000);
|
||||
};
|
||||
|
||||
export const insertTableRow = () => {
|
||||
// visit insert row page
|
||||
cy.visit(
|
||||
`/data/default/schema/public/tables/${getTableName(0, testName)}/insert`
|
||||
);
|
||||
// one serial column. so insert a row directly.
|
||||
cy.getBySel(`typed-input-${1}`).type('123');
|
||||
cy.getBySel(`typed-input-${2}`).type('Some text');
|
||||
cy.getBySel('insert-save-button').click();
|
||||
cy.wait(300);
|
||||
validateInsert(getTableName(0, testName), 1);
|
||||
// now it should invoke the trigger to webhook
|
||||
cy.wait(10000);
|
||||
// check if processed events has a row and it is a successful response
|
||||
cy.visit(
|
||||
`${EVENT_TRIGGER_INDEX_ROUTE}/${getTriggerName(0, testName)}/processed`
|
||||
);
|
||||
cy.wait(10000);
|
||||
cy.get('.rt-tr-group').should('have.length.gte', 1);
|
||||
};
|
||||
|
||||
export const deleteCTTestTrigger = () => {
|
||||
// Go to the settings section of the trigger
|
||||
cy.visit(
|
||||
`${EVENT_TRIGGER_INDEX_ROUTE}/${getTriggerName(0, testName)}/processed`
|
||||
);
|
||||
// click on settings tab
|
||||
cy.getBySel('trigger-modify').click();
|
||||
setPromptValue(getTriggerName(0, testName));
|
||||
// Click on delete
|
||||
cy.getBySel('delete-trigger').click();
|
||||
// Confirm
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(7000);
|
||||
// Match the URL
|
||||
cy.url().should('eq', `${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/manage`);
|
||||
// Validate
|
||||
validateCTrigger(
|
||||
getTriggerName(0, testName),
|
||||
getTableName(0, testName),
|
||||
'public',
|
||||
ResultType.FAILURE
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteCTTestTable = () => {
|
||||
// Go to the modify section of the table
|
||||
cy.visit(
|
||||
`/data/default/schema/public/tables/${getTableName(0, testName)}/browse`
|
||||
);
|
||||
cy.get(getElementFromAlias('table-modify'), { timeout: 5000 }).click();
|
||||
// Click on delete
|
||||
setPromptValue(getTableName(0, testName));
|
||||
cy.getBySel('delete-table').click();
|
||||
// Confirm
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.get(getElementFromAlias('data-create-table'), { timeout: 5000 }).should(
|
||||
'exist'
|
||||
);
|
||||
|
||||
// Match the URL
|
||||
cy.url().should('eq', `${baseUrl}/data/default/schema/public`);
|
||||
// Validate
|
||||
validateCT(getTableName(0, testName), ResultType.FAILURE);
|
||||
};
|
||||
|
||||
export const setValidationMetaData = () => {
|
||||
setMetaData();
|
||||
};
|
||||
|
||||
export const clearHandler = () => {
|
||||
cy.getBySel('webhook-input').clear();
|
||||
};
|
||||
|
||||
export const createEtTransform = () => {
|
||||
// open create event trigger form
|
||||
cy.getBySel('data-sidebar-add').click();
|
||||
// Match the URL
|
||||
cy.url().should('eq', `${baseUrl}${EVENT_TRIGGER_INDEX_ROUTE}/add`);
|
||||
|
||||
// fill up the basic event trigger form
|
||||
createETForm(true);
|
||||
|
||||
// open request transform section
|
||||
toggleRequestTransformSection();
|
||||
cy.wait(AWAIT_SHORT);
|
||||
cy.getBySel('transform-POST').click();
|
||||
|
||||
// give correct body without webhook handler
|
||||
clearHandler();
|
||||
typeIntoRequestUrl('{{$base_url}}');
|
||||
cy.wait(AWAIT_SHORT);
|
||||
// check for error
|
||||
checkTransformRequestUrlError(
|
||||
true,
|
||||
'Please configure your webhook handler to generate request url transform'
|
||||
);
|
||||
|
||||
// clear handler
|
||||
clearHandler();
|
||||
// type into handler
|
||||
cy.getBySel('webhook-input').type(getWebhookURL());
|
||||
|
||||
// give incorrect body
|
||||
clearRequestUrl();
|
||||
typeIntoRequestUrl('{{$url}}/users');
|
||||
cy.wait(AWAIT_SHORT);
|
||||
// check for error
|
||||
checkTransformRequestUrlError(true);
|
||||
|
||||
// give correct body
|
||||
clearRequestUrl();
|
||||
typeIntoRequestUrl('/{{$body.trigger.name}}');
|
||||
typeIntoRequestQueryParams([
|
||||
{ key: 'id', value: '5' },
|
||||
{ key: 'tableName', value: '{{$body.table.name}}' },
|
||||
]);
|
||||
cy.wait(AWAIT_SHORT);
|
||||
// check there is no error
|
||||
checkTransformRequestUrlError(false);
|
||||
// check the preview is correctly shown
|
||||
checkTransformRequestUrlPreview(
|
||||
'http://httpbin.org/post/Apic_test_trigger_ctr_0?id=5&tableName=Apic_test_table_ctr_0'
|
||||
);
|
||||
|
||||
// open payload transform section
|
||||
togglePayloadTransformSection();
|
||||
// give incorrect body
|
||||
clearPayloadTransformBody(EVENT_REQUEST_BODY_TRANSFORM_TEXTAREA);
|
||||
typeIntoTransformBody(
|
||||
statements.createTransformIncorrectPayloadBody,
|
||||
EVENT_REQUEST_BODY_TRANSFORM_TEXTAREA
|
||||
);
|
||||
cy.wait(AWAIT_SHORT);
|
||||
checkTransformRequestBodyError(true);
|
||||
|
||||
// give correct body
|
||||
clearPayloadTransformBody(EVENT_REQUEST_BODY_TRANSFORM_TEXTAREA);
|
||||
typeIntoTransformBody(
|
||||
statements.createTransformPayloadBody,
|
||||
EVENT_REQUEST_BODY_TRANSFORM_TEXTAREA
|
||||
);
|
||||
cy.wait(AWAIT_SHORT);
|
||||
checkTransformRequestBodyError(false);
|
||||
|
||||
// Click on create
|
||||
cy.getBySel('trigger-create').click();
|
||||
// Check if the trigger got created
|
||||
cy.wait(AWAIT_SHORT);
|
||||
cy.get('.notification', { timeout: AWAIT_LONG })
|
||||
.should('be.visible')
|
||||
.and('contain', 'Event Trigger Created');
|
||||
};
|
||||
|
||||
export const modifyEtTransform = () => {
|
||||
cy.getBySel('transform-GET').click();
|
||||
cy.getBySel('transform-requestUrl')
|
||||
.clear()
|
||||
.type('/{{$body.trigger.name}}', { parseSpecialCharSequences: false });
|
||||
cy.getBySel('transform-query-params-kv-remove-button-0').click();
|
||||
togglePayloadTransformSection();
|
||||
cy.getBySel('save-modify-trigger-changes').click();
|
||||
cy.wait(AWAIT_SHORT);
|
||||
cy.get('.notification', { timeout: AWAIT_LONG })
|
||||
.should('be.visible')
|
||||
.and('contain', 'Saved');
|
||||
};
|
||||
|
||||
export const deleteEtTransform = () => {
|
||||
setPromptValue(getTriggerName(0, testName));
|
||||
// Click on delete
|
||||
cy.getBySel('delete-trigger').click();
|
||||
// Confirm
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.wait(AWAIT_SHORT);
|
||||
cy.get('.notification', { timeout: AWAIT_LONG })
|
||||
.should('be.visible')
|
||||
.and('contain', 'Deleted event trigger');
|
||||
};
|
@ -0,0 +1,59 @@
|
||||
/* eslint no-unused-vars: 0 */
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
|
||||
import {
|
||||
checkCreateTriggerRoute,
|
||||
failCTWithoutData,
|
||||
passCT2,
|
||||
failCTDuplicateTrigger,
|
||||
insertTableRow,
|
||||
deleteCTTestTrigger,
|
||||
deleteCTTestTable,
|
||||
passPTCreateTable,
|
||||
passCT1,
|
||||
createEtTransform,
|
||||
modifyEtTransform,
|
||||
deleteEtTransform,
|
||||
} from './spec';
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Check Data Tab', () => {
|
||||
it('Clicking on Data tab opens the correct route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runCreateTriggerTests = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Create Trigger', () => {
|
||||
it('Create test table', passPTCreateTable);
|
||||
it(
|
||||
'Create trigger button opens the correct route',
|
||||
checkCreateTriggerRoute
|
||||
);
|
||||
it('Fails to create trigger without data', failCTWithoutData);
|
||||
it('Successfuly creates trigger with selected columns for update', passCT1);
|
||||
it('Successfuly creates trigger with all columns for update', passCT2);
|
||||
it('Fails to create duplicate trigger', failCTDuplicateTrigger);
|
||||
it('Insert a row and invoke trigger', insertTableRow);
|
||||
it("Delete's the test trigger", deleteCTTestTrigger);
|
||||
it('Create Event Trigger With Transform', createEtTransform);
|
||||
it('Update Event Trigger With Transform', modifyEtTransform);
|
||||
it('Delete Event Trigger With Transform', deleteEtTransform);
|
||||
it("Delete's the test table", deleteCTTestTable);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runCreateTriggerTests();
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
import {
|
||||
getElementFromAlias,
|
||||
getTableName,
|
||||
getTriggerName,
|
||||
getWebhookURL,
|
||||
getNoOfRetries,
|
||||
getIntervalSeconds,
|
||||
getTimeoutSeconds,
|
||||
baseUrl,
|
||||
} from '../../../helpers/eventHelpers';
|
||||
import { validateCTrigger, ResultType } from '../../validators/validators';
|
||||
|
||||
const EVENT_TRIGGER_INDEX_ROUTE = '/events/data';
|
||||
|
||||
export const checkCreateOneOffTriggerRoute = () => {
|
||||
// Click on the one-off scheduled events
|
||||
cy.visit(`${EVENT_TRIGGER_INDEX_ROUTE}/manage`);
|
||||
cy.wait(4000);
|
||||
cy.visit(EVENT_TRIGGER_INDEX_ROUTE);
|
||||
cy.wait(15000);
|
||||
cy.get(getElementFromAlias('one-off-trigger')).click();
|
||||
// Match the URL
|
||||
cy.url().should('eq', `${baseUrl}/events/one-off-scheduled-events/info`);
|
||||
};
|
||||
|
||||
export const scheduleOneoffEvent = () => {
|
||||
// click on the schedule event tab
|
||||
cy.visit('/events/one-off-scheduled-events/add');
|
||||
cy.url().should('eq', `${baseUrl}/events/one-off-scheduled-events/add`);
|
||||
// webhook url
|
||||
cy.get(getElementFromAlias('one-off-webhook')).type(getWebhookURL());
|
||||
// advanced settings
|
||||
cy.get(getElementFromAlias('event-advanced-configuration')).click();
|
||||
// retry configuration
|
||||
cy.get(getElementFromAlias('no-of-retries')).clear().type(getNoOfRetries());
|
||||
cy.get(getElementFromAlias('interval-seconds'))
|
||||
.clear()
|
||||
.type(getIntervalSeconds());
|
||||
cy.get(getElementFromAlias('timeout-seconds'))
|
||||
.clear()
|
||||
.type(getTimeoutSeconds());
|
||||
|
||||
// Click on create
|
||||
cy.get(getElementFromAlias('create-schedule-event')).click();
|
||||
cy.wait(10000);
|
||||
// Check if the trigger got created and navigated to processed events page
|
||||
cy.url().should('eq', `${baseUrl}/events/one-off-scheduled-events/pending`);
|
||||
validateCTrigger(
|
||||
getTriggerName(0),
|
||||
getTableName(0),
|
||||
'public',
|
||||
ResultType.SUCCESS
|
||||
);
|
||||
};
|
||||
|
||||
export const expandOneOffPendingEvent = () => {
|
||||
// expand button
|
||||
cy.get(getElementFromAlias('expand-event')).click();
|
||||
cy.wait(4000);
|
||||
// expand recent invocation
|
||||
cy.get(getElementFromAlias('expand-event')).click();
|
||||
cy.wait(4000);
|
||||
// collaspe
|
||||
cy.get(getElementFromAlias('collapse-event')).first().click();
|
||||
cy.wait(4000);
|
||||
};
|
||||
|
||||
export const expandOneOffProcessedEvent = () => {
|
||||
// processed events tab
|
||||
cy.get(
|
||||
getElementFromAlias('adhoc-events-container-tabs-events-processed')
|
||||
).click();
|
||||
cy.url().should('eq', `${baseUrl}/events/one-off-scheduled-events/processed`);
|
||||
// expand processed event
|
||||
cy.get(getElementFromAlias('expand-event')).first().click();
|
||||
cy.wait(4000);
|
||||
// expand recent invocation
|
||||
cy.get(getElementFromAlias('expand-event')).first().click();
|
||||
cy.wait(4000);
|
||||
// collaspe
|
||||
cy.get(getElementFromAlias('collapse-event')).first().click();
|
||||
cy.wait(4000);
|
||||
};
|
||||
|
||||
export const expandOneOffLogs = () => {
|
||||
// invocation logs tab
|
||||
cy.get(
|
||||
getElementFromAlias('adhoc-events-container-tabs-events-logs')
|
||||
).click();
|
||||
cy.url().should('eq', `${baseUrl}/events/one-off-scheduled-events/logs`);
|
||||
// expand logs
|
||||
cy.get(getElementFromAlias('expand-event')).first().click();
|
||||
cy.wait(4000);
|
||||
// expand recent invocation
|
||||
cy.get(getElementFromAlias('collapse-event')).click();
|
||||
cy.wait(4000);
|
||||
};
|
@ -0,0 +1,40 @@
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
|
||||
import { getIndexRoute } from '../../../helpers/dataHelpers';
|
||||
import {
|
||||
checkCreateOneOffTriggerRoute,
|
||||
expandOneOffLogs,
|
||||
expandOneOffPendingEvent,
|
||||
expandOneOffProcessedEvent,
|
||||
scheduleOneoffEvent,
|
||||
} from './spec';
|
||||
|
||||
const setup = () => {
|
||||
describe('Check Data Tab', () => {
|
||||
it('Clicking on Data tab opens the correct route', () => {
|
||||
// Visit the index route
|
||||
cy.visit(getIndexRoute());
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runCreateOneOffTriggerTests = () => {
|
||||
describe('Create One-off Trigger', () => {
|
||||
it(
|
||||
'One-off trigger button opens the correct route',
|
||||
checkCreateOneOffTriggerRoute
|
||||
);
|
||||
it('schedule an event', scheduleOneoffEvent);
|
||||
it('will expand the pending events', expandOneOffPendingEvent);
|
||||
it('will expand the processed evenets', expandOneOffProcessedEvent);
|
||||
it('will exapnd the invocation logs', expandOneOffLogs);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runCreateOneOffTriggerTests();
|
||||
}
|
@ -0,0 +1,279 @@
|
||||
import {
|
||||
getElementFromAlias,
|
||||
baseUrl,
|
||||
getRemoteSchemaName,
|
||||
getInvalidRemoteSchemaUrl,
|
||||
getRemoteGraphQLURL,
|
||||
getRemoteGraphQLURLFromEnv,
|
||||
getRemoteSchemaRoleName,
|
||||
} from '../../../helpers/remoteSchemaHelpers';
|
||||
|
||||
import { validateRS, ResultType } from '../../validators/validators';
|
||||
import { setPromptValue } from '../../../helpers/common';
|
||||
|
||||
const testName = 'rs';
|
||||
|
||||
export const checkCreateRemoteSchemaRoute = () => {
|
||||
cy.visit('/remote-schemas/manage/schemas', {
|
||||
onBeforeLoad(win) {
|
||||
cy.stub(win, 'prompt').returns('DELETE');
|
||||
},
|
||||
});
|
||||
|
||||
cy.get(getElementFromAlias('data-create-remote-schemas')).click();
|
||||
cy.url().should('eq', `${baseUrl}/remote-schemas/manage/add`);
|
||||
};
|
||||
|
||||
export const failRSWithInvalidRemoteUrl = () => {
|
||||
|
||||
cy.get('[data-testid=name]').type(
|
||||
getRemoteSchemaName(0, testName)
|
||||
);
|
||||
cy.get('[data-testid=url]').type(
|
||||
getInvalidRemoteSchemaUrl()
|
||||
);
|
||||
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.get('.notifications-wrapper').contains('Error');
|
||||
|
||||
};
|
||||
|
||||
export const createSimpleRemoteSchema = () => {
|
||||
cy.get('[data-testid=name]')
|
||||
.clear()
|
||||
.type(getRemoteSchemaName(1, testName));
|
||||
cy.get('[data-testid=url]')
|
||||
.clear()
|
||||
.type(getRemoteGraphQLURL());
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.get('.notifications-wrapper').contains('Success');
|
||||
validateRS(getRemoteSchemaName(1, testName), ResultType.SUCCESS);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/remote-schemas/manage/${getRemoteSchemaName(
|
||||
1,
|
||||
testName
|
||||
)}/details`
|
||||
);
|
||||
};
|
||||
|
||||
export const failRSDuplicateSchemaName = () => {
|
||||
cy.visit('remote-schemas/manage/add');
|
||||
cy.get('[data-testid=name]')
|
||||
.clear()
|
||||
.type(getRemoteSchemaName(1, testName));
|
||||
cy.get('[data-testid=url]')
|
||||
.clear()
|
||||
.type(getRemoteGraphQLURL());
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.get('.notifications-wrapper').contains('Error');
|
||||
cy.url().should('eq', `${baseUrl}/remote-schemas/manage/add`);
|
||||
};
|
||||
|
||||
export const failRSDuplicateSchemaNodes = () => {
|
||||
cy.visit('remote-schemas/manage/add');
|
||||
cy.get('[data-testid=name]')
|
||||
.clear()
|
||||
.type(getRemoteSchemaName(2, testName));
|
||||
cy.get('[data-testid=url]')
|
||||
.clear()
|
||||
.type(getRemoteGraphQLURL());
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.get('.notifications-wrapper').contains('Error');
|
||||
cy.url().should('eq', `${baseUrl}/remote-schemas/manage/add`);
|
||||
};
|
||||
|
||||
export const deleteSimpleRemoteSchemaFailUserConfirmationError = () => {
|
||||
cy.visit(`remote-schemas/manage/${getRemoteSchemaName(1, testName)}/details`);
|
||||
|
||||
cy.get(getElementFromAlias('remote-schemas-modify')).click();
|
||||
setPromptValue(null);
|
||||
cy.get(getElementFromAlias('remote-schema-edit-delete-btn')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/remote-schemas/manage/${getRemoteSchemaName(
|
||||
1,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteSimpleRemoteSchema = () => {
|
||||
cy.visit(`remote-schemas/manage/${getRemoteSchemaName(1, testName)}/details`);
|
||||
|
||||
cy.get(getElementFromAlias('remote-schemas-modify')).click();
|
||||
setPromptValue(getRemoteSchemaName(1, testName));
|
||||
cy.get(getElementFromAlias('remote-schema-edit-delete-btn')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.get(getElementFromAlias('delete-confirmation-error')).should('not.exist');
|
||||
};
|
||||
|
||||
export const failWithRemoteSchemaEnvUrl = () => {
|
||||
cy.visit('remote-schemas/manage/add');
|
||||
cy.get('[data-testid=name]')
|
||||
.clear()
|
||||
.type(getRemoteSchemaName(3, testName));
|
||||
cy.get(
|
||||
'[name="url.type"]'
|
||||
).select('from_env');
|
||||
cy.get('[data-testid=url]')
|
||||
.clear()
|
||||
.type(getRemoteGraphQLURLFromEnv());
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.get('.notifications-wrapper').contains('Error');
|
||||
cy.url().should('eq', `${baseUrl}/remote-schemas/manage/add`);
|
||||
};
|
||||
|
||||
export const failWithRemoteSchemaEnvHeader = () => {
|
||||
cy.visit('remote-schemas/manage/add');
|
||||
cy.get('[data-testid=name]')
|
||||
.clear()
|
||||
.type(getRemoteSchemaName(3, testName));
|
||||
cy.get('[data-testid=url]')
|
||||
.clear()
|
||||
.type(getRemoteGraphQLURL());
|
||||
|
||||
cy.get('[data-testid="add-header')
|
||||
.click()
|
||||
|
||||
cy.get('[name="headers[0].name"]')
|
||||
.clear()
|
||||
.type('sampleHeader1');
|
||||
|
||||
cy.get('[name="headers[0].value"]')
|
||||
.clear()
|
||||
.type('sampleHeaderValue1');
|
||||
|
||||
cy.get('[data-testid="add-header')
|
||||
.click()
|
||||
|
||||
cy.get('[name="headers[1].name"]')
|
||||
.clear()
|
||||
.type('sampleHeader2');
|
||||
|
||||
cy.get(
|
||||
'[name="headers[1].type"]'
|
||||
).select("from_env");
|
||||
|
||||
cy.get('[name="headers[1].value"]')
|
||||
.clear()
|
||||
.type('SAMPLE_ENV_HEADER');
|
||||
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.get('.notifications-wrapper').contains('Error');
|
||||
cy.url().should('eq', `${baseUrl}/remote-schemas/manage/add`);
|
||||
};
|
||||
|
||||
export const passWithRemoteSchemaHeader = () => {
|
||||
cy.visit('remote-schemas/manage/add');
|
||||
cy.get('[data-testid=name]')
|
||||
.clear()
|
||||
.type(getRemoteSchemaName(3, testName));
|
||||
cy.get('[data-testid=url]')
|
||||
.clear()
|
||||
.type(getRemoteGraphQLURL());
|
||||
|
||||
cy.get('[data-testid="add-header')
|
||||
.click()
|
||||
|
||||
cy.get('[name="headers[0].name"]')
|
||||
.clear()
|
||||
.type('sampleHeader1');
|
||||
|
||||
cy.get('[name="headers[0].value"]')
|
||||
.clear()
|
||||
.type('sampleHeaderValue1');
|
||||
|
||||
cy.get('[data-testid="add-header')
|
||||
.click()
|
||||
|
||||
cy.get('[name="headers[1].name"]')
|
||||
.clear()
|
||||
.type('sampleHeader2');
|
||||
|
||||
cy.get('[name="headers[1].value"]')
|
||||
.clear()
|
||||
.type('sampleHeaderValue2');
|
||||
|
||||
cy.get('[data-testid=submit]').click();
|
||||
cy.get('.notifications-wrapper').contains('Success');
|
||||
validateRS(getRemoteSchemaName(3, testName), ResultType.SUCCESS);
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/remote-schemas/manage/${getRemoteSchemaName(
|
||||
3,
|
||||
testName
|
||||
)}/details`
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteRemoteSchema = () => {
|
||||
cy.visit(`remote-schemas/manage/${getRemoteSchemaName(3, testName)}/details`);
|
||||
|
||||
cy.get(getElementFromAlias('remote-schemas-modify')).click();
|
||||
setPromptValue(getRemoteSchemaName(3, testName));
|
||||
cy.get(getElementFromAlias('remote-schema-edit-delete-btn')).click();
|
||||
cy.window().its('prompt').should('be.called');
|
||||
cy.get(getElementFromAlias('delete-confirmation-error')).should('not.exist');
|
||||
};
|
||||
|
||||
export const visitRemoteSchemaPermissionsTab = () => {
|
||||
cy.visit(
|
||||
`${baseUrl}/remote-schemas/manage/${getRemoteSchemaName(
|
||||
1,
|
||||
testName
|
||||
)}/permissions`
|
||||
);
|
||||
};
|
||||
|
||||
export const createSimpleRemoteSchemaPermission = () => {
|
||||
cy.get(getElementFromAlias('role-textbox'))
|
||||
.clear()
|
||||
.type(getRemoteSchemaRoleName(1, testName));
|
||||
cy.get(
|
||||
getElementFromAlias(`${getRemoteSchemaRoleName(1, testName)}-Permission`)
|
||||
).click();
|
||||
cy.get(getElementFromAlias('field-__query_root')).click();
|
||||
cy.get(getElementFromAlias('checkbox-query')).click();
|
||||
cy.get(getElementFromAlias('save-remote-schema-permissions')).click({
|
||||
force: true,
|
||||
});
|
||||
cy.get('.notifications-wrapper').contains('saved')
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/remote-schemas/manage/${getRemoteSchemaName(
|
||||
1,
|
||||
testName
|
||||
)}/permissions`
|
||||
);
|
||||
cy.get(getElementFromAlias('role-test-role-rs-1')).should('be.visible');
|
||||
};
|
||||
|
||||
export const passWithUpdateRemoteSchema = () => {
|
||||
cy.visit(
|
||||
`${baseUrl}/remote-schemas/manage/${getRemoteSchemaName(
|
||||
3,
|
||||
testName
|
||||
)}/modify`
|
||||
);
|
||||
cy.get(getElementFromAlias('remote-schema-schema-name')).should(
|
||||
'have.attr',
|
||||
'disabled'
|
||||
);
|
||||
cy.get(getElementFromAlias('remote-schema-comment'))
|
||||
.clear()
|
||||
.type('This is a new remote schema comment');
|
||||
|
||||
cy.get(getElementFromAlias('remote-schema-edit-save-btn')).click();
|
||||
cy.get('.notifications-wrapper').contains('modified');
|
||||
validateRS(getRemoteSchemaName(3, testName), ResultType.SUCCESS);
|
||||
|
||||
cy.get(getElementFromAlias('remote-schemas-modify')).click();
|
||||
cy.get(getElementFromAlias('remote-schema-schema-name')).should(
|
||||
'have.attr',
|
||||
'value',
|
||||
getRemoteSchemaName(3, testName)
|
||||
);
|
||||
};
|
@ -0,0 +1,83 @@
|
||||
/* eslint no-unused-vars: 0 */
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
|
||||
import {
|
||||
checkCreateRemoteSchemaRoute,
|
||||
failRSWithInvalidRemoteUrl,
|
||||
createSimpleRemoteSchema,
|
||||
failRSDuplicateSchemaName,
|
||||
failRSDuplicateSchemaNodes,
|
||||
deleteSimpleRemoteSchema,
|
||||
deleteSimpleRemoteSchemaFailUserConfirmationError,
|
||||
failWithRemoteSchemaEnvUrl,
|
||||
failWithRemoteSchemaEnvHeader,
|
||||
passWithRemoteSchemaHeader,
|
||||
deleteRemoteSchema,
|
||||
visitRemoteSchemaPermissionsTab,
|
||||
createSimpleRemoteSchemaPermission,
|
||||
passWithUpdateRemoteSchema,
|
||||
} from './spec';
|
||||
|
||||
const setup = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Setup route', () => {
|
||||
it('Visit the index route', () => {
|
||||
// Visit the index route
|
||||
cy.visit('/remote-schemas/manage/schemas');
|
||||
// Get and set validation metadata
|
||||
setMetaData();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const runCreateRemoteSchemaTableTests = () => {
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('Create Remote Schema', () => {
|
||||
it(
|
||||
'Add remote schema button opens the correct route',
|
||||
checkCreateRemoteSchemaRoute
|
||||
);
|
||||
it(
|
||||
'Fails to create remote schema without valid url',
|
||||
failRSWithInvalidRemoteUrl
|
||||
);
|
||||
it('Create a simple remote schema', createSimpleRemoteSchema);
|
||||
it('Fails to add remote schema with same name', failRSDuplicateSchemaName);
|
||||
it(
|
||||
'Fails to add remote schema which is already added',
|
||||
failRSDuplicateSchemaNodes
|
||||
);
|
||||
it(
|
||||
'Delete simple remote schema fail due to user confirmation error',
|
||||
deleteSimpleRemoteSchemaFailUserConfirmationError
|
||||
);
|
||||
it(
|
||||
'Visits the remote schema permissions tab',
|
||||
visitRemoteSchemaPermissionsTab
|
||||
);
|
||||
it(
|
||||
'Create a simple remote schema permission role',
|
||||
createSimpleRemoteSchemaPermission
|
||||
);
|
||||
it('Delete simple remote schema', deleteSimpleRemoteSchema);
|
||||
it(
|
||||
'Fails to create remote schema with url from env',
|
||||
failWithRemoteSchemaEnvUrl
|
||||
);
|
||||
it(
|
||||
'Fails to create remote schema with headers from env',
|
||||
failWithRemoteSchemaEnvHeader
|
||||
);
|
||||
it('Create remote schema with headers', passWithRemoteSchemaHeader);
|
||||
it('Update remote schema on Modify page', passWithUpdateRemoteSchema);
|
||||
it('Delete remote schema with headers', deleteRemoteSchema);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
setup();
|
||||
runCreateRemoteSchemaTableTests();
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
import { getElementFromAlias } from '../../../helpers/eventHelpers';
|
||||
|
||||
type CustomizationSettingsType = {
|
||||
root_fields_namespace: string;
|
||||
type_names: {
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
mapping: Record<string, string>;
|
||||
};
|
||||
field_names: {
|
||||
parent_type: string;
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
mapping: Record<string, string>;
|
||||
}[];
|
||||
};
|
||||
export const modifyCustomization = (
|
||||
customizationSettings: CustomizationSettingsType | undefined
|
||||
) => {
|
||||
cy.get(getElementFromAlias('remote-schema-customization-editor-expand-btn'))
|
||||
.should('exist')
|
||||
.click();
|
||||
|
||||
// add root field name
|
||||
cy.get(getElementFromAlias('remote-schema-customization-root-field-input'))
|
||||
.clear()
|
||||
.type(customizationSettings?.root_fields_namespace || '');
|
||||
|
||||
cy.get(
|
||||
getElementFromAlias('remote-schema-customization-type-name-prefix-input')
|
||||
)
|
||||
.clear()
|
||||
.type(customizationSettings?.type_names.prefix || '');
|
||||
|
||||
cy.get(
|
||||
getElementFromAlias('remote-schema-customization-type-name-suffix-input')
|
||||
)
|
||||
.clear()
|
||||
.type(customizationSettings?.type_names.suffix || '');
|
||||
|
||||
// add type name
|
||||
let key = Object.keys(customizationSettings?.type_names?.mapping || {})[0];
|
||||
cy.get(
|
||||
getElementFromAlias('remote-schema-customization-type-name-lhs-input')
|
||||
).select(key);
|
||||
cy.get(
|
||||
getElementFromAlias('remote-schema-customization-type-name-0-rhs-input')
|
||||
)
|
||||
.clear()
|
||||
.type(customizationSettings?.type_names?.mapping[key] || '');
|
||||
|
||||
cy.get(getElementFromAlias('remote-schema-editor')).should('exist').click();
|
||||
|
||||
cy.get(getElementFromAlias('remote-schema-customization-open-field-mapping'))
|
||||
.should('exist')
|
||||
.click();
|
||||
|
||||
// click the field mapping button
|
||||
cy.get(
|
||||
getElementFromAlias(
|
||||
'remote-schema-customization-field-type-parent-type-input'
|
||||
)
|
||||
).select(customizationSettings?.field_names[0].parent_type || '');
|
||||
|
||||
cy.get(
|
||||
getElementFromAlias(
|
||||
'remote-schema-customization-field-type-field-prefix-input'
|
||||
)
|
||||
)
|
||||
.clear()
|
||||
.type(customizationSettings?.field_names[0].prefix || '');
|
||||
|
||||
cy.get(
|
||||
getElementFromAlias(
|
||||
'remote-schema-customization-field-type-field-suffix-input'
|
||||
)
|
||||
)
|
||||
.clear()
|
||||
.type(customizationSettings?.field_names[0].suffix || '');
|
||||
|
||||
// remote-schema-customization-field-type-lhs-input
|
||||
key = Object.keys(customizationSettings?.field_names[0].mapping || {})[0];
|
||||
cy.get(
|
||||
getElementFromAlias('remote-schema-customization-field-type-lhs-input')
|
||||
).select(key);
|
||||
// remote-schema-customization-field-type-rhs-input
|
||||
cy.get(
|
||||
getElementFromAlias('remote-schema-customization-field-type-0-rhs-input')
|
||||
)
|
||||
.clear()
|
||||
.type(customizationSettings?.field_names[0].mapping[key] || '');
|
||||
|
||||
cy.get(getElementFromAlias('remote-schema-editor')).should('exist').click();
|
||||
|
||||
cy.get(getElementFromAlias('add-field-customization'))
|
||||
.should('exist')
|
||||
.click();
|
||||
|
||||
cy.get(getElementFromAlias('remote-schema-edit-save-btn'))
|
||||
.should('exist')
|
||||
.click();
|
||||
};
|
@ -0,0 +1,103 @@
|
||||
/* eslint no-unused-vars: 0 */
|
||||
import { testMode } from '../../../helpers/common';
|
||||
import { setMetaData } from '../../validators/validators';
|
||||
import { modifyCustomization } from './spec';
|
||||
|
||||
// const visitRoute = () => {
|
||||
// describe('Setup route', () => {
|
||||
// it('Visit the index route', () => {
|
||||
// // Visit the index route
|
||||
// cy.visit('/remote-schemas/manage/schemas');
|
||||
// // Get and set validation metadata
|
||||
// setMetaData();
|
||||
// });
|
||||
// });
|
||||
// };
|
||||
|
||||
const createRemoteSchema = (remoteSchemaName: string) => {
|
||||
const postBody = {
|
||||
type: 'add_remote_schema',
|
||||
args: {
|
||||
name: remoteSchemaName,
|
||||
definition: {
|
||||
url: 'https://graphql-pokemon2.vercel.app',
|
||||
forward_client_headers: true,
|
||||
timeout_seconds: 60,
|
||||
},
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const removeRemoteSchema = (remoteSchemaName: string) => {
|
||||
const postBody = {
|
||||
type: 'remove_remote_schema',
|
||||
args: {
|
||||
name: remoteSchemaName,
|
||||
},
|
||||
};
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const editSchemaTests = () => {
|
||||
describe('Modify an existing remote schema', () => {
|
||||
describe('Create a remote schema for testing', () => {
|
||||
it('add a remote schema via the API', () => {
|
||||
createRemoteSchema('test_remote_schema');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edit the remote schema settings', () => {
|
||||
it('Visit the modify page', () => {
|
||||
cy.visit('/remote-schemas/manage/test_remote_schema/modify');
|
||||
setMetaData();
|
||||
});
|
||||
|
||||
it('Modify the remote schema settings', () => {
|
||||
modifyCustomization({
|
||||
root_fields_namespace: 'test_root_namespace',
|
||||
type_names: {
|
||||
prefix: 'test_prefix',
|
||||
suffix: 'test_suffix',
|
||||
mapping: {
|
||||
Pokemon: 'renamed_type_name_mapping',
|
||||
},
|
||||
},
|
||||
field_names: [
|
||||
{
|
||||
parent_type: 'PokemonDimension',
|
||||
prefix: 'test_parent_type_prefix',
|
||||
suffix: 'test_parent_type_suffix',
|
||||
mapping: {
|
||||
minimum: 'test_field_name',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('expect success notification', () => {
|
||||
cy.expectSuccessNotificationWithTitle('Remote schema modified');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Remove remote schema', () => {
|
||||
it('Remove the remote schema via the API', () => {
|
||||
removeRemoteSchema('test_remote_schema');
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
// setup();
|
||||
editSchemaTests();
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
import { getElementFromAlias } from '../../../helpers/eventHelpers';
|
||||
import { replaceMetadata, resetMetadata } from '../../../helpers/metadata';
|
||||
// import { postgres } from '../../data/manage-database/postgres.spec';
|
||||
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('check if remote schema relationships are displayed properly', () => {
|
||||
before(() => {
|
||||
// create a table called destination_table
|
||||
// postgres.helpers.createTable('destination_table');
|
||||
|
||||
// load stuff into the metadata
|
||||
replaceMetadata({
|
||||
version: 3,
|
||||
sources: [
|
||||
{
|
||||
name: 'default',
|
||||
kind: 'postgres',
|
||||
tables: [
|
||||
{
|
||||
table: {
|
||||
schema: 'public',
|
||||
name: 'destination_table',
|
||||
},
|
||||
},
|
||||
],
|
||||
configuration: {
|
||||
connection_info: {
|
||||
use_prepared_statements: true,
|
||||
database_url: {
|
||||
from_env: 'HASURA_GRAPHQL_DATABASE_URL',
|
||||
},
|
||||
isolation_level: 'read-committed',
|
||||
pool_settings: {
|
||||
connection_lifetime: 600,
|
||||
retries: 1,
|
||||
idle_timeout: 180,
|
||||
max_connections: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
remote_schemas: [
|
||||
{
|
||||
name: 'destination_rs',
|
||||
definition: {
|
||||
url: 'https://graphql-pokemon2.vercel.app/',
|
||||
timeout_seconds: 60,
|
||||
},
|
||||
comment: '',
|
||||
},
|
||||
{
|
||||
name: 'source_rs',
|
||||
definition: {
|
||||
url: 'https://countries.trevorblades.com/',
|
||||
timeout_seconds: 60,
|
||||
},
|
||||
comment: '',
|
||||
remote_relationships: [
|
||||
{
|
||||
relationships: [
|
||||
{
|
||||
definition: {
|
||||
to_source: {
|
||||
relationship_type: 'object',
|
||||
source: 'default',
|
||||
table: 'destination_table',
|
||||
field_mapping: {
|
||||
code: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
name: 'an_example_rs_to_db_relationship',
|
||||
},
|
||||
{
|
||||
definition: {
|
||||
to_remote_schema: {
|
||||
remote_field: {
|
||||
pokemons: {
|
||||
arguments: {},
|
||||
},
|
||||
},
|
||||
remote_schema: 'destination_rs',
|
||||
lhs_fields: ['code'],
|
||||
},
|
||||
},
|
||||
name: 'an_example_rs_to_rs_relationship',
|
||||
},
|
||||
],
|
||||
type_name: 'Country',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('verify if the rows exist on the remote schema table', () => {
|
||||
cy.visit(
|
||||
'http://localhost:3000/remote-schemas/manage/source_rs/relationships'
|
||||
);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).should(
|
||||
'exist'
|
||||
);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table'))
|
||||
.find('tr')
|
||||
.should('have.length', 3);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).contains(
|
||||
'td',
|
||||
'an_example_rs_to_db_relationship'
|
||||
);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).contains(
|
||||
'td',
|
||||
'an_example_rs_to_rs_relationship'
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// reset the metadata
|
||||
resetMetadata();
|
||||
|
||||
// delete the table
|
||||
// postgres.helpers.deleteTable('destination_table');
|
||||
});
|
||||
});
|
@ -0,0 +1,92 @@
|
||||
import { getElementFromAlias } from '../../../helpers/eventHelpers';
|
||||
import { replaceMetadata, resetMetadata } from '../../../helpers/metadata';
|
||||
// import { postgres } from '../../data/manage-database/postgres.spec';
|
||||
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('check if remote schema to db relationships are created properly', () => {
|
||||
before(() => {
|
||||
// create a table called destination_table
|
||||
// postgres.helpers.createTable('destination_table');
|
||||
|
||||
// load stuff into the metadata
|
||||
replaceMetadata({
|
||||
version: 3,
|
||||
sources: [
|
||||
{
|
||||
name: 'default',
|
||||
kind: 'postgres',
|
||||
tables: [
|
||||
{
|
||||
table: {
|
||||
schema: 'public',
|
||||
name: 'destination_table',
|
||||
},
|
||||
},
|
||||
],
|
||||
configuration: {
|
||||
connection_info: {
|
||||
use_prepared_statements: true,
|
||||
database_url: {
|
||||
from_env: 'HASURA_GRAPHQL_DATABASE_URL',
|
||||
},
|
||||
isolation_level: 'read-committed',
|
||||
pool_settings: {
|
||||
connection_lifetime: 600,
|
||||
retries: 1,
|
||||
idle_timeout: 180,
|
||||
max_connections: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
remote_schemas: [
|
||||
{
|
||||
name: 'source_rs',
|
||||
definition: {
|
||||
url: 'https://graphql-pokemon2.vercel.app/',
|
||||
timeout_seconds: 60,
|
||||
},
|
||||
comment: '',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('verify creating a new rs-to-db relationship', () => {
|
||||
cy.visit(
|
||||
'http://localhost:3000/remote-schemas/manage/source_rs/relationships'
|
||||
);
|
||||
cy.get(getElementFromAlias('add-a-new-rs-relationship')).click();
|
||||
cy.get(getElementFromAlias('radio-select-remoteDB')).click();
|
||||
cy.get(getElementFromAlias('rs-to-db-rel-name')).type('RelationshipName');
|
||||
cy.get(getElementFromAlias('select-rel-type')).select('array');
|
||||
cy.get(getElementFromAlias('select-source-type')).select('Pokemon');
|
||||
cy.get(getElementFromAlias('select-ref-db')).select('default');
|
||||
cy.get(getElementFromAlias('select-ref-schema')).select('public');
|
||||
cy.get(getElementFromAlias('select-ref-table')).select('destination_table');
|
||||
cy.get(getElementFromAlias('select-source-field')).select('id');
|
||||
cy.get(getElementFromAlias('select-ref-col')).select('name');
|
||||
cy.get(getElementFromAlias('add-rs-relationship')).click();
|
||||
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).should(
|
||||
'exist'
|
||||
);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table'))
|
||||
.find('tr')
|
||||
.should('have.length', 2);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).contains(
|
||||
'td',
|
||||
'RelationshipName'
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// reset the metadata
|
||||
resetMetadata();
|
||||
|
||||
// delete the table
|
||||
// postgres.helpers.deleteTable('destination_table');
|
||||
});
|
||||
});
|
@ -0,0 +1,116 @@
|
||||
import { getElementFromAlias } from '../../../helpers/eventHelpers';
|
||||
import { replaceMetadata, resetMetadata } from '../../../helpers/metadata';
|
||||
import type { HasuraMetadataV3 } from '../../../../src/metadata/types';
|
||||
|
||||
// Temporarily skipped because of its flakiness, see: https://github.com/hasura/graphql-engine-mono/issues/5433
|
||||
// TODO: Fix and restore it
|
||||
describe.skip('check if remote schema to db relationships are created properly', () => {
|
||||
before(() => {
|
||||
const body: HasuraMetadataV3 = {
|
||||
version: 3,
|
||||
sources: [
|
||||
{
|
||||
name: 'default',
|
||||
kind: 'postgres',
|
||||
tables: [],
|
||||
configuration: {
|
||||
connection_info: {
|
||||
use_prepared_statements: true,
|
||||
database_url: {
|
||||
from_env: 'HASURA_GRAPHQL_DATABASE_URL',
|
||||
},
|
||||
isolation_level: 'read-committed',
|
||||
pool_settings: {
|
||||
connection_lifetime: 600,
|
||||
retries: 1,
|
||||
idle_timeout: 180,
|
||||
max_connections: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
remote_schemas: [
|
||||
{
|
||||
name: 'source_rs',
|
||||
definition: {
|
||||
url: 'https://graphql-pokemon2.vercel.app/',
|
||||
timeout_seconds: 60,
|
||||
},
|
||||
comment: '',
|
||||
},
|
||||
{
|
||||
name: 'ref_rs',
|
||||
definition: {
|
||||
url: 'https://hasura-console-test.herokuapp.com/v1/graphql/',
|
||||
timeout_seconds: 60,
|
||||
},
|
||||
comment: '',
|
||||
},
|
||||
],
|
||||
inherited_roles: [],
|
||||
};
|
||||
// load stuff into the metadata
|
||||
replaceMetadata(body);
|
||||
});
|
||||
|
||||
it('should create a new rs-to-rs relationship from source field', () => {
|
||||
cy.visit(
|
||||
'http://localhost:3000/remote-schemas/manage/source_rs/relationships'
|
||||
);
|
||||
cy.get(getElementFromAlias('add-a-new-rs-relationship')).click();
|
||||
cy.get(getElementFromAlias('radio-select-remoteSchema')).click();
|
||||
cy.get(getElementFromAlias('rs-to-rs-rel-name')).type('RelationshipName');
|
||||
cy.get(getElementFromAlias('select-source-type')).select('Pokemon');
|
||||
cy.get(getElementFromAlias('select-ref-rs')).select('ref_rs');
|
||||
cy.get('.ant-tree-switcher').first().click();
|
||||
cy.get('.ant-tree-switcher').eq(1).click();
|
||||
cy.get('.ant-tree-checkbox').eq(1).click();
|
||||
cy.get(getElementFromAlias('select-argument')).select('Source Field');
|
||||
cy.get(getElementFromAlias('select-source-field')).select('id');
|
||||
cy.get(getElementFromAlias('add-rs-relationship')).click();
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).should(
|
||||
'exist'
|
||||
);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table'))
|
||||
.find('tr')
|
||||
.should('have.length', 2);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).contains(
|
||||
'td',
|
||||
'RelationshipName'
|
||||
);
|
||||
});
|
||||
it('should create a new reverse rs-to-rs relationship with static fill value', () => {
|
||||
cy.visit(
|
||||
'http://localhost:3000/remote-schemas/manage/ref_rs/relationships'
|
||||
);
|
||||
cy.get(getElementFromAlias('add-a-new-rs-relationship')).click();
|
||||
cy.get(getElementFromAlias('radio-select-remoteSchema')).click();
|
||||
cy.get(getElementFromAlias('rs-to-rs-rel-name')).type(
|
||||
'StaticRelationshipName'
|
||||
);
|
||||
cy.get(getElementFromAlias('select-source-type')).select('test');
|
||||
cy.get(getElementFromAlias('select-ref-rs')).select('source_rs');
|
||||
cy.get('.ant-tree-switcher').first().click(); // expand Query
|
||||
cy.get('.ant-tree-switcher').eq(3).click(); // expand pokemon
|
||||
cy.get('.ant-tree-checkbox').eq(1).click(); // check name argument
|
||||
cy.get(getElementFromAlias('select-argument')).select('Static Value');
|
||||
cy.get(getElementFromAlias('select-static-value')).type('Bulbasaur');
|
||||
cy.get(getElementFromAlias('add-rs-relationship')).click();
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).should(
|
||||
'exist'
|
||||
);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table'))
|
||||
.find('tr')
|
||||
.should('have.length', 2);
|
||||
cy.get(getElementFromAlias('remote-schema-relationships-table')).contains(
|
||||
'td',
|
||||
'StaticRelationshipName'
|
||||
);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
// reset the metadata
|
||||
resetMetadata();
|
||||
});
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
import { getElementFromAlias } from '../../../../helpers/dataHelpers';
|
||||
|
||||
// fake inconsistentMetadata api response
|
||||
|
||||
const addDomainOneByOne = (domain: string) => {
|
||||
// click Add Domain
|
||||
cy.get(getElementFromAlias('add-insecure-domain')).click();
|
||||
// type domain name-1
|
||||
cy.get(getElementFromAlias('domain-name')).type(domain);
|
||||
// click Add to Allow List
|
||||
cy.get(getElementFromAlias('add-tls-allow-list')).click();
|
||||
// check metadata
|
||||
cy.get(getElementFromAlias(domain)).contains(domain);
|
||||
};
|
||||
|
||||
export const addSuccessfulInsecureDomain = () => {
|
||||
// open insecure domain page
|
||||
cy.visit('/settings/insecure-domain');
|
||||
// add 1st domain
|
||||
addDomainOneByOne('www.insecuredomain-1.com');
|
||||
// add 2nd domain
|
||||
addDomainOneByOne('www.insecuredomain-2.com');
|
||||
// add 3rd domain
|
||||
addDomainOneByOne('www.insecuredomain-3.com');
|
||||
cy.wait(2000);
|
||||
};
|
||||
|
||||
export const duplicateAndEmptyDomainError = () => {
|
||||
// click add domain
|
||||
cy.get(getElementFromAlias('add-insecure-domain')).click();
|
||||
// type duplicate domain name
|
||||
cy.get(getElementFromAlias('domain-name')).type('www.insecuredomain-1.com');
|
||||
// click Add to Allow List
|
||||
cy.get(getElementFromAlias('add-tls-allow-list')).click();
|
||||
cy.wait(1000);
|
||||
// duplicate error
|
||||
|
||||
// NOTE: is a timeout 5000 really needed?
|
||||
cy.expectErrorNotificationWithTitle(
|
||||
'Adding domain to insecure TLS allow list failed'
|
||||
);
|
||||
|
||||
// close add domain dialog box
|
||||
cy.get(getElementFromAlias('cancel-domain')).click();
|
||||
// click add domain
|
||||
cy.get(getElementFromAlias('add-insecure-domain')).click();
|
||||
// click Add to Allow List with empty domain name
|
||||
cy.get(getElementFromAlias('add-tls-allow-list')).click();
|
||||
// empty domain error
|
||||
// NOTE: is a timeout 5000 really needed?
|
||||
cy.expectErrorNotificationWithTitle('No domain found');
|
||||
cy.get(getElementFromAlias('cancel-domain')).click();
|
||||
};
|
||||
|
||||
export const deleteInsecureDomain = () => {
|
||||
// click to delete domain
|
||||
cy.get(getElementFromAlias('delete-domain-www.insecuredomain-1.com')).click({
|
||||
force: true,
|
||||
});
|
||||
cy.get(getElementFromAlias('delete-domain-www.insecuredomain-2.com')).click({
|
||||
force: true,
|
||||
});
|
||||
cy.get(getElementFromAlias('delete-domain-www.insecuredomain-3.com')).click({
|
||||
force: true,
|
||||
});
|
||||
cy.get(getElementFromAlias('label-no-domain-found'), { timeout: 10000 })
|
||||
.should('be.visible')
|
||||
.and('contain', 'No domains added to insecure TLS allow list');
|
||||
};
|
@ -0,0 +1,21 @@
|
||||
import { testMode } from '../../../../helpers/common';
|
||||
import {
|
||||
addSuccessfulInsecureDomain,
|
||||
deleteInsecureDomain,
|
||||
duplicateAndEmptyDomainError,
|
||||
} from './spec';
|
||||
|
||||
export const testInsecureDomainPage = () => {
|
||||
describe('Insecure Domain', () => {
|
||||
it('should successfully add insecure domain', addSuccessfulInsecureDomain);
|
||||
it(
|
||||
'should show error on passing duplicate and empty domain name',
|
||||
duplicateAndEmptyDomainError
|
||||
);
|
||||
it('should delete domains one by one', deleteInsecureDomain);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
testInsecureDomainPage();
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import { getElementFromAlias, baseUrl } from '../../../helpers/dataHelpers';
|
||||
|
||||
// fake inconsistentMetadata api response
|
||||
const inconsistentMetadata = {
|
||||
is_consistent: false,
|
||||
inconsistent_objects: [
|
||||
{
|
||||
definition: 'DB2',
|
||||
reason: 'Inconsistent object: connection error',
|
||||
name: 'source DB2',
|
||||
type: 'source',
|
||||
message:
|
||||
'could not translate host name "db" to address: Name or service not known\n',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const inconsistentMetadataPage = () => {
|
||||
// Set first column
|
||||
cy.visit('/settings/metadata-status?is_redirected=true');
|
||||
cy.intercept('/v1/metadata', req => {
|
||||
// dynamically respond to a request here
|
||||
|
||||
if (req?.body?.type === 'get_inconsistent_metadata') {
|
||||
// fake inconsistentMetadata api response to test the UI
|
||||
return req.reply(inconsistentMetadata);
|
||||
}
|
||||
|
||||
// send all other requests to the destination server
|
||||
req.reply();
|
||||
});
|
||||
cy.url().should(
|
||||
'eq',
|
||||
`${baseUrl}/settings/metadata-status?is_redirected=true`
|
||||
);
|
||||
|
||||
cy.get(getElementFromAlias('inconsistent_name_0')).contains('DB2');
|
||||
cy.get(getElementFromAlias('inconsistent_type_0')).contains('source');
|
||||
cy.get(getElementFromAlias('inconsistent_reason_0')).contains(
|
||||
'Inconsistent object: connection error'
|
||||
);
|
||||
cy.get(getElementFromAlias('inconsistent_reason_0')).contains(
|
||||
'could not translate host name "db" to address: Name or service not known'
|
||||
);
|
||||
};
|
@ -0,0 +1,16 @@
|
||||
import { testMode } from '../../../helpers/common';
|
||||
|
||||
import { inconsistentMetadataPage } from './spec';
|
||||
|
||||
export const testInconsistentMetadatapage = () => {
|
||||
describe('Inconsistent Metadata', () => {
|
||||
it(
|
||||
'should render inconsistent metadata table with fake data',
|
||||
inconsistentMetadataPage
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
if (testMode !== 'cli') {
|
||||
testInconsistentMetadatapage();
|
||||
}
|
529
frontend/apps/console-ce-e2e/src/e2e/validators/validators.ts
Normal file
529
frontend/apps/console-ce-e2e/src/e2e/validators/validators.ts
Normal file
@ -0,0 +1,529 @@
|
||||
import {
|
||||
makeDataAPIOptions,
|
||||
getColName,
|
||||
QueryEndpoint,
|
||||
} from '../../helpers/dataHelpers';
|
||||
import { migrateModeUrl } from '../../helpers/common';
|
||||
import { toggleOnMigrationMode } from '../data/migration-mode/utils';
|
||||
import {
|
||||
getNoOfRetries,
|
||||
getIntervalSeconds,
|
||||
getTimeoutSeconds,
|
||||
} from '../../helpers/eventHelpers';
|
||||
|
||||
// ***************** UTIL FUNCTIONS **************************
|
||||
|
||||
let adminSecret: string;
|
||||
let dataApiUrl: string;
|
||||
|
||||
export const setMetaData = () => {
|
||||
cy.window().then(win => {
|
||||
adminSecret = win.__env.adminSecret;
|
||||
dataApiUrl = win.__env.dataApiUrl;
|
||||
const { consoleMode } = win.__env;
|
||||
if (consoleMode === 'cli') {
|
||||
toggleOnMigrationMode();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const createView = (sql: string) => {
|
||||
const reqBody = {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql,
|
||||
},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(dataApiUrl, adminSecret, reqBody);
|
||||
cy.request(requestOptions);
|
||||
};
|
||||
|
||||
// ******************* VALIDATION FUNCTIONS *******************************
|
||||
|
||||
export enum ResultType {
|
||||
SUCCESS = 'success',
|
||||
FAILURE = 'failure',
|
||||
}
|
||||
interface RequestBody {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface TableFields {
|
||||
[x: string]: any;
|
||||
id?: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
Content?: string;
|
||||
author_id?: string;
|
||||
rating?: string;
|
||||
user_id?: string;
|
||||
article_id?: string;
|
||||
comment?: string;
|
||||
}
|
||||
/**
|
||||
* Remote Schema validator
|
||||
* @param remoteSchemaName
|
||||
* @param result
|
||||
*/
|
||||
export const validateRS = (
|
||||
remoteSchemaName: string,
|
||||
result: ResultType
|
||||
): void => {
|
||||
const reqBody = {
|
||||
type: 'export_metadata',
|
||||
args: {},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(
|
||||
dataApiUrl,
|
||||
adminSecret,
|
||||
reqBody,
|
||||
'metadata'
|
||||
);
|
||||
cy.request(requestOptions).then(response => {
|
||||
const remoteSchemas = response.body?.remote_schemas ?? [];
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(
|
||||
remoteSchemas.length > 0 && remoteSchemas[0].name === remoteSchemaName
|
||||
).to.be.true;
|
||||
} else {
|
||||
expect(remoteSchemas.length > 0).to.be.false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Vaidate the given function
|
||||
* @param functionName
|
||||
* @param functionSchema
|
||||
* @param result
|
||||
*/
|
||||
export const validateCFunc = (
|
||||
functionName: string,
|
||||
functionSchema: string,
|
||||
result: ResultType
|
||||
): void => {
|
||||
const reqBody = {
|
||||
type: 'export_metadata',
|
||||
args: {},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(
|
||||
dataApiUrl,
|
||||
adminSecret,
|
||||
reqBody,
|
||||
'metadata'
|
||||
);
|
||||
cy.request(requestOptions).then(response => {
|
||||
const defaultSourceData = response.body.sources.find(
|
||||
(source: { name: string }) => source.name === 'default'
|
||||
);
|
||||
const functionData = defaultSourceData.functions;
|
||||
const foundFunction =
|
||||
functionData.filter(
|
||||
(fn: { function: { name: string; schema: string } }) =>
|
||||
fn.function.name === functionName &&
|
||||
fn.function.schema === functionSchema
|
||||
).length === 1;
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(functionData.length && foundFunction).to.be.true;
|
||||
} else {
|
||||
// NOTE: functionData.length may not be true
|
||||
expect(functionData.length && foundFunction).to.be.false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate untracked function
|
||||
* @param functionName
|
||||
* @param functionSchema
|
||||
* @param result
|
||||
*/
|
||||
export const validateUntrackedFunc = (
|
||||
functionName: string,
|
||||
functionSchema: string,
|
||||
result: ResultType
|
||||
): void => {
|
||||
const reqBody = {
|
||||
type: 'export_metadata',
|
||||
args: {},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(
|
||||
dataApiUrl,
|
||||
adminSecret,
|
||||
reqBody,
|
||||
'metadata'
|
||||
);
|
||||
cy.request(requestOptions).then(response => {
|
||||
const defaultSourceData = response.body.sources.find(
|
||||
(source: { name: string }) => source.name === 'default'
|
||||
);
|
||||
const functionData = defaultSourceData?.functions ?? [];
|
||||
const foundFunction =
|
||||
functionData.filter(
|
||||
(fn: { function: { name: string; schema: string } }) =>
|
||||
fn?.function?.name === functionName &&
|
||||
fn?.function?.schema === functionSchema
|
||||
).length === 1;
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(!functionData.length || !foundFunction).to.be.true;
|
||||
} else {
|
||||
// NOTE: functionData.length may not be true
|
||||
expect(!functionData.length || !foundFunction).to.be.false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Make date API Request and check the repsone status
|
||||
* @param reqBody
|
||||
* @param result
|
||||
*/
|
||||
export const dataRequest = (
|
||||
reqBody: RequestBody,
|
||||
result: ResultType,
|
||||
queryType: QueryEndpoint = 'query'
|
||||
) => {
|
||||
const requestOptions = makeDataAPIOptions(
|
||||
dataApiUrl,
|
||||
adminSecret,
|
||||
reqBody,
|
||||
queryType
|
||||
);
|
||||
cy.request(requestOptions).then(response => {
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(
|
||||
(response.body.result_type === 'CommandOk' &&
|
||||
response.body.result === null) ||
|
||||
response.body.message === 'success'
|
||||
).to.be.true;
|
||||
} else {
|
||||
expect(
|
||||
(response.body.result_type === 'CommandOk' &&
|
||||
response.body.result === null) ||
|
||||
response.body.message === 'success'
|
||||
).to.be.false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const trackFunctionRequest = (
|
||||
reqBody: RequestBody,
|
||||
result: ResultType
|
||||
) => {
|
||||
const requestOptions = makeDataAPIOptions(
|
||||
dataApiUrl,
|
||||
adminSecret,
|
||||
reqBody,
|
||||
'metadata'
|
||||
);
|
||||
cy.request(requestOptions).then(response => {
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(response.body.message === ResultType.SUCCESS).to.be.true;
|
||||
} else {
|
||||
expect(response.body.message === ResultType.SUCCESS).to.be.false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Drop a table request
|
||||
* @param reqBody
|
||||
* @param result
|
||||
*/
|
||||
export const dropTableRequest = (reqBody: RequestBody, result: ResultType) => {
|
||||
const requestOptions = makeDataAPIOptions(dataApiUrl, adminSecret, reqBody);
|
||||
cy.request(requestOptions).then(response => {
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(
|
||||
response.body.length > 0 && response.body[0].result_type === 'CommandOk'
|
||||
).to.be.true;
|
||||
} else {
|
||||
expect(
|
||||
response.body.length > 0 && response.body[0].result_type === 'CommandOk'
|
||||
).to.be.false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ****************** Table Validator *********************
|
||||
|
||||
export const validateCT = (tableName: string, result: ResultType) => {
|
||||
const reqBody = {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `SELECT * FROM "public"."${tableName}";`,
|
||||
},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(dataApiUrl, adminSecret, reqBody);
|
||||
cy.request(requestOptions).then(response => {
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(response.status === 200).to.be.true;
|
||||
} else {
|
||||
expect(response.status === 200).to.be.false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// **************** View Validator *******************
|
||||
|
||||
export const validateView = (viewName: string, result: ResultType) => {
|
||||
validateCT(viewName, result);
|
||||
};
|
||||
|
||||
// *************** Column Validator *******************
|
||||
|
||||
export const validateColumn = (
|
||||
tableName: string,
|
||||
column: (string | { name: string; columns: string[] })[],
|
||||
result: ResultType
|
||||
) => {
|
||||
const reqBody = {
|
||||
type: 'select',
|
||||
args: {
|
||||
table: tableName,
|
||||
columns: column,
|
||||
},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(dataApiUrl, adminSecret, reqBody);
|
||||
cy.request(requestOptions).then(response => {
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(response.status === 200).to.be.true;
|
||||
} else {
|
||||
expect(response.status === 200).to.be.false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const validateColumnWhere = (
|
||||
tableName: string,
|
||||
column: string,
|
||||
where: string,
|
||||
result: ResultType
|
||||
) => {
|
||||
const reqBody = {
|
||||
type: 'select',
|
||||
args: {
|
||||
table: tableName,
|
||||
columns: column,
|
||||
where,
|
||||
},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(dataApiUrl, adminSecret, reqBody);
|
||||
cy.request(requestOptions).then(response => {
|
||||
cy.log(JSON.stringify(response));
|
||||
if (result === ResultType.SUCCESS) {
|
||||
expect(response.body.length > 0).to.be.true;
|
||||
} else {
|
||||
expect(response.body.length === 0).to.be.true;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// ******************** Validate Insert *********************
|
||||
|
||||
export const validateInsert = (tableName: string, rows: number) => {
|
||||
const reqBody = {
|
||||
type: 'count',
|
||||
source: 'default',
|
||||
args: {
|
||||
table: tableName,
|
||||
schema: 'public',
|
||||
},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(dataApiUrl, adminSecret, reqBody);
|
||||
cy.request(requestOptions).then(response => {
|
||||
cy.log(JSON.stringify(response));
|
||||
expect(response.body.count === rows).to.be.true;
|
||||
});
|
||||
};
|
||||
|
||||
// ******************* Permissiosn Validator ****************
|
||||
|
||||
export type QueryType = 'insert' | 'select' | 'update' | 'delete';
|
||||
export type CheckType = 'custom' | 'none';
|
||||
interface SchemaObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const compareChecks = (
|
||||
permObj: SchemaObject,
|
||||
check: CheckType,
|
||||
query: QueryType,
|
||||
columns: string[] | null
|
||||
) => {
|
||||
const perm = permObj.permission ?? {};
|
||||
if (check === 'none') {
|
||||
if (query === 'insert') {
|
||||
expect(Object.keys(perm?.check ?? {}).length === 0).to.be.true;
|
||||
expect(perm?.set?.[getColName(0)] === '1').to.be.true;
|
||||
expect(perm?.set?.[getColName(1)] === 'x-hasura-user-id').to.be.true;
|
||||
} else {
|
||||
expect(Object.keys(perm?.filter ?? {}).length === 0).to.be.true;
|
||||
if (query === 'select' || query === 'update') {
|
||||
[0, 1, 2].forEach(index => {
|
||||
expect(perm?.columns.includes(getColName(index)));
|
||||
});
|
||||
if (query === 'update') {
|
||||
expect(perm?.set?.[getColName(0)] === '1').to.be.true;
|
||||
expect(perm?.set?.[getColName(1)] === 'x-hasura-user-id').to.be.true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (query === 'insert') {
|
||||
expect(perm?.check?.[getColName(0)]._eq === 1).to.be.true;
|
||||
} else {
|
||||
expect(perm?.filter?.[getColName(0)]._eq === 1).to.be.true;
|
||||
if (query === 'select' || query === 'update') {
|
||||
if (columns) {
|
||||
columns.forEach((col, index) => {
|
||||
expect(perm?.columns.includes(getColName(index)));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handlePermValidationResponse = (
|
||||
tableSchema: SchemaObject,
|
||||
role: string,
|
||||
query: QueryType,
|
||||
check: CheckType,
|
||||
result: ResultType,
|
||||
columns: string[] | null
|
||||
) => {
|
||||
let rolePerms = {};
|
||||
if (tableSchema?.[`${query}_permissions`]) {
|
||||
rolePerms = tableSchema[`${query}_permissions`].find(
|
||||
(permission: { role: string }) => permission.role === role
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(rolePerms).length) {
|
||||
compareChecks(rolePerms, check, query, columns);
|
||||
} else {
|
||||
// this block can be reached only if the permission doesn't exist (failure case)
|
||||
expect(result === ResultType.FAILURE).to.be.true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate Permissions
|
||||
* @param tableName
|
||||
* @param role
|
||||
* @param query
|
||||
* @param check
|
||||
* @param result
|
||||
* @param columns
|
||||
*/
|
||||
export const validatePermission = (
|
||||
tableName: string,
|
||||
role: string,
|
||||
query: QueryType,
|
||||
check: CheckType,
|
||||
result: ResultType,
|
||||
columns: string[] | null
|
||||
) => {
|
||||
const reqBody = {
|
||||
type: 'export_metadata',
|
||||
args: {},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(
|
||||
dataApiUrl,
|
||||
adminSecret,
|
||||
reqBody,
|
||||
'metadata'
|
||||
);
|
||||
cy.request(requestOptions).then(response => {
|
||||
const sourceInfo = response.body.sources.find(
|
||||
(source: { name: string }) => source.name === 'default'
|
||||
);
|
||||
const tableSchema = sourceInfo.tables.find(
|
||||
(table: { table: { name: string } }) => table.table.name === tableName
|
||||
);
|
||||
handlePermValidationResponse(
|
||||
tableSchema,
|
||||
role,
|
||||
query,
|
||||
check,
|
||||
result,
|
||||
columns
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// ********************** Validate Migration mode ******************
|
||||
/**
|
||||
* Validate the migration mode
|
||||
* @param mode
|
||||
*/
|
||||
export const validateMigrationMode = (mode: boolean) => {
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: migrateModeUrl,
|
||||
}).then(response => {
|
||||
expect(response.body.migration_mode == mode.toString()).to.be.true; // eslint-disable-line
|
||||
});
|
||||
};
|
||||
|
||||
// ****************** Trigger Validator *********************
|
||||
/**
|
||||
* Validate the trigger based on trigger name
|
||||
* @param triggerName
|
||||
* @param result
|
||||
*/
|
||||
export const validateCTrigger = (
|
||||
triggerName: string,
|
||||
tableName: string,
|
||||
schemaName = 'public',
|
||||
result: ResultType,
|
||||
allCols?: boolean
|
||||
) => {
|
||||
const reqBody = {
|
||||
type: 'export_metadata',
|
||||
args: {},
|
||||
};
|
||||
const requestOptions = makeDataAPIOptions(
|
||||
dataApiUrl,
|
||||
adminSecret,
|
||||
reqBody,
|
||||
'metadata'
|
||||
);
|
||||
cy.request(requestOptions).then(response => {
|
||||
const sourceInfo = response.body.sources.find(
|
||||
(source: { name: string }) => source.name === 'default'
|
||||
);
|
||||
const tableInfo =
|
||||
sourceInfo?.tables?.find(
|
||||
(table: { table: { schema: string; name: string } }) =>
|
||||
table.table.schema === schemaName && table.table.name === tableName
|
||||
) ?? {};
|
||||
const trigger =
|
||||
tableInfo?.event_triggers?.find(
|
||||
(trig: { name: string }) => trig.name === triggerName
|
||||
) ?? {};
|
||||
|
||||
if (result === ResultType.SUCCESS && Object.keys(tableInfo).length) {
|
||||
expect(response.status === 200).to.be.true;
|
||||
expect(trigger.definition.insert.columns === '*').to.be.true;
|
||||
expect(trigger.definition.delete.columns === '*').to.be.true;
|
||||
if (allCols) {
|
||||
expect(trigger.definition.update.columns === '*').to.be.true;
|
||||
} else {
|
||||
expect(trigger.definition.update.columns.length === 2).to.be.true;
|
||||
}
|
||||
expect(
|
||||
trigger.retry_conf.interval_sec === parseInt(getIntervalSeconds(), 10)
|
||||
).to.be.true;
|
||||
expect(trigger.retry_conf.num_retries === parseInt(getNoOfRetries(), 10))
|
||||
.to.be.true;
|
||||
expect(
|
||||
trigger.retry_conf.timeout_sec === parseInt(getTimeoutSeconds(), 10)
|
||||
).to.be.true;
|
||||
expect(schemaName === 'public').to.be.true;
|
||||
expect(tableName === 'Apic_test_table_ctr_0').to.be.true;
|
||||
} else {
|
||||
expect(!Object.keys(tableInfo).length || !Object.keys(trigger).length).to
|
||||
.be.true;
|
||||
}
|
||||
});
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io"
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
|
25
frontend/apps/console-ce-e2e/src/global.d.ts
vendored
Normal file
25
frontend/apps/console-ce-e2e/src/global.d.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
interface Env {
|
||||
adminSecret: string;
|
||||
apiHost: string;
|
||||
apiPort: string;
|
||||
assetsPath: string;
|
||||
assetsVersion: string;
|
||||
assetVersion: string;
|
||||
cdnAssets: boolean;
|
||||
consoleAssetVersion: string;
|
||||
consoleMode: string;
|
||||
consolePath: string;
|
||||
dataApiUrl: string;
|
||||
enableTelemetry: boolean;
|
||||
featuresCompatibility: string;
|
||||
isAdminSecretSet: boolean;
|
||||
isproduction: boolean;
|
||||
nodeEnv: string;
|
||||
serverVersion: string;
|
||||
telemetryTopic: string;
|
||||
urlPrefix: string;
|
||||
}
|
||||
|
||||
interface Window {
|
||||
__env: Env;
|
||||
}
|
26
frontend/apps/console-ce-e2e/src/helpers/common.ts
Normal file
26
frontend/apps/console-ce-e2e/src/helpers/common.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { getTestMode } from './core/testMode';
|
||||
|
||||
export const testMode = getTestMode();
|
||||
export const baseUrl = Cypress.config('baseUrl');
|
||||
export const migrateUrl = Cypress.env('MIGRATE_URL');
|
||||
export const migrateModeUrl = `${migrateUrl}/settings`;
|
||||
|
||||
// sets value of window.prompt and reloads page
|
||||
export const setPromptValue = (value: string | null) => {
|
||||
cy.log(`Set window.prompt to "${value}"`).then(() => {
|
||||
cy.removeAllListeners('window:before:load');
|
||||
cy.on('window:before:load', win => {
|
||||
cy.stub(win, 'prompt').returns(value);
|
||||
});
|
||||
});
|
||||
|
||||
cy.reload();
|
||||
};
|
||||
|
||||
// This is works as setPromptValue with no unnecessary waiting
|
||||
export const setPromptWithCb = (value: string | null, cb: () => void) => {
|
||||
cy.window().then(win => {
|
||||
cy.stub(win, 'prompt').returns(value);
|
||||
cb();
|
||||
});
|
||||
};
|
1
frontend/apps/console-ce-e2e/src/helpers/constants.ts
Normal file
1
frontend/apps/console-ce-e2e/src/helpers/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const ADMIN_SECRET_HEADER_KEY = 'x-hasura-admin-secret';
|
28
frontend/apps/console-ce-e2e/src/helpers/core/testMode.ts
Normal file
28
frontend/apps/console-ce-e2e/src/helpers/core/testMode.ts
Normal file
@ -0,0 +1,28 @@
|
||||
type TestMode =
|
||||
// the default one
|
||||
| 'parallel'
|
||||
// prevent all the tests from running at all
|
||||
| 'cli'
|
||||
// mentioned in the cypress/README but never used
|
||||
// TODO: remove it or fix the docs if needed
|
||||
| 'ui';
|
||||
|
||||
/**
|
||||
* Read and check the TEST_MODE.
|
||||
*
|
||||
* @returns {TestMode}
|
||||
*/
|
||||
export function getTestMode(): TestMode {
|
||||
const testMode = Cypress.env('TEST_MODE');
|
||||
|
||||
if (
|
||||
typeof testMode !== 'string' ||
|
||||
(testMode !== 'parallel' && testMode !== 'cli' && testMode !== 'ui')
|
||||
) {
|
||||
throw new Error(
|
||||
`Unexpected Cypress env variable TEST_MODE value: ${testMode}`
|
||||
);
|
||||
}
|
||||
|
||||
return testMode;
|
||||
}
|
264
frontend/apps/console-ce-e2e/src/helpers/dataHelpers.ts
Normal file
264
frontend/apps/console-ce-e2e/src/helpers/dataHelpers.ts
Normal file
@ -0,0 +1,264 @@
|
||||
import { QueryType } from './../integration/validators/validators';
|
||||
import { ADMIN_SECRET_HEADER_KEY } from './constants';
|
||||
|
||||
export const baseUrl = Cypress.config('baseUrl');
|
||||
|
||||
export const getDbRoute = (sourceName = 'default') => `/data/${sourceName}`;
|
||||
|
||||
export const getIndexRoute = (sourceName = 'default', schemaName = 'public') =>
|
||||
`${getDbRoute(sourceName)}/schema/${schemaName}/`;
|
||||
|
||||
export const createVolatileFunction = (name: string) => {
|
||||
return {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `CREATE OR REPLACE FUNCTION public.${name}()
|
||||
RETURNS SETOF text_result
|
||||
LANGUAGE sql
|
||||
AS $function$
|
||||
SELECT * FROM text_result;
|
||||
$function$`,
|
||||
cascade: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const dataTypes = [
|
||||
'serial',
|
||||
'bigserial',
|
||||
'integer',
|
||||
'bigint',
|
||||
'uuid',
|
||||
'text',
|
||||
'numeric',
|
||||
'date',
|
||||
'timestamptz',
|
||||
'timetz',
|
||||
'boolean',
|
||||
];
|
||||
|
||||
export const typeDefaults: { [key: string]: string } = {
|
||||
integer: '5555',
|
||||
bigint: '5555555555',
|
||||
uuid: 'gen_random_uuid()',
|
||||
text: 'test-text',
|
||||
numeric: '0.55555',
|
||||
date: 'now()',
|
||||
timestamptz: 'now()',
|
||||
timetz: 'now()',
|
||||
boolean: 'false',
|
||||
};
|
||||
|
||||
export const queryTypes: QueryType[] = ['insert', 'select', 'update', 'delete'];
|
||||
|
||||
export const getColName = (i: number) => `Apic_test_column_${i}`;
|
||||
|
||||
export const getTableName = (i: number, testName = '') =>
|
||||
`Apic_test_table_${testName}_${i}`;
|
||||
|
||||
export const getElementFromAlias = (alias: string) => `[data-test="${alias}"]`;
|
||||
|
||||
export const getElementFromClassName = (cn: string) => `.${cn}`;
|
||||
|
||||
export const tableColumnTypeSelector = (alias: string) => {
|
||||
cy.get(`${getElementFromAlias(alias)}`)
|
||||
.children('div')
|
||||
.click()
|
||||
.find('input')
|
||||
.focus();
|
||||
};
|
||||
|
||||
export const makeDataAPIUrl = (
|
||||
dataApiUrl: string,
|
||||
queryEndpoint: QueryEndpoint = 'query'
|
||||
) => {
|
||||
if (queryEndpoint === 'query') {
|
||||
return `${dataApiUrl}/v2/query`;
|
||||
}
|
||||
return `${dataApiUrl}/v1/metadata`;
|
||||
};
|
||||
|
||||
interface APIPayload {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export const queryEndpoints = {
|
||||
query: 'query',
|
||||
metadata: 'metadata',
|
||||
} as const;
|
||||
|
||||
export type QueryEndpoint = keyof typeof queryEndpoints;
|
||||
|
||||
export const makeDataAPIOptions = (
|
||||
dataApiUrl: string,
|
||||
key: string,
|
||||
body: APIPayload,
|
||||
queryType: QueryEndpoint = 'query'
|
||||
) => ({
|
||||
method: 'POST',
|
||||
url: makeDataAPIUrl(dataApiUrl, queryType),
|
||||
headers: {
|
||||
[ADMIN_SECRET_HEADER_KEY]: key,
|
||||
},
|
||||
body,
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
|
||||
export const testCustomFunctionDefinition = (
|
||||
i: string
|
||||
) => `create function search_posts${`_${i}`} (search text) returns setof post as $$ select * from post where title ilike ('%' || search || '%') or content ilike ('%' || search || '%') $$ language sql stable;
|
||||
`;
|
||||
|
||||
export const getCustomFunctionName = (i: number) => `search_posts${`_${i}`}`;
|
||||
|
||||
export const getCreateTestFunctionQuery = (i: number) => {
|
||||
return {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `CREATE OR REPLACE FUNCTION public.search_posts_${i}(search text)\n RETURNS SETOF post\n LANGUAGE sql\n STABLE\nAS $function$\n select *\n from post\n where\n title ilike ('%' || search || '%') or\n content ilike ('%' || search || '%')\n $function$\n`,
|
||||
cascade: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getTrackTestFunctionQuery = (i: number) => {
|
||||
return {
|
||||
type: 'pg_track_function',
|
||||
args: {
|
||||
function: `search_posts_${i}`,
|
||||
schema: 'public',
|
||||
source: 'default',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const testCustomFunctionSQLWithSessArg = (
|
||||
name = 'customFunctionWithSessionArg'
|
||||
) => {
|
||||
return {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `CREATE OR REPLACE FUNCTION ${name}(
|
||||
hasura_session json, name text
|
||||
) RETURNS SETOF text_result LANGUAGE sql STABLE AS $$
|
||||
SELECT
|
||||
q.*
|
||||
FROM
|
||||
(
|
||||
VALUES
|
||||
(hasura_session ->> 'x-hasura-role')
|
||||
) q $$`,
|
||||
cascade: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const createUntrackedFunctionSQL = (
|
||||
fnName: string,
|
||||
tableName: string
|
||||
) => {
|
||||
return {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `
|
||||
CREATE OR REPLACE FUNCTION ${fnName}(table_row "${tableName}")
|
||||
RETURNS int
|
||||
LANGUAGE sql
|
||||
STABLE
|
||||
AS $function$
|
||||
SELECT table_row.id
|
||||
$function$
|
||||
`,
|
||||
cascade: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const dropUntrackedFunctionSQL = (fnName: string) => {
|
||||
return {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `
|
||||
DROP FUNCTION public.${fnName};
|
||||
`,
|
||||
cascade: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getTrackFnPayload = (name = 'customfunctionwithsessionarg') => ({
|
||||
type: 'pg_track_function',
|
||||
args: {
|
||||
function: name,
|
||||
source: 'default',
|
||||
schema: 'public',
|
||||
},
|
||||
});
|
||||
|
||||
// has to go to query
|
||||
export const createFunctionTable = () => {
|
||||
return {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: 'create table post (id serial PRIMARY KEY,title TEXT,content TEXT);',
|
||||
cascade: false,
|
||||
},
|
||||
};
|
||||
};
|
||||
// has to go to metadata
|
||||
export const trackCreateFunctionTable = () => {
|
||||
return {
|
||||
type: 'pg_track_table',
|
||||
args: {
|
||||
table: {
|
||||
name: 'post',
|
||||
schema: 'public',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const createSampleTable = () => ({
|
||||
type: 'run_sql',
|
||||
source: 'default',
|
||||
args: {
|
||||
sql: 'CREATE TABLE text_result(result text);',
|
||||
cascade: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const dropTableIfExists = (
|
||||
table: { name: string; schema: string },
|
||||
source = 'default'
|
||||
) => ({
|
||||
type: 'run_sql',
|
||||
source,
|
||||
args: {
|
||||
sql: `DROP TABLE IF EXISTS "${table.schema}"."${table.name}";`,
|
||||
cascade: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const getTrackSampleTableQuery = () => {
|
||||
return {
|
||||
type: 'pg_track_table',
|
||||
source: 'default',
|
||||
args: {
|
||||
table: {
|
||||
name: 'text_result',
|
||||
schema: 'public',
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
export const dropTable = (table = 'post', cascade = false) => {
|
||||
return {
|
||||
type: 'run_sql',
|
||||
args: {
|
||||
sql: `DROP table "public"."${table}"${cascade ? ' CASCADE;' : ';'}`,
|
||||
cascade,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getSchema = () => 'public';
|
35
frontend/apps/console-ce-e2e/src/helpers/eventHelpers.ts
Normal file
35
frontend/apps/console-ce-e2e/src/helpers/eventHelpers.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ADMIN_SECRET_HEADER_KEY } from './constants';
|
||||
|
||||
export const baseUrl = Cypress.config('baseUrl');
|
||||
export const queryTypes = ['insert', 'update', 'delete'];
|
||||
|
||||
export const getTriggerName = (i: number, testName = '') =>
|
||||
`Apic_test_trigger_${testName}_${i}`;
|
||||
|
||||
export const getTableName = (i: number, testName = '') =>
|
||||
`Apic_test_table_${testName}_${i}`;
|
||||
|
||||
export const getWebhookURL = () => 'http://httpbin.org/post';
|
||||
export const getNoOfRetries = () => '5';
|
||||
export const getIntervalSeconds = () => '10';
|
||||
export const getTimeoutSeconds = () => '25';
|
||||
|
||||
export const getElementFromAlias = (alias: string) => `[data-test=${alias}]`;
|
||||
export const makeDataAPIUrl = (dataApiUrl: string) => `${dataApiUrl}/v1/query`;
|
||||
|
||||
interface APIPayload {
|
||||
[key: string]: any;
|
||||
}
|
||||
export const makeDataAPIOptions = (
|
||||
dataApiUrl: string,
|
||||
key: string,
|
||||
body: APIPayload
|
||||
) => ({
|
||||
method: 'POST',
|
||||
url: makeDataAPIUrl(dataApiUrl),
|
||||
headers: {
|
||||
[ADMIN_SECRET_HEADER_KEY]: key,
|
||||
},
|
||||
body,
|
||||
failOnStatusCode: false,
|
||||
});
|
17
frontend/apps/console-ce-e2e/src/helpers/metadata.ts
Normal file
17
frontend/apps/console-ce-e2e/src/helpers/metadata.ts
Normal file
@ -0,0 +1,17 @@
|
||||
export const replaceMetadata = (newMetadata: Record<string, any>) => {
|
||||
const postBody = { type: 'replace_metadata', args: newMetadata };
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const resetMetadata = () => {
|
||||
const postBody = { type: 'clear_metadata', args: {} };
|
||||
cy.request('POST', 'http://localhost:8080/v1/metadata', postBody).then(
|
||||
response => {
|
||||
expect(response.body).to.have.property('message', 'success'); // true
|
||||
}
|
||||
);
|
||||
};
|
@ -0,0 +1,38 @@
|
||||
import { ADMIN_SECRET_HEADER_KEY } from './constants';
|
||||
|
||||
export const baseUrl = Cypress.config('baseUrl');
|
||||
|
||||
export const getRemoteSchemaName = (i: number, schemaName: string) =>
|
||||
`test-remote-schema-${schemaName}-${i}`;
|
||||
|
||||
export const getRemoteGraphQLURL = () =>
|
||||
'https://graphql-pokemon2.vercel.app/';
|
||||
|
||||
export const getRemoteGraphQLURLFromEnv = () => 'GRAPHQL_URL';
|
||||
|
||||
export const getInvalidRemoteSchemaUrl = () => 'http://httpbin.org/post';
|
||||
|
||||
export const getHeaderAccessKey = (i: string) => `ACCESS_KEY-${i}`;
|
||||
|
||||
export const getHeaderAccessKeyValue = () => 'b94264abx98';
|
||||
|
||||
export const getElementFromAlias = (alias: string) => `[data-test=${alias}]`;
|
||||
|
||||
export const makeDataAPIUrl = (dataApiUrl: string) => `${dataApiUrl}/v1/query`;
|
||||
|
||||
export const makeDataAPIOptions = (
|
||||
dataApiUrl: string,
|
||||
key: string,
|
||||
body: { [key: string]: any }
|
||||
) => ({
|
||||
method: 'POST',
|
||||
url: makeDataAPIUrl(dataApiUrl),
|
||||
headers: {
|
||||
[ADMIN_SECRET_HEADER_KEY]: key,
|
||||
},
|
||||
body,
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
|
||||
export const getRemoteSchemaRoleName = (i: number, roleName: string) =>
|
||||
`test-role-${roleName}-${i}`;
|
@ -0,0 +1,82 @@
|
||||
import { getElementFromAlias } from './dataHelpers';
|
||||
|
||||
export const togglePayloadTransformSection = () => {
|
||||
cy.getBySel('toggle-payload-transform').click({
|
||||
force: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const toggleRequestTransformSection = () => {
|
||||
cy.getBySel('toggle-request-transform').click({
|
||||
force: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const clearRequestUrl = () => {
|
||||
cy.get(
|
||||
getElementFromAlias('transform-requestUrl')
|
||||
).type('{selectall}{backspace}', { force: true });
|
||||
};
|
||||
|
||||
export const typeIntoRequestUrl = (content: string) => {
|
||||
cy.getBySel('transform-requestUrl').type(content, {
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const checkTransformRequestUrlError = (
|
||||
exists: boolean,
|
||||
error?: string
|
||||
) => {
|
||||
if (exists) {
|
||||
if (error) {
|
||||
cy.getBySel('transform-requestUrl-error')
|
||||
.should('exist')
|
||||
.and('contain', error);
|
||||
} else {
|
||||
cy.getBySel('transform-requestUrl-error').should('exist');
|
||||
}
|
||||
} else {
|
||||
cy.getBySel('transform-requestUrl-error').should('not.exist');
|
||||
}
|
||||
};
|
||||
|
||||
export const typeIntoRequestQueryParams = (
|
||||
queryParams: { key: string; value: string }[]
|
||||
) => {
|
||||
queryParams.forEach((q, i) => {
|
||||
cy.getBySel(`transform-query-params-kv-key-${i}`).type(q.key, {
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
cy.getBySel(`transform-query-params-kv-value-${i}`).type(q.value, {
|
||||
parseSpecialCharSequences: false,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const checkTransformRequestUrlPreview = (previewText: string) => {
|
||||
cy.getBySel('transform-requestUrl-preview').should('have.value', previewText);
|
||||
};
|
||||
|
||||
export const clearPayloadTransformBody = (textArea: number) => {
|
||||
cy.get('textarea').eq(textArea).type('{selectall}', { force: true });
|
||||
cy.get('textarea').eq(textArea).trigger('keydown', {
|
||||
keyCode: 46,
|
||||
which: 46,
|
||||
force: true,
|
||||
});
|
||||
};
|
||||
|
||||
export const typeIntoTransformBody = (content: string, textArea: number) => {
|
||||
cy.get('textarea')
|
||||
.eq(textArea)
|
||||
.type(content, { force: true, parseSpecialCharSequences: false });
|
||||
};
|
||||
|
||||
export const checkTransformRequestBodyError = (exists: boolean) => {
|
||||
if (exists) {
|
||||
cy.getBySel('transform-requestBody-error').should('exist');
|
||||
} else {
|
||||
cy.getBySel('transform-requestBody-error').should('not.exist');
|
||||
}
|
||||
};
|
@ -1 +0,0 @@
|
||||
export const getGreeting = () => cy.get('h1');
|
@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Clear a Console's textarea.
|
||||
* Work around cy.clear sometimes not working in the Console's textareas.
|
||||
*/
|
||||
Cypress.Commands.add('clearConsoleTextarea', { prevSubject: 'element' }, el => {
|
||||
cy.wrap(el).type('{selectall}', { force: true }).trigger('keydown', {
|
||||
keyCode: 46,
|
||||
which: 46,
|
||||
force: true,
|
||||
});
|
||||
});
|
@ -7,19 +7,11 @@
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
login(email: string, password: string): void;
|
||||
}
|
||||
}
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
Cypress.Commands.add('login', (email, password) => {
|
||||
console.log('Custom command example: Login', email, password);
|
||||
});
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
@ -29,5 +21,16 @@ Cypress.Commands.add('login', (email, password) => {
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
import '@testing-library/cypress/add-commands';
|
||||
|
||||
import './visitEmptyPage';
|
||||
import './clearConsoleTextarea';
|
||||
import './notifications';
|
||||
import './contractIntercept';
|
||||
|
||||
Cypress.Commands.add('getBySel', (selector, ...args) => {
|
||||
return cy.get(`[data-test=${selector}]`, ...args);
|
||||
});
|
||||
|
@ -0,0 +1,168 @@
|
||||
import type {
|
||||
ContractRequest,
|
||||
RunningTestsState,
|
||||
StartContractInterceptOptions,
|
||||
} from './types';
|
||||
|
||||
import { checkAndGetTestInfo } from './helpers/checkAndGetTestInfo';
|
||||
import { generateEmptyTestState } from './helpers/generateEmptyTestState';
|
||||
|
||||
const runningTestState: RunningTestsState = {};
|
||||
|
||||
/**
|
||||
* A wrapper around `cy.intercept` that allows intercepting and recording the request/response series
|
||||
* that made up the contract.
|
||||
*
|
||||
* It could be useful for sharing the contract's fixtures with the server folks.
|
||||
*
|
||||
* Be aware of the current limitations:
|
||||
* 1. Only one test at the time is supported. `runningTestState` can store more tests at once but
|
||||
* the fixture files are all saved starting from "1 - ", even if related to different tests
|
||||
* 2. If you do not call cy.haltContractIntercept, no fixture files will be saved
|
||||
*
|
||||
* @see https://github.com/hasura/graphql-engine-mono/issues/4601
|
||||
*/
|
||||
function startContractIntercept(
|
||||
startContractInterceptOptions: StartContractInterceptOptions,
|
||||
url: string
|
||||
) {
|
||||
const { thisTest, mode, createFixtureName } = startContractInterceptOptions;
|
||||
const { testTitle, testPath } = checkAndGetTestInfo(thisTest);
|
||||
|
||||
if (mode === 'disabled') {
|
||||
Cypress.log({
|
||||
message: `*🤝 ❌ No Contract will be recorded for ${testTitle}*`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Cypress.log({
|
||||
message: `*🤝 ✅ Contract will be recorded for ${testTitle}*`,
|
||||
});
|
||||
|
||||
runningTestState[testTitle] ??= generateEmptyTestState(testPath, testTitle);
|
||||
|
||||
if (Object.keys(runningTestState).length > 1) {
|
||||
throw new Error(`startContractIntercept support only one test at a time`);
|
||||
}
|
||||
// Start intercepting the requests
|
||||
cy.intercept(url, request => {
|
||||
// The recorded could have been halted
|
||||
if (runningTestState[testTitle].halted) {
|
||||
Cypress.log({
|
||||
message: `*🤝 ❌ Contract recording has been halted for: ${testTitle}*`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const fixtureName = createFixtureName(request);
|
||||
if (fixtureName.includes('\\') || fixtureName.includes('/')) {
|
||||
throw new Error(
|
||||
`createFixtureName cannot return names that includes / or \\ like ${fixtureName}`
|
||||
);
|
||||
}
|
||||
|
||||
const contractLength = runningTestState[testTitle].contract.length;
|
||||
|
||||
// start from 1
|
||||
const fixtureIndex = contractLength + 1;
|
||||
const fixtureFileName = `${fixtureIndex}-${fixtureName}.json`;
|
||||
|
||||
const recorded: ContractRequest = {
|
||||
readme:
|
||||
'////////// This fixture has been automatically generated through cy.startContractIntercept //////////',
|
||||
request,
|
||||
fixtureName,
|
||||
fixtureFileName,
|
||||
|
||||
// Temporary, empty, response
|
||||
response: {
|
||||
statusCode: undefined,
|
||||
headers: undefined,
|
||||
body: undefined,
|
||||
},
|
||||
};
|
||||
// Add the request to the Contract
|
||||
runningTestState[testTitle].contract.push(recorded);
|
||||
|
||||
Cypress.log({
|
||||
message: `*🤝 ✅ Recorded ${fixtureFileName} in the contract*`,
|
||||
consoleProps: () => request,
|
||||
});
|
||||
|
||||
request.continue(response => {
|
||||
// Add the request to the Contract too
|
||||
recorded.response = response;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Halt recording the contract and save the fixture files.
|
||||
* Please note that it must be called just once
|
||||
*/
|
||||
function haltContractIntercept(options: {
|
||||
thisTest: Mocha.Context;
|
||||
saveFixtureFiles?: boolean;
|
||||
}) {
|
||||
const { thisTest, saveFixtureFiles = true } = options;
|
||||
const { testTitle, testPath } = checkAndGetTestInfo(thisTest);
|
||||
|
||||
if (!saveFixtureFiles) {
|
||||
Cypress.log({
|
||||
message: `*🤝 ❌ No fixtures will be saved for this test: ${testTitle}*`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (runningTestState[testTitle].halted) {
|
||||
Cypress.log({
|
||||
message: `*🤝 ❌ Contract recording for this test has already been halted: ${testTitle}*`,
|
||||
});
|
||||
}
|
||||
|
||||
// Halt recording the requests for the current test.
|
||||
// Please note that must be done asynchronously because of the double-run nature of the Cypress tests.
|
||||
cy.wrap(null).then(() => {
|
||||
Cypress.log({
|
||||
message: `*🤝 ❌ Halting the contract recording for this test: ${testTitle}*`,
|
||||
});
|
||||
runningTestState[testTitle].halted = true;
|
||||
});
|
||||
|
||||
// Split the current path
|
||||
cy.task('splitPath', { path: testPath }).then(result => {
|
||||
const splittedPath = result as string[];
|
||||
|
||||
// Remove the file name
|
||||
splittedPath.pop();
|
||||
|
||||
// Create the directory
|
||||
cy.task('joinPath', { path: [...splittedPath, 'fixtures'] }).then(path => {
|
||||
cy.task('mkdirSync', {
|
||||
dir: path as string,
|
||||
});
|
||||
});
|
||||
|
||||
const testState = runningTestState[testTitle];
|
||||
|
||||
// Save all the files
|
||||
for (let i = 0, n = testState.contract.length; i < n; i++) {
|
||||
const request = testState.contract[i];
|
||||
|
||||
cy.task('joinPath', {
|
||||
// Stores the fixture files close to the test file, in a "fixtures" directory
|
||||
path: [...splittedPath, 'fixtures', request.fixtureFileName],
|
||||
}).then(filePath => {
|
||||
// Save the fixture file
|
||||
cy.task('writeFileSync', {
|
||||
file: filePath as string,
|
||||
data: JSON.stringify(request, null, 2),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Cypress.Commands.add('startContractIntercept', startContractIntercept);
|
||||
Cypress.Commands.add('haltContractIntercept', haltContractIntercept);
|
@ -0,0 +1,24 @@
|
||||
import { generateTestTitle } from './generateTestTitle';
|
||||
import { throwIfCalledInTestHooks } from './throwIfCalledInTestHooks';
|
||||
import { throwIfCalledInsideArrowFunction } from './throwIfCalledInsideArrowFunction';
|
||||
|
||||
/**
|
||||
* Perform pre-flight checks and return the most important test info.
|
||||
*/
|
||||
export function checkAndGetTestInfo(thisTest: Mocha.Context) {
|
||||
const testTitle = generateTestTitle(thisTest); // ex. 'Describe 1 - Describe 2 - Describe 3 - Test title'
|
||||
const testPath = thisTest?.invocationDetails?.relativeFile;
|
||||
|
||||
throwIfCalledInTestHooks(thisTest.title);
|
||||
throwIfCalledInsideArrowFunction(thisTest.title);
|
||||
|
||||
// TS-only check, it should never happen at runtime
|
||||
if (typeof testPath !== 'string') {
|
||||
throw new Error(`No test Path available for ${testTitle}`);
|
||||
}
|
||||
|
||||
return {
|
||||
testPath,
|
||||
testTitle,
|
||||
};
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Return an array containing all the "describe"s title.
|
||||
*
|
||||
* Ex. return ['Describe 1', 'Describe 2'] in this case
|
||||
* describe('Describe 1', () => {
|
||||
* describe('Describe 2', () => {
|
||||
* it('Test', () => {})
|
||||
* })
|
||||
* })
|
||||
*/
|
||||
export function generateDescribesTitle(
|
||||
parent?: Mocha.Suite | undefined
|
||||
): string[] {
|
||||
if (!parent) return [];
|
||||
|
||||
let parentNames: string[] = [];
|
||||
// parents are recursive
|
||||
if (parent.parent) {
|
||||
parentNames = parentNames.concat(generateDescribesTitle(parent.parent));
|
||||
}
|
||||
|
||||
if (parent.title !== '') {
|
||||
parentNames.push(parent.title);
|
||||
}
|
||||
|
||||
return parentNames;
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/// <reference types="Cypress" />
|
||||
|
||||
import { generateDescribesTitle } from './generateDescribesTitle';
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// TYPES -------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This is a simplified version of Mocha.Suite
|
||||
type SimplifiedTestRoot = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
type SimplifiedTestOrDescribe = {
|
||||
parent: SimplifiedTestRoot | SimplifiedTestOrDescribe;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type SimplifiedTestSuite = SimplifiedTestOrDescribe | SimplifiedTestRoot;
|
||||
// -------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
describe('generateDescribesTitle', () => {
|
||||
it('Should return an array including all the titles of the chain of test.describe and the test itself', () => {
|
||||
// Arrange
|
||||
// In case of a test file like the following
|
||||
// describe('Describe 1', () => {
|
||||
// describe('Describe 2', () => {
|
||||
// describe('Describe 3', () => {
|
||||
// it('Test title', () => {})
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
const testSuite: SimplifiedTestSuite = {
|
||||
parent: {
|
||||
parent: {
|
||||
parent: {
|
||||
title: 'Describe 1',
|
||||
},
|
||||
title: 'Describe 2',
|
||||
},
|
||||
title: 'Describe 3',
|
||||
},
|
||||
title: 'Test title',
|
||||
};
|
||||
|
||||
// Act
|
||||
// testSuite is compatible for what concerns to generateDescribesTitle that will only look for the
|
||||
// `parent` and `title` properties
|
||||
const result = generateDescribesTitle(testSuite.parent as Mocha.Suite);
|
||||
|
||||
// Assert
|
||||
expect(result).to.deep.equal(['Describe 1', 'Describe 2', 'Describe 3']);
|
||||
});
|
||||
|
||||
it('Should return an array including only the test title in case of no describes', () => {
|
||||
// Act
|
||||
// testSuite is compatible for what concerns to generateDescribesTitle that will only look for the
|
||||
// `parent` and `title` properties
|
||||
const result = generateDescribesTitle(undefined);
|
||||
|
||||
// Assert
|
||||
expect(result).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('Should return an array including all the titles of the chain of test.describe and the test hook itself', () => {
|
||||
// Arrange
|
||||
// In case of a test file like the following
|
||||
// describe('Describe 1', () => {
|
||||
// before(() => {})
|
||||
// it('Test title', () => {})
|
||||
// })
|
||||
const testSuite: SimplifiedTestSuite = {
|
||||
parent: {
|
||||
title: 'Describe 1',
|
||||
},
|
||||
title: '"before" hook',
|
||||
};
|
||||
|
||||
// Act
|
||||
// testSuite is compatible for what concerns to generateDescribesTitle that will only look for the
|
||||
// `parent` and `title` properties
|
||||
const result = generateDescribesTitle(testSuite.parent as Mocha.Suite);
|
||||
|
||||
// Assert
|
||||
expect(result).to.deep.equal(['Describe 1']);
|
||||
});
|
||||
});
|
@ -0,0 +1,13 @@
|
||||
import type { RunningTestState } from '../types';
|
||||
|
||||
export function generateEmptyTestState(
|
||||
testPath: string,
|
||||
testTitle: string
|
||||
): RunningTestState {
|
||||
return {
|
||||
testPath,
|
||||
testTitle,
|
||||
halted: false,
|
||||
contract: [],
|
||||
};
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import type { TestTitle } from '../types';
|
||||
import { generateDescribesTitle } from './generateDescribesTitle';
|
||||
|
||||
/**
|
||||
* Return a string containing all the "describe"s titles and the test title concatenated.
|
||||
*
|
||||
* Ex. return 'Describe 1 - Describe 2 - Test' in this case
|
||||
* describe('Describe 1', () => {
|
||||
* describe('Describe 2', () => {
|
||||
* it('Test', () => {})
|
||||
* })
|
||||
* })
|
||||
*/
|
||||
export function generateTestTitle(thisTest: Mocha.Context): TestTitle {
|
||||
const describesTitle = generateDescribesTitle(thisTest.parent);
|
||||
const testTitle = thisTest.title;
|
||||
|
||||
return [...describesTitle, testTitle].join(' - ');
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/// <reference types="Cypress" />
|
||||
|
||||
import { generateTestTitle } from './generateTestTitle';
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// TYPES -------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// This is a simplified version of Mocha.Suite
|
||||
type SimplifiedTestRoot = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
type SimplifiedTestOrDescribe = {
|
||||
parent: SimplifiedTestRoot | SimplifiedTestOrDescribe;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type SimplifiedTestSuite = SimplifiedTestOrDescribe;
|
||||
// -------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
describe('generateTestTitle', () => {
|
||||
it('Should return an array including all the titles of the chain of test.describe and the test itself', () => {
|
||||
// Arrange
|
||||
// In case of a test file like the following
|
||||
// describe('Describe 1', () => {
|
||||
// describe('Describe 2', () => {
|
||||
// describe('Describe 3', () => {
|
||||
// it('Test title', () => {})
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
const testSuite: SimplifiedTestSuite = {
|
||||
parent: {
|
||||
parent: {
|
||||
parent: {
|
||||
title: 'Describe 1',
|
||||
},
|
||||
title: 'Describe 2',
|
||||
},
|
||||
title: 'Describe 3',
|
||||
},
|
||||
title: 'Test title',
|
||||
};
|
||||
|
||||
// Act
|
||||
// testSuite is compatible for what concerns to generateTestTitle that will only look for the
|
||||
// `parent` and `title` properties
|
||||
const result = generateTestTitle((testSuite as unknown) as Mocha.Context);
|
||||
|
||||
// Assert
|
||||
expect(result).to.deep.equal(
|
||||
'Describe 1 - Describe 2 - Describe 3 - Test title'
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,18 @@
|
||||
/// <reference types="Cypress" />
|
||||
|
||||
describe(`cy.task('splitPath')`, () => {
|
||||
it('Should split the passed path', () => {
|
||||
const path =
|
||||
'cypress/support/interceptAndRecordContract/helpers/temp.unit.test.ts';
|
||||
|
||||
cy.task('splitPath', { path }).then(result => {
|
||||
expect(result).to.deep.equal([
|
||||
'cypress',
|
||||
'support',
|
||||
'interceptAndRecordContract',
|
||||
'helpers',
|
||||
'temp.unit.test.ts',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Throws if called from inside a test hook.
|
||||
* The problem is that it's impossible to know which test the hook relates to. Hence, it's
|
||||
* impossible to detect the name of the test.
|
||||
*/
|
||||
export function throwIfCalledInTestHooks(testTitle: string) {
|
||||
switch (testTitle) {
|
||||
case '"after" hook':
|
||||
throw new Error(
|
||||
'interceptAndRecordContract cannot be called inside a "after" hook'
|
||||
);
|
||||
case '"after all" hook':
|
||||
throw new Error(
|
||||
'interceptAndRecordContract cannot be called inside a "after all" hook'
|
||||
);
|
||||
case '"before" hook':
|
||||
throw new Error(
|
||||
'interceptAndRecordContract cannot be called inside a "before" hook'
|
||||
);
|
||||
case '"before all" hook':
|
||||
throw new Error(
|
||||
'interceptAndRecordContract cannot be called inside a "before all" hook'
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/// <reference types="Cypress" />
|
||||
|
||||
import { throwIfCalledInTestHooks } from './throwIfCalledInTestHooks';
|
||||
|
||||
describe('throwIfCalledInTestHooks', () => {
|
||||
it('Should throw an error if called with "after" hook', () => {
|
||||
// Arrange
|
||||
const executor = () => throwIfCalledInTestHooks('"after" hook');
|
||||
|
||||
// Assert
|
||||
expect(executor).to.throw(
|
||||
'interceptAndRecordContract cannot be called inside a "after" hook'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should throw an error if called with "after all" hook', () => {
|
||||
// Arrange
|
||||
const executor = () => throwIfCalledInTestHooks('"after all" hook');
|
||||
|
||||
// Assert
|
||||
expect(executor).to.throw(
|
||||
'interceptAndRecordContract cannot be called inside a "after all" hook'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should throw an error if called with "before" hook', () => {
|
||||
// Arrange
|
||||
const executor = () => throwIfCalledInTestHooks('"before" hook');
|
||||
|
||||
// Assert
|
||||
expect(executor).to.throw(
|
||||
'interceptAndRecordContract cannot be called inside a "before" hook'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should throw an error if called with "before all" hook', () => {
|
||||
// Arrange
|
||||
const executor = () => throwIfCalledInTestHooks('"before all" hook');
|
||||
|
||||
// Assert
|
||||
expect(executor).to.throw(
|
||||
'interceptAndRecordContract cannot be called inside a "before all" hook'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should not throw an error if called with another test title', () => {
|
||||
// Arrange
|
||||
const executor = () => throwIfCalledInTestHooks('Test title');
|
||||
|
||||
// Assert
|
||||
expect(executor).not.to.throw(
|
||||
'interceptAndRecordContract cannot be called inside a "before all" hook'
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Throw if interceptAndRecordContract has been called without the `this.test` element.
|
||||
*
|
||||
* It happens when the test is called inside an arrow function
|
||||
* ex.
|
||||
* it('...', () => { // <- THIS is the problem
|
||||
* cy.startContractIntercept({
|
||||
* thisTest: this.test,
|
||||
* // ...
|
||||
*/
|
||||
export function throwIfCalledInsideArrowFunction(
|
||||
thisTest: Mocha.Context | Record<string, unknown>
|
||||
) {
|
||||
if (Object.keys(thisTest).length === 0) {
|
||||
throw new Error(
|
||||
'interceptAndRecordContract did not receive `this` that refers to the test itself. Have you called interceptAndRecordContract inside an arrow function? If yes, transform function into a regular one'
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/// <reference types="Cypress" />
|
||||
|
||||
import { throwIfCalledInsideArrowFunction } from './throwIfCalledInsideArrowFunction';
|
||||
|
||||
describe('throwIfCalledInsideArrowFunction', () => {
|
||||
it('Should throw an error if called without a the reference to the `this.test`', () => {
|
||||
// Arrange
|
||||
const executor = () => throwIfCalledInsideArrowFunction({});
|
||||
|
||||
// Assert
|
||||
expect(executor).to.throw(
|
||||
'interceptAndRecordContract did not receive `this` that refers to the test itself. Have you called interceptAndRecordContract inside an arrow function? If yes, transform function into a regular one'
|
||||
);
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export * from './contractIntercept';
|
@ -0,0 +1,67 @@
|
||||
import type { CyHttpMessages } from 'cypress/types/net-stubbing';
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// PACT TYPES --------------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Borrowed from Pact's types https://github.com/pactflow/pact-cypress-adapter/blob/main/src/types.ts
|
||||
export type HeaderType = Record<string, string | string[]> | undefined;
|
||||
|
||||
type BaseXHR = {
|
||||
headers: HeaderType;
|
||||
body: any | undefined;
|
||||
};
|
||||
|
||||
export type XHRRequestAndResponse = {
|
||||
request: {
|
||||
method: string;
|
||||
url: string;
|
||||
} & BaseXHR;
|
||||
response: {
|
||||
statusCode: string | number | undefined;
|
||||
// statusText: string | undefined;
|
||||
} & BaseXHR;
|
||||
};
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CONTRACT RECORDING TYPES ------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export type TestTitle = string;
|
||||
type TestPath = string;
|
||||
|
||||
export type ContractRequest = XHRRequestAndResponse & {
|
||||
readme: string;
|
||||
fixtureName: string;
|
||||
fixtureFileName: string;
|
||||
};
|
||||
|
||||
export type RunningTestState = {
|
||||
testTitle: TestTitle;
|
||||
testPath: TestPath;
|
||||
halted: boolean;
|
||||
|
||||
contract: ContractRequest[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Considering the current limitation of "only one test at a time can be recorded", the whole state
|
||||
* and startContractIntercept could be simplified a lot by:
|
||||
* 1. Transforming the State into a Map<Mocha.Context, RunningTestState>
|
||||
* 2. Using the test instance, instead of the test name, to store the test state
|
||||
* 3. Getting read of generateTestTitle and its tests
|
||||
*
|
||||
* Please note that this would result also in not having the test name in the cypress logs but this
|
||||
* is not important since only a single test can be recorded at a time...
|
||||
*/
|
||||
export type RunningTestsState = Record<TestTitle, RunningTestState>;
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// OPTIONS TYPES -----------------------------------------------------
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export type StartContractInterceptOptions = {
|
||||
thisTest: Mocha.Context;
|
||||
mode: 'record' | 'disabled';
|
||||
createFixtureName: (req: CyHttpMessages.IncomingHttpRequest) => string;
|
||||
};
|
73
frontend/apps/console-ce-e2e/src/support/index.d.ts
vendored
Normal file
73
frontend/apps/console-ce-e2e/src/support/index.d.ts
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
// type definition for all custom commands
|
||||
declare namespace Cypress {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
interface Chainable<Subject> {
|
||||
/**
|
||||
* Custom command to select DOM element by data-test attribute.
|
||||
* <button data-test="greeting"> </button>
|
||||
* @example cy.getBySel('greeting')
|
||||
*/
|
||||
getBySel(value: string): Chainable<JQuery<Element>>;
|
||||
|
||||
/**
|
||||
* 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<JQuery<HTMLTextAreaElement>>;
|
||||
|
||||
/**
|
||||
* Visit the initial empty page.
|
||||
* Console's textarea
|
||||
* @example cy.visitEmptyPage()
|
||||
*/
|
||||
visitEmptyPage(): Chainable<unknown>;
|
||||
|
||||
/**
|
||||
* Success notifications
|
||||
*/
|
||||
expectSuccessNotification(): Chainable<unknown>;
|
||||
expectSuccessNotificationWithTitle(title: string): Chainable<unknown>;
|
||||
expectSuccessNotificationWithMessage(message: string): Chainable<unknown>;
|
||||
|
||||
/**
|
||||
* Error notifications
|
||||
*/
|
||||
expectErrorNotification(): Chainable<unknown>;
|
||||
expectErrorNotificationWithTitle(title: string): Chainable<unknown>;
|
||||
expectErrorNotificationWithMessage(message: string): Chainable<unknown>;
|
||||
|
||||
/**
|
||||
* Start intercepting the request/response contract between the Console and the server.
|
||||
* @example
|
||||
* cy.startContractIntercept(
|
||||
* {
|
||||
* thisTest: this.test,
|
||||
* mode: 'record',
|
||||
* createFixtureName: (req: CyHttpMessages.IncomingHttpRequest) => {
|
||||
* if (req.url.endsWith('v1/metadata')) {
|
||||
* return `v1-metadata-${req.body.type}`;
|
||||
* }
|
||||
*
|
||||
* throw new Error(`Unknown url ${req.url}`);
|
||||
* },
|
||||
* },
|
||||
* 'http://localhost:8080/**'
|
||||
* );
|
||||
*/
|
||||
startContractIntercept(
|
||||
startContractInterceptOptions: import('./contractIntercept/types').StartContractInterceptOptions,
|
||||
url: string
|
||||
): Chainable<unknown>;
|
||||
|
||||
/**
|
||||
* Halt intercepting the request/response contract between the Console and the server and save the fixtures.
|
||||
* @example
|
||||
* cy.haltContractIntercept({ thisTest: this.test })
|
||||
*/
|
||||
haltContractIntercept(options: {
|
||||
thisTest: Mocha.Context;
|
||||
saveFixtureFiles?: boolean;
|
||||
}): Chainable<unknown>;
|
||||
}
|
||||
}
|
33
frontend/apps/console-ce-e2e/src/support/notifications.ts
Normal file
33
frontend/apps/console-ce-e2e/src/support/notifications.ts
Normal file
@ -0,0 +1,33 @@
|
||||
Cypress.Commands.add('expectSuccessNotification', () => {
|
||||
cy.get('.notification-success').should('be.visible');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('expectSuccessNotificationWithTitle', (title: string) => {
|
||||
cy.get('.notification-success').should('be.visible').should('contain', title);
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
'expectSuccessNotificationWithMessage',
|
||||
(message: string) => {
|
||||
cy.get('.notification-success')
|
||||
.should('be.visible')
|
||||
.should('contain', message);
|
||||
}
|
||||
);
|
||||
|
||||
Cypress.Commands.add('expectErrorNotification', () => {
|
||||
cy.get('.notification-error').should('be.visible');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('expectErrorNotificationWithTitle', (title: string) => {
|
||||
cy.get('.notification-error').should('be.visible').should('contain', title);
|
||||
});
|
||||
|
||||
Cypress.Commands.add(
|
||||
'expectErrorNotificationWithMessage',
|
||||
(message: string) => {
|
||||
cy.get('.notification-error')
|
||||
.should('be.visible')
|
||||
.should('contain', message);
|
||||
}
|
||||
);
|
4
frontend/apps/console-ce-e2e/src/support/tasks/index.ts
Normal file
4
frontend/apps/console-ce-e2e/src/support/tasks/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { joinPath } from './joinPath';
|
||||
export { mkdirSync } from './mkdirSync';
|
||||
export { splitPath } from './splitPath';
|
||||
export { writeFileSync } from './writeFileSync';
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user