console: e2e snapshot testing for remote schema

This PR adds e2e snapshot testing for remote schema.
We are testing it in 2 ways -

1. testing the shortest possible path to create RS and then modifying it to the longest possible path
2. testing the longest possible path to create RS and then modifying it to shortest possible path
3. [ see doc](https://docs.google.com/document/d/1NjZsvd6xOq90lNMai8nKBaWl-LqBWrkiZCHSQ7wusdE/edit) for more detail

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/8383
GitOrigin-RevId: d045219ca7db00e797492e9d61efb25283c1b3b3
This commit is contained in:
Varun Choudhary 2023-04-05 13:11:16 +05:30 committed by hasura-bot
parent fde4c0fae5
commit 6bf0a49cd6
8 changed files with 444 additions and 9 deletions

View File

@ -0,0 +1,74 @@
exports[`Create RS with shortest possible path > When the users create, modify and delete a RS, everything should work #0`] =
{
"comment": "",
"definition": {
"customization": {
"field_names": [
{
"mapping": {
"code": "country_code"
},
"parent_type": "Continent",
"prefix": "prefix_",
"suffix": "_suffix"
}
],
"root_fields_namespace": "namespace_",
"type_names": {
"mapping": {
"Country": "country_name"
},
"prefix": "prefix_",
"suffix": "_suffix"
}
},
"forward_client_headers": true,
"headers": [
{
"name": "user_id",
"value": "1234"
}
],
"timeout_seconds": 80,
"url": "https://countries.trevorblades.com/"
},
"name": "remote_schema_name"
};
exports[`Create RS with longest possible path > When the users create, modify and delete a RS, everything should work #0`] =
{
"comment": "",
"definition": {
"customization": {
"field_names": [
{
"mapping": {},
"parent_type": "query_root",
"prefix": "prefix_query_root",
"suffix": "query_root_suffix"
},
{
"mapping": {},
"parent_type": "mutation_root",
"prefix": "prefix_mutation_root",
"suffix": "mutation_root_suffix"
}
],
"root_fields_namespace": "namespace_",
"type_names": {
"mapping": {},
"prefix": "prefix_",
"suffix": "_suffix"
}
},
"headers": [
{
"name": "user_id",
"value": "1234"
}
],
"timeout_seconds": 60,
"url": "https://countries.trevorblades.com/"
},
"name": "remote_schema_name"
};

View File

@ -0,0 +1,337 @@
import { HasuraMetadataV3 } from '@hasura/console-legacy-ce';
import { readMetadata } from '../../actions/withTransform/utils/services/readMetadata';
describe('Create RS with shortest possible path', () => {
it('When the users create, modify and delete a RS, everything should work', () => {
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 1: Create a RS with shortest path**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.visit('/remote-schemas/manage/schemas', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('remote_schema_name');
},
});
// --------------------
cy.log('**--- Click on the Add button of the RS panel**');
cy.get('[data-testid=data-create-remote-schemas]').click();
// RS name
cy.log('**--- Type the RS name**');
cy.get('[name=name]').type('remote_schema_name');
// provide webhook URL
cy.log('**--- Add webhook url');
cy.get('[name="url.value"]').type('https://graphql-pokemon2.vercel.app');
// click on create button to save ET
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (JSON.stringify(req.body).includes('add_remote_schema')) {
req.alias = 'addRs';
}
req.continue();
});
cy.intercept('POST', 'http://localhost:9693/apis/migrate', req => {
if (JSON.stringify(req.body).includes('add_remote_schema')) {
req.alias = 'addRs';
}
});
cy.log('**--- Click on Add Remote Schema');
cy.findByRole('button', { name: 'Add Remote Schema' }).click();
cy.wait('@addRs');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 2: Modify RS to longest path and save it**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// go to modify tab of RS
cy.visit('/remote-schemas/manage/remote_schema_name/modify', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('remote_schema_name');
},
});
// edit RS webhook url
cy.log('**--- Modify the webhoook URL');
cy.get('[name=handler]')
.clear()
.type('https://countries.trevorblades.com/');
// check forward client header
cy.log('**--- Check forward client header');
cy.findByText('Forward all headers from client').click();
// add header
cy.log('**--- Click on Add headers button and add some headers');
cy.findAllByPlaceholderText('header name').type('user_id');
cy.findAllByPlaceholderText('header value').type('1234');
// add server timeout
cy.log('**--- Add the gql server timeout');
cy.get('[name=timeout_sec]').clear().type('80');
// click on create button to save ET
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (JSON.stringify(req.body).includes('update_remote_schema')) {
req.alias = 'updateRs';
}
req.continue();
});
cy.intercept('POST', 'http://localhost:9693/apis/migrate', req => {
if (JSON.stringify(req.body).includes('update_remote_schema')) {
req.alias = 'updateRs';
}
});
cy.log('**--- Click on Add Remote Schema');
cy.findByRole('button', { name: 'Save' }).click();
cy.wait('@updateRs');
// to again to modif tab
cy.visit('/remote-schemas/manage/remote_schema_name/modify', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('remote_schema_name');
},
});
// add gql customization
cy.log('**--- Click on Edit and apply some customization');
cy.findByRole('button', { name: 'Edit' }).click();
cy.get(`[name=namespace]`).type('namespace_');
cy.get(`[name=prefix]`).type('prefix_');
cy.get(`[name=suffix]`).type('_suffix');
cy.get('[name=type-name-lhs ]').select('Country');
cy.get(`[name="type-name-rhs[0]"]`).type('country_name');
cy.findByRole('button', { name: 'Add Field Mapping' }).click();
cy.get('[name=field-type]').select('Continent');
cy.get(`[name=field-type-prefix]`).type('prefix_');
cy.get(`[name=field-type-suffix]`).type('_suffix');
cy.get('[name=field-type-lhs]').select('code');
cy.get(`[name="field-type-rhs[0]"]`).type('country_code');
cy.findByRole('button', { name: 'Add Field Customization' }).click();
// save the RS
cy.log('**--- Click on Save to modify the RS');
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (JSON.stringify(req.body).includes('update_remote_schema')) {
req.alias = 'updateRs';
}
req.continue();
});
cy.intercept('POST', 'http://localhost:9693/apis/migrate', req => {
if (JSON.stringify(req.body).includes('update_remote_schema')) {
req.alias = 'updateRs';
}
});
cy.findByRole('button', { name: 'Save' }).click();
cy.wait('@updateRs');
cy.visit('/remote-schemas/manage/remote_schema_name/modify', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('remote_schema_name');
},
});
readMetadata().then((md: { body: HasuraMetadataV3 }) => {
cy.wrap(
md.body?.remote_schemas?.find(rs => rs?.name === 'remote_schema_name')
).toMatchSnapshot({ name: 'Modify the shotest path to longest' });
});
// delete RS
cy.log('**--- Click on Delete to delete the RS');
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (JSON.stringify(req.body).includes('remove_remote_schema')) {
req.alias = 'removeRs';
}
req.continue();
});
cy.intercept('POST', 'http://localhost:9693/apis/migrate', req => {
if (JSON.stringify(req.body).includes('remove_remote_schema')) {
req.alias = 'removeRs';
}
});
cy.findByRole('button', { name: 'Delete' }).click();
cy.wait('@removeRs');
});
});
describe('Create RS with longest possible path', () => {
it('When the users create, modify and delete a RS, everything should work', () => {
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 1: Create a RS with longest path**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.visit('/remote-schemas/manage/schemas', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('remote_schema_name');
},
});
// --------------------
cy.log('**--- Click on the Add button of the RS panel**');
cy.get('[data-testid=data-create-remote-schemas]').click();
// RS name
cy.log('**--- Type the RS name**');
cy.get('[name="name"]').type('remote_schema_name');
// add comment
cy.log('**--- Add comment to RS**');
cy.get(`[name=comment]`).type('RS comment');
// provide webhook URL
cy.log('**--- Add webhook url');
cy.get('[name="url.value"]').type('https://graphql-pokemon2.vercel.app');
// add server timeout
cy.log('**--- Add the gql server timeout');
cy.get('[name=timeout_seconds]').type('80');
// check forward client header
cy.log('**--- Check forward client header');
cy.get('[name=forward_client_headers]').click();
// add header
cy.log('**--- Click on Add headers button and add some headers');
cy.findByRole('button', { name: 'Add additional headers' }).click();
cy.get(`[name="headers[0].name"]`).type('user_id');
cy.get(`[name="headers[0].value"]`).type('1234');
// add gql customization
cy.log('**--- Click on Add GQL Customization and apply some customization');
cy.findByRole('button', { name: 'Add GQL Customization' }).click();
cy.get(`[name="customization.root_fields_namespace"]`).type('namespace_');
cy.get(`[name="customization.type_prefix"]`).type('prefix_');
cy.get(`[name="customization.type_suffix"]`).type('_suffix');
cy.get(`[name="customization.query_root.parent_type"]`).type('query_root');
cy.get(`[name="customization.query_root.prefix"]`).type(
'prefix_query_root'
);
cy.get(`[name="customization.query_root.suffix"]`).type(
'query_root_suffix'
);
cy.get(`[name="customization.mutation_root.parent_type"]`).type(
'mutation_root'
);
cy.get(`[name="customization.mutation_root.prefix"]`).type(
'prefix_mutation_root'
);
cy.get(`[name="customization.mutation_root.suffix"]`).type(
'mutation_root_suffix'
);
// click on create button to save ET
cy.log('**--- Click on Add Remote Schema');
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (JSON.stringify(req.body).includes('add_remote_schema')) {
req.alias = 'addRs';
}
req.continue();
});
cy.intercept('POST', 'http://localhost:9693/apis/migrate', req => {
if (JSON.stringify(req.body).includes('add_remote_schema')) {
req.alias = 'addRs';
}
});
cy.findByRole('button', { name: 'Add Remote Schema' }).click();
cy.wait('@addRs');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**--- Step 2: Modify RS to shortest path and save it**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
cy.log('**------------------------------**');
// go to modify tab of RS
cy.visit('/remote-schemas/manage/remote_schema_name/modify', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('remote_schema_name');
},
});
// edit RS webhook url
cy.log('**--- Modify the webhoook URL');
cy.get('[name=handler]')
.clear()
.type('https://countries.trevorblades.com/');
// check forward client header
cy.log('**--- Check forward client header');
cy.findByText('Forward all headers from client').click();
// clear gql timeout
cy.log('**--- Clear the gql time out');
cy.get('[name=timeout_sec]').clear();
// clear comment
cy.log('**--- Clear the RS comment');
cy.get('[name=comment]').clear();
// click on save button to save ET
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (JSON.stringify(req.body).includes('update_remote_schema')) {
req.alias = 'updateRs';
}
req.continue();
});
cy.intercept('POST', 'http://localhost:9693/apis/migrate', req => {
if (JSON.stringify(req.body).includes('update_remote_schema')) {
req.alias = 'updateRs';
}
});
cy.log('**--- Click on Add Remote Schema');
cy.findByRole('button', { name: 'Save' }).click();
cy.wait('@updateRs');
cy.visit('/remote-schemas/manage/remote_schema_name/modify', {
timeout: 10000,
onBeforeLoad(win) {
cy.stub(win, 'prompt').returns('remote_schema_name');
},
});
readMetadata().then((md: { body: HasuraMetadataV3 }) => {
cy.wrap(
md.body?.remote_schemas?.find(rs => rs?.name === 'remote_schema_name')
).toMatchSnapshot({ name: 'Modify the shotest path to longest' });
});
// delete RS
cy.log('**--- Click on Delete to delete the RS');
cy.intercept('POST', 'http://localhost:8080/v1/metadata', req => {
if (JSON.stringify(req.body).includes('remove_remote_schema')) {
req.alias = 'removeRs';
}
req.continue();
});
cy.intercept('POST', 'http://localhost:9693/apis/migrate', req => {
if (JSON.stringify(req.body).includes('remove_remote_schema')) {
req.alias = 'removeRs';
}
});
cy.findByRole('button', { name: 'Delete' }).click();
cy.wait('@removeRs');
});
});

View File

@ -117,6 +117,7 @@ class Common extends React.Component {
data-test="remote-schema-timeout-conf"
pattern="^\d+$"
title="Only non negative integers are allowed"
name="timeout_sec"
/>
</label>
</React.Fragment>
@ -247,6 +248,7 @@ class Common extends React.Component {
placeholder="Comment"
value={comment}
data-key="comment"
name="comment"
onChange={this.handleInputChange.bind(this)}
disabled={isDisabled}
data-test="remote-schema-comment"

View File

@ -45,6 +45,7 @@ const SelectOne = ({
onChange={onChange}
className={inputStyles}
data-test={label}
name={label}
>
<option value="">Select Type ...</option>
{[...options].sort().map((op, i) => (
@ -93,7 +94,7 @@ const FieldNames = ({
parentType: e.target.value,
});
}}
label={`remote-schema-customization-${label}-parent-type-input`}
label={label}
/>
</div>
</div>
@ -113,6 +114,7 @@ const FieldNames = ({
})
}
data-test={`remote-schema-customization-${label}-field-prefix-input`}
name={`${label}-prefix`}
/>
</div>
</div>
@ -132,6 +134,7 @@ const FieldNames = ({
})
}
data-test={`remote-schema-customization-${label}-field-suffix-input`}
name={`${label}-suffix`}
/>
</div>
</div>

View File

@ -161,6 +161,7 @@ const GraphQLCustomizationEdit = ({
type="text"
className={inputStyles}
placeholder="namespace_"
name="namespace"
value={rootFieldNamespace || ''}
data-test="remote-schema-customization-root-field-input"
onChange={e =>
@ -182,6 +183,7 @@ const GraphQLCustomizationEdit = ({
type="text"
className={inputStyles}
placeholder="prefix_"
name="prefix"
value={typeNames?.prefix || ''}
data-test="remote-schema-customization-type-name-prefix-input"
onChange={e =>
@ -204,6 +206,7 @@ const GraphQLCustomizationEdit = ({
type="text"
className={inputStyles}
placeholder="_suffix"
name="suffix"
value={typeNames?.suffix || ''}
data-test="remote-schema-customization-type-name-suffix-input"
onChange={e =>

View File

@ -27,6 +27,7 @@ const SelectOne = ({
onChange={onChange}
className={`${inputStyles} font-normal`}
data-test={`remote-schema-customization-${label}-lhs-input`}
name={`${label}-lhs`}
>
<option value="" className="text-base">
Select Type ...
@ -99,6 +100,7 @@ const TypeMapping = ({ types, typeMappings, onChange, label }: Props) => {
data-test={`remote-schema-customization-${
label ?? 'no-value'
}-${i}-rhs-input`}
name={`${label}-rhs[${i}]`}
/>
</div>
<div>
@ -136,6 +138,7 @@ const TypeMapping = ({ types, typeMappings, onChange, label }: Props) => {
onChange={e =>
setNewMap({ ...newMap, custom_name: e.target.value })
}
name={`${label}-rhs`}
// onBlur={() => {
// onAddItem(newMap);
// }}

View File

@ -1,9 +1,9 @@
import React, { useReducer } from 'react';
import React from 'react';
import { ReactQueryDecorator } from '../../../../storybook/decorators/react-query';
import { ComponentMeta, Story } from '@storybook/react';
import { RemoteSchema } from '../..';
import { ReduxDecorator } from '../../../../storybook/decorators/redux-decorator';
import { within, userEvent } from '@storybook/testing-library';
import { within, userEvent, waitFor } from '@storybook/testing-library';
import { expect } from '@storybook/jest';
import { handlers } from './mocks/handlers.mock';
@ -17,11 +17,16 @@ export default {
} as ComponentMeta<typeof RemoteSchema.Create>;
export const Playground: Story = () => {
const [formSuccess, toggle] = useReducer(s => !s, false);
const [showSuccessText, setShowSuccessText] = React.useState(false);
const onSuccess = () => {
setShowSuccessText(true);
};
return (
<>
<RemoteSchema.Create onSuccess={() => toggle()} />
<div>{formSuccess ? 'Form saved succesfully!' : null}</div>
<RemoteSchema.Create onSuccess={onSuccess} />;
<p data-testid="@onSuccess">
{showSuccessText ? 'Form saved successfully!' : null}
</p>
</>
);
};
@ -84,7 +89,12 @@ Playground.play = async ({ canvasElement }) => {
userEvent.click(await canvas.findByTestId('submit'));
expect(
await canvas.findByText('Form saved succesfully!')
).toBeInTheDocument();
waitFor(
async () => {
await expect(await canvas.findByTestId('@onSuccess')).toHaveTextContent(
'Form saved successfully!'
);
},
{ timeout: 5000 }
);
};

View File

@ -29,6 +29,7 @@ export const KeyValueHeader = (props: Props) => {
placeholder="Key..."
{...register(keyLabel)}
aria-label={keyLabel}
data-test={`header-test${rowIndex}-key`}
/>
<div className="flex rounded">
{typeSelect ? (
@ -44,6 +45,7 @@ export const KeyValueHeader = (props: Props) => {
<input
{...register(valueLabel)}
aria-label={valueLabel}
data-test={`header-test${rowIndex}-value`}
type="text"
className={`flex-1 min-w-0 h-10 shadow-sm block w-full px-3 py-2 rounded-r ${borderStyle} ${ringStyle}`}
placeholder="Value..."
@ -54,6 +56,7 @@ export const KeyValueHeader = (props: Props) => {
removeRow(rowIndex);
}}
className="cursor-pointer"
data-test={`delete-header-${rowIndex}`}
/>
</div>
</div>