v1.0.0 #28

Merged
bizcochito merged 147 commits from develop into main 2024-01-14 19:49:34 +00:00
9 changed files with 208 additions and 209 deletions
Showing only changes of commit 9cd61c101c - Show all commits

View File

@ -4,12 +4,10 @@
"type": "module", "type": "module",
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.11", "@types/jest": "^29.5.11",
"@types/mongodb-memory-server": "^2.3.0",
"@types/supertest": "^6.0.1", "@types/supertest": "^6.0.1",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/express-list-endpoints": "^6.0.3", "@types/express-list-endpoints": "^6.0.3",
"@types/jsonwebtoken": "^9.0.5", "@types/jsonwebtoken": "^9.0.5",
"@types/mongoose": "^5.11.97",
"bun-types": "latest", "bun-types": "latest",
"jest": "^29.7.0", "jest": "^29.7.0",
"mongodb-memory-server": "^9.1.3", "mongodb-memory-server": "^9.1.3",
@ -30,10 +28,5 @@
"express-list-endpoints": "^6.0.0", "express-list-endpoints": "^6.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.3" "mongoose": "^8.0.3"
},
"config": {
"mongodbMemoryServer": {
"debug": "1"
}
} }
} }

View File

@ -18,24 +18,24 @@ app.post("/images", authControler.authorize, imageController.addImage);
app.post("/login", authControler.login); app.post("/login", authControler.login);
export const startApp = async () => { export const startApp = async () => {
const port = process.env.PORT || 8080; const port = process.env.PORT || 8080;
const mongo_uri: string = process.env.MONGODB_URI || ""; const mongo_uri: string = process.env.MONGODB_URI || "";
const mongo_user = process.env.MONGODB_USER; const mongo_user = process.env.MONGODB_USER;
const mongo_pass = process.env.MONGODB_PASS; const mongo_pass = process.env.MONGODB_PASS;
try {
await mongoose.connect(mongo_uri, {
authSource: "admin",
user: mongo_user,
pass: mongo_pass,
});
app.listen(port, () =>
console.log(`Express server listening on port ${port}`)
);
} catch (error) {
console.error(error);
process.exit(1);
}
};
export default app; try {
await mongoose.connect(mongo_uri, {
authSource: "admin",
user: mongo_user,
pass: mongo_pass,
});
app.listen(port, () =>
console.log(`Express server listening on port ${port}`)
);
} catch (error) {
console.error(error);
process.exit(1);
}
};
export default app;

View File

@ -23,11 +23,9 @@ class ImageController {
} catch (error: any) { } catch (error: any) {
if (error instanceof mongo.MongoServerError && error.code === 11000) { if (error instanceof mongo.MongoServerError && error.code === 11000) {
// Should return 409 Conflict for existing urls // Should return 409 Conflict for existing urls
res res.status(409).json({
.status(409) error: `the image with URL ${error.keyValue.url} already exists`,
.json({ });
error: `the image with URL ${error.keyValue.url} already exists`,
});
} else if (error instanceof mongoose.Error.ValidationError) { } else if (error instanceof mongoose.Error.ValidationError) {
// Should return 400 Bad request for invalid requests // Should return 400 Bad request for invalid requests
res.status(400).json({ error: error.message }); res.status(400).json({ error: error.message });

View File

@ -3,10 +3,6 @@ import { startApp } from "./app";
await startApp(); await startApp();
try {
try{ await populateDatabase();
await populateDatabase(); } catch {}
}
catch {
}

View File

@ -5,16 +5,19 @@ export interface Auth extends Document {
secret: String; secret: String;
} }
const AuthSchema = new mongoose.Schema({ const AuthSchema = new mongoose.Schema(
app: { {
type: String, app: {
required: true, type: String,
index: true, required: true,
index: true,
},
secret: {
type: String,
required: true,
},
}, },
secret: { { collection: "authorizations" }
type: String, );
required: true,
},
}, { collection: "authorizations" });
export default mongoose.model("authorizations", AuthSchema); export default mongoose.model("authorizations", AuthSchema);

View File

@ -6,24 +6,27 @@ export interface Image extends Document {
tags?: String[]; tags?: String[];
} }
const ImageSchema = new mongoose.Schema({ const ImageSchema = new mongoose.Schema(
url: { {
type: String, url: {
required: true, type: String,
index: true, required: true,
unique: true index: true,
}, unique: true,
status: { },
type: String, status: {
enum: { type: String,
values: ["consumed", "unavailable", "available"], enum: {
values: ["consumed", "unavailable", "available"],
},
required: true,
index: true,
},
tags: {
type: [String],
}, },
required: true,
index: true,
}, },
tags: { { collection: "images" }
type: [String], );
},
}, { collection: "images" });
export default mongoose.model<Image>("images", ImageSchema); export default mongoose.model<Image>("images", ImageSchema);

View File

@ -1,4 +1,12 @@
import { afterAll, afterEach, beforeAll, describe, expect, it, mock } from "bun:test"; import {
afterAll,
afterEach,
beforeAll,
describe,
expect,
it,
mock,
} from "bun:test";
import request, { Response } from "supertest"; 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";
@ -10,164 +18,162 @@ const imageServiceOriginal = imageService;
let token: string; let token: string;
beforeAll(async () => { beforeAll(async () => {
if (!process.env.DEDICATED_MONGODB_SERVER) //if (!process.env.DEDICATED_MONGODB_SERVER) await memoryServer.start();
await memoryServer.start(); await startApp();
await startApp(); await populateDatabase();
await populateDatabase();
const tok = await request(app) const tok = await request(app)
.post("/login") .post("/login")
.send({ app: "tester", secret: "test" }); .send({ app: "tester", secret: "test" });
token = tok.body.token; token = tok.body.token;
}); });
afterAll(async () => { /* afterAll(async () => {
if (!process.env.DEDICATED_MONGODB_SERVER) if (!process.env.DEDICATED_MONGODB_SERVER) await memoryServer.stop();
await memoryServer.stop(); });
}) */
afterEach(() => { afterEach(() => {
mock.restore(); mock.restore();
mock.module("../src/services/ImageService", () => ({ mock.module("../src/services/ImageService", () => ({
default: imageServiceOriginal, default: imageServiceOriginal,
})); }));
}); });
describe("/login works as instended", async () => { describe("/login works as instended", async () => {
let correctRespose: Response; let correctRespose: Response;
beforeAll(async () => { beforeAll(async () => {
correctRespose = await request(app) correctRespose = await request(app)
.post("/login") .post("/login")
.send({ app: "tester", secret: "test" }); .send({ app: "tester", secret: "test" });
}); });
it("should return 200 for correct login", async () => { it("should return 200 for correct login", async () => {
expect(correctRespose.status).toBe(200); expect(correctRespose.status).toBe(200);
}); });
it("should contain a token", () => { it("should contain a token", () => {
expect(correctRespose.body).toHaveProperty("token"); expect(correctRespose.body).toHaveProperty("token");
}); });
it("should return 403 for invalid credentials", async () => { it("should return 403 for invalid credentials", async () => {
const res = await request(app).post("/login").send({}); const res = await request(app).post("/login").send({});
expect(res.status).toBe(403); expect(res.status).toBe(403);
}); });
}); });
describe("GET / shows all of the endpoints", async () => { describe("GET / shows all of the endpoints", async () => {
const res = await request(app).get("/"); const res = await request(app).get("/");
it("should be", async () => { it("should be", async () => {
expect(res.body).toHaveProperty("endpoints"); expect(res.body).toHaveProperty("endpoints");
}); });
it("should be an array", () => { it("should be an array", () => {
expect(Array.isArray(res.body.endpoints)).toBeTrue(); expect(Array.isArray(res.body.endpoints)).toBeTrue();
}); });
}); });
describe("GET /images works properly", async () => { describe("GET /images works properly", async () => {
const res = await request(app).get("/images"); const res = await request(app).get("/images");
it("should be an array", () => { it("should be an array", () => {
expect(Array.isArray(res.body.images)).toBeTrue(); expect(Array.isArray(res.body.images)).toBeTrue();
}); });
it("should return a 200", async () => { it("should return a 200", async () => {
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
}); });
}); });
describe("POST /images works properly", () => { describe("POST /images works properly", () => {
it("should return 401 for unauthenticated requests", async () => { it("should return 401 for unauthenticated requests", async () => {
const res = await request(app) const res = await request(app)
.post("/images") .post("/images")
.send({ .send({
url: "https://test.url.com/0", url: "https://test.url.com/0",
status: "available", status: "available",
tags: ["2girls", "touhou"], tags: ["2girls", "touhou"],
}); });
expect(res.status).toBe(401); expect(res.status).toBe(401);
}); });
it("should return 403 for invalid tokens", async () => { it("should return 403 for invalid tokens", async () => {
const res = await request(app) const res = await request(app)
.post("/images") .post("/images")
.set("authorization", `Bearer token`) .set("authorization", `Bearer token`)
.send({ .send({
url: "https://test.url.com/0", url: "https://test.url.com/0",
status: "available", status: "available",
tags: ["2girls", "touhou"], tags: ["2girls", "touhou"],
}); });
expect(res.status).toBe(403); expect(res.status).toBe(403);
}); });
it("should return 201 for new image", async () => { it("should return 201 for new image", async () => {
const res = await request(app) const res = await request(app)
.post("/images") .post("/images")
.set("authorization", `Bearer ${token}`) .set("authorization", `Bearer ${token}`)
.send({ .send({
url: "https://test.url.com/1", url: "https://test.url.com/1",
status: "available", status: "available",
tags: ["2girls", "touhou"], tags: ["2girls", "touhou"],
}); });
expect(res.status).toBe(201); expect(res.status).toBe(201);
}); });
it("should return 409 for a repeated images", async () => { it("should return 409 for a repeated images", async () => {
await request(app) await request(app)
.post("/images") .post("/images")
.set("authorization", `Bearer ${token}`) .set("authorization", `Bearer ${token}`)
.send({ .send({
url: "https://test.url.com/2", url: "https://test.url.com/2",
status: "available", status: "available",
tags: ["2girls", "touhou"], tags: ["2girls", "touhou"],
}); });
const res = await request(app) const res = await request(app)
.post("/images") .post("/images")
.set("authorization", `Bearer ${token}`) .set("authorization", `Bearer ${token}`)
.send({ .send({
url: "https://test.url.com/2", url: "https://test.url.com/2",
status: "available", status: "available",
tags: ["2girls", "touhou"], tags: ["2girls", "touhou"],
}); });
expect(res.status).toBe(409); expect(res.status).toBe(409);
}); });
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: () => { add: () => {
throw new Error("This is an expected testing error"); throw new Error("This is an expected testing error");
}, },
}, },
})); }));
const res = await request(app) const res = await request(app)
.post("/images") .post("/images")
.set("authorization", `Bearer ${token}`) .set("authorization", `Bearer ${token}`)
.send({ .send({
url: "https://test.url.com/3", url: "https://test.url.com/3",
status: "available", status: "available",
tags: ["2girls", "touhou"], tags: ["2girls", "touhou"],
}); });
expect(res.status).toBe(500); expect(res.status).toBe(500);
}); });
it("should return 400 for malformed requests", async () => { it("should return 400 for malformed requests", async () => {
mock.restore(); mock.restore();
const res = await request(app) const res = await request(app)
.post("/images") .post("/images")
.set("authorization", `Bearer ${token}`) .set("authorization", `Bearer ${token}`)
.send({ .send({
url: "https://test.url.com/4", url: "https://test.url.com/4",
status: "wrong", status: "wrong",
tags: ["2girls", "touhou"], tags: ["2girls", "touhou"],
}); });
expect(res.status).toBe(400); expect(res.status).toBe(400);
}); });
}); });

View File

@ -16,4 +16,4 @@ class MemoryServer {
} }
} }
export default new MemoryServer(); export default new MemoryServer();

View File

@ -2,14 +2,14 @@ import authModel from "../src/models/AuthModel";
import imageModel from "../src/models/ImageModel"; import imageModel from "../src/models/ImageModel";
export default async function () { export default async function () {
await imageModel.create({ await imageModel.create({
url: "https://example.com", url: "https://example.com",
status: "consumed", status: "consumed",
tags: ["2girls", "sleeping"], tags: ["2girls", "sleeping"],
}); });
await authModel.create({ await authModel.create({
app: "tester", app: "tester",
secret: "test", secret: "test",
}); });
} }