ajustes nos teste, adições e remoções
This commit is contained in:
parent
68ace0a858
commit
1ce402cdd7
4 changed files with 270 additions and 0 deletions
6
.github/workflows/quality-checks.yml
vendored
6
.github/workflows/quality-checks.yml
vendored
|
|
@ -12,6 +12,12 @@ jobs:
|
||||||
lint-test-build:
|
lint-test-build:
|
||||||
name: Lint, Test and Build
|
name: Lint, Test and Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
BETTER_AUTH_SECRET: test-secret
|
||||||
|
NEXT_PUBLIC_APP_URL: http://localhost:3000
|
||||||
|
BETTER_AUTH_URL: http://localhost:3000
|
||||||
|
NEXT_PUBLIC_CONVEX_URL: http://localhost:3210
|
||||||
|
DATABASE_URL: file:./prisma/db.dev.sqlite
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
|
||||||
58
src/app/api/machines/heartbeat/route.test.ts
Normal file
58
src/app/api/machines/heartbeat/route.test.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { describe, expect, it, beforeEach, vi } from "vitest"
|
||||||
|
|
||||||
|
import { api } from "@/convex/_generated/api"
|
||||||
|
|
||||||
|
const mutationMock = vi.fn()
|
||||||
|
|
||||||
|
vi.mock("@/server/convex-client", () => ({
|
||||||
|
createConvexClient: () => ({
|
||||||
|
mutation: mutationMock,
|
||||||
|
}),
|
||||||
|
ConvexConfigurationError: class extends Error {},
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe("POST /api/machines/heartbeat", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mutationMock.mockReset()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("accepts a valid payload and forwards it to Convex", async () => {
|
||||||
|
const payload = {
|
||||||
|
machineToken: "token-123",
|
||||||
|
status: "online",
|
||||||
|
metrics: { cpu: 42 },
|
||||||
|
}
|
||||||
|
mutationMock.mockResolvedValue({ ok: true })
|
||||||
|
|
||||||
|
const { POST } = await import("./route")
|
||||||
|
const response = await POST(
|
||||||
|
new Request("http://localhost/api/machines/heartbeat", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
const body = await response.json()
|
||||||
|
expect(body).toEqual({ ok: true })
|
||||||
|
expect(mutationMock).toHaveBeenCalledWith(api.machines.heartbeat, payload)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("rejects an invalid payload", async () => {
|
||||||
|
const { POST } = await import("./route")
|
||||||
|
const response = await POST(
|
||||||
|
new Request("http://localhost/api/machines/heartbeat", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({}),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toBe(400)
|
||||||
|
const body = await response.json()
|
||||||
|
expect(body).toHaveProperty("error", "Payload inválido")
|
||||||
|
expect(mutationMock).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
99
src/app/api/machines/inventory/route.test.ts
Normal file
99
src/app/api/machines/inventory/route.test.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { describe, expect, it, beforeEach, vi } from "vitest"
|
||||||
|
|
||||||
|
import { api } from "@/convex/_generated/api"
|
||||||
|
|
||||||
|
const mutationMock = vi.fn()
|
||||||
|
|
||||||
|
vi.mock("@/server/convex-client", () => ({
|
||||||
|
createConvexClient: () => ({
|
||||||
|
mutation: mutationMock,
|
||||||
|
}),
|
||||||
|
ConvexConfigurationError: class extends Error {},
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe("POST /api/machines/inventory", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
mutationMock.mockReset()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("accepts the token mode payload", async () => {
|
||||||
|
const payload = {
|
||||||
|
machineToken: "token-123",
|
||||||
|
hostname: "machine",
|
||||||
|
metrics: { cpu: 50 },
|
||||||
|
}
|
||||||
|
mutationMock.mockResolvedValue({ ok: true, machineId: "machine-123" })
|
||||||
|
|
||||||
|
const { POST } = await import("./route")
|
||||||
|
const response = await POST(
|
||||||
|
new Request("http://localhost/api/machines/inventory", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(mutationMock).toHaveBeenCalledWith(
|
||||||
|
api.machines.heartbeat,
|
||||||
|
expect.objectContaining({
|
||||||
|
machineToken: "token-123",
|
||||||
|
hostname: "machine",
|
||||||
|
metrics: { cpu: 50 },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const body = await response.json()
|
||||||
|
expect(body).toEqual({ ok: true, machineId: "machine-123" })
|
||||||
|
})
|
||||||
|
|
||||||
|
it("accepts the provisioning mode payload", async () => {
|
||||||
|
const payload = {
|
||||||
|
provisioningCode: "a".repeat(32),
|
||||||
|
hostname: "machine",
|
||||||
|
os: { name: "Linux" },
|
||||||
|
macAddresses: ["00:11:22:33"],
|
||||||
|
serialNumbers: [],
|
||||||
|
}
|
||||||
|
mutationMock.mockResolvedValue({ ok: true, status: "updated", machineId: "machine-987" })
|
||||||
|
|
||||||
|
const { POST } = await import("./route")
|
||||||
|
const response = await POST(
|
||||||
|
new Request("http://localhost/api/machines/inventory", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
expect(mutationMock).toHaveBeenCalledWith(
|
||||||
|
api.machines.upsertInventory,
|
||||||
|
expect.objectContaining({
|
||||||
|
provisioningCode: "a".repeat(32),
|
||||||
|
hostname: "machine",
|
||||||
|
os: { name: "Linux" },
|
||||||
|
macAddresses: ["00:11:22:33"],
|
||||||
|
serialNumbers: [],
|
||||||
|
registeredBy: "agent:inventory",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const body = await response.json()
|
||||||
|
expect(body).toEqual({ ok: true, machineId: "machine-987", status: "updated" })
|
||||||
|
})
|
||||||
|
|
||||||
|
it("rejects unknown payloads", async () => {
|
||||||
|
const { POST } = await import("./route")
|
||||||
|
const response = await POST(
|
||||||
|
new Request("http://localhost/api/machines/inventory", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ hostname: "machine" }),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(response.status).toBe(400)
|
||||||
|
const body = await response.json()
|
||||||
|
expect(body).toEqual({ error: "Formato de payload não suportado" })
|
||||||
|
expect(mutationMock).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
107
src/app/api/machines/session/route.test.ts
Normal file
107
src/app/api/machines/session/route.test.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
import { describe, expect, it, beforeEach, vi } from "vitest"
|
||||||
|
import { NextRequest } from "next/server"
|
||||||
|
|
||||||
|
import { MACHINE_CTX_COOKIE, serializeMachineCookie } from "@/server/machines/context"
|
||||||
|
|
||||||
|
const mockAssertSession = vi.fn()
|
||||||
|
vi.mock("@/lib/auth-server", () => ({
|
||||||
|
assertAuthenticatedSession: mockAssertSession,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockCreateConvexClient = vi.fn()
|
||||||
|
vi.mock("@/server/convex-client", () => {
|
||||||
|
class ConvexConfigurationError extends Error {}
|
||||||
|
return {
|
||||||
|
createConvexClient: mockCreateConvexClient,
|
||||||
|
ConvexConfigurationError,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockCookieStore = {
|
||||||
|
get: vi.fn<() => unknown, []>(),
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mock("next/headers", () => ({
|
||||||
|
cookies: vi.fn(async () => mockCookieStore),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe("GET /api/machines/session", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks()
|
||||||
|
mockCookieStore.get.mockReturnValue(undefined)
|
||||||
|
mockCreateConvexClient.mockReturnValue({
|
||||||
|
query: vi.fn(),
|
||||||
|
mutation: vi.fn(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 403 when the current session is not a machine", async () => {
|
||||||
|
mockAssertSession.mockResolvedValue({ user: { role: "admin" } })
|
||||||
|
|
||||||
|
const { GET } = await import("./route")
|
||||||
|
const response = await GET(new NextRequest("https://example.com/api/machines/session"))
|
||||||
|
|
||||||
|
expect(response.status).toBe(403)
|
||||||
|
const payload = await response.json()
|
||||||
|
expect(payload).toEqual({ error: "Sessão de máquina não encontrada." })
|
||||||
|
expect(mockCreateConvexClient).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns machine context and sets cookie when lookup succeeds", async () => {
|
||||||
|
mockAssertSession.mockResolvedValue({
|
||||||
|
user: { role: "machine", email: "device@example.com" },
|
||||||
|
})
|
||||||
|
mockCookieStore.get.mockReturnValueOnce(undefined)
|
||||||
|
|
||||||
|
const sampleContext = {
|
||||||
|
id: "machine-123",
|
||||||
|
tenantId: "tenant-1",
|
||||||
|
companyId: "company-1",
|
||||||
|
companySlug: "acme",
|
||||||
|
persona: "manager",
|
||||||
|
assignedUserId: "user-789",
|
||||||
|
assignedUserEmail: "manager@acme.com",
|
||||||
|
assignedUserName: "Manager Doe",
|
||||||
|
assignedUserRole: "MANAGER",
|
||||||
|
metadata: null,
|
||||||
|
authEmail: "device@example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
let call = 0
|
||||||
|
const queryMock = vi.fn(async (_route: unknown, args: unknown) => {
|
||||||
|
call += 1
|
||||||
|
if (call === 1) {
|
||||||
|
expect(args).toEqual({ authEmail: "device@example.com" })
|
||||||
|
return { id: "machine-123" }
|
||||||
|
}
|
||||||
|
if (call === 2) {
|
||||||
|
expect(args).toEqual({ machineId: "machine-123" })
|
||||||
|
return sampleContext
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
mockCreateConvexClient.mockReturnValue({
|
||||||
|
query: queryMock,
|
||||||
|
mutation: vi.fn(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { GET } = await import("./route")
|
||||||
|
const response = await GET(new NextRequest("https://example.com/api/machines/session"))
|
||||||
|
|
||||||
|
expect(response.status).toBe(200)
|
||||||
|
const payload = await response.json()
|
||||||
|
expect(payload.machine.id).toBe(sampleContext.id)
|
||||||
|
expect(payload.machine.assignedUserEmail).toBe(sampleContext.assignedUserEmail)
|
||||||
|
|
||||||
|
const cookie = response.cookies.get(MACHINE_CTX_COOKIE)
|
||||||
|
expect(cookie?.value).toBe(serializeMachineCookie({
|
||||||
|
machineId: sampleContext.id,
|
||||||
|
persona: sampleContext.persona,
|
||||||
|
assignedUserId: sampleContext.assignedUserId,
|
||||||
|
assignedUserEmail: sampleContext.assignedUserEmail,
|
||||||
|
assignedUserName: sampleContext.assignedUserName,
|
||||||
|
assignedUserRole: sampleContext.assignedUserRole,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Add table
Add a link
Reference in a new issue