v1.0.0 #28
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
});
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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;
|
43
src/index.ts
43
src/index.ts
|
@ -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();
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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();
|
|
@ -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",
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue