v1.0.0 #28

Merged
bizcochito merged 147 commits from develop into main 2024-01-14 19:49:34 +00:00
12 changed files with 230 additions and 187 deletions
Showing only changes of commit 1b45481d27 - Show all commits

View File

@ -12,7 +12,6 @@ services:
MONGO_INITDB_DATABASE: bot
volumes:
- mongodb_data:/data/db
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
bot-api:
image: oven/bun:1
@ -28,6 +27,8 @@ services:
MONGODB_USER: "root"
MONGODB_PASS: "password"
JWTSECRET: "cooljwtsecret"
# DEDICATED_MONGODB_SERVER is for integration testing, for disabling memory server in tests
DEDICATED_MONGODB_SERVER: "true"
volumes:
- ./:/usr/src/app:ro

View File

@ -1,30 +0,0 @@
db.createUser({
user: "root",
pwd: "password",
roles: [
{
role: "readWrite",
db: "admin",
},
{
role: "readWrite",
db: "bot",
},
],
});
db = new Mongo().getDB("bot");
db.images.createIndex({ status: 1 });
db.images.createIndex({ url: 1 }, { unique: true });
db.images.insert({
url: "https://example.com",
status: "consumed",
tags: ["2girls", "sleeping"],
});
db.authorizations.createIndex({ app: 1 });
db.authorizations.insert({
app: "tester",
secret: "test",
});

View File

@ -4,9 +4,11 @@
"type": "module",
"devDependencies": {
"@types/jest": "^29.5.11",
"@types/mongodb-memory-server": "^2.3.0",
"@types/supertest": "^6.0.1",
"bun-types": "latest",
"jest": "^29.7.0",
"mongodb-memory-server": "^9.1.3",
"supertest": "^6.3.3",
"ts-jest": "^29.1.1"
},

41
src/app.ts Normal file
View File

@ -0,0 +1,41 @@
import express from "express";
import listEndpoints from "express-list-endpoints";
import imageController from "./controllers/ImageController";
import authControler from "./controllers/AuthControler";
import mongoose from "mongoose";
export const app = express();
app.use(express.json());
app.get("/", (_, res) => {
const endpoints = listEndpoints(app);
res.json({ endpoints });
});
app.get("/images", imageController.getAllImages);
app.post("/images", authControler.authorize, imageController.addImage);
app.post("/login", authControler.login);
export const startApp = async () => {
const port = process.env.PORT || 8080;
const mongo_uri: string = process.env.MONGODB_URI || "";
const mongo_user = process.env.MONGODB_USER;
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;

View File

@ -1,42 +1,3 @@
import express from "express";
import mongoose from "mongoose";
import listEndpoints from "express-list-endpoints";
import imageController from "./controllers/ImageController";
import authControler from "./controllers/AuthControler";
import { startApp } from "./app";
export const app = express();
app.use(express.json());
app.get("/", (_, res) => {
const endpoints = listEndpoints(app);
res.json({ endpoints });
});
app.get("/images", imageController.getAllImages);
app.post("/images", authControler.authorize, imageController.addImage);
app.post("/login", authControler.login);
const start = async () => {
// Set the default port to 8080, or use the PORT environment variable
const port = process.env.PORT || 8080;
const mongo_uri: string = process.env.MONGODB_URI || "";
const mongo_user = process.env.MONGODB_USER;
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);
}
};
start();
startApp();

View File

@ -9,11 +9,12 @@ const AuthSchema = new mongoose.Schema({
app: {
type: String,
required: true,
index: true,
},
secret: {
type: String,
required: true,
},
});
}, { collection: "authorizations" });
export default mongoose.model("authorizations", AuthSchema);

View File

@ -10,6 +10,8 @@ const ImageSchema = new mongoose.Schema({
url: {
type: String,
required: true,
index: true,
unique: true
},
status: {
type: String,
@ -17,10 +19,11 @@ const ImageSchema = new mongoose.Schema({
values: ["consumed", "unavailable", "available"],
},
required: true,
index: true,
},
tags: {
type: [String],
},
});
}, { collection: "images" });
export default mongoose.model<Image>("images", ImageSchema);

View File

@ -1,8 +1,8 @@
import AuthModel, { Auth } from "../models/AuthModel";
class AuthService {
async find(app: String, secret: String): Promise<Auth | null> {
const auth = await AuthModel.findOne({ app: app, secret: secret }).exec();
async find(app: string, secret: string): Promise<Auth | null> {
const auth = await AuthModel.findOne({ app: app, secret: secret });
return auth;
}
}

View File

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

View File

@ -1,12 +1,25 @@
import { describe, expect, it, mock } from "bun:test";
import { afterAll, beforeAll, describe, expect, it } from "bun:test";
import request from "supertest";
import { app } from "../src";
import app, { startApp } from "../src/app";
import memoryServer from "./memoryServer";
import populateDatabase from "./populateDatabase";
beforeAll(async () => {
if (!process.env.DEDICATED_MONGODB_SERVER)
await memoryServer.start();
await startApp();
await populateDatabase();
});
afterAll(async () => {
if (!process.env.DEDICATED_MONGODB_SERVER)
await memoryServer.stop();
});
describe("/login", async () => {
const correctRespose = await request(app).post("/login").send({
app: "tester",
secret: "test",
});
const correctRespose = await request(app)
.post("/login")
.send({ app: "tester", secret: "test" });
it("should return 200 for correct login", () => {
expect(correctRespose.status).toBe(200);
});

19
tests/memoryServer.ts Normal file
View File

@ -0,0 +1,19 @@
import { MongoMemoryServer } from "mongodb-memory-server";
class MemoryServer {
mongod: MongoMemoryServer | undefined;
async start() {
this.mongod = await MongoMemoryServer.create();
const uri = this.mongod.getUri("bot");
process.env.MONGODB_URI = uri;
}
async stop() {
if (this.mongod) {
await this.mongod.stop();
}
}
}
export default new MemoryServer();

15
tests/populateDatabase.ts Normal file
View File

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