Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(videos): video upload status and review front end tests #466

Closed
wants to merge 8 commits into from
66 changes: 66 additions & 0 deletions app/web/cypress/e2e/upload/review.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright [2023] [Privacypal Authors]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
describe("Video review page", () => {
const testApptId = 0;

beforeEach(() => {
cy.visit(`/upload/review/${testApptId}`);

// Stub the processing API to return a success message
cy.intercept(
{
method: "GET",
url: `/api/video?apptId=${testApptId}`,
},
{
statusCode: 200,
body: "",
headers: {
"content-type": "video/mp4",
},
},
).as("processedAPI");
});

it("should display a heading", () => {
cy.get("h1").contains("Review");
});

it("should display an accept button", () => {
cy.get("button[aria-label='Accept video']").should("exist");
});

it("should display a reject button", () => {
cy.get("button[aria-label='Reject video']").should("exist");
});

it("should display a video player", () => {
cy.get("video").should("exist");
});

it("should display a video player with the correct source", () => {
cy.get("source").should(
"have.attr",
"src",
`/api/video?apptId=${testApptId}`,
);
});

it("should redirect to not-found when the video is not found", () => {
cy.visit("/upload/review/not-a-real-video");
cy.get("h1").contains("Video not found");
});
});
137 changes: 137 additions & 0 deletions app/web/cypress/e2e/upload/status.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Copyright [2023] [Privacypal Authors]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
describe("Upload status page", () => {
const testVideoId = "e2e-test-video.mp4";

beforeEach(() => {
cy.visit(`/upload/status/${testVideoId}`);

// Stub the status API to return a processing message
// cy.intercept(
// {
// method: "GET",
// url: `/api/video/status?filename=${testVideoId}`,
// },
// {
// statusCode: 200,
// body: JSON.stringify({ message: "Processing", status: 200 }),
// headers: {
// "content-type": "application/json",
// },
// },
// ).as("statusAPIProcessing");

// Stub the status API to return a not found message
// cy.intercept(
// {
// method: "GET",
// url: `/api/video/status?filename=${testVideoId}`,
// },
// {
// statusCode: 404,
// body: JSON.stringify({ message: "File not found", status: 404 }),
// headers: {
// "content-type": "application/json",
// },
// },
// ).as("statusAPI404");
});

it("should display a heading", () => {
cy.get("h1").contains("Upload Status");
});

it("should display a status message", () => {
cy.get("p").contains("Processing");
});

it("should redirect to /upload/review/[id] when finished", () => {
// Stub the status API to return a completed message
cy.intercept(
{
method: "GET",
url: `/api/video/status?filename=${testVideoId}`,
},
{
statusCode: 200,
body: JSON.stringify({ message: "True", status: 200 }),
headers: {
"content-type": "application/json",
},
},
).as("statusAPIComplete");

// Capture the request
cy.wait("@statusAPIComplete", { requestTimeout: 5500 }).then(
(interception) => {
// Check the status code is 200
expect(interception.response.statusCode).to.equal(200);
// Check the status message is present
cy.url().should("include", `/upload/review/${testVideoId}`);
},
);
});

it("should display 'File not found' for nonexistant video", () => {
// Stub the status API to return a not found message
cy.intercept(
{
method: "GET",
url: `/api/video/status?filename=${testVideoId}`,
},
{
statusCode: 404,
body: JSON.stringify({ message: "False", status: 404 }),
headers: {
"content-type": "application/json",
},
},
).as("statusAPI404");

// Capture the request
cy.wait("@statusAPI404", { requestTimeout: 5500 }).then((interception) => {
// Check the status code is 404
expect(interception.response.statusCode).to.equal(404);
// Check the status message is present
cy.get("p").contains("File not found");
});
});

it("should display error for a failed video upload or server error", () => {
// Stub the status API to return a failed message
cy.intercept(
{
method: "GET",
url: `/api/video/status?filename=${testVideoId}`,
},
{
statusCode: 500,
body: JSON.stringify({ message: "False", status: 500 }),
headers: {
"content-type": "application/json",
},
},
).as("statusAPI500");

// Capture the request
cy.wait("@statusAPI500", { requestTimeout: 5500 }).then((interception) => {
// Check the status code is 500
expect(interception.response.statusCode).to.equal(500);
// Check the status message is present
cy.get("p").contains("Error processing file");
});
});
});
6 changes: 4 additions & 2 deletions app/web/src/app/upload/review/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ import VideoReview from "@components/VideoReview";
export default async function VideoReviewPage({
params,
}: {
params: { id: string };
params: { id: number };
}) {
const apptId = params.id;

return (
<main>
<VideoReview videoId={params.id} />
<VideoReview apptId={apptId} />
</main>
);
}
53 changes: 47 additions & 6 deletions app/web/src/components/VideoReview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@ import {
Card,
CardBody,
CardTitle,
Spinner,
} from "@patternfly/react-core";
import { CheckIcon, TimesIcon } from "@patternfly/react-icons";
import style from "@assets/style";
import { useEffect, useState } from "react";
import { notFound } from "next/navigation";
import Link from "next/link";

