fix: allow removing orphaned machine agents
This commit is contained in:
parent
30dd503082
commit
515d1718a6
3 changed files with 107 additions and 5 deletions
100
src/app/api/admin/machines/delete/route.test.ts
Normal file
100
src/app/api/admin/machines/delete/route.test.ts
Normal 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()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -48,6 +48,7 @@ export async function POST(request: Request) {
|
||||||
return NextResponse.json({ error: "Falha ao identificar o administrador" }, { status: 500 })
|
return NextResponse.json({ error: "Falha ao identificar o administrador" }, { status: 500 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let machineMissing = false
|
||||||
try {
|
try {
|
||||||
await convex.mutation(api.machines.remove, {
|
await convex.mutation(api.machines.remove, {
|
||||||
machineId: parsed.data.machineId as Id<"machines">,
|
machineId: parsed.data.machineId as Id<"machines">,
|
||||||
|
|
@ -56,16 +57,17 @@ export async function POST(request: Request) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : ""
|
const message = error instanceof Error ? error.message : ""
|
||||||
if (message.includes("Máquina não encontrada")) {
|
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`
|
const machineEmail = `machine-${parsed.data.machineId}@machines.local`
|
||||||
await prisma.authUser.deleteMany({ where: { email: machineEmail } })
|
await prisma.authUser.deleteMany({ where: { email: machineEmail } })
|
||||||
|
|
||||||
return NextResponse.json({ ok: true })
|
return NextResponse.json({ ok: true, machineMissing })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[machines.delete] Falha ao excluir", error)
|
console.error("[machines.delete] Falha ao excluir", error)
|
||||||
return NextResponse.json({ error: "Falha ao excluir máquina" }, { status: 500 })
|
return NextResponse.json({ error: "Falha ao excluir máquina" }, { status: 500 })
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
pool: "vmThreads",
|
pool: (process.env.VITEST_POOL as "threads" | "forks" | "vmThreads" | undefined) ?? "threads",
|
||||||
environment: "node",
|
environment: "node",
|
||||||
globals: true,
|
globals: true,
|
||||||
include: ["src/**/*.test.ts", "tests/**/*.test.ts"],
|
include: ["src/**/*.test.ts", "tests/**/*.test.ts"],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue