console: e2e snapshot testing for event triggers

This PR adds e2e snapshot testing for event triggers.
We are testing it in 2 ways -
1. testing the shortest possible path to create ET and then modifying it to the longest possible path
    - here we are testing the shortest path and see no extra fields are added to avoid regression. Then we modify the shortest path to the longest one and see nothing will break on the metadata side
2. testing the longest possible path to create ET and then modifying it to the shortest possible path
   - here we are testing the longest path and see if all the fields are passing to avoid regression. Then we modify the longest path to the shortest one and see nothing will break on the metadata side
3. More info in the [doc](https://docs.google.com/document/d/1NjZsvd6xOq90lNMai8nKBaWl-LqBWrkiZCHSQ7wusdE/edit)

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8341
GitOrigin-RevId: f44d5297e70fe50fc4f2a2ac43a1707b71398ba1
This commit is contained in:
Varun Choudhary 2023-04-03 22:53:14 +05:30 committed by hasura-bot
parent c3451bd622
commit 6621dc6adb
10 changed files with 353 additions and 16 deletions

View File

@ -1,4 +1,4 @@
const createTable = (tableName: string) => { export const createTable = (tableName: string) => {
const postBody = { const postBody = {
type: 'run_sql', type: 'run_sql',
args: { args: {
@ -14,7 +14,7 @@ const createTable = (tableName: string) => {
} }
); );
}; };
const deleteTable = (tableName: string) => { export const deleteTable = (tableName: string) => {
const postBody = { const postBody = {
type: 'run_sql', type: 'run_sql',
args: { args: {

View File

@ -0,0 +1,74 @@
exports[`Create event trigger with shortest possible path > When the users create, modify and delete an Event trigger, everything should work #0`] =
{
"definition": {
"enable_manual": false,
"insert": {
"columns": "*"
},
"update": {
"columns": [
"id"
]
}
},
"headers": [
{
"name": "x-hasura-user-id",
"value": "1234"
}
],
"name": "event_trigger_test",
"request_transform": {
"body": {
"action": "transform",
"template": "{\n \"table\": {\n \"name\": {{$body.table.name}},\n \"schema\": {{$body.table.schema}}\n }\n}"
},
"method": "GET",
"query_params": {
"x-hasura-user-id": "my-user-id"
},
"request_headers": {
"add_headers": {},
"remove_headers": [
"content-type"
]
},
"template_engine": "Kriti",
"url": "{{$base_url}}/transformUrl",
"version": 2
},
"retry_conf": {
"interval_sec": 5,
"num_retries": 10,
"timeout_sec": 70
},
"webhook": "http://httpbin.org/post"
};
exports[`Create event trigger with logest possible path > When the users create, modify and delete an Event trigger, everything should work #0`] =
{
"definition": {
"enable_manual": false,
"insert": {
"columns": "*"
},
"update": {
"columns": [
"id"
]
}
},
"headers": [
{
"name": "x-hasura-user-id",
"value": "1234"
}
],
"name": "event_trigger_test",
"retry_conf": {
"interval_sec": 5,
"num_retries": 10,
"timeout_sec": 70
},
"webhook": "http://httpbin.org/post"
};

View File

@ -0,0 +1,271 @@
import { HasuraMetadataV3 } from '@hasura/console-legacy-ce';
import { readMetadata } from '../actions/withTransform/utils/services/readMetadata';
import { postgres } from '../data/manage-database/postgres.spec';
describe('Create event trigger with shortest possible path', () => {
before(() => {
// create a table first
postgres.helpers.createTable('user_table');
// track the table
cy.visit('/data/default/schema/public', {
timeout: 10000,
});
cy.get('[data-test=add-track-table-user_table]', {
timeout: 10000,
}).click();
});
after(() => {
// delete the table
cy.log('**--- Delete the table');
postgres.helpers.deleteTable('user_table');
});
it('When the users create, modify and delete an Event trigger, everything should work', () => {
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 1: Create an ET with shortest path**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.visit('/events/data/manage', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('event_trigger_test');
},
});
// --------------------
cy.log('**--- Click on the Create button of the ET panel**');
cy.get('[data-testid=data-create-trigger]').click();
// trigger name
cy.log('**--- Type the event trigger name**');
cy.findByPlaceholderText('trigger_name').type('event_trigger_test');
// select source
cy.log('**--- Select the DB');
cy.get('[name="source"]').select('default');
// select schema and table
cy.log('**--- Select the schema and table');
cy.get('[name="schema"]').select('public');
cy.get('[name="tableName"]').select('user_table');
// select the trigger operation
cy.log('**--- Select the ET operation');
cy.get('[name=insert]').click();
// add webhook url
cy.log('**--- Add webhook url');
cy.get('[name=handler]').type('http://httpbin.org/post');
// click on create button to save ET
cy.log('**--- Click on Create Event Trigger');
cy.findByRole('button', { name: 'Create Event Trigger' }).click();
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 2: Modify an ET to longest path and save it**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// modfy the trigger operation
cy.log(
'**--- Click on Edit trigger operation and modfiy the trigger operation'
);
cy.findAllByRole('button', { name: 'Edit' }).eq(1).click();
cy.get('[name=update]').click();
cy.get('[name=column-id]').click();
cy.findByRole('button', { name: 'Save' }).click();
// modify the retry config
cy.log('**--- Click on Edit retry config and modify the config');
cy.findAllByRole('button', { name: 'Edit' }).eq(1).click();
cy.get('[name=num_retries]').clear().type('10');
cy.get('[name=interval_sec]').clear().type('5');
cy.get('[name=timeout_sec]').clear().type('70');
cy.findByRole('button', { name: 'Save' }).click();
// add headers
cy.log('**--- Click on Edit retry config and add header');
cy.findAllByRole('button', { name: 'Edit' }).eq(2).click();
cy.findByPlaceholderText('key').type('x-hasura-user-id');
cy.findByPlaceholderText('value').type('1234');
cy.findByRole('button', { name: 'Save' }).click();
// add Sample context
cy.log('**--- Click on show sample context and fill the form');
cy.findByText('Show Sample Context').click();
cy.findByPlaceholderText('Key...').type('env-var');
cy.findByPlaceholderText('Value...').type('env-var-value');
// add Request Options Transform
cy.log('**--- Click on Add Request Options Transform and fill the form');
cy.findByText('Add Request Options Transform').click();
cy.get('[name=GET]').click();
cy.get('[name=request_url]').type('/transformUrl');
cy.findAllByPlaceholderText('Key...').eq(2).type('x-hasura-user-id');
cy.findAllByPlaceholderText('Value...').eq(2).type('my-user-id');
// add Payload Transform
cy.log('**--- Click on Add Payload Transform and fill the form');
cy.findByText('Add Payload Transform').click();
// save the ET
cy.log('**--- Click on Save Event trigger to modify the ET');
cy.findByRole('button', { name: 'Save Event Trigger' }).click();
readMetadata().then((md: { body: HasuraMetadataV3 }) => {
cy.wrap(
(md.body.sources || [])
.find(source => source.name === 'default')
.tables.find(table => table?.table?.name === 'user_table')
?.event_triggers?.[0]
).toMatchSnapshot({ name: 'Modify the shotest path to longest' });
});
// delete ET
cy.findByRole('button', { name: 'Delete Event Trigger' }).click();
});
});
describe('Create event trigger with logest possible path', () => {
before(() => {
// create a table first
postgres.helpers.createTable('user_table');
// track the table
cy.visit('/data/default/schema/public', {
timeout: 10000,
});
cy.get('[data-test=add-track-table-user_table]', {
timeout: 10000,
}).click();
});
after(() => {
// delete the table
cy.log('**--- Delete the table');
postgres.helpers.deleteTable('user_table');
});
it('When the users create, modify and delete an Event trigger, everything should work', () => {
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 1: Create an ET with longest path**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.visit('/events/data/manage', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('event_trigger_test');
},
});
// --------------------
cy.log('**--- Click on the Create button of the ET panel**');
cy.get('[data-testid=data-create-trigger]').click();
// trigger name
cy.log('**--- Type the event trigger name**');
cy.findByPlaceholderText('trigger_name').type('event_trigger_test');
// select source
cy.log('**--- Select the DB');
cy.get('[name="source"]').select('default');
// select schema and table
cy.log('**--- Select the schema and table');
cy.get('[name="schema"]').select('public');
cy.get('[name="tableName"]').select('user_table');
// select the trigger operation
cy.log('**--- Select the ET operation');
cy.get('[name=insert]').click();
// add webhook url
cy.log('**--- Add webhook url');
cy.get('[name=handler]').type('http://httpbin.org/post');
// toggle the adv setting, add retry config and headers
cy.log('**--- Add retry config');
cy.findByText('Advanced Settings').click();
cy.get('[name=num_retries]').clear().type('10');
cy.get('[name=interval_sec]').clear().type('5');
cy.get('[name=timeout_sec]').clear().type('70');
// add headers
cy.log('**--- Add header');
cy.findByPlaceholderText('key').type('x-hasura-user-id');
cy.findByPlaceholderText('value').type('1234');
// add Sample context
cy.log('**--- Click on show sample context and fill the form');
cy.findByText('Show Sample Context').click();
cy.findByPlaceholderText('Key...').type('env-var');
cy.findByPlaceholderText('Value...').type('env-var-value');
// add Request Options Transform
cy.log('**--- Click on Add Request Options Transform and fill the form');
cy.findByText('Add Request Options Transform').click();
cy.get('[name=GET]').click();
cy.get('[name=request_url]').type('/transformUrl');
cy.findAllByPlaceholderText('Key...').eq(2).type('x-hasura-user-id');
cy.findAllByPlaceholderText('Value...').eq(2).type('my-user-id');
// add Payload Transform
cy.log('**--- Click on Add Payload Transform and fill the form');
cy.findByText('Add Payload Transform').click();
// click on create button to save ET
cy.log('**--- Click on Create Event Trigger');
cy.get('[data-test=trigger-create]').click();
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 2: Modify an ET to shortest path and save it**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// modfy the trigger operation
cy.log(
'**--- Click on Edit trigger operation and modfiy the trigger operation'
);
cy.findAllByRole('button', { name: 'Edit' }).eq(1).click();
cy.get('[name=update]').click();
cy.get('[name=column-id]').click();
cy.findByRole('button', { name: 'Save' }).click();
// remove Request Options Transform
cy.log('**--- Click on Remove Request Options Transform');
cy.findByText('Remove Request Options Transform').click();
// remove Payload Transform
cy.log('**--- Click on Remove Payload Transform');
cy.findByText('Remove Payload Transform').click();
// save the ET
cy.log('**--- Click on Save Event trigger to modify the ET');
cy.findByRole('button', { name: 'Save Event Trigger' }).click();
readMetadata().then((md: { body: HasuraMetadataV3 }) => {
cy.wrap(
(md.body.sources || [])
.find(source => source.name === 'default')
.tables.find(table => table?.table?.name === 'user_table')
?.event_triggers?.[0]
).toMatchSnapshot({ name: 'Modify the longest path to shortest path' });
});
// delete ET
cy.findByRole('button', { name: 'Delete Event Trigger' }).click();
});
});

View File

@ -120,7 +120,6 @@ const ConfigureTransformation: React.FC<
<Button <Button
color="white" color="white"
size="sm" size="sm"
data-test="toggle-context-area"
onClick={() => { onClick={() => {
toggleContextArea(!isContextAreaActive); toggleContextArea(!isContextAreaActive);
}} }}
@ -160,7 +159,6 @@ const ConfigureTransformation: React.FC<
size="sm" size="sm"
icon={!isRequestUrlTransform ? <AddIcon /> : undefined} icon={!isRequestUrlTransform ? <AddIcon /> : undefined}
iconPosition="start" iconPosition="start"
data-test="toggle-request-transform"
onClick={() => { onClick={() => {
requestUrlTransformOnChange(!isRequestUrlTransform); requestUrlTransformOnChange(!isRequestUrlTransform);
resetSampleInput(); resetSampleInput();
@ -203,7 +201,6 @@ const ConfigureTransformation: React.FC<
<Button <Button
color="white" color="white"
size="sm" size="sm"
data-test="toggle-payload-transform"
onClick={() => { onClick={() => {
requestPayloadTransformOnChange(!isRequestPayloadTransform); requestPayloadTransformOnChange(!isRequestPayloadTransform);
resetSampleInput(); resetSampleInput();
@ -245,7 +242,6 @@ const ConfigureTransformation: React.FC<
<Button <Button
color="white" color="white"
size="sm" size="sm"
data-test="toggle-response-transform"
onClick={() => { onClick={() => {
responsePayloadTransformOnChange( responsePayloadTransformOnChange(
!responseTransformState.isResponsePayloadTransform !responseTransformState.isResponsePayloadTransform

View File

@ -44,7 +44,6 @@ const KeyValueInput: React.FC<KeyValueInputProps> = ({
value={name} value={name}
onChange={setPairKey} onChange={setPairKey}
type="text" type="text"
name="table_name"
id="table_name" id="table_name"
className={`w-full ${inputStyles}`} className={`w-full ${inputStyles}`}
placeholder="Key..." placeholder="Key..."
@ -56,7 +55,6 @@ const KeyValueInput: React.FC<KeyValueInputProps> = ({
value={value} value={value}
onChange={setPairValue} onChange={setPairValue}
type="text" type="text"
name="table_name"
id="table_name" id="table_name"
className={`w-full ${inputStyles}`} className={`w-full ${inputStyles}`}
placeholder="Value..." placeholder="Value..."

View File

@ -121,6 +121,7 @@ const DDButton: React.FC<DropDownButtonProps> = props => {
value={inputVal || ''} value={inputVal || ''}
placeholder={inputPlaceHolder} placeholder={inputPlaceHolder}
data-test={`${testId}-input`} data-test={`${testId}-input`}
name={`${dataKey}[${dataIndex}]`}
/> />
</InputGroup> </InputGroup>
); );

View File

@ -83,8 +83,8 @@ const Headers: React.FC<HeadersListProps> = ({
inputVal={value} inputVal={value}
id={`header-value-${i}`} id={`header-value-${i}`}
inputPlaceHolder={type === 'env' ? 'HEADER_FROM_ENV' : 'value'} inputPlaceHolder={type === 'env' ? 'HEADER_FROM_ENV' : 'value'}
testId={`header-value-${i}`}
disabled={disabled} disabled={disabled}
testId={`header-value-${i}`}
/> />
</div> </div>
{i < headers.length - 1 ? ( {i < headers.length - 1 ? (

View File

@ -36,8 +36,6 @@ class Editor extends React.Component {
const { const {
readOnlyMode = false, readOnlyMode = false,
isCollapsable, isCollapsable,
service,
property,
collapseButtonText, collapseButtonText,
expandButtonText, expandButtonText,
} = this.props; } = this.props;
@ -52,7 +50,6 @@ class Editor extends React.Component {
mode="default" mode="default"
size="sm" size="sm"
className="mr-sm" className="mr-sm"
data-test={`${service}-${isEditing ? 'close' : 'edit'}-${property}`}
onClick={this.toggleEditor} onClick={this.toggleEditor}
disabled={readOnlyMode} disabled={readOnlyMode}
> >

View File

@ -94,7 +94,6 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
/> />
<input <input
type="text" type="text"
data-test="trigger-name"
placeholder="trigger_name" placeholder="trigger_name"
required required
pattern="^[A-Za-z]+[A-Za-z0-9_\\-]*$" pattern="^[A-Za-z]+[A-Za-z0-9_\\-]*$"
@ -108,8 +107,8 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
<select <select
className={`${inputStyles} pl-md w-72`} className={`${inputStyles} pl-md w-72`}
onChange={handleDatabaseChange} onChange={handleDatabaseChange}
data-test="select-source"
value={source} value={source}
name="source"
> >
<option value="">Select database</option> <option value="">Select database</option>
{dataSourcesList {dataSourcesList
@ -125,9 +124,9 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
<div className="flex"> <div className="flex">
<select <select
onChange={handleSchemaChange} onChange={handleSchemaChange}
data-test="select-schema"
className={`${inputStyles} w-72`} className={`${inputStyles} w-72`}
value={table.schema} value={table.schema}
name="schema"
> >
<option value="">Select schema</option> <option value="">Select schema</option>
{Object.keys(databaseInfo) {Object.keys(databaseInfo)
@ -140,10 +139,10 @@ const CreateETForm: React.FC<CreateETFormProps> = props => {
</select> </select>
<select <select
onChange={handleTableChange} onChange={handleTableChange}
data-test="select-table"
required required
className={`${inputStyles} w-72 ml-md`} className={`${inputStyles} w-72 ml-md`}
value={table.name} value={table.name}
name="tableName"
> >
<option value="">Select table</option> <option value="">Select table</option>
{filterSchemas && {filterSchemas &&

View File

@ -149,6 +149,7 @@ export const OperationEditor: React.FC<OperationEditorProps> = props => {
> >
<input <input
type="checkbox" type="checkbox"
name={`column-${col.name}`}
className={`!mr-xs cursor-pointer ${focusYellowRing} disabled:bg-gray-200 disabled:cursor-not-allowed disabled:text-gray-200 border-gray-200 rounded-sm bg-white`} className={`!mr-xs cursor-pointer ${focusYellowRing} disabled:bg-gray-200 disabled:cursor-not-allowed disabled:text-gray-200 border-gray-200 rounded-sm bg-white`}
checked={col.enabled} checked={col.enabled}
disabled={readOnly} disabled={readOnly}