export const videoReviewStyle = {
...style,
Expand All @@ -39,10 +43,28 @@ export const videoReviewStyle = {
};

interface VideoReviewProps {
videoId: string;
apptId: number;
}

export const VideoReview = ({ videoId }: VideoReviewProps) => {
export const VideoReview = ({ apptId }: VideoReviewProps) => {
// const router = useRouter();
const [videoExists, setVideoExists] = useState(false);
const [loading, setLoading] = useState(true);

// check if the video exists (needed if the user navigated directly to this route)
useEffect(() => {
const checkIfVideoExists = async () => {
const res = await fetch(`/api/video?apptId=${apptId}`);
if (!res.ok) {
setVideoExists(false);
} else if (res.ok) {
setVideoExists(true);
}
setLoading(false);
};
checkIfVideoExists();
}, []);

const handleVideoRequest = async (action: string) => {
const successMsg =
action == "accept"
Expand All @@ -56,8 +78,7 @@ export const VideoReview = ({ videoId }: VideoReviewProps) => {
await fetch("/api/video/review", {
method: "POST",
body: JSON.stringify({
apptId: "1", // FIXME: pass apptId value
filename: videoId,
apptId, // FIXME: pass apptId value
action: action,
}),
})
Expand Down Expand Up @@ -86,21 +107,41 @@ export const VideoReview = ({ videoId }: VideoReviewProps) => {
}
};

if (loading) {
return <Spinner size="lg" aria-label="Video review is loading" />;
}

if (!videoExists) {
return (
<Card style={style.card}>
<CardTitle component="h1">Video not found</CardTitle>
<CardBody>
<Link href="/dashboard">Go back to your dashboard</Link>
</CardBody>
</Card>
);
}

return (
<Card style={style.card}>
<CardTitle component="h1">Review Your Submission</CardTitle>
<CardBody>
<video controls autoPlay={false} style={videoReviewStyle.videoPlayer}>
<source src={`/api/video/processed?file=${videoId}`} />
<source src={`/api/video?apptId=${apptId}`} />
</video>
<ActionList style={style.actionList}>
<ActionListItem>
<Button icon={<CheckIcon />} onClick={getHandler("accept")}>
<Button
aria-label="Accept video"
icon={<CheckIcon />}
onClick={getHandler("accept")}
>
This looks good
</Button>
</ActionListItem>
<ActionListItem>
<Button
aria-label="Reject video"
variant="danger"
icon={<TimesIcon />}
onClick={getHandler("reject")}
Expand Down
9 changes: 8 additions & 1 deletion app/web/src/components/upload/UploadStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ export const UploadStatus = ({ filename }: UploadStatusProps) => {
return;
}

if (response.status === 500) {
setStatus(true);
setStatusMessage("Error processing file.");
return;
}

if (response.ok) {
const json = await response.json();
if (json.data.message === VideoStatus.DONE) {
Expand All @@ -66,7 +72,8 @@ export const UploadStatus = ({ filename }: UploadStatusProps) => {

return (
<div>
<p>Status: {statusMessage}</p>
<h1>Upload Status</h1>
<p>{statusMessage}</p>
</div>
);
};
Loading