feat(assignments): partial return action and ReturnButton modal

This commit is contained in:
2026-06-25 21:18:34 +02:00
parent de40e0bf73
commit 2c03cd4d66
5 changed files with 494 additions and 24 deletions
@@ -0,0 +1,104 @@
import { beforeEach, describe, expect, it, vi } from "vitest"
import { en } from "@/i18n/dictionaries/en"
const mocks = vi.hoisted(() => ({
revalidatePath: vi.fn(),
getI18n: vi.fn(),
getAuthenticatedUserId: vi.fn(),
returnAssignmentUseCase: vi.fn(),
}))
vi.mock("next/cache", () => ({
revalidatePath: mocks.revalidatePath,
}))
vi.mock("@/i18n/server", () => ({
getI18n: mocks.getI18n,
}))
vi.mock("@/services/auth.service", () => ({
getAuthenticatedUserId: mocks.getAuthenticatedUserId,
}))
vi.mock("@/use-cases/assignment.use-cases", () => ({
returnAssignmentUseCase: mocks.returnAssignmentUseCase,
}))
import { returnAssignment } from "@/actions/assignment.actions"
describe("returnAssignment action", () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.getI18n.mockResolvedValue({ dictionary: en, locale: "en" })
mocks.getAuthenticatedUserId.mockResolvedValue("user-1")
})
it("returns validation errors for a missing assignment id", async () => {
const result = await returnAssignment({ id: "" })
expect(result).toEqual({
success: false,
errors: {
id: [en.inventory.assignments.schema.idRequired],
},
})
expect(mocks.returnAssignmentUseCase).not.toHaveBeenCalled()
})
it("forwards returns to the use case and revalidates on success", async () => {
mocks.returnAssignmentUseCase.mockResolvedValue({ success: true })
const result = await returnAssignment({
id: "assignment-1",
returns: [
{ assignmentLineId: "line-1", quantity: 2, notes: "Slightly damaged" },
],
})
expect(result).toEqual({
success: true as const,
message: en.inventory.assignments.actions.returnSuccess,
})
expect(mocks.returnAssignmentUseCase).toHaveBeenCalledWith({
id: "assignment-1",
actorId: "user-1",
returns: [
{ assignmentLineId: "line-1", quantity: 2, notes: "Slightly damaged" },
],
})
expect(mocks.revalidatePath).toHaveBeenCalledWith("/assignments")
})
it("surfaces use-case errors and skips revalidation on failure", async () => {
mocks.returnAssignmentUseCase.mockResolvedValue({
success: false,
errors: { error: ["errorConcurrent"] },
})
const result = await returnAssignment({
id: "assignment-1",
returns: [{ assignmentLineId: "line-1", quantity: 2 }],
})
expect(result).toEqual({
success: false as const,
errors: { error: ["errorConcurrent"] },
message: en.inventory.assignments.actions.returnFailure,
})
expect(mocks.revalidatePath).not.toHaveBeenCalled()
})
it("preserves the legacy full-return shortcut without returns", async () => {
mocks.returnAssignmentUseCase.mockResolvedValue({ success: true })
const result = await returnAssignment({ id: "assignment-1" })
expect(result.success).toBe(true)
expect(mocks.returnAssignmentUseCase).toHaveBeenCalledWith({
id: "assignment-1",
actorId: "user-1",
})
expect(mocks.revalidatePath).toHaveBeenCalledWith("/assignments")
})
})