Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a21a5e1
testing: OM-409 test E2E created and passed
leoAchu16 May 4, 2026
fd6b7b8
testing: OM-324 test E2E created and passed
leoAchu16 May 4, 2026
57222a9
Merge branch 'dev' of https://github.com/CrisNAC/BackendMarketplace i…
leoAchu16 May 4, 2026
41793b5
testing: OM-326 test E2E created and passed
leoAchu16 May 5, 2026
09dfb5a
Merge branch 'dev' of https://github.com/CrisNAC/BackendMarketplace i…
leoAchu16 May 5, 2026
87d257e
testing: OM-487 test E2E created and passed
leoAchu16 May 5, 2026
81bb777
Merge branch 'dev' of https://github.com/CrisNAC/BackendMarketplace i…
leoAchu16 May 5, 2026
22eb1d7
testing: OM-492 test E2E created and passed
leoAchu16 May 5, 2026
ef46cac
Merge branch 'dev' of https://github.com/CrisNAC/BackendMarketplace i…
leoAchu16 May 5, 2026
15036c8
testing: OM-328 test E2E created and passed
leoAchu16 May 5, 2026
79aa344
testing: OM-493 test E2E created and passed
leoAchu16 May 5, 2026
8720c42
testing: OM-485 test E2E created and passed
leoAchu16 May 5, 2026
6778308
Merge branch 'dev' of https://github.com/CrisNAC/BackendMarketplace i…
leoAchu16 May 7, 2026
26a72e3
testing: OM-322 test E2E created and passed
leoAchu16 May 7, 2026
fc00ab5
testing: OM-489 test E2E created and passed
leoAchu16 May 8, 2026
12c418c
fix: test E2E delivery.test.js
leoAchu16 May 8, 2026
e3ad27a
testing: OM-490 test E2E created and passed
leoAchu16 May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions tests/e2e/assignments-complete.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { vi, describe, it, expect, beforeEach, beforeAll } from "vitest";

Check warning on line 1 in tests/e2e/assignments-complete.test.js

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this unused import of 'beforeAll'.

See more on https://sonarcloud.io/project/issues?id=CrisNAC_BackendMarketplace&issues=AZ4ImNuhJxyiBuPnRDDE&open=AZ4ImNuhJxyiBuPnRDDE&pullRequest=147
import request from "supertest";
import app from "../../src/app.js";
import { prisma } from "../../src/lib/prisma.js";

const mockTx = {
deliveryAssignments: {
update: vi.fn(),
},
orders: {
update: vi.fn(),
},
};

vi.mock("../../src/lib/prisma.js", () => ({
prisma: {
deliveryAssignments: {
findUnique: vi.fn(),
},
orders: {
update: vi.fn(),
},
$transaction: vi.fn((callbackOrArray) => {
if (typeof callbackOrArray === "function") {
return callbackOrArray(mockTx);
}

return Promise.all(callbackOrArray.map((operation) => Promise.resolve(operation)));
}),
},
}));

// Mock del middleware de autenticación para controlar req.user según cookie
vi.mock("../../src/config/jwt.config.js", () => ({
default: vi.fn((req, res, next) => {
const cookie = req.cookies?.userToken;

if (!cookie) {
return res.status(401).json({ error: { code: 401, message: "No autenticado" } });
}

// si la cookie es 'other-delivery' simulamos otro usuario
req.user = cookie === "other-delivery" ? { id_user: 99, role: "DELIVERY" } : { id_user: 5, role: "DELIVERY" };
next();
}),
}));

const authCookie = "userToken=mock-token";
const otherDeliveryCookie = "userToken=other-delivery";

const mockAcceptedAssignment = {
id_delivery_assignment: 1,
fk_order: 100,
fk_delivery: 5,
assignment_status: "ACCEPTED",
status: true,
delivery: { fk_user: 5 },
};

const mockDeliveredAssignment = {
id_delivery_assignment: 1,
fk_order: 100,
fk_delivery: 5,
assignment_status: "DELIVERED",
status: true,
};

describe("POST /api/assignments/:id/complete", () => {
beforeEach(() => {
vi.clearAllMocks();

mockTx.deliveryAssignments.update.mockReset();
mockTx.orders.update.mockReset();

prisma.deliveryAssignments.findUnique.mockReset();
prisma.orders.update.mockReset();
prisma.$transaction.mockReset();
});

it("retrona 200 tras completar asignación con exito", async () => {
prisma.deliveryAssignments.findUnique.mockResolvedValue(mockAcceptedAssignment);
mockTx.deliveryAssignments.update.mockResolvedValue(mockDeliveredAssignment);
mockTx.orders.update.mockResolvedValue({ id_order: 100, order_status: "DELIVERED" });

// prisma.$transaction mock invocado arriba devolverá el resultado de callback(mockTx)
const res = await request(app)
.post("/api/assignments/1/complete")
.set("Cookie", authCookie)
.send();

expect(res.status).toBe(200);
expect(res.body).toMatchObject({ assignment_status: "DELIVERED", id_delivery_assignment: 1 });

expect(mockTx.deliveryAssignments.update).toHaveBeenCalledWith(
expect.objectContaining({ where: { id_delivery_assignment: 1 }, data: { assignment_status: "DELIVERED" } })
);

expect(mockTx.orders.update).toHaveBeenCalledWith(
expect.objectContaining({ where: { id_order: 100 }, data: { order_status: "DELIVERED" } })
);
});

it("retorna 403 tras rechazar por usuario no asignado)", async () => {
prisma.deliveryAssignments.findUnique.mockResolvedValue(mockAcceptedAssignment);

const res = await request(app)
.post("/api/assignments/1/complete")
.set("Cookie", otherDeliveryCookie)
.send();

expect(res.status).toBe(403);
expect(res.body.error.message).toMatch(/permiso/i);
// Asegurarse de que no se actualizó nada en la transacción
expect(mockTx.deliveryAssignments.update).not.toHaveBeenCalled();
expect(mockTx.orders.update).not.toHaveBeenCalled();
});

it("retorna 409 tras intentar completar asignación con estado inválido", async () => {
prisma.deliveryAssignments.findUnique.mockResolvedValue({ ...mockAcceptedAssignment, assignment_status: "PENDING" });

const res = await request(app)
.post("/api/assignments/1/complete")
.set("Cookie", authCookie)
.send();

expect(res.status).toBe(409);
expect(res.body.error.message).toMatch(/solo se pueden completar asignaciones aceptadas/i);
expect(mockTx.deliveryAssignments.update).not.toHaveBeenCalled();
expect(mockTx.orders.update).not.toHaveBeenCalled();
});

it("retorna 404 tras intentar completar asignación no encontrada", async () => {
prisma.deliveryAssignments.findUnique.mockResolvedValue(null);

const res = await request(app)
.post("/api/assignments/999/complete")
.set("Cookie", authCookie)
.send();

expect(res.status).toBe(404);
expect(res.body.error.message).toMatch(/Asignación no encontrada/i);
expect(mockTx.deliveryAssignments.update).not.toHaveBeenCalled();
});
});
142 changes: 142 additions & 0 deletions tests/e2e/deliveries-list-and-disengagement.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { vi, describe, it, expect, beforeEach } from "vitest";
import request from "supertest";
import app from "../../src/app.js";
import { prisma } from "../../src/lib/prisma.js";


let deliveriesTxMock = { findFirst: vi.fn(), update: vi.fn() };

// Mock Prisma
vi.mock("../../src/lib/prisma.js", () => ({
prisma: {
stores: { findUnique: vi.fn() },
deliveries: { findMany: vi.fn() },
deliveryReviews: { findMany: vi.fn() },
$transaction: vi.fn(async (fn) => fn({ deliveries: deliveriesTxMock })),
},
}));

// Mock JWT middleware
vi.mock("../../src/config/jwt.config.js", () => ({
default: vi.fn((req, res, next) => {
const cookie = req.cookies?.userToken;

if (!cookie) return res.status(401).json({ error: { code: 401, message: "No autenticado" } });

if (cookie === "seller-token") req.user = { id_user: 2, email: "seller@test.com", role: "SELLER" };
else if (cookie === "delivery-token") req.user = { id_user: 3, email: "delivery@test.com", role: "DELIVERY" };
else req.user = { id_user: 1, email: "customer@test.com", role: "CUSTOMER" };

next();
}),
}));

const sellerCookie = "userToken=seller-token";

const mockStore = { id_store: 1, fk_user: 2, status: true, user: { id_user: 2, status: true } };
const mockStoreOtherOwner = { id_store: 1, fk_user: 99, status: true, user: { id_user: 99, status: true } };

// Mocks de deliveries
const mockDeliveryAvailable = {
id_delivery: 10,
delivery_status: "ACTIVE",
vehicle_type: "MOTORCYCLE",
user: { id_user: 2, name: "Juan Pérez", email: "juan@delivery.com", phone: "+56912345678" },
delivery_assignments: [],
delivery_reviews: [{ rating: 5 }, { rating: 4 }],
};

const mockDeliveryInDelivery = {
id_delivery: 11,
delivery_status: "ACTIVE",
vehicle_type: "CAR",
user: { id_user: 3, name: "María González", email: "maria@delivery.com", phone: "+56923456789" },
delivery_assignments: [
{ assignment_status: "ACCEPTED" },
{ assignment_status: "DELIVERED" },
{ assignment_status: "DELIVERED" },
],
delivery_reviews: [{ rating: 5 }, { rating: 5 }, { rating: 4 }],
};

const mockDeliveryUnavailable = {
id_delivery: 12,
delivery_status: "INACTIVE",
vehicle_type: "BICYCLE",
user: { id_user: 4, name: "Carlos Ramírez", email: "carlos@delivery.com", phone: "+56934567890" },
delivery_assignments: [{ assignment_status: "DELIVERED" }, { assignment_status: "REJECTED" }],
delivery_reviews: [],
};

describe("OM-322: GET /stores/:id/deliveries & DELETE /stores/:id/deliveries/:deliveryId", () => {
beforeEach(() => {
vi.clearAllMocks();
// reset tx mocks
deliveriesTxMock.findFirst = vi.fn();
deliveriesTxMock.update = vi.fn();
});

it("GET - retorna lista y estadísticas exitosamente", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStore);
prisma.deliveries.findMany.mockResolvedValue([mockDeliveryAvailable, mockDeliveryInDelivery, mockDeliveryUnavailable]);

const res = await request(app).get("/api/stores/1/deliveries").set("Cookie", sellerCookie);

expect(res.status).toBe(200);
expect(res.body.stats).toMatchObject({ total: 3, available: 1, inDelivery: 1 });
// ratings: [5,4] + [5,5,4] = [5,4,5,5,4] => 23/5 = 4.6
expect(res.body.stats.avgRating).toBe(4.6);
expect(Array.isArray(res.body.deliveries)).toBe(true);
expect(res.body.deliveries[0]).toMatchObject({ user: { id: 2, name: "Juan Pérez" }, vehicleType: "MOTORCYCLE" });
});

it("GET - 403 si el requester no es dueño del comercio", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStoreOtherOwner);

const res = await request(app).get("/api/stores/1/deliveries").set("Cookie", sellerCookie);
expect(res.status).toBe(403);
});

it("GET - 404 si la tienda no existe", async () => {
prisma.stores.findUnique.mockResolvedValue(null);

const res = await request(app).get("/api/stores/1/deliveries").set("Cookie", sellerCookie);
expect(res.status).toBe(404);
});

it("DELETE - desvincula delivery correctamente", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStore);
// en la transacción, findFirst devuelve sin assignments activos
deliveriesTxMock.findFirst.mockResolvedValue({ id_delivery: 10, delivery_assignments: [] });
deliveriesTxMock.update.mockResolvedValue({});

const res = await request(app).delete("/api/stores/1/deliveries/10").set("Cookie", sellerCookie);
expect(res.status).toBe(204);
expect(deliveriesTxMock.update).toHaveBeenCalledWith({ where: { id_delivery: 10 }, data: { fk_store: null, delivery_status: "INACTIVE" } });
});

it("DELETE - retorna 400 si tiene assignments activos (PENDING/ACCEPTED)", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStore);
deliveriesTxMock.findFirst.mockResolvedValue({ id_delivery: 10, delivery_assignments: [{ id_delivery_assignment: 1 }] });

const res = await request(app).delete("/api/stores/1/deliveries/10").set("Cookie", sellerCookie);
expect(res.status).toBe(400);
expect(res.body.message).toMatch(/entregas activas/i);
});

it("DELETE - 403 si el requester no es dueño del comercio", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStoreOtherOwner);

const res = await request(app).delete("/api/stores/1/deliveries/10").set("Cookie", sellerCookie);
expect(res.status).toBe(403);
});

it("DELETE - 404 si el delivery no pertenece a la tienda", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStore);
deliveriesTxMock.findFirst.mockResolvedValue(null);

const res = await request(app).delete("/api/stores/1/deliveries/999").set("Cookie", sellerCookie);
expect(res.status).toBe(404);
expect(res.body.message).toMatch(/delivery no encontrado/i);
});
Comment on lines +118 to +141

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Buscar el handler o controller del DELETE /stores/:id/deliveries/:deliveryId
rg -n --type=js -C5 'fk_store.*null|entregas activas|delivery no encontrado' --iglob '!tests/**'

Repository: CrisNAC/BackendMarketplace

Length of output: 15382


🏁 Script executed:

rg -n "ValidationError|NotFoundError" --type=js -A2 --iglob '!tests/**' | head -50

Repository: CrisNAC/BackendMarketplace

Length of output: 3795


🏁 Script executed:

# Find error middleware or error handler
rg -n "express.*error|app\.use.*error|Error.*handler" --type=js --iglob '!tests/**' | head -20

Repository: CrisNAC/BackendMarketplace

Length of output: 108


🏁 Script executed:

# Look for response formatting in error handlers
rg -n "res\.status.*json|res\.json.*error|res\.json.*message" --type=js -B2 -A2 --iglob '!tests/**' | grep -A2 -B2 "error\|message" | head -40

Repository: CrisNAC/BackendMarketplace

Length of output: 2420


🏁 Script executed:

# Find the error class definitions
cat src/lib/errors.js

Repository: CrisNAC/BackendMarketplace

Length of output: 1421


🏁 Script executed:

# View the complete errorHandler middleware
cat src/middlewares/errorHandler.js

Repository: CrisNAC/BackendMarketplace

Length of output: 2213


Las aserciones de error del DELETE usan res.body.message pero el middleware devuelve res.body.error.message

El middleware global errorHandler.js formatea todos los AppError (incluyendo ValidationError y NotFoundError) con estructura { error: { code, message } }. Las aserciones en L124 y L140 acceden directamente a res.body.message, lo que causará que fallen porque la propiedad no existe. Deben cambiar a res.body.error.message para coincidir con la respuesta real del servidor.

Cambios requeridos
  it("DELETE - retorna 400 si tiene assignments activos (PENDING/ACCEPTED)", async () => {
    prisma.stores.findUnique.mockResolvedValue(mockStore);
    deliveriesTxMock.findFirst.mockResolvedValue({ id_delivery: 10, delivery_assignments: [{ id_delivery_assignment: 1 }] });

    const res = await request(app).delete("/api/stores/1/deliveries/10").set("Cookie", sellerCookie);
    expect(res.status).toBe(400);
-   expect(res.body.message).toMatch(/entregas activas/i);
+   expect(res.body.error.message).toMatch(/entregas activas/i);
  });

  it("DELETE - 404 si el delivery no pertenece a la tienda", async () => {
    prisma.stores.findUnique.mockResolvedValue(mockStore);
    deliveriesTxMock.findFirst.mockResolvedValue(null);

    const res = await request(app).delete("/api/stores/1/deliveries/999").set("Cookie", sellerCookie);
    expect(res.status).toBe(404);
-   expect(res.body.message).toMatch(/delivery no encontrado/i);
+   expect(res.body.error.message).toMatch(/delivery no encontrado/i);
  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("DELETE - retorna 400 si tiene assignments activos (PENDING/ACCEPTED)", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStore);
deliveriesTxMock.findFirst.mockResolvedValue({ id_delivery: 10, delivery_assignments: [{ id_delivery_assignment: 1 }] });
const res = await request(app).delete("/api/stores/1/deliveries/10").set("Cookie", sellerCookie);
expect(res.status).toBe(400);
expect(res.body.message).toMatch(/entregas activas/i);
});
it("DELETE - 403 si el requester no es dueño del comercio", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStoreOtherOwner);
const res = await request(app).delete("/api/stores/1/deliveries/10").set("Cookie", sellerCookie);
expect(res.status).toBe(403);
});
it("DELETE - 404 si el delivery no pertenece a la tienda", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStore);
deliveriesTxMock.findFirst.mockResolvedValue(null);
const res = await request(app).delete("/api/stores/1/deliveries/999").set("Cookie", sellerCookie);
expect(res.status).toBe(404);
expect(res.body.message).toMatch(/delivery no encontrado/i);
});
it("DELETE - retorna 400 si tiene assignments activos (PENDING/ACCEPTED)", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStore);
deliveriesTxMock.findFirst.mockResolvedValue({ id_delivery: 10, delivery_assignments: [{ id_delivery_assignment: 1 }] });
const res = await request(app).delete("/api/stores/1/deliveries/10").set("Cookie", sellerCookie);
expect(res.status).toBe(400);
expect(res.body.error.message).toMatch(/entregas activas/i);
});
it("DELETE - 403 si el requester no es dueño del comercio", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStoreOtherOwner);
const res = await request(app).delete("/api/stores/1/deliveries/10").set("Cookie", sellerCookie);
expect(res.status).toBe(403);
});
it("DELETE - 404 si el delivery no pertenece a la tienda", async () => {
prisma.stores.findUnique.mockResolvedValue(mockStore);
deliveriesTxMock.findFirst.mockResolvedValue(null);
const res = await request(app).delete("/api/stores/1/deliveries/999").set("Cookie", sellerCookie);
expect(res.status).toBe(404);
expect(res.body.error.message).toMatch(/delivery no encontrado/i);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/deliveries-list-and-disengagement.test.js` around lines 118 - 141,
The tests in deliveries-list-and-disengagement.test.js assert error text against
res.body.message but the global errorHandler formats errors as { error: { code,
message } }; update the two failing assertions in the DELETE tests ("DELETE -
retorna 400 si tiene assignments activos (PENDING/ACCEPTED)" and "DELETE - 404
si el delivery no pertenece a la tienda") to check res.body.error.message
instead of res.body.message so they match the middleware output (leave the
status assertions as-is).

});
Loading
Loading