* Initial RESTful API

* RESTful API tests

* Scrapping error handling refactored

* Initial GraphQL API

* GraphQL API tests
This commit is contained in:
David
2021-03-28 23:17:47 +02:00
committed by GitHub
parent 7288e9ace7
commit 2938f780aa
15 changed files with 1873 additions and 671 deletions

View File

@@ -2,8 +2,11 @@ import { render, screen } from "../reactUtils";
import faker from "faker";
import CustomError from "../../components/CustomError";
const code = faker.random.number({ min: 400, max: 599 });
const text = faker.random.words();
it("loads the layout correctly", async () => {
render(<CustomError statusCode={404} />);
render(<CustomError statusCode={code} statusText={text} />);
expect(screen.getByRole("link", { name: /skip to content/i })).toBeEnabled();
expect(await screen.findByRole("img", { name: /logo/i })).toBeVisible();
@@ -12,13 +15,10 @@ it("loads the layout correctly", async () => {
expect(screen.getByText(/\xA9/)).toBeVisible();
});
it("renders a not found message on 404 code", () => {
render(<CustomError statusCode={404} />);
expect(screen.getByText(/this page could not be found/i)).toBeVisible();
});
it("renders the correct status code", () => {
it("renders the correct status code & text", () => {
const code = faker.random.number({ min: 400, max: 599 });
render(<CustomError statusCode={code} />);
render(<CustomError statusCode={code} statusText={text} />);
expect(screen.getByText(code)).toBeVisible();
expect(screen.getByText(text)).toBeVisible();
});

View File

@@ -173,12 +173,6 @@ describe("Page", () => {
expect(btnCopy).toBeEnabled();
});
it("renders error page on status code", async () => {
const code = faker.random.number({ min: 400, max: 599 });
render(<Page statusCode={code} />);
await waitFor(() => expect(screen.getByText(code)).toBeVisible());
});
it("shows alert correctly on error", async () => {
const errorMsg = faker.random.words();
render(<Page errorMsg={errorMsg} />);

View File

@@ -0,0 +1,179 @@
import { createTestClient } from "apollo-server-testing";
import { ApolloServer } from "apollo-server-micro";
import faker from "faker";
import { htmlRes, resolveFetchWith } from "../../commonUtils";
import { typeDefs, resolvers } from "../../../pages/api/graphql";
beforeEach(() => {
fetchMock.resetMocks();
});
const { query } = createTestClient(new ApolloServer({ typeDefs, resolvers }));
it("doesn't trigger fetch if neither target nor audio are specified", async () => {
const text = faker.random.words();
const { data } = await query({
query: `
query($text: String!) {
translation(query: $text) {
source {
text
}
}
}
`,
variables: { text }
});
expect(data).toMatchObject({ translation: { source: { text } } });
expect(fetch).not.toHaveBeenCalled();
});
it("returns translation triggering fetch", async () => {
const text = faker.random.words();
const translation = faker.random.words();
resolveFetchWith(htmlRes(translation));
const { data } = await query({
query: `
query($text: String!) {
translation(query: $text) {
target {
text
}
}
}
`,
variables: { text }
});
expect(data).toMatchObject({ translation: { target: { text: translation } } });
expect(fetch).toHaveBeenCalledTimes(1);
});
it("returns audio triggering fetch", async () => {
const lang = faker.random.locale();
const text = faker.random.words();
resolveFetchWith({ status: 200 });
const { data } = await query({
query: `
query($lang: String! $text: String!) {
audio(lang: $lang query: $text) {
lang
text
audio
}
}
`,
variables: { lang, text }
});
expect(data).toMatchObject({ audio: { lang, text, audio: expect.any(Array) } });
expect(fetch).toHaveBeenCalledTimes(1);
});
it("returns null on translation error", async () => {
const text = faker.random.words();
fetchMock.mockRejectOnce();
const { data } = await query({
query: `
query($text: String!) {
translation(query: $text) {
target {
text
}
}
}
`,
variables: { text }
});
expect(data).toMatchObject({ translation: { target: { text: null } } });
});
it("returns null on audio error", async () => {
const lang = faker.random.locale();
const text = faker.random.words();
fetchMock.mockRejectOnce();
const { data } = await query({
query: `
query($lang: String! $text: String!) {
audio(lang: $lang query: $text) {
audio
}
}
`,
variables: { lang, text }
});
expect(data).toMatchObject({ audio: { audio: null } });
});
it("keeps a default value for both source and target languages", async () => {
const text = faker.random.words();
fetchMock.mockRejectOnce();
const { data } = await query({
query: `
query($text: String!) {
translation(query: $text) {
source {
lang
}
target {
lang
}
}
}
`,
variables: { text }
});
expect(data).toMatchObject({ translation: { source: { lang: "auto" }, target: { lang: "en" } } });
});
it("throws error on empty query in translation", async () => {
const { errors } = await query({
query: `
query {
translation {
source {
lang
}
target {
lang
}
}
}
`
});
expect(errors).toBeTruthy();
});
it("throws error on empty lang or query in audio", async () => {
const lang = faker.random.locale();
const text = faker.random.words();
const { errors: queryErrors } = await query({
query: `
query($lang: String!) {
audio(lang: $lang) {
lang
text
}
}
`,
variables: { lang }
});
expect(queryErrors).toBeTruthy();
const { errors: langErrors } = await query({
query: `
query($text: String!) {
audio(query: $text) {
lang
text
}
}
`,
variables: { text }
});
expect(langErrors).toBeTruthy();
});

View File

@@ -0,0 +1,95 @@
import httpMocks from "node-mocks-http";
import faker from "faker";
import { htmlRes, resolveFetchWith } from "../../../commonUtils";
import handler from "../../../../pages/api/v1/[[...slug]]";
beforeEach(() => {
fetchMock.resetMocks();
});
const source = faker.random.locale();
const target = faker.random.locale();
const query = faker.random.words();
const slug = [source, target, query];
it("returns 404 on <3 params", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: [source, target] }
});
await handler(req, res);
expect(res.statusCode).toBe(404);
});
it("returns 404 on >3 params", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: [source, target, query, ""] }
});
await handler(req, res);
expect(res.statusCode).toBe(404);
});
it("returns 405 on forbidden method", async () => {
const { req, res } = httpMocks.createMocks<any, any>({
method: "POST",
query: { slug }
});
await handler(req, res);
expect(res.statusCode).toBe(405);
});
it("returns translation on scrapping resolve", async () => {
const translationRes = faker.random.words();
resolveFetchWith(htmlRes(translationRes));
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug }
});
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toStrictEqual({ translation: translationRes });
});
it("returns 500 on scrapping error", async () => {
fetchMock.mockRejectOnce();
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug }
});
await handler(req, res);
expect(res.statusCode).toBe(500);
expect(res._getJSONData()).toStrictEqual({ error: expect.any(String) });
});
it("returns audio on audio request", async () => {
resolveFetchWith({ status: 200 });
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: ["audio", target, query] }
});
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(res._getJSONData()).toStrictEqual({ audio: expect.any(Array) });
});
it("returns 500 on audio request error", async () => {
fetchMock.mockRejectOnce();
const { req, res } = httpMocks.createMocks<any, any>({
method: "GET",
query: { slug: ["audio", target, query] }
});
await handler(req, res);
expect(res.statusCode).toBe(500);
});

View File

@@ -19,11 +19,12 @@ describe("googleScrape", () => {
expect(await googleScrape(source, target, query)).toStrictEqual({ translationRes });
});
it("returns status code on request error", async () => {
it("returns correct message on request error", async () => {
const status = faker.random.number({ min: 400, max: 499 });
resolveFetchWith({ status });
expect(await googleScrape(source, target, query)).toStrictEqual({ statusCode: status });
const res = await googleScrape(source, target, query);
expect(res?.errorMsg).toMatch(/retrieving/);
});
it("returns correct message on network error", async () => {