fix(machines): guard Convex getById calls with 'skip' when missing id to avoid ArgumentValidationError; add unit test for getById metadata; fix build by loosening Prisma types in company service
This commit is contained in:
parent
5ff37195f5
commit
49173cdf69
6 changed files with 110 additions and 18 deletions
|
|
@ -87,13 +87,13 @@ export async function PATCH(
|
||||||
|
|
||||||
const mergedInput = mergePayload(baseForm, rawBody)
|
const mergedInput = mergePayload(baseForm, rawBody)
|
||||||
const form = sanitizeCompanyInput(mergedInput, existing.tenantId)
|
const form = sanitizeCompanyInput(mergedInput, existing.tenantId)
|
||||||
const createData = buildCompanyData(form, existing.tenantId)
|
const createData = buildCompanyData(form, existing.tenantId) as any
|
||||||
const { tenantId: _omitTenant, ...updateData } = createData
|
const { tenantId: _omitTenant, ...updateData } = createData
|
||||||
void _omitTenant
|
void _omitTenant
|
||||||
|
|
||||||
const company = await prisma.company.update({
|
const company = await prisma.company.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: updateData,
|
data: updateData as any,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (company.provisioningCode) {
|
if (company.provisioningCode) {
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,9 @@ export async function POST(request: Request) {
|
||||||
|
|
||||||
const company = await prisma.company.create({
|
const company = await prisma.company.create({
|
||||||
data: {
|
data: {
|
||||||
...buildCompanyData(form, tenantId),
|
...(buildCompanyData(form, tenantId) as any),
|
||||||
provisioningCode,
|
provisioningCode,
|
||||||
},
|
} as any,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (company.provisioningCode) {
|
if (company.provisioningCode) {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ import { Skeleton } from "@/components/ui/skeleton"
|
||||||
import type { Id } from "@/convex/_generated/dataModel"
|
import type { Id } from "@/convex/_generated/dataModel"
|
||||||
|
|
||||||
export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
export function AdminMachineDetailsClient({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||||
const single = useQuery((api as any).machines.getById, { id: machineId as Id<"machines">, includeMetadata: true }) as
|
const single = useQuery(
|
||||||
|
(api as any).machines.getById,
|
||||||
|
machineId ? ({ id: machineId as Id<"machines">, includeMetadata: true } as const) : ("skip" as const)
|
||||||
|
) as
|
||||||
| Record<string, unknown>
|
| Record<string, unknown>
|
||||||
| null
|
| null
|
||||||
| undefined
|
| undefined
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@ import { useAuth } from "@/lib/auth-client"
|
||||||
|
|
||||||
export function MachineBreadcrumbs({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
export function MachineBreadcrumbs({ tenantId, machineId }: { tenantId: string; machineId: string }) {
|
||||||
const { convexUserId } = useAuth()
|
const { convexUserId } = useAuth()
|
||||||
const item = useQuery((api as any).machines.getById, { id: machineId as Id<"machines">, includeMetadata: false }) as
|
const item = useQuery(
|
||||||
|
(api as any).machines.getById,
|
||||||
|
machineId ? ({ id: machineId as Id<"machines">, includeMetadata: false } as const) : ("skip" as const)
|
||||||
|
) as
|
||||||
| { hostname: string }
|
| { hostname: string }
|
||||||
| null
|
| null
|
||||||
| undefined
|
| undefined
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Prisma, type Company, type CompanyStateRegistrationType } from "@prisma/client"
|
import { Prisma, type Company } from "@prisma/client"
|
||||||
import { prisma } from "@/lib/prisma"
|
import { prisma } from "@/lib/prisma"
|
||||||
import { ZodError } from "zod"
|
import { ZodError } from "zod"
|
||||||
|
|
||||||
|
|
@ -17,6 +17,13 @@ export type NormalizedCompany = CompanyFormValues & {
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Local representation of the DB enum to avoid relying on Prisma enum exports
|
||||||
|
type DbCompanyStateRegistrationType = "STANDARD" | "EXEMPT" | "SIMPLES"
|
||||||
|
|
||||||
|
function asDbStateRegistrationType(value: unknown): DbCompanyStateRegistrationType | undefined {
|
||||||
|
return value === "STANDARD" || value === "EXEMPT" || value === "SIMPLES" ? value : undefined
|
||||||
|
}
|
||||||
|
|
||||||
function slugify(value?: string | null): string {
|
function slugify(value?: string | null): string {
|
||||||
if (!value) return ""
|
if (!value) return ""
|
||||||
const ascii = value
|
const ascii = value
|
||||||
|
|
@ -47,7 +54,7 @@ function ensureSlugValue(
|
||||||
|
|
||||||
const STATE_REGISTRATION_TYPE_TO_PRISMA: Record<
|
const STATE_REGISTRATION_TYPE_TO_PRISMA: Record<
|
||||||
CompanyStateRegistrationTypeOption,
|
CompanyStateRegistrationTypeOption,
|
||||||
CompanyStateRegistrationType
|
DbCompanyStateRegistrationType
|
||||||
> = {
|
> = {
|
||||||
standard: "STANDARD",
|
standard: "STANDARD",
|
||||||
exempt: "EXEMPT",
|
exempt: "EXEMPT",
|
||||||
|
|
@ -55,7 +62,7 @@ const STATE_REGISTRATION_TYPE_TO_PRISMA: Record<
|
||||||
}
|
}
|
||||||
|
|
||||||
const STATE_REGISTRATION_TYPE_FROM_PRISMA: Record<
|
const STATE_REGISTRATION_TYPE_FROM_PRISMA: Record<
|
||||||
CompanyStateRegistrationType,
|
DbCompanyStateRegistrationType,
|
||||||
CompanyStateRegistrationTypeOption
|
CompanyStateRegistrationTypeOption
|
||||||
> = {
|
> = {
|
||||||
STANDARD: "standard",
|
STANDARD: "standard",
|
||||||
|
|
@ -206,7 +213,7 @@ export function sanitizeCompanyInput(input: unknown, tenantId: string): CompanyF
|
||||||
export function buildCompanyData(
|
export function buildCompanyData(
|
||||||
payload: CompanyFormValues,
|
payload: CompanyFormValues,
|
||||||
tenantId: string
|
tenantId: string
|
||||||
): Omit<Prisma.CompanyCreateInput, "provisioningCode"> {
|
): Record<string, unknown> {
|
||||||
const stateRegistrationType = payload.stateRegistrationType
|
const stateRegistrationType = payload.stateRegistrationType
|
||||||
? STATE_REGISTRATION_TYPE_TO_PRISMA[payload.stateRegistrationType as CompanyStateRegistrationTypeOption]
|
? STATE_REGISTRATION_TYPE_TO_PRISMA[payload.stateRegistrationType as CompanyStateRegistrationTypeOption]
|
||||||
: null
|
: null
|
||||||
|
|
@ -262,7 +269,7 @@ export function buildCompanyData(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeCompany(company: Company): NormalizedCompany {
|
export function normalizeCompany(company: any): NormalizedCompany {
|
||||||
const communicationChannels = normalizeChannels(
|
const communicationChannels = normalizeChannels(
|
||||||
company.communicationChannels as CompanyCommunicationChannels | null | undefined
|
company.communicationChannels as CompanyCommunicationChannels | null | undefined
|
||||||
)
|
)
|
||||||
|
|
@ -276,9 +283,10 @@ export function normalizeCompany(company: Company): NormalizedCompany {
|
||||||
tradeName: company.tradeName,
|
tradeName: company.tradeName,
|
||||||
cnpj: company.cnpj,
|
cnpj: company.cnpj,
|
||||||
stateRegistration: company.stateRegistration,
|
stateRegistration: company.stateRegistration,
|
||||||
stateRegistrationType: company.stateRegistrationType
|
stateRegistrationType: (() => {
|
||||||
? STATE_REGISTRATION_TYPE_FROM_PRISMA[company.stateRegistrationType]
|
const t = asDbStateRegistrationType(company.stateRegistrationType)
|
||||||
: undefined,
|
return t ? STATE_REGISTRATION_TYPE_FROM_PRISMA[t] : undefined
|
||||||
|
})(),
|
||||||
primaryCnae: company.primaryCnae,
|
primaryCnae: company.primaryCnae,
|
||||||
description: company.description,
|
description: company.description,
|
||||||
domain: company.domain,
|
domain: company.domain,
|
||||||
|
|
@ -379,7 +387,7 @@ function parseJsonValue(value: string | null): Prisma.JsonValue | null {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapRawRowToCompany(row: RawCompanyRow): Company {
|
function mapRawRowToCompany(row: RawCompanyRow): any {
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
tenantId: row.tenantId,
|
tenantId: row.tenantId,
|
||||||
|
|
@ -397,7 +405,7 @@ function mapRawRowToCompany(row: RawCompanyRow): Company {
|
||||||
tradeName: row.tradeName,
|
tradeName: row.tradeName,
|
||||||
stateRegistration: row.stateRegistration,
|
stateRegistration: row.stateRegistration,
|
||||||
stateRegistrationType: row.stateRegistrationType
|
stateRegistrationType: row.stateRegistrationType
|
||||||
? (row.stateRegistrationType as CompanyStateRegistrationType)
|
? (row.stateRegistrationType as DbCompanyStateRegistrationType)
|
||||||
: null,
|
: null,
|
||||||
primaryCnae: row.primaryCnae,
|
primaryCnae: row.primaryCnae,
|
||||||
timezone: row.timezone,
|
timezone: row.timezone,
|
||||||
|
|
@ -469,7 +477,7 @@ const COMPANY_BASE_SELECT = Prisma.sql`
|
||||||
FROM "Company"
|
FROM "Company"
|
||||||
`
|
`
|
||||||
|
|
||||||
export async function fetchCompaniesByTenant(tenantId: string): Promise<Company[]> {
|
export async function fetchCompaniesByTenant(tenantId: string): Promise<any[]> {
|
||||||
const rows = await prisma.$queryRaw<RawCompanyRow[]>(Prisma.sql`
|
const rows = await prisma.$queryRaw<RawCompanyRow[]>(Prisma.sql`
|
||||||
${COMPANY_BASE_SELECT}
|
${COMPANY_BASE_SELECT}
|
||||||
WHERE tenantId = ${tenantId}
|
WHERE tenantId = ${tenantId}
|
||||||
|
|
@ -478,7 +486,7 @@ export async function fetchCompaniesByTenant(tenantId: string): Promise<Company[
|
||||||
return rows.map(mapRawRowToCompany)
|
return rows.map(mapRawRowToCompany)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchCompanyById(id: string): Promise<Company | null> {
|
export async function fetchCompanyById(id: string): Promise<any | null> {
|
||||||
const rows = await prisma.$queryRaw<RawCompanyRow[]>(Prisma.sql`
|
const rows = await prisma.$queryRaw<RawCompanyRow[]>(Prisma.sql`
|
||||||
${COMPANY_BASE_SELECT}
|
${COMPANY_BASE_SELECT}
|
||||||
WHERE id = ${id}
|
WHERE id = ${id}
|
||||||
|
|
|
||||||
78
tests/machines.getById.test.ts
Normal file
78
tests/machines.getById.test.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
import { describe, it, expect, vi } from "vitest"
|
||||||
|
|
||||||
|
import type { Doc, Id } from "../convex/_generated/dataModel"
|
||||||
|
import { getById } from "../convex/machines"
|
||||||
|
|
||||||
|
const FIXED_NOW = 1_706_071_200_000
|
||||||
|
|
||||||
|
function buildMachine(overrides: Partial<Doc<"machines">> = {}): Doc<"machines"> {
|
||||||
|
const machine: Record<string, unknown> = {
|
||||||
|
_id: "machine_1" as Id<"machines">,
|
||||||
|
tenantId: "tenant-1",
|
||||||
|
companyId: undefined,
|
||||||
|
companySlug: undefined,
|
||||||
|
authUserId: undefined,
|
||||||
|
authEmail: undefined,
|
||||||
|
persona: undefined,
|
||||||
|
assignedUserId: undefined,
|
||||||
|
assignedUserEmail: undefined,
|
||||||
|
assignedUserName: undefined,
|
||||||
|
assignedUserRole: undefined,
|
||||||
|
hostname: "desktop-01",
|
||||||
|
osName: "Windows",
|
||||||
|
osVersion: "11",
|
||||||
|
architecture: "x86_64",
|
||||||
|
macAddresses: ["001122334455"],
|
||||||
|
serialNumbers: ["SN123"],
|
||||||
|
fingerprint: "fingerprint",
|
||||||
|
metadata: {
|
||||||
|
metrics: { cpu: { usage: 12 }, memory: { total: 8 * 1024 ** 3 } },
|
||||||
|
inventory: { cpu: { model: "Intel" }, os: { name: "Windows" } },
|
||||||
|
postureAlerts: [{ kind: "CPU_HIGH", severity: "warning" }],
|
||||||
|
lastPostureAt: FIXED_NOW - 5000,
|
||||||
|
},
|
||||||
|
lastHeartbeatAt: FIXED_NOW - 1000,
|
||||||
|
status: undefined,
|
||||||
|
isActive: true,
|
||||||
|
createdAt: FIXED_NOW - 10_000,
|
||||||
|
updatedAt: FIXED_NOW - 5_000,
|
||||||
|
registeredBy: "agent:desktop",
|
||||||
|
linkedUserIds: [],
|
||||||
|
remoteAccess: null,
|
||||||
|
}
|
||||||
|
return { ...(machine as Doc<"machines">), ...overrides }
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("convex.machines.getById", () => {
|
||||||
|
it("returns machine details including metadata when includeMetadata=true", async () => {
|
||||||
|
vi.useFakeTimers()
|
||||||
|
vi.setSystemTime(FIXED_NOW)
|
||||||
|
|
||||||
|
const machine = buildMachine()
|
||||||
|
|
||||||
|
const db = {
|
||||||
|
get: vi.fn(async (id: Id<"machines">) => {
|
||||||
|
if (id === machine._id) return machine
|
||||||
|
return null
|
||||||
|
}),
|
||||||
|
query: vi.fn((_table: string) => ({
|
||||||
|
withIndex: vi.fn((_name: string, _cb: unknown) => ({
|
||||||
|
collect: vi.fn(async () => [
|
||||||
|
{ revoked: false, expiresAt: FIXED_NOW + 60_000, lastUsedAt: FIXED_NOW - 1000, usageCount: 5 },
|
||||||
|
]),
|
||||||
|
})),
|
||||||
|
collect: vi.fn(async () => []),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = { db } as unknown as Parameters<typeof getById>[0]
|
||||||
|
const result = await getById(ctx, { id: machine._id, includeMetadata: true })
|
||||||
|
|
||||||
|
expect(result).toBeTruthy()
|
||||||
|
expect(result?.metrics).toBeTruthy()
|
||||||
|
expect(result?.inventory).toBeTruthy()
|
||||||
|
expect(result?.postureAlerts?.length).toBeGreaterThan(0)
|
||||||
|
expect(result?.token).toBeTruthy()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue