fix: allow removing orphaned machine agents

This commit is contained in:
Esdras Renan 2025-10-19 15:36:00 -03:00
parent 30dd503082
commit 515d1718a6
3 changed files with 107 additions and 5 deletions

View file

@ -0,0 +1,100 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
const mutationMock = vi.fn()
const deleteManyMock = vi.fn()
const assertAuthenticatedSession = vi.fn()
vi.mock("convex/browser", () => ({
ConvexHttpClient: vi.fn().mockImplementation(() => ({
mutation: mutationMock,
})),
}))
vi.mock("@/lib/prisma", () => ({
prisma: {
authUser: {
deleteMany: deleteManyMock,
},
},
}))
vi.mock("@/lib/auth-server", () => ({
assertAuthenticatedSession: assertAuthenticatedSession,
}))
describe("POST /api/admin/machines/delete", () => {
const originalEnv = process.env.NEXT_PUBLIC_CONVEX_URL
let restoreConsole: (() => void) | undefined
beforeEach(() => {
process.env.NEXT_PUBLIC_CONVEX_URL = "https://convex.example"
mutationMock.mockReset()
deleteManyMock.mockReset()
assertAuthenticatedSession.mockReset()
assertAuthenticatedSession.mockResolvedValue({
user: {
email: "admin@example.com",
name: "Admin",
role: "ADMIN",
tenantId: "tenant-1",
avatarUrl: null,
},
})
mutationMock.mockResolvedValueOnce({ _id: "user_123" })
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {})
restoreConsole = () => consoleSpy.mockRestore()
})
afterEach(() => {
process.env.NEXT_PUBLIC_CONVEX_URL = originalEnv
restoreConsole?.()
})
it("returns ok when the machine removal succeeds", async () => {
mutationMock.mockResolvedValueOnce({ ok: true })
const { POST } = await import("./route")
const response = await POST(
new Request("http://localhost/api/admin/machines/delete", {
method: "POST",
body: JSON.stringify({ machineId: "jn_machine" }),
})
)
expect(response.status).toBe(200)
await expect(response.json()).resolves.toEqual({ ok: true, machineMissing: false })
expect(deleteManyMock).toHaveBeenCalledWith({ where: { email: "machine-jn_machine@machines.local" } })
expect(mutationMock).toHaveBeenNthCalledWith(1, expect.anything(), expect.objectContaining({ email: "admin@example.com" }))
expect(mutationMock).toHaveBeenNthCalledWith(2, expect.anything(), expect.objectContaining({ machineId: "jn_machine" }))
})
it("still succeeds when the Convex machine is already missing", async () => {
mutationMock.mockRejectedValueOnce(new Error("Máquina não encontrada"))
const { POST } = await import("./route")
const response = await POST(
new Request("http://localhost/api/admin/machines/delete", {
method: "POST",
body: JSON.stringify({ machineId: "jn_machine" }),
})
)
expect(response.status).toBe(200)
await expect(response.json()).resolves.toEqual({ ok: true, machineMissing: true })
expect(deleteManyMock).toHaveBeenCalledWith({ where: { email: "machine-jn_machine@machines.local" } })
})
it("returns an error for other Convex failures", async () => {
mutationMock.mockRejectedValueOnce(new Error("timeout error"))
const { POST } = await import("./route")
const response = await POST(
new Request("http://localhost/api/admin/machines/delete", {
method: "POST",
body: JSON.stringify({ machineId: "jn_machine" }),
})
)
expect(response.status).toBe(500)
await expect(response.json()).resolves.toEqual({ error: "Falha ao remover máquina no Convex" })
expect(deleteManyMock).not.toHaveBeenCalled()
})
})

View file

@ -48,6 +48,7 @@ export async function POST(request: Request) {
return NextResponse.json({ error: "Falha ao identificar o administrador" }, { status: 500 })
}
let machineMissing = false
try {
await convex.mutation(api.machines.remove, {
machineId: parsed.data.machineId as Id<"machines">,
@ -56,16 +57,17 @@ export async function POST(request: Request) {
} catch (error) {
const message = error instanceof Error ? error.message : ""
if (message.includes("Máquina não encontrada")) {
return NextResponse.json({ error: "Máquina não encontrada" }, { status: 404 })
machineMissing = true
} else {
console.error("[machines.delete] Convex failure", error)
return NextResponse.json({ error: "Falha ao remover máquina no Convex" }, { status: 500 })
}
console.error("[machines.delete] Convex failure", error)
return NextResponse.json({ error: "Falha ao remover máquina no Convex" }, { status: 500 })
}
const machineEmail = `machine-${parsed.data.machineId}@machines.local`
await prisma.authUser.deleteMany({ where: { email: machineEmail } })
return NextResponse.json({ ok: true })
return NextResponse.json({ ok: true, machineMissing })
} catch (error) {
console.error("[machines.delete] Falha ao excluir", error)
return NextResponse.json({ error: "Falha ao excluir máquina" }, { status: 500 })