Implemented GET /images endpoint with params
Unit Tests with docker compose / unit-test (pull_request) Failing after 32s Details

This commit is contained in:
Sugui 2024-01-06 20:46:12 +01:00
parent e015500057
commit a878d6255c
5 changed files with 3140 additions and 11 deletions

View File

@ -1,6 +1,7 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import imageService from "../services/ImageService"; import imageService from "../services/ImageService";
import mongoose, { mongo } from "mongoose"; import mongoose, { mongo } from "mongoose";
import { Image } from "../models/ImageModel";
class ImageController { class ImageController {
async getImageById(req: Request, res: Response) { async getImageById(req: Request, res: Response) {
@ -20,14 +21,24 @@ class ImageController {
} }
} }
async getAllImages(_: Request, res: Response): Promise<void> { async getAllImages(req: Request, res: Response): Promise<void> {
try { try {
const images = await imageService.findAll(); if (req.query.status !== undefined && !["consumed", "unavailable", "available"].includes(req.query.status as string)) {
throw TypeError("if present, `status` should have the values `consumed`, `unavailable`, or `available`")
}
const limit = req.query.limit ? Number(req.query.limit) : undefined;
const status = req.query.status ? req.query.status as Image["status"] : undefined;
const images = await imageService.findAll(limit, status);
res.json({ images }); res.json({ images });
} catch (error) { } catch (error) {
if (error instanceof TypeError) {
res.status(400).json({ error });
} else {
res.status(500).json({ error: "Internal Server Error" }); res.status(500).json({ error: "Internal Server Error" });
} }
} }
}
async addImage(req: Request, res: Response): Promise<void> { async addImage(req: Request, res: Response): Promise<void> {
try { try {
@ -47,7 +58,7 @@ class ImageController {
res.status(400).json({ error: error.message }); res.status(400).json({ error: error.message });
} else { } else {
// Return 500 in other case // Return 500 in other case
res.status(500).json({ error: error }); res.status(500).json({ error });
} }
} }
} }

View File

@ -1,9 +1,9 @@
import mongoose, { Document } from "mongoose"; import mongoose, { Document } from "mongoose";
export interface Image extends Document { export interface Image extends Document {
url: String; url: string;
enum: "consumed" | "unavailable" | "available"; status: "consumed" | "unavailable" | "available";
tags?: String[]; tags?: string[];
} }
const ImageSchema = new mongoose.Schema( const ImageSchema = new mongoose.Schema(

View File

@ -5,10 +5,23 @@ class ImageService {
const image = await imageModel.findOne({ _id: id }); const image = await imageModel.findOne({ _id: id });
return image; return image;
} }
async findAll(): Promise<Image[]> {
const allImages = await imageModel.find(); async findAll(limit?: number, status?: Image["status"]): Promise<Image[]> {
const typeError = TypeError("if present, `limit` must be a non-negative integer");
const filter = status !== undefined ? { status } : {};
let query = imageModel.find(filter);
if (limit !== undefined) {
if (Number.isInteger(limit)) {
if (limit > 0) query = query.limit(limit);
else if (limit < 0) throw typeError;
} else {
throw typeError;
}
}
const allImages = await query;
return allImages; return allImages;
} }
async add(image: Image): Promise<Image> { async add(image: Image): Promise<Image> {
const newImage = await imageModel.create(image); const newImage = await imageModel.create(image);
return newImage; return newImage;

View File

@ -3,6 +3,8 @@ import request, { Response } from "supertest";
import app, { startApp } from "../src/app"; import app, { startApp } from "../src/app";
import imageService from "../src/services/ImageService"; import imageService from "../src/services/ImageService";
import populateDatabase from "./populateDatabase"; import populateDatabase from "./populateDatabase";
import { Image } from "../src/models/ImageModel";
import { faker } from "@faker-js/faker";
const imageServiceOriginal = imageService; const imageServiceOriginal = imageService;
@ -73,7 +75,7 @@ describe("GET /images works properly", async () => {
it("should return 500 for an error on the service", async () => { it("should return 500 for an error on the service", async () => {
mock.module("../src/services/ImageService", () => ({ mock.module("../src/services/ImageService", () => ({
default: { default: {
add: () => { findAll: () => {
throw new Error("This is an expected testing error"); throw new Error("This is an expected testing error");
}, },
}, },
@ -83,6 +85,68 @@ describe("GET /images works properly", async () => {
expect(res.status).toBe(500); expect(res.status).toBe(500);
}); });
it("should return 400 for an invalid status param value", async () => {
const res = await request(app).get("/images?status=foo");
expect(res.statusCode).toBe(400);
});
it("should return 200 for a request with valid status param", async () => {
const status = faker.helpers.arrayElement(["consumed", "available", "unavailable"])
const res = await request(app).get(`/images?status=${status}`);
expect(res.statusCode).toBe(200);
});
it("should only have the requested status in the images", async () => {
const status = faker.helpers.arrayElement(["consumed", "available", "unavailable"])
const res = await request(app).get(`/images?status=${status}`);
expect(res.body.images
.map((image: Image) => image.status)
.every((imageStatus: string) => imageStatus === status)).toBeTrue();
});
it("should return 400 for a floating point value in limit value", async () => {
const res = await request(app).get("/images?limit=2.4");
expect(res.statusCode).toBe(400);
});
it("should return 400 for a negative value in limit value", async () => {
const res = await request(app).get("/images?limit=-1");
expect(res.statusCode).toBe(400);
});
it("should return 400 for a NaN limit value", async () => {
const res = await request(app).get("/images?limit=foo");
expect(res.statusCode).toBe(400);
});
it("should return 200 for a request with valid limit param", async () => {
const limit = faker.number.int({ min: 5, max: 50 });
const res = await request(app).get(`/images?limit=${limit}`);
expect(res.statusCode).toBe(200);
});
it("should return 200 for a request with valid limit param", async () => {
const limit = faker.number.int({ min: 5, max: 50 });
const res = await request(app).get(`/images?limit=${limit}`);
expect(res.body.images.length).toBeLessThanOrEqual(limit);
});
it("should return 200 for a request with both valid params", async () => {
const res = await request(app).get("/images?limit=3&status=available");
expect(res.statusCode).toBe(200);
});
it("should return the same ids using limit=0 and using no limit parameter", async () => {
const resLimit0 = await request(app).get("/images?limit=0");
const resNoLimit = await request(app).get("/images");
const ids1 = resNoLimit.body.images.map((image: Image) => image._id);
const ids2 = resLimit0.body.images.map((image: Image) => image._id);
expect(ids1.length).toBe(ids2.length);
ids1.forEach((id: string) => expect(ids2).toContain(id));
})
}); });
describe("POST /images works properly", () => { describe("POST /images works properly", () => {
@ -178,7 +242,7 @@ describe("POST /images works properly", () => {
}); });
}); });
describe("/images/:id works properly", () => { describe("GET /images/:id works properly", () => {
it("should return 200 for existing ids", async () => { it("should return 200 for existing ids", async () => {
const list = await request(app).get("/images"); const list = await request(app).get("/images");
const id = list.body.images[0]._id; const id = list.body.images[0]._id;

3041
yarn.lock Normal file

File diff suppressed because it is too large Load Diff