This commit is contained in:
BillieFaiqul 2025-05-23 09:19:35 +07:00
parent 1093f01526
commit 61982c6275
79 changed files with 3866 additions and 2430 deletions

View File

@ -169,7 +169,7 @@ public function submit(Request $request)
if ($temporary_file) { if ($temporary_file) {
$path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name); $path = storage_path('app/' . $request->folder_path . '/' . $temporary_file->file_name);
$submission->addMedia($path)->toMediaCollection('submissions', 'public_submissions_files'); $submission->addMedia($path)->toMediaCollection('submissions', 'nodejs_public_submissions_files');
if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) { if ($this->is_dir_empty(storage_path('app/' . $request->folder_path))) {
rmdir(storage_path('app/' . $request->folder_path)); rmdir(storage_path('app/' . $request->folder_path));
} }

View File

@ -1,15 +1,15 @@
<?php <?php
namespace App\Jobs\NodeJS; namespace App\Jobs;
use App\Models\NodeJS\ExecutionStep; use App\Models\ExecutionStep;
use App\Models\NodeJS\Submission; use App\Models\Submission;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;

View File

@ -1,240 +0,0 @@
const mongoose = require("mongoose");
const request = require("supertest");
const app = require("../../app");
const packages = require("../../package.json");
require("dotenv").config();
mongoose.set("strictQuery", true);
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
beforeAll(async () => {
await connectDB().then(
async () => {
console.log("Database connected successfully");
},
(err) => {
console.log("There is problem while connecting database " + err);
}
);
});
describe("Testing application configuration", () => {
// Testing the package.json file for the necessary development packages
// the packages are cross-env, jest, nodemon, supertest, jest-image-snapshot, jest-expect-message, puppeteer
it("Should have the necessary development packages", (done) => {
expect(
packages.devDependencies,
).toHaveProperty("cross-env");
expect(
packages.devDependencies,
).toHaveProperty("jest");
expect(
packages.devDependencies,
).toHaveProperty("nodemon");
expect(
packages.devDependencies,
).toHaveProperty("supertest");
// expect(
// packages.devDependencies,
// ).toHaveProperty("jest-image-snapshot");
// expect(
// packages.devDependencies,
// ).toHaveProperty("jest-expect-message");
// expect(
// packages.devDependencies,
// ).toHaveProperty("puppeteer");
done();
});
// Testing the package.json file for the necessary production packages
// the packages are dotenv, ejs, express, express-ejs-layouts, mongoose, mongoose-slug-generator
it("should have the necessary production packages", (done) => {
expect(
packages.dependencies,
`The package "dotenv" was not found in the dependencies object. Install the package by running this command "npm i dotenv --save"`,
options
).toHaveProperty("dotenv");
expect(
packages.dependencies,
`The package "ejs" was not found in the dependencies object. Install the package by running this command "npm i ejs --save"`,
options
).toHaveProperty("ejs");
expect(
packages.dependencies,
`The package "express" was not found in the dependencies object. Install the package by running this command "npm i express --save"`,
options
).toHaveProperty("express");
expect(
packages.dependencies,
`The package "express-ejs-layouts" was not found in the dependencies object. Install the package by running this command "npm i express-ejs-layouts --save"`,
options
).toHaveProperty("express-ejs-layouts");
expect(
packages.dependencies,
`The package "mongoose" was not found in the dependencies object. Install the package by running this command "npm i mongoose --save"`,
options
).toHaveProperty("mongoose");
expect(
packages.dependencies,
`The package "mongoose-slug-generator" was not found in the dependencies object. Install the package by running this command "npm i mongoose-slug-generator --save"`,
options
).toHaveProperty("mongoose-slug-generator");
done();
});
// Testing the application name
// the application name should be "api-experiment"
it("should have the right name and packages", (done) => {
expect(
packages.name,
`The name provided "${packages.name}" is wrong. The application name should be "api-experiment", check the package.json file`,
options
).toBe("api-experiment");
done();
});
// Testing the application environment variables
// the application should have the following environment variables
// MONGODB_URI, MONGODB_URI_TEST, PORT
it("should have the right environment variables", (done) => {
expect(
process.env,
`The environment variable "MONGODB_URI" was not found. Check the .env file`,
options
).toHaveProperty("MONGODB_URI");
expect(
process.env,
`The environment variable "MONGODB_URI_TEST" was not found. Check the .env.test file`,
options
).toHaveProperty("MONGODB_URI_TEST");
expect(
process.env.MONGODB_URI !== process.env.MONGODB_URI_TEST,
`The environment variable "MONGODB_URI" and "MONGODB_URI_TEST" should not be the same. Check the .env`,
options
).toBeTruthy();
expect(
process.env,
`The environment variable "PORT" was not found. Check the .env file`,
options
).toHaveProperty("PORT");
expect(
process.env.NODE_ENV,
`The environment variable "NODE_ENV" was not found. Check the test script in the package.json file`
).toBe("test");
done();
});
// Testing the application connection to the database using the test environment
it("should have the right database connection", (done) => {
expect(
mongoose.connection.readyState,
`The application is not connected to the database. Check the correctness of the MONGODB_URI_TEST variable in the .env file or the connection to the internet`,
options
).toBe(1);
done();
});
// Testing the application configuration
it("should be using json format and express framework", (done) => {
let application_stack = [];
app._router.stack.forEach((element) => {
application_stack.push(element.name);
});
expect(
application_stack,
`The application is not using the json format. Check the app.js file`,
options
).toContain("query");
expect(
application_stack,
`The application is not using the express framework. Check the app.js file`,
options
).toContain("expressInit");
expect(
application_stack,
`The application is not using the json format. Check the app.js file`,
options
).toContain("jsonParser");
expect(
application_stack,
`The application is not using the urlencoded format. Check the app.js file`,
options
).toContain("urlencodedParser");
done();
});
});
// Testing the application testing route
describe("Testing GET /api/v1/test", () => {
// Testing the application testing route without request
it("should return alive", async () => {
const res = await request(app).get("/api/v1/test");
expect(
res.statusCode,
`The status code should be 200, but it is "${res.statusCode}", change the status code in the function that handles the GET /api/v1/test route`,
options
).toBe(200);
expect(
res.body,
`The response should contain the property "alive", but it does not, change the response in the function that handles the GET /api/v1/test route to return {alive: 'True'}`,
options
).toHaveProperty("alive");
expect(
res.body.alive,
`The response should be {alive: 'True'}, but it is "${res.body.alive}", change the response in the function that handles the GET /api/v1/test route to return {alive: 'True'}`,
options
).toBe("True");
expect(
res.req.method,
`The request method should be GET, but it is "${res.req.method}", change the request method in the function that handles the GET /api/v1/test route`,
options
).toBe("GET");
expect(
res.type,
`The response type should be application/json, but it is "${res.type}", change the response type in the function that handles the GET /api/v1/test route`
).toBe("application/json");
});
// Testing the application testing route with request
it("should return the same message", async () => {
const res = await request(app)
.get("/api/v1/test")
.send({ message: "Hello" });
expect(
res.statusCode,
`The status code should be 200, but it is "${res.statusCode}", change the status code in the function that handles the GET /api/v1/test route`,
options
).toBe(200);
expect(
res.body,
`The response should contain the property "message", but it does not, change the response in the function that handles the GET /api/v1/test route to return {message: req.body.message}`,
options
).toHaveProperty("message");
expect(
res.body.message,
`The response should be {message: 'Hello'}, but it is "${res.body.message}", change the response in the function that handles the GET /api/v1/test route to return {message: req.body.message}`,
options
).toBe("Hello");
const res2 = await request(app)
.get("/api/v1/test")
.send({ message: "Hello World" });
expect(
res2.body.message,
`The response should be {message: 'Hello World'}, but it is "${res2.body.message}", change the response in the function that handles the GET /api/v1/test route to return {message: req.body.message}`,
options
).toBe("Hello World");
});
});
afterAll(async () => {
await disconnectDB();
});
async function connectDB() {
return mongoose.connect(process.env.MONGODB_URI_TEST, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
}
async function disconnectDB() {
await mongoose.connection.close();
}

View File

@ -1,306 +0,0 @@
const mongoose = require("mongoose");
const request = require("supertest");
const app = require("../../app");
const Product = require("../../models/product.model");
require("dotenv").config();
mongoose.set("strictQuery", true);
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
beforeAll(async () => {
await connectDB(process.env.MONGODB_URI_TEST).then(
async () => {
console.log("Database connected successfully");
await createProducts();
},
(err) => {
console.log("There is problem while connecting database " + err);
}
);
});
describe("GET /api/v1/products", () => {
it("should return all products", async () => {
const res = await request(app).get("/api/v1/products");
expect(
res.statusCode,
`When calling GET /api/v1/products, the status code should be "200", but it was "${res.statusCode}", update your code to return 200`,
options
).toBe(200);
expect(
res.body.message,
`When calling GET /api/v1/products, the message should be "Products found", but it was "${res.body.message}", update your code to return "Products found"`,
options
).toBe("Products found");
expect(
res.body.products.length,
`When calling GET /api/v1/products, the products array should return more than 0 products, but it was "${res.body.products.length}", update your code to return more than 0 products`,
options
).toBeGreaterThan(0);
expect(
res.body.products[0].name === "Product 1" ||
res.body.products[0].name === "Product 2",
`The data received from GET /api/v1/products was not correct, update your code to return the correct data`,
options
).toBeTruthy();
expect(
res.req.method,
`When calling GET /api/v1/products, the method should be "GET", but it was "${res.req.method}", update the method to be "GET"`,
options
).toBe("GET");
expect(
res.type,
`When calling GET /api/v1/products, the content type should be "application/json", but it was "${res.type}", update the content type to be "application/json"`,
options
).toBe("application/json");
});
it("should check items in database", async () => {
await disconnectDB().then(async () => {
await connectDB(process.env.MONGODB_URI).then(async () => {
const products = await Product.find();
expect(
products.length,
`The database should contain all the "10 products" from the initial_data.json file, but it was "${products.length}", use the initial_data.json file to add more products to the api-experiment database`,
options
).toEqual(10);
await disconnectDB().then(async () => {
await connectDB(process.env.MONGODB_URI_TEST);
});
});
});
});
it("should return no products", async () => {
await Product.deleteMany();
const res = await request(app).get("/api/v1/products");
expect(
res.statusCode,
`The status code should be "404" because there are no products, but it was "${res.statusCode}", update the status code to be 404 when there are no products`,
options
).toBe(404);
expect(
res.body.message,
`The message should be "No products found" because there are no products, but it was "${res.body.message}", update the message to be "No products found" when there are no products`,
options
).toBe("No products found");
await createProducts();
});
it("should return error 500 if the database disconnected", async () => {
await disconnectDB().then(async () => {
const res = await request(app).get("/api/v1/products");
expect(
res.statusCode,
`The application should return 500 for the status code if the database is disconnected`,
options
).toBe(500);
await connectDB(process.env.MONGODB_URI_TEST);
});
});
});
describe("GET /api/v1/product/:slug", () => {
it("should return one product", async () => {
const res = await request(app).get("/api/v1/product/product-2");
expect(
res.statusCode,
`When calling GET /api/v1/product/:slug, the status code should be "200", but it was "${res.statusCode}", update your code to return 200`
).toBe(200);
expect(
res.body.product.name,
`The expected product was not correct, update your code to return the correct product`,
options
).toBe("Product 2");
expect(
res.req.method,
`When calling GET /api/v1/product/:slug, the method should be "GET", but it was "${res.req.method}", update the method to be "GET"`,
options
).toBe("GET");
expect(
res.type,
`When calling GET /api/v1/product/:slug, the content type should be "application/json", but it was "${res.type}", update the content type to be "application/json"`,
options
).toBe("application/json");
});
it("should return no product", async () => {
const res = await request(app).get("/api/v1/product/product-3");
expect(
res.statusCode,
`The status code should be "404" because there is no product, but it was "${res.statusCode}", update the status code to be 404 when there is no product found`,
options
).toBe(404);
expect(
res.body.message,
`The message should be "No product found" because there is no product, but it was "${res.body.message}", update the message to be "No product found" when there is no product found`,
options
).toBe("No product found");
});
it("should return error 500 if the database disconnected", async () => {
await disconnectDB().then(async () => {
const res = await request(app).get("/api/v1/product/product-2");
expect(
res.statusCode,
`The application should return 500 for the status code if the database is disconnected`,
options
).toBe(500);
await connectDB(process.env.MONGODB_URI_TEST);
});
});
});
describe("GET /api/v1/products with filters", () => {
it("should not return any products", async () => {
const formData = {
search: "John Doe",
};
const res = await request(app).get("/api/v1/products").query(formData);
expect(
res.statusCode,
`When applying search filters, if there are no products matching the search query, the status code should be "404". but it was "${res.statusCode}", update your code to return "404" when there are no products matching the search query`,
options
).toBe(404);
expect(
res.body.message,
`When applying search filters, if there are no products matching the search query, the message should be "No products found". but it was "${res.body.message}", update your code to return "No products found" when there are no products matching the search query`,
options
).toBe("No products found");
});
it("should return one products that fits the minimum price", async () => {
const formData = {
price: {
minPrice: 200,
},
};
const res = await request(app).get("/api/v1/products").query(formData);
expect(
res.statusCode,
`When applying price filters, if there are products matching the price query, the status code should be "200". but it was "${res.statusCode}", update your code to return "200" when there are products matching the price query`,
options
).toBe(200);
expect(
res.body.message,
`When applying price filters, if there are products matching the price query, the message should be "Products found". but it was "${res.body.message}", update your code to return "Products found" when there are products matching the price query`,
options
).toBe("Products found");
expect(
res.body.products.length,
`When applying price filters, if there are products matching the price query, the length of the products should the same with the products that match the price query, update your code to return the correct amount of products`,
options
).toBe(1);
expect(
res.body.products[0].price,
`When applying price filters, if there are products matching the price query, the price of the products should be greater than or equal to the minimum price, update your code to return the correct price of the products`,
options
).toBeGreaterThanOrEqual(200);
});
it("should return two products that fits the maximum price", async () => {
const formData = {
price: {
maxPrice: 1000,
},
};
const res = await request(app).get("/api/v1/products").query(formData);
expect(
res.statusCode,
`When applying price filters, if there are products matching the price query, the status code should be "200". but it was "${res.statusCode}", update your code to return "200" when there are products matching the price query`,
options
).toBe(200);
expect(
res.body.message,
`When applying price filters, if there are products matching the price query, the message should be "Products found". but it was "${res.body.message}", update your code to return "Products found" when there are products matching the price query`,
options
).toBe("Products found");
expect(
res.body.products.length,
`When applying price filters, if there are products matching the price query, the length of the products should the same with the products that match the price query, update your code to return the correct amount of products`,
options
).toBe(2);
expect(
res.body.products[0].price,
`When applying price filters, if there are products matching the price query, the price of the products should be less than or equal to the maximum price, update your code to return the correct price of the products`,
options
).toBeLessThanOrEqual(1000);
});
it("should return products", async () => {
const formData = {
search: "Product",
price: {
minPrice: 200,
maxPrice: 1000,
},
};
const res = await request(app).get("/api/v1/products").query(formData);
expect(
res.statusCode,
`When applying search and price filters, if there are products matching the search and price query, the status code should be "200". but it was "${res.statusCode}", update your code to return "200" when there are products matching the search and price query`,
options
).toBe(200);
expect(
res.body.message,
`When applying search and price filters, if there are products matching the search and price query, the message should be "Products found". but it was "${res.body.message}", update your code to return "Products found" when there are products matching the search and price query`,
options
).toBe("Products found");
expect(
res.body.products.length,
`When applying search and price filters, if there are products matching the search and price query, the length of the products should the same with the products that match the search and price query, update your code to return the correct amount of products`,
options
).toBe(1);
res.body.products.forEach((product) => {
expect(
product.price,
`When applying search and price filters, if there are products matching the search and price query, the price of the products should be greater than or equal to the minimum price and less than or equal to the maximum price, update your code to return the correct price of the products`,
options
).toBeGreaterThanOrEqual(200);
expect(
product.price,
`When applying search and price filters, if there are products matching the search and price query, the price of the products should be greater than or equal to the minimum price and less than or equal to the maximum price, update your code to return the correct price of the products`,
options
).toBeLessThanOrEqual(1000);
});
});
});
afterAll(async () => {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.drop();
}
await disconnectDB();
});
async function createProducts() {
await Product.create(
{
name: "Product 1",
price: 100,
description: "Description 1",
},
{
name: "Product 2",
price: 200,
description: "Description 2",
}
);
}
async function connectDB(url) {
return mongoose.connect(url, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
}
async function disconnectDB() {
await mongoose.connection.close();
}

View File

@ -1,243 +0,0 @@
const mongoose = require("mongoose");
const request = require("supertest");
const app = require("../../app");
const Product = require("../../models/product.model");
require("dotenv").config();
mongoose.set("strictQuery", true);
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
beforeAll(async () => {
await connectDB(process.env.MONGODB_URI_TEST).then(
async () => {
console.log("Database connected successfully");
await createProducts();
},
(err) => {
console.log("There is problem while connecting database " + err);
}
);
});
describe("POST /api/v1/product", () => {
it("should create a product", async () => {
const res = await request(app).post("/api/v1/product").send({
name: "Product 3",
price: 1009,
description: "Description 3",
});
expect(
res.statusCode,
`Expected status code "201", but got "${res.statusCode}", the "201" is the status code for "Created" and it is the status code that we are expecting to get back from the server when we create a new product.`,
options
).toBe(201);
expect(
res.body,
`Expected the response body to have a property called "message" and the value of that property should be "Product created"`,
options
).toHaveProperty("message");
expect(
res.body.message,
`Expected the value of the "message" property to be "Product created"`,
options
).toBe("Product created");
expect(
res.body,
`Expected the response body to have a property called "product" and the value of that property should be an object`,
options
).toHaveProperty("product");
expect(
res.body.product.name,
`The value of the object returned doesn't match the value of the "name" property that we sent to the server.`,
options
).toBe("Product 3");
expect(
res.req.method,
`Expected the request method to be "POST"`,
options
).toBe("POST");
expect(
res.type,
`Expected the response type to be "application/json"`,
options
).toBe("application/json");
});
it("should not create a product because it already exists", async () => {
const res = await request(app).post("/api/v1/product").send({
name: "Product 3",
price: 1009,
description: "Description 3",
});
expect(
res.statusCode,
`Expected status code "409", but got "${res.statusCode}", the "409" is the status code for "Conflict" and it is the status code that we are expecting to get back from the server when we try to create a product that already exists.`,
options
).toBe(409);
expect(
res.body,
`Expected the response body to have a property called "message" and the value of that property should be "Product already exists"`,
options
).toHaveProperty("message");
expect(
res.body.message,
`Expected the value of the "message" property to be "Product already exists"`,
options
).toBe("Product already exists");
expect(
res.body,
`Expected the response body to have a property called "product" for the existed product and the value of that property should be an object`,
options
).toHaveProperty("product");
expect(
res.body.product.name,
`The value of the object returned doesn't match the value of the "name" property that we sent to the server.`,
options
).toBe("Product 3");
});
it("should not create a product because of the name is not provided", async () => {
const res = await request(app).post("/api/v1/product").send({
name: "",
price: 1009,
description: "Description 3",
});
expect(
res.statusCode,
`Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product without providing the name.`,
options
).toBe(500);
expect(
res.body.errors.name.message,
`Expected the value of the "message" property to be "Name is required", but got "${res.body.errors.name.message}" instead. Change the validation property of the "name" property in the "product.model.js" file to "required: true" and then run the test again.`,
options
).toBe("Name is required");
expect(
res.body.message,
`Expected the value of the "message" property to be "Product validation failed: name: Name is required", but got "${res.body.message}" instead. Change the validation property of the "name" property in the "product.model.js" file to "required: true" and then run the test again.`,
options
).toContain("Name is required");
});
it("should not create a product because of the price is not provided", async () => {
const res = await request(app).post("/api/v1/product").send({
name: "Product 4",
price: "",
description: "Description 4",
});
expect(
res.statusCode,
`Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product without providing the price.`,
options
).toBe(500);
expect(
res.body.message,
`Expected the value of the "message" property to be "Cast to Number failed" for value "" at path "price" for model "Product", but got "${res.body.message}" instead. Change the validation property of the "price" property in the "product.model.js" file to "required: true" and then run the test again.`,
options
).toContain("Cast to Number failed");
});
it("should not create a product because of the description is not provided", async () => {
const res = await request(app).post("/api/v1/product").send({
name: "Product 4",
price: 1009,
description: "",
});
expect(
res.statusCode,
`Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product without providing the description.`,
options
).toBe(500);
expect(
res.body.errors.description.message,
`Expected the value of the "message" property to be "Description is required", but got "${res.body.errors.description.message}" instead. Change the validation property of the "description" property in the "product.model.js" file to "required: true" and then run the test again.`,
options
).toBe("Description is required");
expect(
res.body.message,
`Expected the value of the "message" property to be "Product validation failed: description: Description is required", but got "${res.body.message}" instead. Change the validation property of the "description" property in the "product.model.js" file to "required: true" and then run the test again.`,
options
).toContain("Description is required");
});
it("should not create a product because of the price is less than 0", async () => {
const res = await request(app).post("/api/v1/product").send({
name: "Product 4",
price: -1009,
description: "Description 4",
});
expect(
res.statusCode,
`Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product with a price less than 0.
`,
options
).toBe(500);
expect(
res.body.errors.price.message,
`Expected the value of the "message" property to be "Price must be greater than 0", but got "${res.body.errors.price.message}" instead. Change the validation property of the "price" property in the "product.model.js" file to "min: 0" and then run the test again.`,
options
).toBe("Price must be greater than 0");
expect(
res.body.message,
`Expected the value of the "message" property to be "Price must be greater than 0", but got "${res.body.message}" instead. Change the validation property of the "price" property in the "product.model.js" file to "min: 0" and then run the test again.`,
options
).toContain("Price must be greater than 0");
});
it("should return error 500", async () => {
await disconnectDB().then(async () => {
const res = await request(app).post("/api/v1/product").send({
name: "Product 4",
price: 1009,
description: "Description 4",
});
expect(
res.statusCode,
`Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to create a product without database connection.`,
options
).toBe(500);
await connectDB();
});
});
});
afterAll(async () => {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.drop();
}
await disconnectDB();
});
async function createProducts() {
await Product.create(
{
name: "Product 1",
price: 100,
description: "Description 1",
},
{
name: "Product 2",
price: 200,
description: "Description 2",
}
);
}
async function connectDB() {
return mongoose.connect(process.env.MONGODB_URI_TEST, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
}
async function disconnectDB() {
await mongoose.connection.close();
}

View File

@ -1,153 +0,0 @@
const mongoose = require("mongoose");
const request = require("supertest");
const app = require("../../app");
const Product = require("../../models/product.model");
require("dotenv").config();
mongoose.set("strictQuery", true);
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
beforeAll(async () => {
await connectDB(process.env.MONGODB_URI_TEST).then(
async () => {
console.log("Database connected successfully");
await createProducts();
},
(err) => {
console.log("There is problem while connecting database " + err);
}
);
});
describe("PATCH /api/v1/product/:slug", () => {
it("should update a product", async () => {
const FindProduct = await Product.findOne({ slug: "product-3" })
.lean()
.exec();
const res = await request(app).patch("/api/v1/product/product-3").send({
name: "Product 3 updated",
price: 109,
description: "Description 3 updated",
});
expect(
res.statusCode,
`Expected status code 200, but got "${res.statusCode}", the status 200 means that the request has succeeded. Change it in the file "controllers/api/product.controller.js"`
).toBe(200);
expect(
res.body,
`Expected the response body to have a property called "message" and the value of that property should be "Product updated"`,
options
).toHaveProperty("message");
expect(
res.body.message,
`Expected the value of the "message" property to be "Product updated"`,
options
).toBe("Product updated");
expect(
res.body,
`Expected the response body to have a property called "product" and the value of that property should be an object`,
options
).toHaveProperty("product");
expect(
res.body.product,
`Expected the value of the product sent to the server to be updated but it is not. Make sure that you are using the "findByIdAndUpdate" method and that you are passing the correct parameters to it.`
).not.toEqual(FindProduct);
expect(
res.req.method,
`Expected the request method to be "PATCH"`,
options
).toBe("PATCH");
expect(
res.type,
`Expected the response content type to be "application/json"`,
options
).toBe("application/json");
});
it("should not update a product because it does not exist", async () => {
const res = await request(app).patch("/api/v1/product/product_4").send({
name: "Product 3 updated",
price: 109,
description: "Description 3",
});
expect(
res.statusCode,
`Expected status code 404, but got "${res.statusCode}", the status 404 means that the server can not find the requested resource. Change it in the file "controllers/api/product.controller.js"`,
options
).toBe(404);
expect(
res.body,
`Expected the response body to have a property called "message" and the value of that property should be "No product found"`,
options
).toHaveProperty("message");
expect(
res.body.message,
`Expected the value of the "message" property to be "No product found"`,
options
).toBe("No product found");
});
it("should return error 500", async () => {
await disconnectDB().then(async () => {
const res = await request(app).patch("/api/v1/product/product_4").send({
name: "Product 3 updated",
price: 109,
description: "Description 3",
});
expect(
res.statusCode,
`Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to update a product without database connection.`,
options
).toBe(500);
await connectDB();
});
});
});
afterAll(async () => {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.drop();
}
await disconnectDB();
});
async function createProducts() {
await Product.create(
{
name: "Product 1",
price: 100,
description: "Description 1",
},
{
name: "Product 2",
price: 200,
description: "Description 2",
},
{
name: "Product 3",
price: 1009,
description: "Description 3",
}
);
}
async function connectDB() {
return mongoose.connect(process.env.MONGODB_URI_TEST, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
}
async function disconnectDB() {
await mongoose.connection.close();
}

View File

@ -1,141 +0,0 @@
const mongoose = require("mongoose");
const request = require("supertest");
const app = require("../../app");
const Product = require("../../models/product.model");
require("dotenv").config();
mongoose.set("strictQuery", true);
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
beforeAll(async () => {
await connectDB(process.env.MONGODB_URI_TEST).then(
async () => {
console.log("Database connected successfully");
await createProducts();
},
(err) => {
console.log("There is problem while connecting database " + err);
}
);
});
describe("DELETE /api/v1/products/:slug", () => {
it("should delete a product", async () => {
const product = await Product.findOne({ slug: "product-3" }).lean().exec();
const res = await request(app).delete("/api/v1/product/product-3");
expect(
res.statusCode,
`Expected status code 200 when requesting to delete a product, but got "${res.statusCode}", the status 200 means that the request has succeeded. Change it in the file "controllers/api/product.controller.js"`
).toBe(200);
expect(
res.body,
`Expected the response body to have a property called "message" and the value of that property should be "Product deleted"`,
options
).toHaveProperty("message");
expect(
res.body.message,
`Expected the value of the "message" property to be "Product deleted"`,
options
).toBe("Product deleted");
expect(
res.body,
`Expected the response body to have a property called "product" for the deleted product and the value of that property should be an object`,
options
).toHaveProperty("product");
expect(
res.body.product.name,
`Expected the product deleted to be the same as the product sent to the server but it is not. Make sure that you are using the "findByIdAndDelete" method and that you are passing the correct parameters to it.`,
options
).toBe("Product 3");
const checkProduct = await Product.findById(product._id).lean().exec();
expect(
checkProduct,
`Expected the product to be deleted from the database but it is not. Make sure that you are using the "findByIdAndDelete" method and that you are passing the correct parameters to it.`,
options
).toBeNull();
expect(
res.req.method,
`Expected the request method to be "DELETE" but it is not`,
options
).toBe("DELETE");
expect(
res.type,
`Expected the response type to be "application/json" but it is not`,
options
).toBe("application/json");
});
it("should not delete a product because it does not exist", async () => {
const res = await request(app).delete("/api/v1/product/product-3");
expect(
res.statusCode,
`Expected status code "404" when requesting to delete a product that does not exist, but got "${res.statusCode}", the status 404 means that the server can not find the requested resource. Change it in the file "controllers/api/product.controller.js"`
).toBe(404);
expect(
res.body,
`Expected the response body to have a property called "message" and the value of that property should be "No product found"`,
options
).toHaveProperty("message");
expect(
res.body.message,
`Expected the value of the "message" property to be "No product found"`,
options
).toBe("No product found");
});
it("should return error 500", async () => {
await disconnectDB().then(async () => {
const res = await request(app).delete("/api/v1/product/product_4");
expect(
res.statusCode,
`Expected status code "500", but got "${res.statusCode}", the "500" is the status code for "Internal Server Error" and it is the status code that we are expecting to get back from the server when we try to delete a product without database connection.`,
options
).toBe(500);
await connectDB();
});
});
});
afterAll(async () => {
const collections = await mongoose.connection.db.collections();
for (let collection of collections) {
await collection.drop();
}
await disconnectDB();
});
async function createProducts() {
await Product.create(
{
name: "Product 1",
price: 100,
description: "Description 1",
},
{
name: "Product 2",
price: 200,
description: "Description 2",
},
{
name: "Product 3",
price: 1009,
description: "Description 3",
}
);
}
async function connectDB() {
return mongoose.connect(process.env.MONGODB_URI_TEST, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
}
async function disconnectDB() {
await mongoose.connection.close();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,209 +0,0 @@
const fs = require("fs");
const puppeteer = require("puppeteer");
const { toMatchImageSnapshot } = require("jest-image-snapshot");
expect.extend({ toMatchImageSnapshot });
require("dotenv").config();
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: true,
slowMo: 0,
devtools: false,
defaultViewport: {
width: 1024,
height: 768,
},
});
page = await browser.newPage();
await page.setDefaultTimeout(10000);
await page.setDefaultNavigationTimeout(20000);
});
beforeEach(async () => {
await page.goto(`http://localhost:${process.env.PORT}/`);
});
afterAll(async () => {
await browser.close();
});
describe("Testing the index page title and content", () => {
it("should have the right title", async () => {
const title = await page.title();
expect(
title,
`The title for the web page "${title}" is wrong it should be "API-Experiment | Home" Make sure that the function handling the GET "/" route is sending the right title`,
options
).toBe("API-Experiment | Home");
});
it("should have a button with the text 'Products' and url `/products` ", async () => {
const button = await page.$eval(
".btn.btn-primary",
(el) => el.textContent
);
expect(
button,
`The button with the text "Products" is not present on the page`,
options
).toBe("Products");
const url = await page.$eval(".btn.btn-primary", (el) => el.href);
expect(
url,
`The button with the text "Products" is not sending the user to the right url`,
options
).toBe(`http://localhost:${process.env.PORT}/products`);
const backgroundColor = await page.evaluate(() => {
const button = document.querySelector(".btn.btn-primary");
const style = window.getComputedStyle(button);
return style.getPropertyValue("background-color");
});
expect(
backgroundColor,
`The button has the wrong background color "${backgroundColor}" it should be "rgb(0, 161, 189)"`
).toBe("rgb(0, 161, 189)");
});
it("should have nav bar with 2 links", async () => {
const navBar = await page.$eval("nav", (el) => el.textContent);
expect(
navBar,
`The page should contain a link to the home page. Check the "main.ejs" file in the "web/views/layouts" folder to find the nav bar"`,
options
).toContain("Home");
expect(
navBar,
`The page should contain a link to the products page. Check the "main.ejs" file in the "web/views/layouts" folder to find the nav bar`,
options
).toContain("Products");
});
});
describe("Testing the index page for receiving messages", () => {
it("should receive a message and display it", async () => {
await page.goto(
`http://localhost:${process.env.PORT}/?message=Hello test`
);
let message = await page.$eval(".message", (el) => el.textContent);
expect(
message,
`the message "${message}" received is wrong it should be "Hello test"`,
options
).toBe("Hello test");
await page.goto(
`http://localhost:${process.env.PORT}/?message=This is another test`
);
message = await page.$eval(".message", (el) => el.textContent);
expect(
message,
`the message "${message}" received is wrong it should be "This is another test"`,
options
).toBe("This is another test");
});
it("should have the correct color for the box after receiving a message", async () => {
await page.goto(
`http://localhost:${process.env.PORT}/?message=yet, another test`
);
const backgroundColor = await page.evaluate(() => {
const message = document.querySelector(".alert.alert-success");
const style = window.getComputedStyle(message);
return style.getPropertyValue("background-color");
});
expect(
backgroundColor,
`The message box has the wrong background color "${backgroundColor}" it should be "rgb(239, 162, 95)"`
).toBe("rgb(239, 162, 95)");
});
});
describe("Testing the error `Not Found` page", () => {
it("should have the right title", async () => {
await page.goto(
`http://localhost:${process.env.PORT}/thisurldoesnotexist`
);
const title = await page.title();
expect(
title,
`The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the GET "/:url" route is sending the right title`,
options
).toBe("API-Experiment | Error");
});
it("should have a status code of 404", async () => {
await page.goto(
`http://localhost:${process.env.PORT}/thisurldoesnotexist`
);
const statusCode = await page.$eval(".title", (el) => el.textContent);
expect(
statusCode,
`The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the GET "/:url" route is sending the right status code`,
options
).toBe("404");
});
it("should have a message saying `NOT FOUND`", async () => {
await page.goto(
`http://localhost:${process.env.PORT}/thisurldoesnotexist`
);
const message = await page.$eval(".message", (el) => el.textContent);
expect(
message,
`The message "${message}" is wrong it should be "NOT FOUND" Make sure that the function handling the GET "/:url" route is sending the right message`,
options
).toBe("NOT FOUND");
});
});
describe("Testing the index page and error `Not Found` page image snapshots", () => {
it("matches the expected styling", async () => {
if (!fs.existsSync("tests/web/images/index-page.png")) {
throw new Error(
`The reference image for the index page does not exist, please import the image from the "tests/web/images/index-page.png"`
);
}
const screenshot = await page.screenshot({ fullPage: true });
expect(
screenshot,
`The web styling for the index page is not correct check the file "tests/web/images/__diff_output__/index-page-diff.png" to find the difference`,
options
).toMatchImageSnapshot({
customDiffConfig: { threshold: 0.9 },
customSnapshotsDir: "tests/web/images",
customSnapshotIdentifier: "index-page",
});
});
it("matches the expected styling", async () => {
if (!fs.existsSync("tests/web/images/error-notFound-page.png")) {
throw new Error(
`The reference image for the error page does not exist, please import the image from the "tests/web/images/error-notFound-page.png"`
);
}
await page.goto(
`http://localhost:${process.env.PORT}/thisurldoesnotexist`
);
const screenshot = await page.screenshot({ fullPage: true });
expect(
screenshot,
`The web styling for the error "Not Found" page is not correct check the file "tests/web/images/__diff_output__/error-notFound-page-diff.png" to find the difference`,
options
).toMatchImageSnapshot({
customDiffConfig: { threshold: 0.9 },
customSnapshotsDir: "tests/web/images",
customSnapshotIdentifier: "error-notFound-page",
});
});
});

View File

@ -1,399 +0,0 @@
const fs = require("fs");
const puppeteer = require("puppeteer");
const { toMatchImageSnapshot } = require("jest-image-snapshot");
const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
expect.extend({ toMatchImageSnapshot });
require("dotenv").config();
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: true,
slowMo: 0,
devtools: false,
defaultViewport: {
width: 1024,
height: 768,
},
});
page = await browser.newPage();
await page.setDefaultTimeout(10000);
await page.setDefaultNavigationTimeout(20000);
});
beforeEach(async () => {
await page.goto(`http://localhost:${process.env.PORT}/products`);
});
afterAll(async () => {
await browser.close();
});
describe("Testing the products page title and content", () => {
it("should have the right title", async () => {
const title = await page.title();
expect(
title,
`The title for the web page "${title}" is wrong it should be "API-Experiment | Products" Make sure that the function handling the GET "/products" route is sending the right title`,
options
).toBe("API-Experiment | Products");
});
it("should have a header with the text 'Products' with 'title' class", async () => {
const header = await page.$eval(".title", (el) => el.textContent);
expect(
header,
`The header with the text "Products" is not present on the page`,
options
).toBe("Products");
});
it("should have the search form with the three inputs and the submit button", async () => {
const inputs = await page.$$("input");
expect(
inputs.length,
`The number of inputs in the search form is wrong, it should be 3`,
options
).toBe(3);
const button = await page.$eval(
"form > button.btn.btn-primary",
(el) => el.textContent
);
expect(
button,
`The submit button is not present on the page`,
options
).toBeTruthy();
expect(
button,
`The submit button should have the text "Search", but it has "${button}"`,
options
).toBe("Search");
});
it("should have the correct form action and method", async () => {
const form = await page.$eval("form", (el) => ({
action: el.action,
method: el.method,
}));
expect(
form.action,
`The form action should be "http://localhost:${process.env.PORT}/products", but it has "${form.action}", You can change it in the "web/views/products/index.ejs" file.`,
options
).toBe(`http://localhost:${process.env.PORT}/products`);
expect(
form.method,
`The form method should be "GET", but it has "${form.method}", You can change it in the "web/views/products/index.ejs" file.`,
options
).toBe("get");
});
it("should the right inputs names and types", async () => {
const search_input = await page.$eval("#search", (el) => ({
name: el.name,
type: el.type,
}));
expect(
search_input.name,
`The first input should have the name "search", but it has "${search_input.name}"`,
options
).toBe("search");
expect(
search_input.type,
`The first input should have the type "text", but it has "${search_input.type}"`,
options
).toBe("text");
const minPrice_input = await page.$eval("#minPrice", (el) => ({
name: el.name,
type: el.type,
}));
expect(
minPrice_input.name,
`The second input should have the name "price[minPrice]", but it has "${minPrice_input.name}"`,
options
).toBe("price[minPrice]");
expect(
minPrice_input.type,
`The second input should have the type "number", but it has "${minPrice_input.type}"`,
options
).toBe("number");
const maxPrice_input = await page.$eval("#maxPrice", (el) => ({
name: el.name,
type: el.type,
}));
expect(
maxPrice_input.name,
`The third input should have the name "price[maxPrice]", but it has "${maxPrice_input.name}"`,
options
).toBe("price[maxPrice]");
expect(
maxPrice_input.type,
`The third input should have the type "number", but it has "${maxPrice_input.type}"`,
options
).toBe("number");
});
it("should have a button to create a new product", async () => {
const button = await page.$eval(".btn.btn-primary", (el) => ({
text: el.textContent,
url: el.href,
}));
expect(
button.text,
`The button to create a new product should have the text "Create a new product", but it has "${button.text}"`,
options
).toBe("Create a new product");
expect(
button.url,
`The button to create a new product should have the url "http://localhost:${process.env.PORT}/products/create", but it has "${button.url}"`,
options
).toBe(`http://localhost:${process.env.PORT}/products/create`);
});
});
describe("Testing the products page table", () => {
it("should have the right number of products", async () => {
const products = await page.$$("tbody tr");
expect(
products.length,
`The number of products is wrong, it should be 10`,
options
).toBe(10);
});
it("should have the right data", async () => {
const products_data = await page.$$eval("tbody tr", (rows) =>
rows.map((row) => {
const [no, name, price, description, slug] = row.children;
return {
name: name.textContent,
price: parseFloat(price.textContent.replace("$", "")),
description: description.textContent,
slug: slug.children[0].href.split("/show/").pop(),
};
})
);
initial_data.forEach((product) => {
delete product._id;
delete product.createdAt;
delete product.updatedAt;
});
products_data.sort((a, b) => a.name.localeCompare(b.name));
initial_data.sort((a, b) => a.name.localeCompare(b.name));
expect(products_data, `The products data is wrong`, options).toEqual(
initial_data
);
});
});
describe("Testing the products details page", () => {
it("should go to the details page when clicking on a product", async () => {
await page.click("tbody tr:first-child a");
const url = await page.url();
const slug = url.split("/show/").pop();
const product = initial_data.find((product) => product.slug === slug);
expect(
product,
`The product with the slug "${slug}" is not present in the initial_data.json file`,
options
).toBeTruthy();
expect(
url,
`The url for the details page is wrong, it should be "http://localhost:${process.env.PORT}/products/show/${product.slug}", but it is "${url}"`,
options
).toBe(
`http://localhost:${process.env.PORT}/products/show/${product.slug}`
);
});
it("should have the button to edit and delete the product", async () => {
await page.click("tbody tr:first-child a");
const url = await page.url();
const slug = url.split("/show/").pop();
const product = initial_data.find((product) => product.slug === slug);
const productName = await page.$eval(
".card-title",
(el) => el.textContent
);
const productPrice = await page.$eval(
".card-subtitle",
(el) => el.textContent
);
const productDescription = await page.$eval(
".card-text",
(el) => el.textContent
);
const editButton = await page.$eval("a.btn", (el) => ({
text: el.textContent.trim(),
url: el.href.trim(),
}));
const deleteButton = await page.$eval("form > button.btn", (el) => ({
text: el.textContent.trim(),
url: el.parentElement.action.trim(),
}));
expect(
productName,
`The product name is wrong, it should be "${product.name}", but it is "${productName}"`,
options
).toBe(product.name);
expect(
productPrice,
`The product price is wrong, it should be "${product.price}", but it is "$${productPrice}"`,
options
).toBe("$" + product.price);
expect(
productDescription,
`The product description is wrong, it should be "${product.description}", but it is "${productDescription}"`,
options
).toBe(product.description);
expect(
editButton.text,
`The edit button should have the text "Edit this product", but it has "${editButton.text}", change it in the "views/products/details.ejs" file`,
options
).toBe("Edit this product");
expect(
editButton.url,
`The edit button should have the url "http://localhost:${process.env.PORT}/products/update/${product.slug}", but it has "${editButton.url}", make sure you are using the correct slug by using "/products/update/<%= product.slug %>" url, change it in the "views/products/details.ejs" file`,
options
).toBe(
`http://localhost:${process.env.PORT}/products/update/${product.slug}`
);
expect(
deleteButton.text,
`The delete button should have the text "Delete this product", but it has "${deleteButton.text}", change it in the "views/products/details.ejs" file`,
options
).toBe("Delete this product");
expect(
deleteButton.url,
`The delete button should have the url "http://localhost:${process.env.PORT}/products/delete/${product.slug}", but it has "${deleteButton.url}", make sure you are using the correct slug by using "/products/delete/<%= product.slug %>" url, change it in the "views/products/details.ejs" file`,
options
).toBe(
`http://localhost:${process.env.PORT}/products/delete/${product.slug}`
);
});
it("should don't go to the product's details page if the product does not exist", async () => {
await page.goto(
`http://localhost:${process.env.PORT}/products/show/123thisproductdoenotexist`
);
const title = await page.title();
expect(
title,
`The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the GET getProduct method return the error title if the product was not found`,
options
).toBe("API-Experiment | Error");
const statusCode = await page.$eval(".title", (el) => el.textContent);
expect(
statusCode,
`The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the GET getProduct method return the error status code if the product was not found`,
options
).toBe("404");
const message = await page.$eval(".message", (el) => el.textContent);
expect(
message,
`The message "${message}" is wrong it should be "No product found" Make sure that the function handling the GET getProduct method return the error message if the product was not found`,
options
).toBe("No product found");
});
});
describe("Testing the product pages image snapshots", () => {
it("should have the right image for the products page", async () => {
if (!fs.existsSync("tests/web/images/products-table-page.png")) {
throw new Error(
`The reference image for the products table page does not exist, please import the image from the "tests/web/images/products-table-page.png"`
);
}
const image = await page.screenshot({ fullPage: true });
expect(
image,
`The image for the products table page is wrong, it should be the same as the "tests/web/images/__diff_output__/products-table-page-diff.png" image`,
options
).toMatchImageSnapshot({
customDiffConfig: { threshold: 0.9 },
customSnapshotsDir: "tests/web/images",
customSnapshotIdentifier: "products-table-page",
});
});
it("should have the right image for the details page", async () => {
if (!fs.existsSync("tests/web/images/product-details-page.png")) {
throw new Error(
`The reference image for the product details page does not exist, please import the image from the "tests/web/images/product-details-page.png"`
);
}
await page.goto(
`http://localhost:${process.env.PORT}/products/show/${initial_data[0].slug}`
);
const image = await page.screenshot({ fullPage: true });
expect(
image,
`The image for the product details page is wrong, it should be the same as the "tests/web/images/__diff_output__/product-details-page-diff.png" image`,
options
).toMatchImageSnapshot({
customDiffConfig: { threshold: 0.9 },
customSnapshotsDir: "tests/web/images",
customSnapshotIdentifier: "product-details-page",
});
});
it("should match the not found product snapshot", async () => {
if (!fs.existsSync("tests/web/images/not-found-product-page.png")) {
throw new Error(
`The reference image for the not found product page does not exist, please import the image from the "tests/web/images/not-found-product-page.png"`
);
}
await page.goto(
`http://localhost:${process.env.PORT}/products/show/123`
);
const image = await page.screenshot({ fullPage: true });
expect(
image,
`The image for the not found product page is wrong, it should be the same as the "tests/web/images/__diff_output__/not-found-product-page-diff.png" image`
).toMatchImageSnapshot({
customDiffConfig: { threshold: 0.9 },
customSnapshotsDir: "tests/web/images",
customSnapshotIdentifier: "not-found-product-page",
});
});
it("should match no products found snapshot", async () => {
if (!fs.existsSync("tests/web/images/no-products-found-page.png")) {
throw new Error(
`The reference image for the no products found page does not exist, please import the image from the "tests/web/images/no-products-found-page.png"`
);
}
await page.goto(
`http://localhost:${process.env.PORT}/products?search=123ThisIsNotAProduct`
);
const image = await page.screenshot({ fullPage: true });
expect(
image,
`The image for the no products found page is wrong, it should be the same as the "tests/web/images/__diff_output__/no-products-found-page-diff.png" image`
).toMatchImageSnapshot({
customDiffConfig: { threshold: 0.9 },
customSnapshotsDir: "tests/web/images",
customSnapshotIdentifier: "no-products-found-page",
});
});
});

View File

@ -1,246 +0,0 @@
const fs = require("fs");
const puppeteer = require("puppeteer");
const { toMatchImageSnapshot } = require("jest-image-snapshot");
const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
const mongoose = require("mongoose");
expect.extend({ toMatchImageSnapshot });
require("dotenv").config();
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
let browser;
let page;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: true,
slowMo: 0,
devtools: false,
defaultViewport: {
width: 1024,
height: 768,
},
});
page = await browser.newPage();
await page.setDefaultTimeout(10000);
await page.setDefaultNavigationTimeout(20000);
});
beforeEach(async () => {
await page.goto(`http://localhost:${process.env.PORT}/products/create`);
mongoose.set("strictQuery", true);
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
await mongoose.connection.collection("products").deleteMany({});
initial_data.forEach((product) => {
delete product._id;
delete product.createdAt;
delete product.updatedAt;
});
await mongoose.connection.collection("products").insertMany(initial_data);
await mongoose.connection.close();
});
afterAll(async () => {
await browser.close();
});
describe("Testing the create product page title and content", () => {
it("should have the correct title", async () => {
const title = await page.title();
expect(
title,
`The title received "${title}" of the page is not correct, it should be "API-Experiment | Create Product". Change the title of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
options
).toBe("API-Experiment | Create Product");
});
it("should have the correct content title and description", async () => {
const title = await page.$eval(".title", (el) => el.textContent);
const description = await page.$eval(
".description",
(el) => el.textContent
);
expect(
title,
`The title received "${title}" of the page's header is not correct, it should be "Create a new product". Change the title of the page to match the expected one. You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("Create a new product");
expect(
description,
`The description received "${description}" of the page's header is not correct, it should be "Fill the form below to create a new product". Change the description of the page to match the expected one. You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("Fill the form below to create a new product");
});
});
describe("Testing the create product page form", () => {
it("should have the correct form fields", async () => {
const inputs = await page.$$("input");
expect(
inputs.length,
`The number of inputs in the create form is wrong, it should be 2`,
options
).toBe(2);
const textarea = await page.$$("textarea");
expect(
textarea.length,
`The number of textarea in the create form is wrong, it should be 1`,
options
).toBe(1);
const button = await page.$eval(
"form > button.btn.btn-primary",
(el) => el.textContent
);
expect(
button,
`The submit button is not present on the page, You can change it in the "web/views/products/create.ejs" file.`,
options
).toBeTruthy();
expect(
button,
`The submit button should have the text "Create", but it has "${button}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("Create");
});
it("should the right inputs names and types", async () => {
const name_input = await page.$eval("#name", (el) => ({
name: el.name,
type: el.type,
}));
expect(
name_input.name,
`The name input should have the name "name", but it has "${name_input.name}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("name");
expect(
name_input.type,
`The name input should have the type "text", but it has "${name_input.type}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("text");
const price_input = await page.$eval("#price", (el) => ({
name: el.name,
type: el.type,
}));
expect(
price_input.name,
`The price input should have the name "price", but it has "${price_input.name}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("price");
expect(
price_input.type,
`The price input should have the type "number", but it has "${price_input.type}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("number");
const description_input = await page.$eval("#description", (el) => ({
name: el.name,
rows: el.rows,
}));
expect(
description_input.name,
`The description textarea should have the name "description", but it has "${description_input.name}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("description");
expect(
description_input.rows,
`The description textarea should have "5" rows "number", but it has "${description_input.rows}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe(5);
});
it("should have the correct form action and method", async () => {
const form = await page.$eval("form", (el) => ({
action: el.action,
method: el.method,
}));
expect(
form.action,
`The form action should be "http://localhost:${process.env.PORT}/products/create", but it has "${form.action}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe(`http://localhost:${process.env.PORT}/products/create`);
expect(
form.method,
`The form method should be "POST", but it has "${form.method}", You can change it in the "web/views/products/create.ejs" file.`,
options
).toBe("post");
});
});
describe("Testing the create product page form submission", () => {
it("should create a new product", async () => {
await page.type("#name", "Test Product");
await page.type("#price", "100");
await page.type("#description", "Test Product Description");
await page.click("form > button.btn.btn-primary");
await new Promise((resolve) => setTimeout(resolve, 1000));
const message = await page.$eval("p.message", (el) => el.textContent);
expect(
message,
`The message received "${message}" of the page is not correct, it should be "Product created". Change the message of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
options
).toBe("Product created");
const newProduct = await page.$eval(
"table > tbody > tr:last-child",
(el) => ({
name: el.children[1].textContent,
price: el.children[2].textContent,
description: el.children[3].textContent,
})
);
expect(
newProduct,
`The test product created seems to be not in the table of products, make sure that the product after being created is added to the table of products. You can change it in the "controllers/web/product.controller.js" file.`
).toEqual({
name: "Test Product",
price: "$100",
description: "Test Product Description",
});
});
it("should not create a new product with empty fields", async () => {
await page.type("#name", "New Product");
await page.click("form > button.btn.btn-primary");
await new Promise((resolve) => setTimeout(resolve, 1000));
const message = await page.$eval("p.message", (el) => el.textContent);
expect(
message,
`The message received "${message}" of the page is not correct, it should be "Please fill all fields". Change the message of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
options
).toBe("Please fill all fields");
});
});
describe("Testing the create page image snapshot", () => {
it("should match the reference image", async () => {
if (!fs.existsSync("tests/web/images/create-product-page.png")) {
throw new Error(
`The reference image for the create product page does not exist, please import the image from the "tests/web/images/create-product-page.png"`
);
}
const image = await page.screenshot({ fullPage: true });
expect(
image,
`The image for the create product page is wrong, it should be the same as the "tests/web/images/__diff_output__/create-product-page-diff.png" image`
).toMatchImageSnapshot({
customDiffConfig: { threshold: 0.9 },
customSnapshotsDir: "tests/web/images",
customSnapshotIdentifier: "create-product-page",
});
});
});

View File

@ -1,342 +0,0 @@
const fs = require("fs");
const puppeteer = require("puppeteer");
const { toMatchImageSnapshot } = require("jest-image-snapshot");
const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
const mongoose = require("mongoose");
expect.extend({ toMatchImageSnapshot });
require("dotenv").config();
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
let browser;
let page;
let product;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: true,
slowMo: 0,
devtools: false,
defaultViewport: {
width: 1024,
height: 768,
},
});
page = await browser.newPage();
await page.setDefaultTimeout(10000);
await page.setDefaultNavigationTimeout(20000);
});
beforeEach(async () => {
mongoose.set("strictQuery", true);
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
await mongoose.connection.collection("products").deleteMany({});
initial_data.forEach((product) => {
delete product._id;
delete product.createdAt;
delete product.updatedAt;
});
await mongoose.connection.collection("products").insertMany(initial_data);
await mongoose.connection.close();
await page.goto(`http://localhost:${process.env.PORT}/products`);
await page.click("tbody tr:first-child a");
const url = await page.url();
const slug = url.split("/show/").pop();
product = initial_data.find((product) => product.slug === slug);
await page.goto(
`http://localhost:${process.env.PORT}/products/update/${slug}`
);
});
afterAll(async () => {
await browser.close();
});
describe("Testing the update page title and content", () => {
it("should have the correct title", async () => {
const title = await page.title();
expect(
title,
`The title received "${title}" of the page is not correct, it should be "API-Experiment | Update Product". Change the title of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
options
).toBe("API-Experiment | Update Product");
});
it("should have the correct content title and description", async () => {
const title = await page.$eval(".title", (el) => el.textContent);
const description = await page.$eval(
".description",
(el) => el.textContent
);
expect(
title,
`The title received "${title}" of the page's header is not correct, it should be "Update this product". Change the title of the page to match the expected one. You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("Update this product");
expect(
description,
`The description received "${description}" of the page's header is not correct, it should be "Fill the form below to update this product". Change the description of the page to match the expected one. You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("Fill the form below to update this product");
});
});
describe("Testing the create product page form", () => {
it("should have the correct form fields", async () => {
const inputs = await page.$$("input");
expect(
inputs.length,
`The number of inputs in the create form is wrong, it should be 2`,
options
).toBe(2);
const textarea = await page.$$("textarea");
expect(
textarea.length,
`The number of textarea in the create form is wrong, it should be 1`,
options
).toBe(1);
const button = await page.$eval(
"form > button.btn.btn-primary",
(el) => el.textContent
);
expect(
button,
`The submit button is not present on the page, You can change it in the "web/views/products/update.ejs" file.`,
options
).toBeTruthy();
expect(
button,
`The submit button should have the text "Update", but it has "${button}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("Update");
});
it("should the right inputs names and types", async () => {
const name_input = await page.$eval("#name", (el) => ({
name: el.name,
type: el.type,
value: el.value,
}));
expect(
name_input.name,
`The name input should have the name "name", but it has "${name_input.name}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("name");
expect(
name_input.type,
`The name input should have the type "text", but it has "${name_input.type}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("text");
expect(
name_input.value,
`The name input should have the value "${product.name}", but it has "${name_input.value}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe(product.name);
const price_input = await page.$eval("#price", (el) => ({
name: el.name,
type: el.type,
value: el.value,
}));
expect(
price_input.name,
`The price input should have the name "price", but it has "${price_input.name}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("price");
expect(
price_input.type,
`The price input should have the type "number", but it has "${price_input.type}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("number");
expect(
price_input.value,
`The price input should have the value "${product.price}", but it has "${price_input.value}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe(product.price.toString());
const description_input = await page.$eval("#description", (el) => ({
name: el.name,
rows: el.rows,
value: el.textContent.trim(),
}));
expect(
description_input.name,
`The description textarea should have the name "description", but it has "${description_input.name}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("description");
expect(
description_input.rows,
`The description textarea should have "5" rows "number", but it has "${description_input.rows}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe(5);
expect(
description_input.value,
`The description textarea should have the value "${product.description}", but it has "${description_input.value}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe(product.description);
});
it("should have the correct form action and method", async () => {
const form = await page.$eval("form", (el) => ({
action: el.action,
method: el.method,
}));
expect(
form.action,
`The form action should be "http://localhost:${process.env.PORT}/products/update/${product.slug}", but it has "${form.action}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe(
`http://localhost:${process.env.PORT}/products/update/${product.slug}`
);
expect(
form.method,
`The form method should be "POST", but it has "${form.method}", You can change it in the "web/views/products/update.ejs" file.`,
options
).toBe("post");
});
});
describe("Testing the create product page form submission", () => {
it("should update the product", async () => {
let nameInput = await page.$("#name");
let priceInput = await page.$("#price");
let descriptionInput = await page.$("#description");
await nameInput.click({ clickCount: 3 });
await nameInput.press("Backspace");
await priceInput.click({ clickCount: 3 });
await priceInput.press("Backspace");
await descriptionInput.click({ clickCount: 3 });
await descriptionInput.press("Backspace");
await nameInput.type("Updated product");
await priceInput.type("99.99");
await descriptionInput.type("Updated description");
await page.click("form > button.btn.btn-primary");
await new Promise((resolve) => setTimeout(resolve, 1000));
const url = await page.url();
expect(
url,
`The page url should be "http://localhost:${process.env.PORT}/products/update/${product.slug}", but it has "${url}", You can change it in the "controllers/web/products.controller.js" file.`,
options
).toBe(
`http://localhost:${process.env.PORT}/products/update/${product.slug}`
);
const message = await page.$eval("p.message", (el) => el.textContent);
expect(
message,
`The message should be "Product updated", but it has "${message}", You can change it in the "controllers/web/products.controller.js" file.`,
options
).toBe("Product updated");
const productName = await page.$eval(
".card-title",
(el) => el.textContent
);
const productPrice = await page.$eval(
".card-subtitle",
(el) => el.textContent
);
const productDescription = await page.$eval(
".card-text",
(el) => el.textContent
);
expect(
productName,
`The product after updated should have the new updated name, but it has "${productName}", You can change the update method in the "controllers/web/products.controller.js" file.`,
options
).toBe("Updated product");
expect(
productPrice,
`The product after updated should have the new updated price, but it has "${productPrice}", You can change the update method in the "controllers/web/products.controller.js" file.`,
options
).toBe("$99");
expect(
productDescription,
`The product after updated should have the new updated description, but it has "${productDescription}", You can change the update method in the "controllers/web/products.controller.js" file.`,
options
).toBe("Updated description");
});
it("should not update the product if the name is empty", async () => {
let nameInput = await page.$("#name");
await nameInput.click({ clickCount: 3 });
await nameInput.press("Backspace");
await page.click("form > button.btn.btn-primary");
await new Promise((resolve) => setTimeout(resolve, 1000));
const message = await page.$eval("p.message", (el) => el.textContent);
expect(
message,
`The message received "${message}" of the page is not correct, it should be "Please fill all fields". Change the message of the page to match the expected one. You can change it in the "controllers/web/product.controller.js" file.`,
options
).toBe("Please fill all fields");
});
it("should don't update the product if the product does not exist", async () => {
await page.goto(
`http://localhost:${process.env.PORT}/products/update/123thisproductdoenotexist`
);
const title = await page.title();
expect(
title,
`The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the GET updateProduct method return the error title if the product was not found`,
options
).toBe("API-Experiment | Error");
const statusCode = await page.$eval(".title", (el) => el.textContent);
expect(
statusCode,
`The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the GET updateProduct method return the error status code if the product was not found`,
options
).toBe("404");
const message = await page.$eval(".message", (el) => el.textContent);
expect(
message,
`The message "${message}" is wrong it should be "No product found" Make sure that the function handling the GET updateProduct method return the error message if the product was not found`,
options
).toBe("No product found");
});
});
describe("Testing the update product page image snapshot", () => {
it("should match the update product page image snapshot", async () => {
let nameInput = await page.$("#name");
let priceInput = await page.$("#price");
let descriptionInput = await page.$("#description");
await nameInput.click({ clickCount: 3 });
await nameInput.press("Backspace");
await priceInput.click({ clickCount: 3 });
await priceInput.press("Backspace");
await descriptionInput.click({ clickCount: 3 });
await descriptionInput.press("Backspace");
if (!fs.existsSync("tests/web/images/update-product-page.png")) {
throw new Error(
`The reference image for the update product page does not exist, please import the image from the "tests/web/images/update-product-page.png"`
);
}
const image = await page.screenshot({ fullPage: true });
expect(
image,
`The image for the update product page is wrong, it should be the same as the "tests/web/images/__diff_output__/update-product-page-diff.png" image`
).toMatchImageSnapshot({
customDiffConfig: { threshold: 0.9 },
customSnapshotsDir: "tests/web/images",
customSnapshotIdentifier: "update-product-page",
});
});
});

View File

@ -1,141 +0,0 @@
const fs = require("fs");
const puppeteer = require("puppeteer");
const { toMatchImageSnapshot } = require("jest-image-snapshot");
const initial_data = JSON.parse(fs.readFileSync("./initial_data.json"));
const mongoose = require("mongoose");
expect.extend({ toMatchImageSnapshot });
require("dotenv").config();
const options = {
showPrefix: false,
showMatcherMessage: true,
showStack: true,
};
let browser;
let page;
let product;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: true,
slowMo: 0,
devtools: false,
defaultViewport: {
width: 1024,
height: 768,
},
});
page = await browser.newPage();
await page.setDefaultTimeout(10000);
await page.setDefaultNavigationTimeout(20000);
});
beforeEach(async () => {
mongoose.set("strictQuery", true);
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
await mongoose.connection.collection("products").deleteMany({});
initial_data.forEach((product) => {
delete product._id;
delete product.createdAt;
delete product.updatedAt;
});
await mongoose.connection.collection("products").insertMany(initial_data);
await mongoose.connection.close();
await page.goto(`http://localhost:${process.env.PORT}/products`);
await page.click("tbody tr:first-child a");
const url = await page.url();
const slug = url.split("/show/").pop();
product = initial_data.find((product) => product.slug === slug);
});
afterAll(async () => {
await browser.close();
});
describe("Testing the delete form in the details page", () => {
it("should delete a product", async () => {
await page.click("form > button.btn");
await new Promise((resolve) => setTimeout(resolve, 1000));
const url = await page.url();
expect(
url,
`The url for deleting "${url}" a product is not correct, it should be "http://localhost:${process.env.PORT}/products/delete/${product.slug}"`,
options
).toBe(
`http://localhost:${process.env.PORT}/products/delete/${product.slug}`
);
const message = await page.$eval("p.message", (el) => el.textContent);
expect(
message,
`The message for deleting "${message}" a product is not correct, it should be "Product deleted"`,
options
).toBe("Product deleted");
await page.goto(`http://localhost:${process.env.PORT}/products`);
const products_data = await page.$$eval("tbody tr", (rows) =>
rows.map((row) => {
const [no, name, price, description, slug] = row.children;
return {
name: name.textContent,
price: parseFloat(price.textContent.replace("$", "")),
description: description.textContent,
slug: slug.children[0].href.split("/show/").pop(),
};
})
);
initial_data.forEach((product) => {
delete product._id;
delete product.createdAt;
delete product.updatedAt;
});
products_data.sort((a, b) => a.name.localeCompare(b.name));
initial_data.sort((a, b) => a.name.localeCompare(b.name));
expect(
products_data,
`The deleted product should not be in the list of products`,
options
).not.toContainEqual(product);
});
it("should don't delete the product if the product does not exist", async () => {
await page.setRequestInterception(true);
page.on("request", (interceptedRequest) => {
var data = {
method: "POST",
};
interceptedRequest.continue(data);
});
await page.goto(
`http://localhost:${process.env.PORT}/products/delete/1234567890`
);
const title = await page.title();
expect(
title,
`The title for the web page "${title}" is wrong it should be "API-Experiment | Error" Make sure that the function handling the POST deleteProduct method return the error title if the product was not found`,
options
).toBe("API-Experiment | Error");
const statusCode = await page.$eval(".title", (el) => el.textContent);
expect(
statusCode,
`The status code "${statusCode}" is wrong it should be "404" Make sure that the function handling the POST deleteProduct method return the error status code if the product was not found`,
options
).toBe("404");
const message = await page.$eval(".message", (el) => el.textContent);
expect(
message,
`The message "${message}" is wrong it should be "No product found" Make sure that the function handling the POST deleteProduct method return the error message if the product was not found`,
options
).toBe("No product found");
});
});

View File

@ -0,0 +1,166 @@
const request = require('supertest');
const mongoose = require('mongoose');
const {app} = require('../app');
const packages = require('../package.json');
const connectDB = require('../src/config/database');
describe("Pengujian konfigurasi aplikasi", () => {
it("Harus memiliki paket-paket development yang diperlukan", (done) => {
try {
expect(packages.devDependencies).toHaveProperty("jest");
} catch (error) {
throw new Error('Paket "jest" tidak ditemukan di devDependencies. Jalankan "npm i jest --save-dev".');
}
try {
expect(packages.devDependencies).toHaveProperty("nodemon");
} catch (error) {
throw new Error('Paket "nodemon" tidak ditemukan di devDependencies. Jalankan "npm i nodemon --save-dev".');
}
try {
expect(packages.devDependencies).toHaveProperty("supertest");
} catch (error) {
throw new Error('Paket "supertest" tidak ditemukan di devDependencies. Jalankan "npm i supertest --save-dev".');
}
try {
expect(packages.devDependencies).toHaveProperty("cross-env");
} catch (error) {
throw new Error('Paket "cross-env" tidak ditemukan di devDependencies. Jalankan "npm i cross-env --save-dev".');
}
done();
});
it("Harus memiliki paket-paket production yang diperlukan", (done) => {
try {
expect(packages.dependencies).toHaveProperty("dotenv");
} catch (error) {
throw new Error('Paket "dotenv" tidak ditemukan di dependencies. Jalankan "npm i dotenv --save".');
}
try {
expect(packages.dependencies).toHaveProperty("express");
} catch (error) {
throw new Error('Paket "express" tidak ditemukan di dependencies. Jalankan "npm i express --save".');
}
try {
expect(packages.dependencies).toHaveProperty("mongoose");
} catch (error) {
throw new Error('Paket "mongoose" tidak ditemukan di dependencies. Jalankan "npm i mongoose --save".');
}
done();
});
it("Harus memiliki nama yang benar", (done) => {
try {
expect(packages.name).toBe("restaurant-reservation");
} catch (error) {
throw new Error(`Nama aplikasi harus "restaurant-reservation", tetapi ditemukan "${packages.name}".`);
}
done();
});
it("Harus memiliki variabel lingkungan yang benar", (done) => {
try {
expect(process.env).toHaveProperty("MONGODB_URL");
} catch (error) {
throw new Error('Variabel lingkungan "MONGODB_URL" tidak ditemukan. Periksa file .env');
}
try {
expect(process.env).toHaveProperty("PORT");
} catch (error) {
throw new Error('Variabel lingkungan "PORT" tidak ditemukan. Periksa file .env');
}
try {
expect(process.env).toHaveProperty("MONGODB_URL_TEST");
} catch (error) {
throw new Error('Variabel lingkungan "MONGODB_URL_TEST" tidak ditemukan. Periksa file .env');
}
try {
expect(process.env).toHaveProperty("NODE_ENV");
} catch (error) {
throw new Error('Variabel lingkungan "NODE_ENV" tidak ditemukan. Periksa file .env');
}
done();
});
});
describe("Pengujian Middleware Aplikasi", () => {
it("Harus memiliki middleware yang diperlukan", (done) => {
let application_stack = [];
app._router.stack.forEach((element) => {
application_stack.push(element.name);
});
// Test for JSON middleware
expect(application_stack).toContain("jsonParser");
if (!application_stack.includes("jsonParser")) {
throw new Error("Aplikasi tidak menggunakan format JSON. Periksa file app.js");
}
// Test for Express middleware
expect(application_stack).toContain("expressInit");
if (!application_stack.includes("expressInit")) {
throw new Error("Aplikasi tidak menggunakan express framework. Periksa file app.js");
}
// Test for URL-encoded middleware
expect(application_stack).toContain("urlencodedParser");
if (!application_stack.includes("urlencodedParser")) {
throw new Error("Aplikasi tidak menggunakan format urlencoded. Periksa file app.js");
}
done();
});
});
describe('Pengujian Koneksi Database', () => {
it('Harus berhasil terhubung ke database MongoDB', async () => {
try {
const state = mongoose.connection.readyState;
expect(state).toBe(1);
} catch (error) {
throw new Error(`Gagal terhubung ke database: ${error.message}`);
}
});
});
describe('Pengujian API Utama', () => {
it('Harus mengembalikan pesan yang sesuai', async () => {
try {
const res = await request(app).get('/test');
expect(res.statusCode).toBe(200);
expect(res.body).toHaveProperty('status', 'success');
expect(res.body).toHaveProperty('message', 'Welcome to Restaurant Reservation API');
expect(res.body).toHaveProperty('version', '1.0.0');
} catch (error) {
throw new Error(`Terjadi kesalahan pada pengujian GET /: ${error.message}`);
}
});
});
async function disconnectDB() {
await mongoose.connection.close();
}
beforeAll(async () => {
try {
await connectDB();
dbConnected = true;
} catch (error) {
console.warn('⚠️ Gagal terhubung ke database:', error.message);
}
});
afterAll(async () => {
await disconnectDB();
});

View File

@ -0,0 +1,68 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../app");
const Menu = require("../src/models/menuModel");
const connectDB = require("../src/config/database");
describe("Pengujian Integrasi - API Menu", () => {
beforeAll(async () => {
await connectDB();
await Menu.deleteMany({}); // Pastikan database dalam keadaan kosong sebelum pengujian
});
beforeEach(async () => {
await Menu.deleteMany({}); // Hapus semua data sebelum setiap pengujian
});
afterAll(async () => {
await mongoose.connection.close(); // Tutup koneksi database setelah semua pengujian selesai
});
it("harus berhasil membuat item menu baru melalui API", async () => {
const newItem = {
name: "Pizza",
description: "Pizza keju yang lezat",
price: 12.99,
category: "main",
isAvailable: true,
};
const res = await request(app).post("/createMenu").send(newItem);
expect(res.status).toBe(201);
expect(res.body.name).toBe(newItem.name);
expect(res.body.price).toBe(newItem.price);
});
it("harus mengambil semua item menu melalui API", async () => {
await Menu.create([
{ name: "Burger", price: 9.99, category: "main", isAvailable: true },
{ name: "Salad", price: 5.99, category: "appetizer", isAvailable: true },
]);
const res = await request(app).get("/menu");
expect(res.status).toBe(200);
expect(res.body.length).toBe(2);
});
it("harus mengambil item menu berdasarkan kategori melalui API", async () => {
await Menu.create([
{ name: "Steak", price: 19.99, category: "main", isAvailable: true },
{ name: "Soda", price: 2.99, category: "beverage", isAvailable: true },
]);
const res = await request(app).get("/menu/main");
expect(res.status).toBe(200);
expect(res.body.length).toBe(1);
expect(res.body[0].name).toBe("Steak");
});
it("harus mengembalikan 404 jika kategori tidak ditemukan", async () => {
const res = await request(app).get("/menu/dessert");
expect(res.status).toBe(404);
expect(res.body.error).toBe("Menu with category 'dessert' not found");
});
});

View File

@ -0,0 +1,89 @@
const Menu = require("../src/models/menuModel");
const menuController = require("../src/controllers/menuController");
// Mock model Menu
jest.mock("../src/models/menuModel");
describe("Pengujian Unit - Controller Menu", () => {
let req, res;
beforeEach(() => {
jest.clearAllMocks();
req = {
body: {
name: "Item Uji",
description: "Deskripsi Uji",
price: 9.99,
category: "main",
isAvailable: true,
},
params: {
category: "main",
},
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};
});
// ✅ Pengujian Pembuatan Item Menu
describe("createMenuItem", () => {
it("harus berhasil membuat item menu", async () => {
const savedItem = { ...req.body, _id: "123" };
Menu.mockImplementation(() => ({
save: jest.fn().mockResolvedValue(savedItem),
}));
await menuController.createMenuItem(req, res);
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith(savedItem);
});
});
// ✅ Pengujian Mendapatkan Semua Item Menu
describe("getAllMenuItems", () => {
it("harus mengembalikan semua item menu dengan sukses", async () => {
const items = [
{ name: "Item 1", price: 9.99 },
{ name: "Item 2", price: 14.99 },
];
Menu.find.mockResolvedValue(items);
await menuController.getAllMenuItems(req, res);
expect(Menu.find).toHaveBeenCalledWith({});
expect(res.json).toHaveBeenCalledWith(items);
});
});
// ✅ Pengujian Mendapatkan Menu Berdasarkan Kategori
describe("getMenuByCategory", () => {
it("harus mengembalikan item untuk kategori yang valid", async () => {
const items = [
{ name: "Main Course 1", category: "main" },
{ name: "Main Course 2", category: "main" },
];
Menu.find.mockResolvedValue(items);
await menuController.getMenuByCategory(req, res);
expect(Menu.find).toHaveBeenCalledWith({ category: "main" });
expect(res.json).toHaveBeenCalledWith(items);
});
it("harus menangani kategori yang tidak ditemukan", async () => {
Menu.find.mockResolvedValue([]);
await menuController.getMenuByCategory(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
error: "Menu with category 'main' not found",
});
});
});
});

View File

@ -0,0 +1,252 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../app");
const Meja = require("../src/models/mejaModel");
const connectDB = require("../src/config/database");
describe("Pengujian Integrasi - API Meja", () => {
beforeAll(async () => {
await connectDB();
await Meja.deleteMany({}); // Pastikan database dalam keadaan kosong sebelum pengujian
});
beforeEach(async () => {
await Meja.deleteMany({}); // Hapus semua data sebelum setiap pengujian
});
afterAll(async () => {
await mongoose.connection.close(); // Tutup koneksi database setelah semua pengujian selesai
});
describe("POST add/meja", () => {
test("Harus berhasil membuat data meja baru", async () => {
const dataMeja = {
tableNumber: 1,
capacity: 4
};
const response = await request(app)
.post("/add/meja")
.send(dataMeja);
expect(response.status).toBe(201);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty("tableNumber", 1);
expect(response.body.data).toHaveProperty("capacity", 4);
expect(response.body.data).toHaveProperty("status", "available");
});
test("Harus gagal ketika data tableNumber tidak disediakan", async () => {
const dataMeja = {
capacity: 4
};
const response = await request(app)
.post("/add/meja")
.send(dataMeja);
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
expect(response.body.error).toBeDefined();
});
test("Harus gagal ketika nomor meja sudah ada", async () => {
// Buat meja pertama
await Meja.create({
tableNumber: 5,
capacity: 4
});
// Coba buat meja dengan nomor yang sama
const response = await request(app)
.post("/add/meja")
.send({
tableNumber: 5,
capacity: 2
});
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
});
});
describe("GET /meja", () => {
test("Harus mendapatkan daftar meja kosong", async () => {
const response = await request(app).get("/meja");
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toEqual([]);
});
test("Harus mendapatkan semua data meja", async () => {
// Tambahkan beberapa meja untuk pengujian
await Meja.create([
{ tableNumber: 1, capacity: 2 },
{ tableNumber: 2, capacity: 4 },
{ tableNumber: 3, capacity: 6 }
]);
const response = await request(app).get("/meja");
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.length).toBe(3);
expect(response.body.data[0]).toHaveProperty("tableNumber", 1);
expect(response.body.data[1]).toHaveProperty("tableNumber", 2);
expect(response.body.data[2]).toHaveProperty("tableNumber", 3);
});
});
describe("PUT /meja/:tableNumber/reserve", () => {
test("Harus berhasil memesan meja yang tersedia", async () => {
// Buat data meja terlebih dahulu
await Meja.create({
tableNumber: 10,
capacity: 4,
status: "available",
});
const response = await request(app)
.put("/meja/10/reserve")
.send({ customerName: "John Doe" }); // Tambahkan customerName
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty("tableNumber", 10);
expect(response.body.data).toHaveProperty("status", "reserved");
expect(response.body.data).toHaveProperty("customerName", "John Doe"); // Verifikasi customerName
});
test("Harus gagal memesan meja yang tidak ada", async () => {
const response = await request(app)
.put("/meja/99/reserve")
.send({ customerName: "John Doe" }); // Tambahkan customerName
expect(response.status).toBe(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe("Meja tidak tersedia");
});
test("Harus gagal memesan meja yang sudah dipesan", async () => {
// Buat meja yang sudah dipesan
await Meja.create({
tableNumber: 11,
capacity: 4,
status: "reserved",
});
const response = await request(app)
.put("/meja/11/reserve")
.send({ customerName: "John Doe" }); // Tambahkan customerName
expect(response.status).toBe(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe("Meja tidak tersedia");
});
test("Harus gagal memesan meja jika customerName tidak disediakan", async () => {
// Buat data meja terlebih dahulu
await Meja.create({
tableNumber: 14,
capacity: 4,
status: "available",
});
const response = await request(app)
.put("/meja/14/reserve")
.send({}); // Tidak menyertakan customerName
expect(response.status).toBe(400);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe("Nama pelanggan harus diisi");
});
});
describe("PUT /meja/:tableNumber/cancel", () => {
test("Harus berhasil membatalkan reservasi meja", async () => {
// Buat meja yang sudah dipesan
await Meja.create({
tableNumber: 12,
capacity: 6,
status: "reserved",
customerName: "John Doe", // Tambahkan customerName
});
const response = await request(app)
.put("/meja/12/cancel")
.send({});
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data).toHaveProperty("tableNumber", 12);
expect(response.body.data).toHaveProperty("status", "available");
expect(response.body.message).toBe("Reservation for table 12 has been cancelled");
});
test("Harus gagal membatalkan reservasi meja yang tidak ada", async () => {
const response = await request(app)
.put("/meja/99/cancel")
.send({});
expect(response.status).toBe(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe("Table not found or not currently reserved");
});
test("Harus gagal membatalkan reservasi meja dengan status available", async () => {
// Buat meja dengan status available
await Meja.create({
tableNumber: 13,
capacity: 4,
status: "available",
});
const response = await request(app)
.put("/meja/13/cancel")
.send({});
expect(response.status).toBe(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe("Table not found or not currently reserved");
});
});
describe("Skenario Alur Pengelolaan Meja", () => {
test("Harus menjalankan alur lengkap: membuat, memesan, dan membatalkan reservasi meja", async () => {
// 1. Membuat meja baru
const createResponse = await request(app)
.post("/add/meja")
.send({ tableNumber: 20, capacity: 4 });
expect(createResponse.status).toBe(201);
expect(createResponse.body.data).toHaveProperty("tableNumber", 20);
expect(createResponse.body.data).toHaveProperty("status", "available");
// 2. Memesan meja
const reserveResponse = await request(app)
.put("/meja/20/reserve")
.send({ customerName: "John Doe" }); // Tambahkan customerName
expect(reserveResponse.status).toBe(200);
expect(reserveResponse.body.data).toHaveProperty("status", "reserved");
expect(reserveResponse.body.data).toHaveProperty("customerName", "John Doe"); // Verifikasi customerName
// 3. Membatalkan reservasi
const cancelResponse = await request(app)
.put("/meja/20/cancel")
.send({});
expect(cancelResponse.status).toBe(200);
expect(cancelResponse.body.data).toHaveProperty("status", "available");
// 4. Verifikasi status akhir
const getResponse = await request(app).get("/meja");
expect(getResponse.status).toBe(200);
expect(getResponse.body.data.length).toBe(1);
expect(getResponse.body.data[0]).toHaveProperty("tableNumber", 20);
expect(getResponse.body.data[0]).toHaveProperty("status", "available");
});
});
});

View File

@ -0,0 +1,307 @@
const mongoose = require('mongoose');
const Meja = require('../src/models/mejaModel');
const mejaController = require('../src/controllers/mejaController');
// Mock Express req dan res objects
const mockRequest = (body = {}, params = {}) => ({
body,
params
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
// Mock the Meja model methods
jest.mock('../src/models/mejaModel');
describe('Meja Controller', () => {
afterEach(() => {
jest.clearAllMocks();
});
describe('createMeja', () => {
test('harus membuat meja baru dan mengembalikan status 201', async () => {
// Arrange
const mockMeja = {
tableNumber: 1,
capacity: 4,
status: 'available'
};
Meja.create.mockResolvedValue(mockMeja);
const req = mockRequest({ tableNumber: 1, capacity: 4 });
const res = mockResponse();
// Act
await mejaController.createMeja(req, res);
// Assert
expect(Meja.create).toHaveBeenCalledWith({
tableNumber: 1,
capacity: 4
});
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMeja
});
});
test('harus menangani error dan mengembalikan status 400', async () => {
// Arrange
const errorMessage = 'Validation error';
Meja.create.mockRejectedValue(new Error(errorMessage));
const req = mockRequest({ tableNumber: 1, capacity: 4 });
const res = mockResponse();
// Act
await mejaController.createMeja(req, res);
// Assert
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage
});
});
});
describe('getAllMeja', () => {
test('harus mengembalikan semua meja dengan status 200', async () => {
// Arrange
const mockMejaList = [
{ tableNumber: 1, capacity: 4, status: 'available' },
{ tableNumber: 2, capacity: 2, status: 'reserved' }
];
// Setup chaining untuk find().sort()
const mockSort = jest.fn().mockResolvedValue(mockMejaList);
const mockFind = jest.fn().mockReturnValue({ sort: mockSort });
Meja.find = mockFind;
const req = mockRequest();
const res = mockResponse();
// Act
await mejaController.getAllMeja(req, res);
// Assert
expect(Meja.find).toHaveBeenCalled();
expect(mockSort).toHaveBeenCalledWith({ tableNumber: 1 });
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMejaList
});
});
test('harus menangani error dan mengembalikan status 400', async () => {
// Arrange
const errorMessage = 'Database error';
// Setup chaining untuk find().sort() yang mengembalikan error
const mockSort = jest.fn().mockRejectedValue(new Error(errorMessage));
const mockFind = jest.fn().mockReturnValue({ sort: mockSort });
Meja.find = mockFind;
const req = mockRequest();
const res = mockResponse();
// Act
await mejaController.getAllMeja(req, res);
// Assert
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage
});
});
});
describe('reserveMeja', () => {
test('harus memesan meja yang tersedia dan mengembalikan status 200', async () => {
// Arrange
const tableNumber = '5';
const customerName = 'John Doe';
const mockMeja = {
tableNumber: 5,
capacity: 4,
status: 'reserved',
customerName: 'John Doe'
};
Meja.findOneAndUpdate.mockResolvedValue(mockMeja);
const req = mockRequest({ customerName }, { tableNumber });
const res = mockResponse();
// Act
await mejaController.reserveMeja(req, res);
// Assert
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber, status: 'available' },
{ status: 'reserved', customerName },
{ new: true }
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMeja
});
});
test('harus mengembalikan 404 ketika meja tidak tersedia', async () => {
// Arrange
const tableNumber = '5';
const customerName = 'John Doe';
Meja.findOneAndUpdate.mockResolvedValue(null);
const req = mockRequest({ customerName }, { tableNumber });
const res = mockResponse();
// Act
await mejaController.reserveMeja(req, res);
// Assert
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber, status: 'available' },
{ status: 'reserved', customerName },
{ new: true }
);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: 'Meja tidak tersedia'
});
});
test('harus mengembalikan 400 ketika customerName tidak disediakan', async () => {
// Arrange
const tableNumber = '5';
const req = mockRequest({ tableNumber }, {});
const res = mockResponse();
// Act
await mejaController.reserveMeja(req, res);
// Assert
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: 'Nama pelanggan harus diisi'
});
});
test('harus menangani error dan mengembalikan status 400', async () => {
// Arrange
const tableNumber = '5';
const customerName = 'John Doe';
const errorMessage = 'Database error';
Meja.findOneAndUpdate.mockRejectedValue(new Error(errorMessage));
const req = mockRequest({ customerName }, { tableNumber });
const res = mockResponse();
// Act
await mejaController.reserveMeja(req, res);
// Assert
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber, status: 'available' },
{ status: 'reserved', customerName },
{ new: true }
);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage
});
});
});
describe('cancelReservation', () => {
test('harus membatalkan reservasi dan mengembalikan status 200', async () => {
// Arrange
const tableNumber = '3';
const mockMeja = {
tableNumber: 3,
capacity: 4,
status: 'available',
customerName: ''
};
Meja.findOneAndUpdate.mockResolvedValue(mockMeja);
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
// Act
await mejaController.cancelReservation(req, res);
// Assert
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber, status: 'reserved' },
{ status: 'available', customerName: '', updatedAt: expect.any(Number) },
{ new: true }
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
message: `Reservation for table ${tableNumber} has been cancelled`,
data: mockMeja
});
});
test('harus mengembalikan 404 ketika meja tidak ditemukan atau tidak sedang dipesan', async () => {
// Arrange
const tableNumber = '3';
Meja.findOneAndUpdate.mockResolvedValue(null);
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
// Act
await mejaController.cancelReservation(req, res);
// Assert
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: 'Table not found or not currently reserved'
});
});
test('harus menangani error dan mengembalikan status 400', async () => {
// Arrange
const tableNumber = '3';
const errorMessage = 'Database error';
Meja.findOneAndUpdate.mockRejectedValue(new Error(errorMessage));
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
// Act
await mejaController.cancelReservation(req, res);
// Assert
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage
});
});
});
});

View File

@ -0,0 +1,34 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const connectDB = require("../../src/config/database");
describe("Module 1 - Database & Basic API", () => {
beforeAll(async () => {
await connectDB();
});
afterAll(async () => {
await mongoose.connection.close();
});
describe("Database Connection", () => {
test("harus berhasil terhubung dengan MongoDB", () => {
const connectionState = mongoose.connection.readyState;
expect(connectionState).toBe(1);
});
});
describe("Test API Route", () => {
test("harus mengembalikan format respons yang benar dari rute pengujian", async () => {
const response = await request(app).get("/test");
expect(response.status).toBe(200);
expect(response.body).toEqual({
status: "success",
message: "Welcome to Restaurant Reservation API",
version: "1.0.0",
});
});
});
});

View File

@ -0,0 +1,128 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const Menu = require("../../src/models/menuModel");
const connectDB = require("../../src/config/database");
describe("Module 2 - Menu API", () => {
beforeAll(async () => {
await connectDB();
});
beforeEach(async () => {
await Menu.deleteMany({});
});
afterAll(async () => {
await mongoose.connection.close();
});
describe("POST /createMenu", () => {
test("harus berhasil membuat item menu baru", async () => {
const menuItem = {
name: "Nasi Goreng",
description: "Nasi goreng dengan telur mata sapi dan ayam kampung",
price: 25000,
category: "main",
isAvailable: true,
};
const response = await request(app).post("/createMenu").send(menuItem);
expect(response.status).toBe(200);
expect(response.body).toHaveProperty("name", "Nasi Goreng");
expect(response.body).toHaveProperty("price", 25000);
expect(response.body).toHaveProperty("category", "main");
});
});
describe("GET /menu", () => {
test("harus mengembalikan seluruh item menu", async () => {
const firstItem = await Menu.create({
name: "Nasi Putih",
price: 5000,
category: "main",
isAvailable: true,
});
const secondItem = await Menu.create({
name: "Kerupuk Udang",
price: 3000,
category: "appetizer",
isAvailable: true,
});
const response = await request(app).get("/menu");
expect(response.status).toBe(200);
expect(response.body.length).toBeGreaterThanOrEqual(2);
const nasiInResponse = response.body.find(
(item) => item._id === firstItem._id.toString(),
);
const kerupukInResponse = response.body.find(
(item) => item._id === secondItem._id.toString(),
);
expect(nasiInResponse).toBeDefined();
expect(kerupukInResponse).toBeDefined();
expect(nasiInResponse).toHaveProperty("name", "Nasi Putih");
expect(kerupukInResponse).toHaveProperty("name", "Kerupuk Udang");
});
test("harus mengembalikan array kosong jika tidak ada item menu", async () => {
await Menu.deleteMany({});
const response = await request(app).get("/menu");
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBe(true);
expect(response.body.length).toBe(0);
});
});
describe("GET /menu/:category", () => {
test("harus mengembalikan item menu berdasarkan kategori yang ditentukan", async () => {
await Menu.deleteMany({});
await Menu.create([
{
name: "Nasi Rames",
price: 20000,
category: "main",
isAvailable: true,
},
{
name: "Gado-gado",
price: 15000,
category: "appetizer",
isAvailable: true,
},
{
name: "Soto Ayam",
price: 18000,
category: "main",
isAvailable: true,
},
]);
const response = await request(app).get("/menu/main");
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBe(true);
expect(response.body.length).toBe(2);
response.body.forEach((item) => {
expect(item.category).toBe("main");
});
});
test("harus mengembalikan status 404 jika kategori tidak ditemukan", async () => {
const response = await request(app).get("/menu/bukanmakanan");
expect(response.status).toBe(404);
expect(response.body).toHaveProperty(
"error",
"Menu with category 'bukanmakanan' not found",
);
});
});
});

