diff --git a/e2e/.gitignore b/e2e/.gitignore index 93ea0cc..17c11d5 100644 --- a/e2e/.gitignore +++ b/e2e/.gitignore @@ -27,4 +27,8 @@ yarn-error.log* # Cypress cypress.env.json -cypress/videos/**/*.mp4 \ No newline at end of file +cypress/videos/**/*.mp4 + +cypress/screenshots/**/*.png + +cypress/fixtures/upload-response.json \ No newline at end of file diff --git a/e2e/cypress/fixtures/example.json b/e2e/cypress/fixtures/example.json deleted file mode 100644 index 02e4254..0000000 --- a/e2e/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/e2e/cypress/fixtures/profile.json b/e2e/cypress/fixtures/profile.json deleted file mode 100644 index b6c355c..0000000 --- a/e2e/cypress/fixtures/profile.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": 8739, - "name": "Jane", - "email": "jane@example.com" -} \ No newline at end of file diff --git a/e2e/cypress/fixtures/users.json b/e2e/cypress/fixtures/users.json deleted file mode 100644 index 79b699a..0000000 --- a/e2e/cypress/fixtures/users.json +++ /dev/null @@ -1,232 +0,0 @@ -[ - { - "id": 1, - "name": "Leanne Graham", - "username": "Bret", - "email": "Sincere@april.biz", - "address": { - "street": "Kulas Light", - "suite": "Apt. 556", - "city": "Gwenborough", - "zipcode": "92998-3874", - "geo": { - "lat": "-37.3159", - "lng": "81.1496" - } - }, - "phone": "1-770-736-8031 x56442", - "website": "hildegard.org", - "company": { - "name": "Romaguera-Crona", - "catchPhrase": "Multi-layered client-server neural-net", - "bs": "harness real-time e-markets" - } - }, - { - "id": 2, - "name": "Ervin Howell", - "username": "Antonette", - "email": "Shanna@melissa.tv", - "address": { - "street": "Victor Plains", - "suite": "Suite 879", - "city": "Wisokyburgh", - "zipcode": "90566-7771", - "geo": { - "lat": "-43.9509", - "lng": "-34.4618" - } - }, - "phone": "010-692-6593 x09125", - "website": "anastasia.net", - "company": { - "name": "Deckow-Crist", - "catchPhrase": "Proactive didactic contingency", - "bs": "synergize scalable supply-chains" - } - }, - { - "id": 3, - "name": "Clementine Bauch", - "username": "Samantha", - "email": "Nathan@yesenia.net", - "address": { - "street": "Douglas Extension", - "suite": "Suite 847", - "city": "McKenziehaven", - "zipcode": "59590-4157", - "geo": { - "lat": "-68.6102", - "lng": "-47.0653" - } - }, - "phone": "1-463-123-4447", - "website": "ramiro.info", - "company": { - "name": "Romaguera-Jacobson", - "catchPhrase": "Face to face bifurcated interface", - "bs": "e-enable strategic applications" - } - }, - { - "id": 4, - "name": "Patricia Lebsack", - "username": "Karianne", - "email": "Julianne.OConner@kory.org", - "address": { - "street": "Hoeger Mall", - "suite": "Apt. 692", - "city": "South Elvis", - "zipcode": "53919-4257", - "geo": { - "lat": "29.4572", - "lng": "-164.2990" - } - }, - "phone": "493-170-9623 x156", - "website": "kale.biz", - "company": { - "name": "Robel-Corkery", - "catchPhrase": "Multi-tiered zero tolerance productivity", - "bs": "transition cutting-edge web services" - } - }, - { - "id": 5, - "name": "Chelsey Dietrich", - "username": "Kamren", - "email": "Lucio_Hettinger@annie.ca", - "address": { - "street": "Skiles Walks", - "suite": "Suite 351", - "city": "Roscoeview", - "zipcode": "33263", - "geo": { - "lat": "-31.8129", - "lng": "62.5342" - } - }, - "phone": "(254)954-1289", - "website": "demarco.info", - "company": { - "name": "Keebler LLC", - "catchPhrase": "User-centric fault-tolerant solution", - "bs": "revolutionize end-to-end systems" - } - }, - { - "id": 6, - "name": "Mrs. Dennis Schulist", - "username": "Leopoldo_Corkery", - "email": "Karley_Dach@jasper.info", - "address": { - "street": "Norberto Crossing", - "suite": "Apt. 950", - "city": "South Christy", - "zipcode": "23505-1337", - "geo": { - "lat": "-71.4197", - "lng": "71.7478" - } - }, - "phone": "1-477-935-8478 x6430", - "website": "ola.org", - "company": { - "name": "Considine-Lockman", - "catchPhrase": "Synchronised bottom-line interface", - "bs": "e-enable innovative applications" - } - }, - { - "id": 7, - "name": "Kurtis Weissnat", - "username": "Elwyn.Skiles", - "email": "Telly.Hoeger@billy.biz", - "address": { - "street": "Rex Trail", - "suite": "Suite 280", - "city": "Howemouth", - "zipcode": "58804-1099", - "geo": { - "lat": "24.8918", - "lng": "21.8984" - } - }, - "phone": "210.067.6132", - "website": "elvis.io", - "company": { - "name": "Johns Group", - "catchPhrase": "Configurable multimedia task-force", - "bs": "generate enterprise e-tailers" - } - }, - { - "id": 8, - "name": "Nicholas Runolfsdottir V", - "username": "Maxime_Nienow", - "email": "Sherwood@rosamond.me", - "address": { - "street": "Ellsworth Summit", - "suite": "Suite 729", - "city": "Aliyaview", - "zipcode": "45169", - "geo": { - "lat": "-14.3990", - "lng": "-120.7677" - } - }, - "phone": "586.493.6943 x140", - "website": "jacynthe.com", - "company": { - "name": "Abernathy Group", - "catchPhrase": "Implemented secondary concept", - "bs": "e-enable extensible e-tailers" - } - }, - { - "id": 9, - "name": "Glenna Reichert", - "username": "Delphine", - "email": "Chaim_McDermott@dana.io", - "address": { - "street": "Dayna Park", - "suite": "Suite 449", - "city": "Bartholomebury", - "zipcode": "76495-3109", - "geo": { - "lat": "24.6463", - "lng": "-168.8889" - } - }, - "phone": "(775)976-6794 x41206", - "website": "conrad.com", - "company": { - "name": "Yost and Sons", - "catchPhrase": "Switchable contextually-based project", - "bs": "aggregate real-time technologies" - } - }, - { - "id": 10, - "name": "Clementina DuBuque", - "username": "Moriah.Stanton", - "email": "Rey.Padberg@karina.biz", - "address": { - "street": "Kattie Turnpike", - "suite": "Suite 198", - "city": "Lebsackbury", - "zipcode": "31428-2261", - "geo": { - "lat": "-38.2386", - "lng": "57.2232" - } - }, - "phone": "024-648-3804", - "website": "ambrose.net", - "company": { - "name": "Hoeger LLC", - "catchPhrase": "Centralized empowering task-force", - "bs": "target end-to-end models" - } - } -] \ No newline at end of file diff --git a/e2e/cypress/integration/01-upload.spec.js b/e2e/cypress/integration/01-upload.spec.js new file mode 100644 index 0000000..d749b02 --- /dev/null +++ b/e2e/cypress/integration/01-upload.spec.js @@ -0,0 +1,40 @@ +import { RESPONSE_FILE_PATH } from '../utils/constant'; +import { aliasMutation } from '../utils/graphql-test-utils'; + +describe('File upload', () => { + it('Video.upload method', () => { + cy.visit('/'); + const filepath = 'video/video-sample.mp4'; + cy.get('input[type="file"]').attachFile(filepath); + cy.get('[data-cy=submit]').click(); + + cy.intercept('/graphql', (req) => { + aliasMutation(req, 'createVideoObject'); + }); + cy.intercept('/graphql', (req) => { + aliasMutation(req, 'createVodAsset'); + }); + cy.intercept('PUT', '*PutObject').as('s3Put'); + + cy.wait(['@createVideoObject', '@createVodAsset', '@s3Put'], { + responseTimeout: 60000, + }).spread((createVideoObject, createVodAsset, s3Put) => { + expect( + createVideoObject.response.body.data.createVideoObject, + ).to.not.equal(null); + expect(createVodAsset.response.body.data.createVodAsset).to.not.equal( + null, + ); + expect(s3Put.response.statusCode).to.equal(200); + cy.wait(1000); + cy.get('[data-cy=pre-upload]') + .invoke('html') + .then((response) => { + cy.wrap(response).as('response'); + }); + cy.get('@response').then((response) => { + cy.writeFile(RESPONSE_FILE_PATH, response); + }); + }); + }); +}); diff --git a/e2e/cypress/integration/02-metadata.spec.js b/e2e/cypress/integration/02-metadata.spec.js new file mode 100644 index 0000000..a132053 --- /dev/null +++ b/e2e/cypress/integration/02-metadata.spec.js @@ -0,0 +1,37 @@ +import { RESPONSE_FILE_PATH } from '../utils/constant'; +import { aliasMutation } from '../utils/graphql-test-utils'; + +describe('Fetching metadata', () => { + beforeEach(() => { + cy.intercept('/graphql', (req) => { + aliasMutation(req, 'getVodAsset'); + }); + }); + + it('Video.metadata method should return null when id does not exist', () => { + cy.visit('/'); + cy.readFile(RESPONSE_FILE_PATH).then(({ data }) => { + cy.get('input[type="text"]').type(data.key); + cy.get('[data-cy=fetch]').click(); + }); + cy.wait('@getVodAsset').then(({ response }) => { + const { getVodAsset } = response.body.data; + expect(getVodAsset).to.equal(null); + }); + }); + + it('Video.metadata method should VodAsset metadata', () => { + let id = null; + cy.visit('/'); + cy.readFile(RESPONSE_FILE_PATH).then(({ data }) => { + id = data.key.split('.')[0]; + cy.get('input[type="text"]').type(id); + cy.get('[data-cy=fetch]').click(); + }); + cy.wait('@getVodAsset').then(({ response }) => { + const { getVodAsset } = response.body.data; + expect(getVodAsset).to.not.equal(null); + expect(expect(getVodAsset.id).to.equal(id)); + }); + }); +}); diff --git a/e2e/cypress/integration/upload/upload.spec.js b/e2e/cypress/integration/upload/upload.spec.js deleted file mode 100644 index 0aa8b29..0000000 --- a/e2e/cypress/integration/upload/upload.spec.js +++ /dev/null @@ -1,56 +0,0 @@ -const hasOperationName = (req, operationName) => { - const { body } = req; - return body.hasOwnProperty('query') && body.query.includes(operationName); -}; - -const aliasMutation = (req, operationName) => { - if (hasOperationName(req, operationName)) { - req.alias = operationName; - } -}; - -const responseAssertion = (interception, target) => { - expect(interception.response.body.data[target]).to.not.equal(null); -}; - -describe('File upload', () => { - before(() => { - cy.signIn(); - }); - - after(() => { - cy.clearLocalStorageSnapshot(); - cy.clearLocalStorage(); - }); - - beforeEach(() => { - cy.restoreLocalStorage(); - }); - - afterEach(() => { - cy.saveLocalStorage(); - }); - - it('Video.upload method', () => { - cy.visit('/'); - const filepath = 'video/video-sample.mp4'; - cy.get('input[type="file"]').attachFile(filepath); - cy.get('[data-cy=submit]').click(); - - cy.intercept('/graphql', (req) => { - aliasMutation(req, 'createVideoObject'); - }); - cy.intercept('/graphql', (req) => { - aliasMutation(req, 'createVodAsset'); - }); - cy.intercept('PUT', '*PutObject').as('s3Put'); - - cy.wait(['@createVideoObject', '@createVodAsset', '@s3Put']).spread( - (createVideoObject, createVodAsset, s3Put) => { - responseAssertion(createVideoObject, 'createVideoObject'); - responseAssertion(createVodAsset, 'createVodAsset'); - expect(s3Put.response.statusCode).to.equal(200); - }, - ); - }); -}); diff --git a/e2e/cypress/support/commands.js b/e2e/cypress/support/commands.js index 4229534..083706c 100644 --- a/e2e/cypress/support/commands.js +++ b/e2e/cypress/support/commands.js @@ -26,7 +26,7 @@ console.log(awsconfig); Auth.configure(awsconfig); Cypress.Commands.add('signIn', () => { - cy.then({ timeout: 6000 }, () => Auth.signIn(username, password)).then( + cy.then({ timeout: 60000 }, () => Auth.signIn(username, password)).then( (cognitoUser) => { const idToken = cognitoUser.signInUserSession.idToken.jwtToken; const accessToken = cognitoUser.signInUserSession.accessToken.jwtToken; diff --git a/e2e/cypress/support/index.js b/e2e/cypress/support/index.js index d68db96..584c1ee 100644 --- a/e2e/cypress/support/index.js +++ b/e2e/cypress/support/index.js @@ -14,7 +14,24 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import './commands'; + +before(() => { + cy.signIn(); +}); + +after(() => { + cy.clearLocalStorageSnapshot(); + cy.clearLocalStorage(); +}); + +beforeEach(() => { + cy.restoreLocalStorage(); +}); + +afterEach(() => { + cy.saveLocalStorage(); +}); // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/e2e/cypress/utils/constant.js b/e2e/cypress/utils/constant.js new file mode 100644 index 0000000..d48f38e --- /dev/null +++ b/e2e/cypress/utils/constant.js @@ -0,0 +1 @@ +export const RESPONSE_FILE_PATH = 'cypress/fixtures/upload-response.json'; diff --git a/e2e/cypress/utils/graphql-test-utils.js b/e2e/cypress/utils/graphql-test-utils.js new file mode 100644 index 0000000..3e72da6 --- /dev/null +++ b/e2e/cypress/utils/graphql-test-utils.js @@ -0,0 +1,21 @@ +export const hasOperationName = (req, operationName) => { + const { body } = req; + return body.hasOwnProperty('query') && body.query.includes(operationName); +}; + +export const aliasMutation = (req, operationName) => { + if (hasOperationName(req, operationName)) { + req.alias = operationName; + } +}; + +export const responseAssertionNotNull = ({ interception, target, id }) => { + const data = interception.response.body.data[target]; + expect(data).to.not.equal(null); + if (!id) return; + expect(data.id).to.equal(id); +}; + +export const responseAssertionNull = (interception, target) => { + expect(interception.response.body.data[target]).to.equal(null); +}; diff --git a/e2e/src/components/atoms/button/index.tsx b/e2e/src/components/atoms/button/index.tsx index 79da66c..92a90e0 100644 --- a/e2e/src/components/atoms/button/index.tsx +++ b/e2e/src/components/atoms/button/index.tsx @@ -28,10 +28,12 @@ type Props = { children: ReactNode; className: string; disabled: boolean; + dataCy: string; }; const Button = (props: Props) => { - const { type, onClick, children, theme, size, className, disabled } = props; + const { type, dataCy, onClick, children, theme, size, className, disabled } = + props; const classProps: string = classnames( 'button', theme, @@ -42,7 +44,7 @@ const Button = (props: Props) => { return ( )} diff --git a/e2e/src/components/organisms/metadata/index.tsx b/e2e/src/components/organisms/metadata/index.tsx new file mode 100644 index 0000000..e49d9ce --- /dev/null +++ b/e2e/src/components/organisms/metadata/index.tsx @@ -0,0 +1,49 @@ +import { + MouseEventHandler, + MutableRefObject, + ReactNode, + useRef, + useState, +} from 'react'; +import Block from 'components/molecules/block'; +import Input from 'components/atoms/input'; +import Video from 'amplify-video.js/dist'; + +interface Props { + children?: ReactNode; + className?: string; + title: string; + button?: string; + onClick?: MouseEventHandler; + callback?: any; +} + +const Metadata = (props: Props) => { + const inputRef = useRef() as MutableRefObject; + const [loading, setLoading] = useState(false); + + const handleSubmit = async () => { + if (!inputRef.current.value) return; + setLoading(true); + const vodAssetVideoId = inputRef.current.value; + const { data } = await Video.metadata(vodAssetVideoId); + props.callback(data); + setLoading(false); + }; + + return ( + await handleSubmit()} + > + {props.children} + + + ); +}; + +export default Metadata; diff --git a/e2e/src/components/organisms/upload/index.tsx b/e2e/src/components/organisms/upload/index.tsx index 695fbbe..3f645d9 100644 --- a/e2e/src/components/organisms/upload/index.tsx +++ b/e2e/src/components/organisms/upload/index.tsx @@ -48,6 +48,7 @@ const Upload = (props: Props) => { return ( { onClick={async () => await handleSubmit()} > {props.children} - + ); }; diff --git a/e2e/src/components/templates/grid/index.tsx b/e2e/src/components/templates/grid/index.tsx index 204e99f..20261e1 100644 --- a/e2e/src/components/templates/grid/index.tsx +++ b/e2e/src/components/templates/grid/index.tsx @@ -1,28 +1,44 @@ import { AmplifySignOut } from '@aws-amplify/ui-react'; import classNames from 'classnames'; import Upload from 'components/organisms/upload'; +import Metadata from 'components/organisms/metadata'; import { useState } from 'react'; import './style.scss'; const Grid = () => { - const [result, setResult] = useState(null); + const [result, setResult] = useState(null); return (
setResult(data)} - className={classNames('item-left')} + callback={(data: any) => setResult({ upload: data, ...result })} >

Testing Upload method
Video.upload();

-
{result ? JSON.stringify(result) : null}
+
+          {result && result.upload ? JSON.stringify(result.upload, null, 2) : null}
+        
+ setResult({ metadata: data, ...result })} + > +

+ Testing Metadata method +
+ Video.metadata(); +

+
+          {result && result.metadata ? JSON.stringify(result.metadata, null, 2) : null}
+        
+
); };