M5-T07: add latest-reading cards and Recharts trend charts; readings return most-recent rows

This commit is contained in:
2026-06-22 14:17:59 +02:00
parent 2dc469274a
commit 68165f0e01
13 changed files with 1454 additions and 23 deletions
+133
View File
@@ -212,3 +212,136 @@ describe('useTestReadDevice', () => {
})
})
})
describe('useLatestReading', () => {
beforeEach(() => vi.clearAllMocks())
it('calls GET /api/modbus/devices/{uuid}/latest with path param', async () => {
mockGet.mockResolvedValue({
data: { found: true, recorded_at: '2026-06-22T10:00:00Z', payload: { voltage: 230.2 } },
})
const { Wrapper } = makeWrapper()
const { useLatestReading } = await import('./hooks')
const { result } = renderHook(() => useLatestReading('test-uuid-1'), { wrapper: Wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockGet).toHaveBeenCalledWith('/api/modbus/devices/{uuid}/latest', {
params: { path: { uuid: 'test-uuid-1' } },
})
expect(result.current.data?.found).toBe(true)
expect(result.current.data?.payload).toEqual({ voltage: 230.2 })
})
it('returns found=false when device has no readings', async () => {
mockGet.mockResolvedValue({
data: { found: false, recorded_at: null, payload: null },
})
const { Wrapper } = makeWrapper()
const { useLatestReading } = await import('./hooks')
const { result } = renderHook(() => useLatestReading('test-uuid-2'), { wrapper: Wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data?.found).toBe(false)
expect(result.current.data?.payload).toBeNull()
})
})
describe('useMetrics', () => {
beforeEach(() => vi.clearAllMocks())
it('calls GET /api/modbus/devices/{uuid}/metrics with path param', async () => {
mockGet.mockResolvedValue({
data: {
profile: 'sdm120',
metrics: [{ key: 'voltage', label: 'Voltage', unit: 'V', device_class: 'voltage' }],
},
})
const { Wrapper } = makeWrapper()
const { useMetrics } = await import('./hooks')
const { result } = renderHook(() => useMetrics('test-uuid-1'), { wrapper: Wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockGet).toHaveBeenCalledWith('/api/modbus/devices/{uuid}/metrics', {
params: { path: { uuid: 'test-uuid-1' } },
})
expect(result.current.data?.metrics).toHaveLength(1)
expect(result.current.data?.metrics[0].key).toBe('voltage')
})
})
describe('useReadings', () => {
beforeEach(() => vi.clearAllMocks())
it('calls GET /api/modbus/devices/{uuid}/readings with window and limit', async () => {
mockGet.mockResolvedValue({
data: { items: [{ recorded_at: '2026-06-22T10:00:00Z', payload: { voltage: 230.2 } }] },
})
const { Wrapper } = makeWrapper()
const { useReadings } = await import('./hooks')
const params = { start: '2026-06-22T09:00:00Z', end: '2026-06-22T10:00:00Z', limit: 500 }
const { result } = renderHook(() => useReadings('test-uuid-1', params), { wrapper: Wrapper })
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockGet).toHaveBeenCalledWith('/api/modbus/devices/{uuid}/readings', {
params: {
path: { uuid: 'test-uuid-1' },
query: {
start: '2026-06-22T09:00:00Z',
end: '2026-06-22T10:00:00Z',
limit: 500,
},
},
})
expect(result.current.data?.items).toHaveLength(1)
})
it('caps limit at READINGS_MAX_LIMIT (1000) even if caller requests more', async () => {
mockGet.mockResolvedValue({ data: { items: [] } })
const { Wrapper } = makeWrapper()
const { useReadings } = await import('./hooks')
const { result } = renderHook(
() => useReadings('test-uuid-1', { limit: 99999 }),
{ wrapper: Wrapper },
)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
// The query param limit should be capped at 1000.
expect(mockGet).toHaveBeenCalledWith('/api/modbus/devices/{uuid}/readings',
expect.objectContaining({
params: expect.objectContaining({
query: expect.objectContaining({ limit: 1000 }),
}),
}),
)
})
it('omits start/end from query params when not provided', async () => {
mockGet.mockResolvedValue({ data: { items: [] } })
const { Wrapper } = makeWrapper()
const { useReadings } = await import('./hooks')
const { result } = renderHook(
() => useReadings('test-uuid-1', {}),
{ wrapper: Wrapper },
)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(mockGet).toHaveBeenCalledWith('/api/modbus/devices/{uuid}/readings',
expect.objectContaining({
params: expect.objectContaining({
query: expect.not.objectContaining({ start: expect.anything() }),
}),
}),
)
})
})