View File

@ -0,0 +1,177 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const Meja = require("../../src/models/mejaModel");
const connectDB = require("../../src/config/database");
describe("Module 3 - Meja/Table API", () => {
beforeAll(async () => {
await connectDB();
});
beforeEach(async () => {
await Meja.deleteMany({});
});
afterAll(async () => {
await mongoose.connection.close();
});
describe("POST /createMeja", () => {
test("harus berhasil membuat meja baru", async () => {
const newTable = {
tableNumber: 5,
capacity: 4,
};
const response = await request(app).post("/createMeja").send(newTable);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty("success", true);
expect(response.body.data).toHaveProperty("tableNumber", 5);
expect(response.body.data).toHaveProperty("capacity", 4);
expect(response.body.data).toHaveProperty("status", "available");
});
});
describe("GET /meja", () => {
test("harus mengembalikan semua meja", async () => {
await Meja.create({ tableNumber: 1, capacity: 2 });
await Meja.create({ tableNumber: 2, capacity: 4 });
const response = await request(app).get("/meja");
expect(response.status).toBe(200);
expect(response.body).toHaveProperty("success", true);
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBe(2);
const returnedTables = response.body.data;
const returnedTable1 = returnedTables.find((t) => t.tableNumber === 1);
const returnedTable2 = returnedTables.find((t) => t.tableNumber === 2);
expect(returnedTable1 || returnedTable2).toBeDefined();
});
});
describe("PUT /meja/:tableNumber/reserve", () => {
test("harus berhasil memesan meja yang tersedia", async () => {
const tableNumber = 10;
await Meja.create({
tableNumber: tableNumber,
capacity: 4,
status: "available",
});
const tableBeforeReserve = await Meja.findOne({ tableNumber });
expect(tableBeforeReserve).toBeDefined();
expect(tableBeforeReserve.status).toBe("available");
const response = await request(app)
.put(`/meja/${tableNumber}/reserve`)
.send({ customerName: "Susilo Bambang" });
if (response.status !== 200) {
console.log("Unexpected status code:", response.status);
console.log("Response body:", response.body);
}
expect(response.body).toHaveProperty("success");
if (response.body.success) {
expect(response.body.data).toHaveProperty("customerName");
expect(response.body.data).toHaveProperty("status", "reserved");
expect(response.body.data.customerName).toBe("Susilo Bambang");
}
});
test("harus mengembalikan status 400 jika nama pelanggan tidak disediakan", async () => {
await Meja.create({
tableNumber: 11,
capacity: 4,
status: "available",
});
const response = await request(app).put("/meja/11/reserve").send({});
expect(response.status).toBe(400);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Nama pelanggan harus diisi",
);
});
test("harus mengembalikan status 404 jika meja tidak tersedia", async () => {
await Meja.create({
tableNumber: 12,
capacity: 4,
status: "reserved",
customerName: "Dewi Sartika",
});
const response = await request(app)
.put("/meja/12/reserve")
.send({ customerName: "Budi Sudarsono" });
expect(response.status).toBe(404);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty("error", "Meja tidak tersedia");
});
});
describe("PUT /meja/:tableNumber/cancel", () => {
test("harus berhasil membatalkan pemesanan meja", async () => {
// Create a reserved table first with correct status and customerName
await Meja.create({
tableNumber: 15,
capacity: 4,
status: "reserved",
customerName: "Anita Wijaya",
});
// Verify that the table was created with the right status
const createdTable = await Meja.findOne({ tableNumber: 15 });
expect(createdTable).toBeDefined();
expect(createdTable.status).toBe("reserved");
const response = await request(app).put("/meja/15/cancel").send({});
if (response.status !== 200) {
console.log("Unexpected status code for cancel:", response.status);
console.log("Cancel response body:", response.body);
}
expect(response.body).toHaveProperty("success");
});
test("harus mengembalikan status 404 jika meja tidak ditemukan", async () => {
const response = await request(app).put("/meja/99/cancel").send({});
expect(response.status).toBe(404);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Table not found or not currently reserved",
);
});
test("harus mengembalikan status 404 jika meja tidak dalam status dipesan", async () => {
// Create an available table first
await Meja.create({
tableNumber: 16,
capacity: 4,
status: "available",
});
const response = await request(app).put("/meja/16/cancel").send({});
expect(response.status).toBe(404);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Table not found or not currently reserved",
);
});
});
});

View File

@ -0,0 +1,165 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const Order = require("../../src/models/orderModel");
const Meja = require("../../src/models/mejaModel");
const Menu = require("../../src/models/menuModel");
const connectDB = require("../../src/config/database");
describe("Module 4 - Order API", () => {
let testMenuId1, testMenuId2;
beforeAll(async () => {
await connectDB();
const menu1 = await Menu.create({
name: "Nasi Goreng Spesial",
description: "Nasi goreng dengan telur, ayam, dan sayuran segar",
price: 25000,
category: "main",
isAvailable: true,
});
const menu2 = await Menu.create({
name: "Sate Ayam",
description: "Sate ayam dengan bumbu kacang khas Indonesia",
price: 20000,
category: "appetizer",
isAvailable: true,
});
testMenuId1 = menu1._id;
testMenuId2 = menu2._id;
});
beforeEach(async () => {
await Order.deleteMany({});
await Meja.deleteMany({});
});
afterAll(async () => {
await Menu.deleteMany({});
await Order.deleteMany({});
await Meja.deleteMany({});
await mongoose.connection.close();
});
describe("POST /createOrders", () => {
test("harus berhasil membuat pesanan baru", async () => {
const table = await Meja.create({
tableNumber: 3,
capacity: 4,
status: "available",
});
const orderData = {
tableNumber: 3,
items: [
{ menuId: testMenuId1, quantity: 1 },
{ menuId: testMenuId2, quantity: 2 },
],
};
const response = await request(app).post("/createOrders").send(orderData);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty("success", true);
expect(response.body.data).toHaveProperty("tableNumber", 3);
expect(response.body.data).toHaveProperty("total", 65000);
expect(response.body.data).toHaveProperty("status", "pending");
const updatedTable = await Meja.findOne({ tableNumber: 3 });
expect(updatedTable.status).toBe("reserved");
});
test("harus mengembalikan error ketika meja tidak tersedia", async () => {
const table = await Meja.create({
tableNumber: 4,
capacity: 4,
status: "reserved",
});
const orderData = {
tableNumber: 4,
items: [{ menuId: testMenuId1, quantity: 1 }],
};
const response = await request(app).post("/createOrders").send(orderData);
expect(response.status).toBe(400);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Meja tidak tersedia atau sedang dipesan",
);
});
});
describe("GET /orders", () => {
test("harus mengembalikan daftar pesanan", async () => {
await Meja.create({
tableNumber: 2,
capacity: 4,
status: "available",
});
const orderData = {
tableNumber: 2,
items: [
{ menuId: testMenuId1, quantity: 1 },
{ menuId: testMenuId2, quantity: 2 },
],
};
await request(app).post("/createOrders").send(orderData);
const response = await request(app).get("/orders");
expect(response.status).toBe(200);
expect(response.body).toHaveProperty("success", true);
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBeGreaterThan(0);
expect(response.body.data[0]).toHaveProperty("tableNumber", 2);
});
});
describe("PUT /orders/:orderId/status", () => {
test("harus mengubah status pesanan", async () => {
const table = await Meja.create({
tableNumber: 5,
capacity: 4,
status: "reserved",
});
const order = await Order.create({
tableNumber: 5,
items: [
{ menuId: testMenuId1, quantity: 1 },
{ menuId: testMenuId2, quantity: 2 },
],
total: 65000,
status: "pending",
});
const response = await request(app)
.put(`/orders/${order._id}/status`)
.send({ status: "completed" });
expect(response.status).toBe(200);
expect(response.body).toHaveProperty("success", true);
expect(response.body.data).toHaveProperty("status", "completed");
});
test("harus mengembalikan status 404 jika pesanan tidak ditemukan", async () => {
const nonExistentOrderId = new mongoose.Types.ObjectId();
const response = await request(app)
.put(`/orders/${nonExistentOrderId}/status`)
.send({ status: "completed" });
expect(response.status).toBe(404);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty("error", "Pesanan tidak ditemukan");
});
});
});

View File

@ -0,0 +1,94 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const Order = require("../../src/models/orderModel");
const Meja = require("../../src/models/mejaModel");
const Menu = require("../../src/models/menuModel");
const connectDB = require("../../src/config/database");
describe("Module 5 - Error Handling", () => {
let testMenuId;
beforeAll(async () => {
await connectDB();
// Create a test menu item
const menu = await Menu.create({
name: "Nasi Goreng",
description: "Nasi goreng spesial dengan telur dan ayam",
price: 25000,
category: "main",
isAvailable: true,
});
testMenuId = menu._id;
});
beforeEach(async () => {
// Clean collections before each test
await Order.deleteMany({});
await Meja.deleteMany({});
});
afterAll(async () => {
await Menu.deleteMany({});
await Order.deleteMany({});
await Meja.deleteMany({});
await mongoose.connection.close();
});
describe("POST /createOrders - Error handling", () => {
test("harus mengembalikan status 400 ketika meja tidak tersedia", async () => {
await Meja.create({
tableNumber: 7,
capacity: 4,
status: "reserved",
customerName: "Budi Santoso",
});
const orderData = {
tableNumber: 7,
items: [{ menuId: testMenuId, quantity: 2 }],
};
const response = await request(app).post("/createOrders").send(orderData);
expect(response.status).toBe(400);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Meja tidak tersedia atau sedang dipesan",
);
});
test("harus mengembalikan status 400 ketika item menu tidak valid", async () => {
// Create available table
await Meja.create({
tableNumber: 8,
capacity: 4,
status: "available",
});
const invalidMenuId = new mongoose.Types.ObjectId();
const orderData = {
tableNumber: 8,
items: [{ menuId: invalidMenuId, quantity: 1 }],
};
const response = await request(app).post("/createOrders").send(orderData);
expect(response.status).toBe(400);
expect(response.body).toHaveProperty("success", false);
expect(response.body.error).toContain("Beberapa item menu tidak valid");
});
});
describe("Global error handling", () => {
test("harus menangani rute yang tidak ada dengan status 404", async () => {
const response = await request(app).get("/non-existent-endpoint");
expect(response.status).toBe(404);
});
});
});

View File

@ -0,0 +1,103 @@
const mongoose = require("mongoose");
const connectDB = require("../../src/config/database");
const errorHandler = require("../../src/middleware/errorHandler");
const mockRequest = (body = {}, params = {}) => ({
body,
params,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
describe("Modul 1 - Unit Tests", () => {
beforeEach(() => {
jest.clearAllMocks();
console.error = jest.fn();
});
describe("Error Handler Middleware", () => {
test("harus menangani error dengan status code yang disediakan", () => {
const err = new Error("Bad Request Error");
err.statusCode = 400;
const req = mockRequest();
const res = mockResponse();
const next = jest.fn();
errorHandler(err, req, res, next);
expect(console.error).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Bad Request Error",
});
});
test("harus menggunakan default status code 500 jika tidak disediakan", () => {
const err = new Error("Server Error Without Status Code");
const req = mockRequest();
const res = mockResponse();
const next = jest.fn();
errorHandler(err, req, res, next);
expect(console.error).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Server Error Without Status Code",
});
});
});
describe("Custom Error Creation", () => {
test("harus dapat membuat custom error dengan status code", () => {
const createCustomError = (message, statusCode) => {
const error = new Error(message);
error.statusCode = statusCode;
return error;
};
const notFoundError = createCustomError("Resource not found", 404);
const validationError = createCustomError("Validation failed", 400);
expect(notFoundError).toBeInstanceOf(Error);
expect(notFoundError.message).toBe("Resource not found");
expect(notFoundError.statusCode).toBe(404);
expect(validationError).toBeInstanceOf(Error);
expect(validationError.message).toBe("Validation failed");
expect(validationError.statusCode).toBe(400);
});
test("harus dapat meneruskan error ke middleware error handler", () => {
const err = new Error("Test Error");
err.statusCode = 422;
const req = mockRequest();
const res = mockResponse();
const next = jest.fn();
next(err);
errorHandler(err, req, res, next);
expect(next).toHaveBeenCalledWith(err);
expect(res.status).toHaveBeenCalledWith(422);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Test Error",
});
});
});
describe("Express Middleware Structure", () => {
test("harus memiliki struktur errorHandler yang benar", () => {
expect(errorHandler).toBeInstanceOf(Function);
expect(errorHandler.length).toBe(4);
});
});
});

View File

@ -0,0 +1,155 @@
const Menu = require("../../src/models/menuModel");
const menuController = require("../../src/controllers/menuController");
jest.mock("../../src/models/menuModel");
jest.mock("../../src/controllers/menuController", () => {
const originalModule = jest.requireActual(
"../../src/controllers/menuController",
);
return {
...originalModule,
withCallback: (promise, callback) => {
return promise
.then((data) => callback(null, data))
.catch((err) => callback(err));
},
};
});
describe("Modul 2 - Menu Controller", () => {
let req, res;
beforeEach(() => {
jest.clearAllMocks();
req = {
body: {
name: "Nasi Goreng",
description: "Nasi goreng dengan telur mata sapi",
price: 25000,
category: "main",
isAvailable: true,
},
params: {
category: "main",
},
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};
});
describe("createMenuItem", () => {
test("harus berhasil membuat menu baru", (done) => {
const mockSavedMenu = { ...req.body, _id: "menu123" };
Menu.mockImplementation(() => ({
save: jest.fn().mockResolvedValue(mockSavedMenu),
}));
res.status = jest.fn().mockReturnThis();
res.json = jest.fn().mockImplementation(() => {
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith(mockSavedMenu);
done();
});
menuController.createMenuItem(req, res);
});
test("harus menangani error saat pembuatan menu", (done) => {
const errorMessage = "Validation error";
Menu.mockImplementation(() => ({
save: jest.fn().mockRejectedValue(new Error(errorMessage)),
}));
res.status = jest.fn().mockReturnThis();
res.json = jest.fn().mockImplementation(() => {
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({ error: errorMessage });
done();
});
menuController.createMenuItem(req, res);
});
});
describe("getAllMenuItems", () => {
test("harus mengembalikan semua menu", (done) => {
const mockItems = [
{ name: "Nasi Goreng", price: 25000, category: "main" },
{ name: "Sate Ayam", price: 20000, category: "appetizer" },
];
Menu.find.mockResolvedValue(mockItems);
res.json = jest.fn().mockImplementation((data) => {
expect(Menu.find).toHaveBeenCalledWith({});
expect(data).toEqual(mockItems);
done();
return res;
});
menuController.getAllMenuItems(req, res);
});
test("harus menangani error saat mengambil menu", (done) => {
const errorMessage = "Database error";
Menu.find.mockRejectedValue(new Error(errorMessage));
res.status = jest.fn().mockReturnThis();
res.json = jest.fn().mockImplementation(() => {
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({ error: errorMessage });
done();
return res;
});
menuController.getAllMenuItems(req, res);
});
});
describe("getMenuByCategory", () => {
test("harus mengembalikan menu berdasarkan kategori", (done) => {
const mockCategoryItems = [
{ name: "Nasi Goreng", price: 25000, category: "main" },
{ name: "Mie Goreng", price: 23000, category: "main" },
];
Menu.find.mockResolvedValue(mockCategoryItems);
res.json = jest.fn().mockImplementation((data) => {
expect(Menu.find).toHaveBeenCalledWith({ category: "main" });
expect(data).toEqual(mockCategoryItems);
done();
return res;
});
menuController.getMenuByCategory(req, res);
});
test("harus mengembalikan 404 ketika kategori tidak ditemukan", (done) => {
Menu.find.mockResolvedValue([]);
res.status = jest.fn().mockReturnThis();
res.json = jest.fn().mockImplementation(() => {
expect(Menu.find).toHaveBeenCalledWith({ category: "main" });
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
error: "Menu with category 'main' not found",
});
done();
return res;
});
menuController.getMenuByCategory(req, res);
});
});
});

View File

@ -0,0 +1,242 @@
const Meja = require("../../src/models/mejaModel");
const mejaController = require("../../src/controllers/mejaController");
const mockRequest = (body = {}, params = {}) => ({
body,
params,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
jest.mock("../../src/models/mejaModel");
describe("Modul 3 - Meja Controller", () => {
afterEach(() => {
jest.clearAllMocks();
});
describe("createMeja", () => {
test("harus berhasil membuat meja baru", async () => {
const mockMeja = {
tableNumber: 5,
capacity: 4,
status: "available",
};
Meja.create.mockResolvedValue(mockMeja);
const req = mockRequest({ tableNumber: 5, capacity: 4 });
const res = mockResponse();
await mejaController.createMeja(req, res);
expect(Meja.create).toHaveBeenCalledWith({
tableNumber: 5,
capacity: 4,
});
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMeja,
});
});
test("harus mengembalikan error 400 ketika validasi gagal", async () => {
const errorMessage = "Validation error";
Meja.create.mockRejectedValue(new Error(errorMessage));
const req = mockRequest({ tableNumber: 5 });
const res = mockResponse();
await mejaController.createMeja(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
describe("getAllMeja", () => {
test("harus mengembalikan semua data meja", async () => {
const mockMejaList = [
{ tableNumber: 1, capacity: 2, status: "available" },
{ tableNumber: 2, capacity: 4, status: "reserved" },
];
const mockSort = jest.fn().mockResolvedValue(mockMejaList);
const mockFind = jest.fn().mockReturnValue({ sort: mockSort });
Meja.find = mockFind;
const req = mockRequest();
const res = mockResponse();
await mejaController.getAllMeja(req, res);
expect(Meja.find).toHaveBeenCalled();
expect(mockSort).toHaveBeenCalledWith({ tableNumber: 1 });
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMejaList,
});
});
test("harus mengembalikan error 400 ketika terjadi kesalahan database", async () => {
const errorMessage = "Database error";
const mockSort = jest.fn().mockRejectedValue(new Error(errorMessage));
const mockFind = jest.fn().mockReturnValue({ sort: mockSort });
Meja.find = mockFind;
const req = mockRequest();
const res = mockResponse();
await mejaController.getAllMeja(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
describe("reserveMeja", () => {
test("harus berhasil mereservasi meja", async () => {
const tableNumber = "10";
const customerName = "Susilo Bambang";
const mockMeja = {
tableNumber: 10,
capacity: 4,
status: "reserved",
customerName: "Susilo Bambang",
};
Meja.findOneAndUpdate.mockResolvedValue(mockMeja);
const req = mockRequest({ customerName }, { tableNumber });
const res = mockResponse();
await mejaController.reserveMeja(req, res);
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber, status: "available" },
{ status: "reserved", customerName },
{ new: true },
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMeja,
});
});
test("harus mengembalikan error 400 ketika nama pelanggan tidak disediakan", async () => {
const tableNumber = "10";
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
await mejaController.reserveMeja(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Nama pelanggan harus diisi",
});
});
test("harus mengembalikan error 404 ketika meja tidak tersedia", async () => {
const tableNumber = "10";
const customerName = "Susilo Bambang";
Meja.findOneAndUpdate.mockResolvedValue(null);
const req = mockRequest({ customerName }, { tableNumber });
const res = mockResponse();
await mejaController.reserveMeja(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Meja tidak tersedia",
});
});
});
describe("cancelReservation", () => {
test("harus berhasil membatalkan reservasi meja", async () => {
const tableNumber = "15";
const mockMeja = {
tableNumber: 15,
capacity: 4,
status: "available",
customerName: "",
};
Meja.findOneAndUpdate.mockResolvedValue(mockMeja);
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
await mejaController.cancelReservation(req, res);
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber, status: "reserved" },
{
status: "available",
customerName: "",
updatedAt: expect.any(Number),
},
{ new: true },
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
message: `Reservation for table ${tableNumber} has been cancelled`,
data: mockMeja,
});
});
test("harus mengembalikan error 404 ketika meja tidak ditemukan", async () => {
const tableNumber = "99";
Meja.findOneAndUpdate.mockResolvedValue(null);
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
await mejaController.cancelReservation(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Table not found or not currently reserved",
});
});
test("harus mengembalikan error 400 ketika terjadi kesalahan database", async () => {
const tableNumber = "15";
const errorMessage = "Database error";
Meja.findOneAndUpdate.mockRejectedValue(new Error(errorMessage));
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
await mejaController.cancelReservation(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
});

View File

@ -0,0 +1,236 @@
const mongoose = require("mongoose");
const Order = require("../../src/models/orderModel");
const Meja = require("../../src/models/mejaModel");
const Menu = require("../../src/models/menuModel");
const orderController = require("../../src/controllers/orderController");
const mockRequest = (body = {}, params = {}) => ({
body,
params,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
jest.mock("../../src/models/orderModel");
jest.mock("../../src/models/mejaModel");
jest.mock("../../src/models/menuModel");
const mockObjectId = new mongoose.Types.ObjectId();
describe("Modul 4 - Order Controller", () => {
let req, res, next;
beforeEach(() => {
jest.clearAllMocks();
req = {
body: {
tableNumber: 3,
items: [
{ menuId: mockObjectId.toString(), quantity: 1 },
{ menuId: mockObjectId.toString(), quantity: 2 },
],
},
params: {
orderId: mockObjectId.toString(),
},
};
res = mockResponse();
next = jest.fn();
});
describe("createOrder", () => {
test("harus berhasil membuat pesanan baru", async () => {
const mockMeja = {
tableNumber: 3,
status: "available",
};
const mockMenuItems = [
{ _id: mockObjectId, price: 25000 },
{ _id: mockObjectId, price: 20000 },
];
const mockSavedOrder = {
_id: mockObjectId,
tableNumber: 3,
items: req.body.items,
total: 65000,
status: "pending",
};
Meja.findOne.mockResolvedValue(mockMeja);
Menu.find.mockResolvedValue(mockMenuItems);
Order.prototype.save = jest.fn().mockResolvedValue(mockSavedOrder);
Meja.findOneAndUpdate.mockResolvedValue({ status: "reserved" });
await orderController.createOrder(req, res, next);
expect(Meja.findOne).toHaveBeenCalledWith({
tableNumber: 3,
status: "available",
});
expect(Menu.find).toHaveBeenCalled();
expect(Order.prototype.save).toHaveBeenCalled();
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber: 3 },
{ status: "reserved" },
);
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockSavedOrder,
});
expect(next).not.toHaveBeenCalled();
});
test("harus menangani error ketika meja tidak tersedia", async () => {
Meja.findOne.mockResolvedValue(null);
await orderController.createOrder(req, res, next);
expect(Meja.findOne).toHaveBeenCalledWith({
tableNumber: 3,
status: "available",
});
expect(next).toHaveBeenCalledWith(expect.any(Error));
expect(next.mock.calls[0][0].message).toBe(
"Meja tidak tersedia atau sedang dipesan",
);
expect(next.mock.calls[0][0].statusCode).toBe(400);
});
test("harus menangani error ketika item menu tidak valid", async () => {
Meja.findOne.mockResolvedValue({ tableNumber: 3, status: "available" });
Menu.find.mockResolvedValue([]);
await orderController.createOrder(req, res, next);
expect(Menu.find).toHaveBeenCalled();
expect(next).toHaveBeenCalledWith(expect.any(Error));
expect(next.mock.calls[0][0].message).toBe(
"Beberapa item menu tidak valid",
);
expect(next.mock.calls[0][0].statusCode).toBe(400);
});
});
describe("getAllOrders", () => {
test("harus mengembalikan semua pesanan", async () => {
const mockOrders = [
{ _id: mockObjectId, tableNumber: 1, total: 50000, status: "pending" },
{
_id: mockObjectId,
tableNumber: 2,
total: 75000,
status: "completed",
},
];
const mockSort = jest.fn().mockResolvedValue(mockOrders);
Order.find = jest.fn().mockReturnValue({ sort: mockSort });
await orderController.getAllOrders(req, res);
expect(Order.find).toHaveBeenCalled();
expect(mockSort).toHaveBeenCalledWith({ createdAt: -1 });
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockOrders,
});
});
test("harus menangani error saat mengambil pesanan", async () => {
const errorMessage = "Database error";
Order.find = jest.fn().mockImplementation(() => {
throw new Error(errorMessage);
});
await orderController.getAllOrders(req, res);
expect(Order.find).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
describe("updateOrderStatus", () => {
test("harus berhasil mengupdate status pesanan", async () => {
req.body = { status: "completed" };
const mockUpdatedOrder = {
_id: mockObjectId,
tableNumber: 3,
status: "completed",
};
Order.findByIdAndUpdate.mockResolvedValue(mockUpdatedOrder);
Meja.findOneAndUpdate.mockResolvedValue({ status: "available" });
await orderController.updateOrderStatus(req, res);
expect(Order.findByIdAndUpdate).toHaveBeenCalledWith(
mockObjectId.toString(),
{ status: "completed" },
{ new: true },
);
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber: 3 },
{ status: "available" },
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockUpdatedOrder,
});
});
test("harus mengembalikan 404 ketika pesanan tidak ditemukan", async () => {
req.body = { status: "completed" };
Order.findByIdAndUpdate.mockResolvedValue(null);
await orderController.updateOrderStatus(req, res);
expect(Order.findByIdAndUpdate).toHaveBeenCalledWith(
mockObjectId.toString(),
{ status: "completed" },
{ new: true },
);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Pesanan tidak ditemukan",
});
});
test("harus menangani error saat mengupdate status", async () => {
req.body = { status: "completed" };
const errorMessage = "Database error";
Order.findByIdAndUpdate.mockRejectedValue(new Error(errorMessage));
await orderController.updateOrderStatus(req, res);
expect(Order.findByIdAndUpdate).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
});

View File

@ -0,0 +1,142 @@
const errorHandler = require("../../src/middleware/errorHandler");
const mongoose = require("mongoose");
const Order = require("../../src/models/orderModel");
const Meja = require("../../src/models/mejaModel");
const Menu = require("../../src/models/menuModel");
const orderController = require("../../src/controllers/orderController");
const mockRequest = (body = {}, params = {}) => ({
body,
params,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
jest.mock("../../src/models/orderModel");
jest.mock("../../src/models/mejaModel");
jest.mock("../../src/models/menuModel");
const mockObjectId = new mongoose.Types.ObjectId();
describe("Modul 5 - Error Handling", () => {
let req, res, next;
beforeEach(() => {
jest.clearAllMocks();
console.error = jest.fn();
req = {
body: {
tableNumber: 7,
items: [{ menuId: mockObjectId.toString(), quantity: 2 }],
},
params: {},
};
res = mockResponse();
next = jest.fn();
});
describe("Error Handler Middleware", () => {
test("harus menangani error dengan status code dari error", () => {
const error = new Error("Validation error");
error.statusCode = 400;
errorHandler(error, req, res, next);
expect(console.error).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Validation error",
});
});
test("harus menangani error tanpa status code dengan default 500", () => {
const error = new Error("Server error");
errorHandler(error, req, res, next);
expect(console.error).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Server error",
});
});
});
describe("Order Controller Error Handling", () => {
test("harus menangani error meja tidak tersedia dengan status 400", async () => {
Meja.findOne.mockResolvedValue(null);
await orderController.createOrder(req, res, next);
expect(next).toHaveBeenCalledWith(expect.any(Error));
expect(next.mock.calls[0][0].message).toBe(
"Meja tidak tersedia atau sedang dipesan",
);
expect(next.mock.calls[0][0].statusCode).toBe(400);
});
test("harus menangani error menu tidak valid dengan status 400", async () => {
Meja.findOne.mockResolvedValue({ tableNumber: 7, status: "available" });
Menu.find.mockResolvedValue([]);
await orderController.createOrder(req, res, next);
expect(next).toHaveBeenCalledWith(expect.any(Error));
expect(next.mock.calls[0][0].message).toBe(
"Beberapa item menu tidak valid",
);
expect(next.mock.calls[0][0].statusCode).toBe(400);
});
test("harus menangani exception dengan middleware error", async () => {
const errorMessage = "Database connection failed";
Meja.findOne.mockImplementation(() => {
throw new Error(errorMessage);
});
await orderController.createOrder(req, res, next);
expect(next).toHaveBeenCalledWith(expect.any(Error));
expect(next.mock.calls[0][0].message).toBe(errorMessage);
});
test("harus mengubah error menjadi format error response", () => {
const error = new Error("Custom error");
error.statusCode = 422;
errorHandler(error, req, res, next);
expect(res.status).toHaveBeenCalledWith(422);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Custom error",
});
});
});
describe("Custom Error Creation", () => {
test("harus membuat error dengan statusCode yang tepat", async () => {
const createError = (message, statusCode) => {
const error = new Error(message);
error.statusCode = statusCode;
return error;
};
const error = createError("Resource not found", 404);
expect(error).toBeInstanceOf(Error);
expect(error.message).toBe("Resource not found");
expect(error.statusCode).toBe(404);
});
});
});

View File

@ -0,0 +1,34 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const connectDB = require("../../src/config/database");
describe("Module 1 - Database & Basic API", () => {
beforeAll(async () => {
jest.spyOn(console, 'log').mockImplementation(() => { });
await connectDB();
});
afterAll(async () => {
await mongoose.connection.close();
});
describe("Database Connection", () => {
test("harus berhasil terhubung dengan MongoDB", () => {
const connectionState = mongoose.connection.readyState;
expect(connectionState).toBe(1);
});
});
describe("Test API Route", () => {
test("harus mengembalikan format respons yang benar dari rute pengujian", async () => {
const response = await request(app).get("/test");
expect(response.status).toBe(200);
expect(response.body).toEqual({
message: "Welcome to Restaurant Reservation API",
});
});
});
});

View File

@ -0,0 +1,130 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const Menu = require("../../src/models/menuModel");
const connectDB = require("../../src/config/database");
describe("Module 2 - Menu API", () => {
beforeAll(async () => {
jest.spyOn(console, 'log').mockImplementation(() => { });
await connectDB();
});
beforeEach(async () => {
await Menu.deleteMany({});
});
afterAll(async () => {
await mongoose.connection.close();
});
describe("POST /createMenu", () => {
test("harus berhasil membuat item menu baru", async () => {
const menuItem = {
name: "Nasi Goreng",
description: "Nasi goreng dengan telur mata sapi dan ayam kampung",
price: 25000,
category: "main",
isAvailable: true,
};
const response = await request(app).post("/createMenu").send(menuItem);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty("name", "Nasi Goreng");
expect(response.body).toHaveProperty("price", 25000);
expect(response.body).toHaveProperty("category", "main");
});
});
describe("GET /menu", () => {
test("harus mengembalikan seluruh item menu", async () => {
const firstItem = await Menu.create({
name: "Nasi Putih",
price: 5000,
category: "main",
isAvailable: true,
});
const secondItem = await Menu.create({
name: "Kerupuk Udang",
price: 3000,
category: "appetizer",
isAvailable: true,
});
const response = await request(app).get("/menu");
expect(response.status).toBe(200);
expect(response.body.length).toBeGreaterThanOrEqual(2);
const nasiInResponse = response.body.find(
(item) => item._id === firstItem._id.toString(),
);
const kerupukInResponse = response.body.find(
(item) => item._id === secondItem._id.toString(),
);
expect(nasiInResponse).toBeDefined();
expect(kerupukInResponse).toBeDefined();
expect(nasiInResponse).toHaveProperty("name", "Nasi Putih");
expect(kerupukInResponse).toHaveProperty("name", "Kerupuk Udang");
});
test("harus mengembalikan array kosong jika tidak ada item menu", async () => {
await Menu.deleteMany({});
const response = await request(app).get("/menu");
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBe(true);
expect(response.body.length).toBe(0);
});
});
describe("GET /menu/:category", () => {
test("harus mengembalikan item menu berdasarkan kategori yang ditentukan", async () => {
await Menu.deleteMany({});
await Menu.create([
{
name: "Nasi Rames",
price: 20000,
category: "main",
isAvailable: true,
},
{
name: "Gado-gado",
price: 15000,
category: "appetizer",
isAvailable: true,
},
{
name: "Soto Ayam",
price: 18000,
category: "main",
isAvailable: true,
},
]);
const response = await request(app).get("/menu/main");
expect(response.status).toBe(200);
expect(Array.isArray(response.body)).toBe(true);
expect(response.body.length).toBe(2);
response.body.forEach((item) => {
expect(item.category).toBe("main");
});
});
test("harus mengembalikan status 404 jika kategori tidak ditemukan", async () => {
const response = await request(app).get("/menu/bukanmakanan");
expect(response.status).toBe(404);
expect(response.body).toHaveProperty(
"error",
"Menu with category 'bukanmakanan' not found",
);
});
});
});

View File

@ -0,0 +1,180 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const Meja = require("../../src/models/mejaModel");
const connectDB = require("../../src/config/database");
describe("Module 3 - Meja/Table API", () => {
beforeAll(async () => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.spyOn(console, 'error').mockImplementation(() => { });
await connectDB();
});
beforeEach(async () => {
await Meja.deleteMany({});
});
afterAll(async () => {
await mongoose.connection.close();
});
describe("POST /createMeja", () => {
test("harus berhasil membuat meja baru", async () => {
const newTable = {
tableNumber: 5,
capacity: 4,
};
const response = await request(app).post("/createMeja").send(newTable);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty("success", true);
expect(response.body.data).toHaveProperty("tableNumber", 5);
expect(response.body.data).toHaveProperty("capacity", 4);
expect(response.body.data).toHaveProperty("status", "available");
});
});
describe("GET /meja", () => {
test("harus mengembalikan semua meja", async () => {
await Meja.create({ tableNumber: 1, capacity: 2 });
await Meja.create({ tableNumber: 2, capacity: 4 });
const response = await request(app).get("/meja");
expect(response.status).toBe(200);
expect(response.body).toHaveProperty("success", true);
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBe(2);
const returnedTables = response.body.data;
const returnedTable1 = returnedTables.find((t) => t.tableNumber === 1);
const returnedTable2 = returnedTables.find((t) => t.tableNumber === 2);
expect(returnedTable1 || returnedTable2).toBeDefined();
});
});
describe("PUT /meja/:tableNumber/reserve", () => {
test("harus berhasil memesan meja yang tersedia", async () => {
const tableNumber = 10;
await Meja.create({
tableNumber: tableNumber,
capacity: 4,
status: "available",
});
const tableBeforeReserve = await Meja.findOne({ tableNumber });
expect(tableBeforeReserve).toBeDefined();
expect(tableBeforeReserve.status).toBe("available");
const response = await request(app)
.put(`/meja/${tableNumber}/reserve`)
.send({ customerName: "Susilo Bambang" });
if (response.status !== 200) {
console.log("Unexpected status code:", response.status);
console.log("Response body:", response.body);
}
expect(response.body).toHaveProperty("success");
if (response.body.success) {
expect(response.body.data).toHaveProperty("customerName");
expect(response.body.data).toHaveProperty("status", "reserved");
expect(response.body.data.customerName).toBe("Susilo Bambang");
}
});
test("harus mengembalikan status 400 jika nama pelanggan tidak disediakan", async () => {
await Meja.create({
tableNumber: 11,
capacity: 4,
status: "available",
});
const response = await request(app).put("/meja/11/reserve").send({});
expect(response.status).toBe(400);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Nama pelanggan harus diisi",
);
});
test("harus mengembalikan status 404 jika meja tidak tersedia", async () => {
await Meja.create({
tableNumber: 12,
capacity: 4,
status: "reserved",
customerName: "Dewi Sartika",
});
const response = await request(app)
.put("/meja/12/reserve")
.send({ customerName: "Budi Sudarsono" });
expect(response.status).toBe(404);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty("error", "Meja tidak tersedia");
});
});
describe("PUT /meja/:tableNumber/cancel", () => {
test("harus berhasil membatalkan pemesanan meja", async () => {
// Create a reserved table first with correct status and customerName
await Meja.create({
tableNumber: 15,
capacity: 4,
status: "reserved",
customerName: "Anita Wijaya",
});
// Verify that the table was created with the right status
const createdTable = await Meja.findOne({ tableNumber: 15 });
expect(createdTable).toBeDefined();
expect(createdTable.status).toBe("reserved");
const response = await request(app).put("/meja/15/cancel").send({});
if (response.status !== 200) {
console.log("Unexpected status code for cancel:", response.status);
console.log("Cancel response body:", response.body);
}
expect(response.body).toHaveProperty("success");
});
test("harus mengembalikan status 404 jika meja tidak ditemukan", async () => {
const response = await request(app).put("/meja/99/cancel").send({});
expect(response.status).toBe(404);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Table not found or not currently reserved",
);
});
test("harus mengembalikan status 404 jika meja tidak dalam status dipesan", async () => {
// Create an available table first
await Meja.create({
tableNumber: 16,
capacity: 4,
status: "available",
});
const response = await request(app).put("/meja/16/cancel").send({});
expect(response.status).toBe(404);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Table not found or not currently reserved",
);
});
});
});

View File

@ -0,0 +1,171 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const Order = require("../../src/models/orderModel");
const Meja = require("../../src/models/mejaModel");
const Menu = require("../../src/models/menuModel");
const connectDB = require("../../src/config/database");
describe("Module 4 - Order API", () => {
let testMenuId1, testMenuId2;
beforeAll(async () => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.spyOn(console, 'error').mockImplementation(() => { });
await connectDB();
const menu1 = await Menu.create({
name: "Nasi Goreng Spesial",
description: "Nasi goreng dengan telur, ayam, dan sayuran segar",
price: 25000,
category: "main",
isAvailable: true,
});
const menu2 = await Menu.create({
name: "Sate Ayam",
description: "Sate ayam dengan bumbu kacang khas Indonesia",
price: 20000,
category: "appetizer",
isAvailable: true,
});
testMenuId1 = menu1._id;
testMenuId2 = menu2._id;
});
beforeEach(async () => {
await Order.deleteMany({});
await Meja.deleteMany({});
});
afterAll(async () => {
await Menu.deleteMany({});
await Order.deleteMany({});
await Meja.deleteMany({});
await mongoose.connection.close();
});
describe("POST /createOrders", () => {
test("harus berhasil membuat pesanan baru", async () => {
const table = await Meja.create({
tableNumber: 3,
capacity: 4,
status: "available",
});
const orderData = {
tableNumber: 3,
items: [
{ menuId: testMenuId1, quantity: 1 },
{ menuId: testMenuId2, quantity: 2 },
],
};
const response = await request(app).post("/createOrders").send(orderData);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty("success", true);
expect(response.body.data).toHaveProperty("tableNumber", 3);
expect(response.body.data).toHaveProperty("total", 65000);
expect(response.body.data).toHaveProperty("status", "pending");
const updatedTable = await Meja.findOne({ tableNumber: 3 });
expect(updatedTable.status).toBe("reserved");
});
test("harus mengembalikan error ketika meja tidak tersedia", async () => {
const table = await Meja.create({
tableNumber: 4,
capacity: 4,
status: "reserved",
});
const orderData = {
tableNumber: 4,
items: [{ menuId: testMenuId1, quantity: 1 }],
};
const response = await request(app).post("/createOrders").send(orderData);
console.log(response.status);
console.log(response.body);
expect(response.status).toBe(400);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Meja tidak tersedia atau sedang dipesan",
);
});
});
describe("GET /orders", () => {
test("harus mengembalikan daftar pesanan", async () => {
await Meja.create({
tableNumber: 2,
capacity: 4,
status: "available",
});
const orderData = {
tableNumber: 2,
items: [
{ menuId: testMenuId1, quantity: 1 },
{ menuId: testMenuId2, quantity: 2 },
],
};
await request(app).post("/createOrders").send(orderData);
const response = await request(app).get("/orders");
expect(response.status).toBe(200);
expect(response.body).toHaveProperty("success", true);
expect(Array.isArray(response.body.data)).toBe(true);
expect(response.body.data.length).toBeGreaterThan(0);
expect(response.body.data[0]).toHaveProperty("tableNumber", 2);
});
});
describe("PUT /orders/:orderId/status", () => {
test("harus mengubah status pesanan", async () => {
const table = await Meja.create({
tableNumber: 5,
capacity: 4,
status: "reserved",
});
const order = await Order.create({
tableNumber: 5,
items: [
{ menuId: testMenuId1, quantity: 1 },
{ menuId: testMenuId2, quantity: 2 },
],
total: 65000,
status: "pending",
});
const response = await request(app)
.put(`/orders/${order._id}/status`)
.send({ status: "completed" });
expect(response.status).toBe(200);
expect(response.body).toHaveProperty("success", true);
expect(response.body.data).toHaveProperty("status", "completed");
});
test("harus mengembalikan status 404 jika pesanan tidak ditemukan", async () => {
const nonExistentOrderId = new mongoose.Types.ObjectId();
const response = await request(app)
.put(`/orders/${nonExistentOrderId}/status`)
.send({ status: "completed" });
expect(response.status).toBe(404);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty("error", "Pesanan tidak ditemukan");
});
});
});

View File

@ -0,0 +1,97 @@
const mongoose = require("mongoose");
const request = require("supertest");
const { app } = require("../../app");
const Order = require("../../src/models/orderModel");
const Meja = require("../../src/models/mejaModel");
const Menu = require("../../src/models/menuModel");
const connectDB = require("../../src/config/database");
describe("Module 5 - Error Handling", () => {
let testMenuId;
beforeAll(async () => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.spyOn(console, 'error').mockImplementation(() => { });
await connectDB();
// Create a test menu item
const menu = await Menu.create({
name: "Nasi Goreng",
description: "Nasi goreng spesial dengan telur dan ayam",
price: 25000,
category: "main",
isAvailable: true,
});
testMenuId = menu._id;
});
beforeEach(async () => {
// Clean collections before each test
await Order.deleteMany({});
await Meja.deleteMany({});
});
afterAll(async () => {
await Menu.deleteMany({});
await Order.deleteMany({});
await Meja.deleteMany({});
await mongoose.connection.close();
});
describe("POST /createOrders - Error handling", () => {
test("harus mengembalikan status 400 ketika meja tidak tersedia", async () => {
await Meja.create({
tableNumber: 7,
capacity: 4,
status: "reserved",
customerName: "Budi Santoso",
});
const orderData = {
tableNumber: 7,
items: [{ menuId: testMenuId, quantity: 2 }],
};
const response = await request(app).post("/createOrders").send(orderData);
expect(response.status).toBe(400);
expect(response.body).toHaveProperty("success", false);
expect(response.body).toHaveProperty(
"error",
"Meja tidak tersedia atau sedang dipesan",
);
});
test("harus mengembalikan status 400 ketika item menu tidak valid", async () => {
// Create available table
await Meja.create({
tableNumber: 8,
capacity: 4,
status: "available",
});
const invalidMenuId = new mongoose.Types.ObjectId();
const orderData = {
tableNumber: 8,
items: [{ menuId: invalidMenuId, quantity: 1 }],
};
const response = await request(app).post("/createOrders").send(orderData);
expect(response.status).toBe(400);
expect(response.body).toHaveProperty("success", false);
expect(response.body.error).toContain("Beberapa item menu tidak valid");
});
});
describe("Global error handling", () => {
test("harus menangani rute yang tidak ada dengan status 404", async () => {
const response = await request(app).get("/non-existent-endpoint");
expect(response.status).toBe(404);
});
});
});

View File

@ -0,0 +1,104 @@
const mongoose = require("mongoose");
const connectDB = require("../../src/config/database");
const errorHandler = require("../../src/middleware/errorHandler");
const mockRequest = (body = {}, params = {}) => ({
body,
params,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
describe("Modul 1 - Unit Tests", () => {
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.clearAllMocks();
console.error = jest.fn();
});
describe("Error Handler Middleware", () => {
test("harus menangani error dengan status code yang disediakan", () => {
const err = new Error("Bad Request Error");
err.statusCode = 400;
const req = mockRequest();
const res = mockResponse();
const next = jest.fn();
errorHandler(err, req, res, next);
expect(console.error).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Bad Request Error",
});
});
test("harus menggunakan default status code 500 jika tidak disediakan", () => {
const err = new Error("Server Error Without Status Code");
const req = mockRequest();
const res = mockResponse();
const next = jest.fn();
errorHandler(err, req, res, next);
expect(console.error).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Server Error Without Status Code",
});
});
});
describe("Custom Error Creation", () => {
test("harus dapat membuat custom error dengan status code", () => {
const createCustomError = (message, statusCode) => {
const error = new Error(message);
error.statusCode = statusCode;
return error;
};
const notFoundError = createCustomError("Resource not found", 404);
const validationError = createCustomError("Validation failed", 400);
expect(notFoundError).toBeInstanceOf(Error);
expect(notFoundError.message).toBe("Resource not found");
expect(notFoundError.statusCode).toBe(404);
expect(validationError).toBeInstanceOf(Error);
expect(validationError.message).toBe("Validation failed");
expect(validationError.statusCode).toBe(400);
});
test("harus dapat meneruskan error ke middleware error handler", () => {
const err = new Error("Test Error");
err.statusCode = 422;
const req = mockRequest();
const res = mockResponse();
const next = jest.fn();
next(err);
errorHandler(err, req, res, next);
expect(next).toHaveBeenCalledWith(err);
expect(res.status).toHaveBeenCalledWith(422);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Test Error",
});
});
});
describe("Express Middleware Structure", () => {
test("harus memiliki struktur errorHandler yang benar", () => {
expect(errorHandler).toBeInstanceOf(Function);
expect(errorHandler.length).toBe(4);
});
});
});

View File

@ -0,0 +1,156 @@
const Menu = require("../../src/models/menuModel");
const menuController = require("../../src/controllers/menuController");
jest.mock("../../src/models/menuModel");
jest.mock("../../src/controllers/menuController", () => {
const originalModule = jest.requireActual(
"../../src/controllers/menuController",
);
return {
...originalModule,
withCallback: (promise, callback) => {
return promise
.then((data) => callback(null, data))
.catch((err) => callback(err));
},
};
});
describe("Modul 2 - Menu Controller", () => {
let req, res;
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.clearAllMocks();
req = {
body: {
name: "Nasi Goreng",
description: "Nasi goreng dengan telur mata sapi",
price: 25000,
category: "main",
isAvailable: true,
},
params: {
category: "main",
},
};
res = {
status: jest.fn().mockReturnThis(),
json: jest.fn(),
};
});
describe("createMenuItem", () => {
test("harus berhasil membuat menu baru", (done) => {
const mockSavedMenu = { ...req.body, _id: "menu123" };
Menu.mockImplementation(() => ({
save: jest.fn().mockResolvedValue(mockSavedMenu),
}));
res.status = jest.fn().mockReturnThis();
res.json = jest.fn().mockImplementation(() => {
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith(mockSavedMenu);
done();
});
menuController.createMenuItem(req, res);
});
test("harus menangani error saat pembuatan menu", (done) => {
const errorMessage = "Validation error";
Menu.mockImplementation(() => ({
save: jest.fn().mockRejectedValue(new Error(errorMessage)),
}));
res.status = jest.fn().mockReturnThis();
res.json = jest.fn().mockImplementation(() => {
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({ error: errorMessage });
done();
});
menuController.createMenuItem(req, res);
});
});
describe("getAllMenuItems", () => {
test("harus mengembalikan semua menu", (done) => {
const mockItems = [
{ name: "Nasi Goreng", price: 25000, category: "main" },
{ name: "Sate Ayam", price: 20000, category: "appetizer" },
];
Menu.find.mockResolvedValue(mockItems);
res.json = jest.fn().mockImplementation((data) => {
expect(Menu.find).toHaveBeenCalledWith({});
expect(data).toEqual(mockItems);
done();
return res;
});
menuController.getAllMenuItems(req, res);
});
test("harus menangani error saat mengambil menu", (done) => {
const errorMessage = "Database error";
Menu.find.mockRejectedValue(new Error(errorMessage));
res.status = jest.fn().mockReturnThis();
res.json = jest.fn().mockImplementation(() => {
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({ error: errorMessage });
done();
return res;
});
menuController.getAllMenuItems(req, res);
});
});
describe("getMenuByCategory", () => {
test("harus mengembalikan menu berdasarkan kategori", (done) => {
const mockCategoryItems = [
{ name: "Nasi Goreng", price: 25000, category: "main" },
{ name: "Mie Goreng", price: 23000, category: "main" },
];
Menu.find.mockResolvedValue(mockCategoryItems);
res.json = jest.fn().mockImplementation((data) => {
expect(Menu.find).toHaveBeenCalledWith({ category: "main" });
expect(data).toEqual(mockCategoryItems);
done();
return res;
});
menuController.getMenuByCategory(req, res);
});
test("harus mengembalikan 404 ketika kategori tidak ditemukan", (done) => {
Menu.find.mockResolvedValue([]);
res.status = jest.fn().mockReturnThis();
res.json = jest.fn().mockImplementation(() => {
expect(Menu.find).toHaveBeenCalledWith({ category: "main" });
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
error: "Menu with category 'main' not found",
});
done();
return res;
});
menuController.getMenuByCategory(req, res);
});
});
});

View File

@ -0,0 +1,256 @@
const Meja = require("../../src/models/mejaModel");
const mejaController = require("../../src/controllers/mejaController");
const mockRequest = (body = {}, params = {}) => ({
body,
params,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
jest.mock("../../src/models/mejaModel");
describe("Modul 3 - Meja Controller", () => {
afterEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.clearAllMocks();
});
describe("createMeja", () => {
test("harus berhasil membuat meja baru", async () => {
const mockMeja = {
tableNumber: 5,
capacity: 4,
status: "available",
};
Meja.create.mockResolvedValue(mockMeja);
const req = mockRequest({ tableNumber: 5, capacity: 4 });
const res = mockResponse();
await mejaController.createMeja(req, res);
expect(Meja.create).toHaveBeenCalledWith({
tableNumber: 5,
capacity: 4,
});
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMeja,
});
});
test("harus mengembalikan error 400 ketika validasi gagal", async () => {
const errorMessage = "Validation error";
Meja.create.mockRejectedValue(new Error(errorMessage));
const req = mockRequest({ tableNumber: 5 });
const res = mockResponse();
await mejaController.createMeja(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
describe("getAllMeja", () => {
test("harus mengembalikan semua data meja", async () => {
const mockMejaList = [
{ tableNumber: 1, capacity: 2, status: "available" },
{ tableNumber: 2, capacity: 4, status: "reserved" },
];
const mockSort = jest.fn().mockResolvedValue(mockMejaList);
const mockThen = jest.fn().mockImplementation(callback => Promise.resolve(callback(mockMejaList)));
const mockFindResult = { sort: mockSort, then: mockThen };
Meja.find = jest.fn().mockReturnValue(mockFindResult);
const req = mockRequest();
const res = mockResponse();
await mejaController.getAllMeja(req, res);
expect(Meja.find).toHaveBeenCalled();
// Don't test for sort being called specifically, as it may not be in all implementations
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMejaList,
});
});
test("harus mengembalikan error 400 ketika terjadi kesalahan database", async () => {
const errorMessage = "Database error";
// Create a more flexible mock that handles both implementations
const mockFindResult = {};
// For implementations with sort
const mockSort = jest.fn().mockRejectedValue(new Error(errorMessage));
mockFindResult.sort = mockSort;
// For implementations without sort
mockFindResult.then = jest.fn().mockImplementation(() =>
Promise.reject(new Error(errorMessage))
);
Meja.find = jest.fn().mockReturnValue(mockFindResult);
const req = mockRequest();
const res = mockResponse();
await mejaController.getAllMeja(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
describe("reserveMeja", () => {
test("harus berhasil mereservasi meja", async () => {
const tableNumber = "10";
const customerName = "Susilo Bambang";
const mockMeja = {
tableNumber: 10,
capacity: 4,
status: "reserved",
customerName: "Susilo Bambang",
};
Meja.findOneAndUpdate.mockResolvedValue(mockMeja);
const req = mockRequest({ customerName }, { tableNumber });
const res = mockResponse();
await mejaController.reserveMeja(req, res);
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber, status: "available" },
{ status: "reserved", customerName },
{ new: true },
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockMeja,
});
});
test("harus mengembalikan error 400 ketika nama pelanggan tidak disediakan", async () => {
const tableNumber = "10";
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
await mejaController.reserveMeja(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Nama pelanggan harus diisi",
});
});
test("harus mengembalikan error 404 ketika meja tidak tersedia", async () => {
const tableNumber = "10";
const customerName = "Susilo Bambang";
Meja.findOneAndUpdate.mockResolvedValue(null);
const req = mockRequest({ customerName }, { tableNumber });
const res = mockResponse();
await mejaController.reserveMeja(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Meja tidak tersedia",
});
});
});
describe("cancelReservation", () => {
test("harus berhasil membatalkan reservasi meja", async () => {
const tableNumber = "15";
const mockMeja = {
tableNumber: 15,
capacity: 4,
status: "available",
customerName: "",
};
Meja.findOneAndUpdate.mockResolvedValue(mockMeja);
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
await mejaController.cancelReservation(req, res);
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber, status: "reserved" },
{
status: "available",
customerName: "",
updatedAt: expect.any(Number),
},
{ new: true },
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
message: `Reservation for table ${tableNumber} has been cancelled`,
data: mockMeja,
});
});
test("harus mengembalikan error 404 ketika meja tidak ditemukan", async () => {
const tableNumber = "99";
Meja.findOneAndUpdate.mockResolvedValue(null);
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
await mejaController.cancelReservation(req, res);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Table not found or not currently reserved",
});
});
test("harus mengembalikan error 400 ketika terjadi kesalahan database", async () => {
const tableNumber = "15";
const errorMessage = "Database error";
Meja.findOneAndUpdate.mockRejectedValue(new Error(errorMessage));
const req = mockRequest({}, { tableNumber });
const res = mockResponse();
await mejaController.cancelReservation(req, res);
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
});

View File

@ -0,0 +1,227 @@
const mongoose = require("mongoose");
const Order = require("../../src/models/orderModel");
const Meja = require("../../src/models/mejaModel");
const Menu = require("../../src/models/menuModel");
const orderController = require("../../src/controllers/orderController");
const mockRequest = (body = {}, params = {}) => ({
body,
params,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
jest.mock("../../src/models/orderModel");
jest.mock("../../src/models/mejaModel");
jest.mock("../../src/models/menuModel");
const mockObjectId = new mongoose.Types.ObjectId();
describe("Modul 4 - Order Controller", () => {
let req, res, next;
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.clearAllMocks();
req = {
body: {
tableNumber: 3,
items: [
{ menuId: mockObjectId.toString(), quantity: 1 },
{ menuId: mockObjectId.toString(), quantity: 2 },
],
},
params: {
orderId: mockObjectId.toString(),
},
};
res = mockResponse();
next = jest.fn();
});
describe("createOrder", () => {
test("harus berhasil membuat pesanan baru", async () => {
const mockMeja = {
tableNumber: 3,
status: "available",
};
const mockMenuItems = [
{ _id: mockObjectId, price: 25000 },
{ _id: mockObjectId, price: 20000 },
];
const mockSavedOrder = {
_id: mockObjectId,
tableNumber: 3,
items: req.body.items,
total: 65000,
status: "pending",
};
Meja.findOne.mockResolvedValue(mockMeja);
Menu.find.mockResolvedValue(mockMenuItems);
Order.prototype.save = jest.fn().mockResolvedValue(mockSavedOrder);
Meja.findOneAndUpdate.mockResolvedValue({ status: "reserved" });
await orderController.createOrder(req, res, next);
expect(Meja.findOne).toHaveBeenCalledWith({
tableNumber: 3,
status: "available",
});
expect(Menu.find).toHaveBeenCalled();
expect(Order.prototype.save).toHaveBeenCalled();
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber: 3 },
{ status: "reserved" },
);
expect(res.status).toHaveBeenCalledWith(201);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockSavedOrder,
});
});
test("harus menangani error ketika meja tidak tersedia", async () => {
Meja.findOne.mockResolvedValue(null);
await orderController.createOrder(req, res, next);
expect(Meja.findOne).toHaveBeenCalledWith({
tableNumber: 3,
status: "available",
});
});
test("harus menangani error ketika item menu tidak valid", async () => {
Meja.findOne.mockResolvedValue({ tableNumber: 3, status: "available" });
Menu.find.mockResolvedValue([]);
await orderController.createOrder(req, res, next);
expect(Menu.find).toHaveBeenCalled();
});
});
describe("getAllOrders", () => {
test("harus mengembalikan semua pesanan", async () => {
const mockOrders = [
{ _id: mockObjectId, tableNumber: 1, total: 50000, status: "pending" },
{
_id: mockObjectId,
tableNumber: 2,
total: 75000,
status: "completed",
},
];
const mockSort = jest.fn().mockResolvedValue(mockOrders);
Order.find = jest.fn().mockReturnValue({ sort: mockSort });
await orderController.getAllOrders(req, res);
expect(Order.find).toHaveBeenCalled();
expect(mockSort).toHaveBeenCalledWith({ createdAt: -1 });
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockOrders,
});
});
test("harus menangani error saat mengambil pesanan", async () => {
const errorMessage = "Database error";
Order.find = jest.fn().mockImplementation(() => {
throw new Error(errorMessage);
});
await orderController.getAllOrders(req, res);
expect(Order.find).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
describe("updateOrderStatus", () => {
test("harus berhasil mengupdate status pesanan", async () => {
req.body = { status: "completed" };
const mockUpdatedOrder = {
_id: mockObjectId,
tableNumber: 3,
status: "completed",
};
Order.findByIdAndUpdate.mockResolvedValue(mockUpdatedOrder);
Meja.findOneAndUpdate.mockResolvedValue({ status: "available" });
await orderController.updateOrderStatus(req, res);
expect(Order.findByIdAndUpdate).toHaveBeenCalledWith(
mockObjectId.toString(),
{ status: "completed" },
{ new: true },
);
expect(Meja.findOneAndUpdate).toHaveBeenCalledWith(
{ tableNumber: 3 },
{ status: "available" },
);
expect(res.status).toHaveBeenCalledWith(200);
expect(res.json).toHaveBeenCalledWith({
success: true,
data: mockUpdatedOrder,
});
});
test("harus mengembalikan 404 ketika pesanan tidak ditemukan", async () => {
req.body = { status: "completed" };
Order.findByIdAndUpdate.mockResolvedValue(null);
await orderController.updateOrderStatus(req, res);
expect(Order.findByIdAndUpdate).toHaveBeenCalledWith(
mockObjectId.toString(),
{ status: "completed" },
{ new: true },
);
expect(res.status).toHaveBeenCalledWith(404);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Pesanan tidak ditemukan",
});
});
test("harus menangani error saat mengupdate status", async () => {
req.body = { status: "completed" };
const errorMessage = "Database error";
Order.findByIdAndUpdate.mockRejectedValue(new Error(errorMessage));
await orderController.updateOrderStatus(req, res);
expect(Order.findByIdAndUpdate).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: errorMessage,
});
});
});
});

View File

@ -0,0 +1,143 @@
const errorHandler = require("../../src/middleware/errorHandler");
const mongoose = require("mongoose");
const Order = require("../../src/models/orderModel");
const Meja = require("../../src/models/mejaModel");
const Menu = require("../../src/models/menuModel");
const orderController = require("../../src/controllers/orderController");
const mockRequest = (body = {}, params = {}) => ({
body,
params,
});
const mockResponse = () => {
const res = {};
res.status = jest.fn().mockReturnValue(res);
res.json = jest.fn().mockReturnValue(res);
return res;
};
jest.mock("../../src/models/orderModel");
jest.mock("../../src/models/mejaModel");
jest.mock("../../src/models/menuModel");
const mockObjectId = new mongoose.Types.ObjectId();
describe("Modul 5 - Error Handling", () => {
let req, res, next;
beforeEach(() => {
jest.spyOn(console, 'log').mockImplementation(() => { });
jest.clearAllMocks();
console.error = jest.fn();
req = {
body: {
tableNumber: 7,
items: [{ menuId: mockObjectId.toString(), quantity: 2 }],
},
params: {},
};
res = mockResponse();
next = jest.fn();
});
describe("Error Handler Middleware", () => {
test("harus menangani error dengan status code dari error", () => {
const error = new Error("Validation error");
error.statusCode = 400;
errorHandler(error, req, res, next);
expect(console.error).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(400);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Validation error",
});
});
test("harus menangani error tanpa status code dengan default 500", () => {
const error = new Error("Server error");
errorHandler(error, req, res, next);
expect(console.error).toHaveBeenCalled();
expect(res.status).toHaveBeenCalledWith(500);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Server error",
});
});
});
describe("Order Controller Error Handling", () => {
test("harus menangani error meja tidak tersedia dengan status 400", async () => {
Meja.findOne.mockResolvedValue(null);
await orderController.createOrder(req, res, next);
expect(next).toHaveBeenCalledWith(expect.any(Error));
expect(next.mock.calls[0][0].message).toBe(
"Meja tidak tersedia atau sedang dipesan",
);
expect(next.mock.calls[0][0].statusCode).toBe(400);
});
test("harus menangani error menu tidak valid dengan status 400", async () => {
Meja.findOne.mockResolvedValue({ tableNumber: 7, status: "available" });
Menu.find.mockResolvedValue([]);
await orderController.createOrder(req, res, next);
expect(next).toHaveBeenCalledWith(expect.any(Error));
expect(next.mock.calls[0][0].message).toBe(
"Beberapa item menu tidak valid",
);
expect(next.mock.calls[0][0].statusCode).toBe(400);
});
test("harus menangani exception dengan middleware error", async () => {
const errorMessage = "Database connection failed";
Meja.findOne.mockImplementation(() => {
throw new Error(errorMessage);
});
await orderController.createOrder(req, res, next);
expect(next).toHaveBeenCalledWith(expect.any(Error));
expect(next.mock.calls[0][0].message).toBe(errorMessage);
});
test("harus mengubah error menjadi format error response", () => {
const error = new Error("Custom error");
error.statusCode = 422;
errorHandler(error, req, res, next);
expect(res.status).toHaveBeenCalledWith(422);
expect(res.json).toHaveBeenCalledWith({
success: false,
error: "Custom error",
});
});
});
describe("Custom Error Creation", () => {
test("harus membuat error dengan statusCode yang tepat", async () => {
const createError = (message, statusCode) => {
const error = new Error(message);
error.statusCode = statusCode;
return error;
};
const error = createError("Resource not found", 404);
expect(error).toBeInstanceOf(Error);
expect(error.message).toBe("Resource not found");
expect(error.statusCode).toBe(404);
});
});
});

View File

@ -5,7 +5,7 @@
{{ __('Dashboard') }} {{ __('Dashboard') }}
</h2> </h2>
<button id="resetIntroBtn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> <button id="resetIntroBtn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
<i class="fas fa-question-circle mr-1"></i> Lihat Tutorial <i class="fas fa-question-circle mr-1"></i>Tutorial
</button> </button>
</div> </div>
</x-slot> </x-slot>

View File

@ -100,7 +100,7 @@ class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:
}, },
}, },
allowMultiple: false, allowMultiple: false,
acceptedFileTypes: ['application/x-zip-compressed'], acceptedFileTypes: ['application/x-zip-compressed', 'application/zip', 'application/octet-stream'],
fileValidateTypeDetectType: (source, type) => fileValidateTypeDetectType: (source, type) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
resolve(type); resolve(type);

View File

@ -5,7 +5,7 @@
{{ __('Project:') . ' ' . $project->title }} {{ __('Project:') . ' ' . $project->title }}
</h2> </h2>
<button id="resetIntroBtn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> <button id="resetIntroBtn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
<i class="fas fa-question-circle mr-1"></i> Lihat Tutorial <i class="fas fa-question-circle mr-1"></i> Tutorial
</button> </button>
</div> </div>
</x-slot> </x-slot>

View File

@ -162,7 +162,7 @@ function loadRankings(projectId) {
}, },
error: function(xhr) { error: function(xhr) {
console.error('Error loading rankings', xhr); console.error('Error loading rankings', xhr);
alert('Gagal memuat peringkat. Silakan coba lagi.'); alert('Failed to load ranking. Please try again..');
}, },
complete: function() { complete: function() {
$('#loading').hide(); $('#loading').hide();

View File

@ -5,7 +5,7 @@
{{ __('Submissions') }} {{ __('Submissions') }}
</h2> </h2>
<button id="resetIntroBtn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> <button id="resetIntroBtn" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
<i class="fas fa-question-circle mr-1"></i> Lihat Tutorial <i class="fas fa-question-circle mr-1"></i>Tutorial
</button> </button>
</div> </div>
</x-slot> </x-slot>