142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
import { describe, expect, test } from 'bun:test'
|
|
import {
|
|
createBatteriesResponse,
|
|
createDashboardSnapshot,
|
|
DEVICE_STATUS,
|
|
getDeviceStatus,
|
|
MYSQL_BOOLEAN,
|
|
POWER_STATUS,
|
|
toBatteryInfo,
|
|
} from './battery'
|
|
|
|
const rows = [
|
|
{
|
|
id: 1,
|
|
userId: 7,
|
|
mac: 'RING-A03',
|
|
devModel: '2401-A',
|
|
devName: 'RING-A03',
|
|
isLowPower: MYSQL_BOOLEAN.FALSE,
|
|
powerStatus: POWER_STATUS.FULL,
|
|
power: 94,
|
|
createTime: new Date('2026-05-10T23:00:00.000Z'),
|
|
remark: 'v3.8.2',
|
|
},
|
|
{
|
|
id: 2,
|
|
userId: 7,
|
|
mac: 'RING-B11',
|
|
devModel: '2402-B',
|
|
devName: 'RING-B11',
|
|
isLowPower: MYSQL_BOOLEAN.TRUE,
|
|
powerStatus: POWER_STATUS.CHARGING,
|
|
power: 84,
|
|
createTime: '2026-05-10 22:00:00',
|
|
remark: null,
|
|
},
|
|
]
|
|
|
|
describe('battery domain', () => {
|
|
test('preserves legacy SOH status thresholds', () => {
|
|
expect(getDeviceStatus(91)).toBe(DEVICE_STATUS.HEALTHY)
|
|
expect(getDeviceStatus(90)).toBe(DEVICE_STATUS.WATCH)
|
|
expect(getDeviceStatus(85)).toBe(DEVICE_STATUS.WARNING)
|
|
})
|
|
|
|
test('builds batteries response counters from records', () => {
|
|
const now = new Date('2026-05-11T00:00:00.000Z')
|
|
const items = rows.map(toBatteryInfo)
|
|
const response = createBatteriesResponse(items, now)
|
|
|
|
expect(response.updatedAt).toBe('2026-05-11T00:00:00.000Z')
|
|
expect(response.total).toBe(items.length)
|
|
expect(response.lowPower).toBe(1)
|
|
expect(response.charging).toBe(1)
|
|
expect(response.nextCursor).toBeNull()
|
|
expect(response.items[0]?.createTime).toBe('2026-05-10T23:00:00.000Z')
|
|
})
|
|
|
|
test('keeps explicit window summaries for limited history slices', () => {
|
|
const now = new Date('2026-05-11T00:00:00.000Z')
|
|
const items = rows.map(toBatteryInfo)
|
|
const response = createBatteriesResponse(items, now, {
|
|
total: items.length,
|
|
lowPower: 1,
|
|
charging: 1,
|
|
})
|
|
|
|
expect(response.total).toBe(2)
|
|
expect(response.lowPower).toBe(1)
|
|
expect(response.charging).toBe(1)
|
|
})
|
|
|
|
test('creates dashboard aggregate shape without using power as fake SOH', () => {
|
|
const now = new Date('2026-05-11T00:00:00.000Z')
|
|
const snapshot = createDashboardSnapshot(rows.map(toBatteryInfo), now)
|
|
|
|
expect(snapshot.devices).toHaveLength(2)
|
|
expect(snapshot.devices.every((device) => device.sohSource === 'unavailable')).toBe(true)
|
|
expect(snapshot.devices.every((device) => device.soh === null)).toBe(true)
|
|
expect(snapshot.devices.every((device) => device.soh30d === null)).toBe(true)
|
|
expect(snapshot.devices.every((device) => device.soh90d === null)).toBe(true)
|
|
expect(snapshot.devices.every((device) => device.soh60d === null)).toBe(true)
|
|
expect(snapshot.devices.every((device) => device.cycles === 0)).toBe(true)
|
|
expect(snapshot.devices.every((device) => device.temperature === 0)).toBe(true)
|
|
expect(snapshot.devices.every((device) => device.chargeEfficiency === 0)).toBe(true)
|
|
expect(snapshot.devices[0]?.firmware).toBe('v3.8.2')
|
|
expect(snapshot.devices[1]?.firmware).toBe('未提供')
|
|
expect(snapshot.soh.history).toHaveLength(0)
|
|
expect(snapshot.soh.forecast).toHaveLength(0)
|
|
expect(snapshot.summary.totalDevices).toBe(snapshot.devices.length)
|
|
expect(snapshot.summary.avgSoh).toBeNull()
|
|
expect(snapshot.summary.avgSoh30d).toBeNull()
|
|
expect(snapshot.summary.avgSoh90d).toBeNull()
|
|
expect(snapshot.summary.warningCount + snapshot.summary.watchCount + snapshot.summary.healthyCount).toBe(
|
|
snapshot.devices.length,
|
|
)
|
|
})
|
|
|
|
test('uses AI prediction values when available', () => {
|
|
const now = new Date('2026-05-11T00:00:00.000Z')
|
|
const items = rows.map(toBatteryInfo)
|
|
const snapshot = createDashboardSnapshot(
|
|
items,
|
|
now,
|
|
new Map([
|
|
[
|
|
'RING-A03',
|
|
{
|
|
mac: 'RING-A03',
|
|
nowSoh: 60,
|
|
monthSoh: 58,
|
|
trmonthSoh: 52,
|
|
riskScore: 40,
|
|
riskLevel: 'high',
|
|
status: '危险',
|
|
modelName: 'XGBoost',
|
|
cyclesUsed: 6,
|
|
updatedAt: '2026-05-11T00:00:00.000Z',
|
|
},
|
|
],
|
|
]),
|
|
)
|
|
const predicted = snapshot.devices.find((device) => device.id === 'RING-A03')
|
|
|
|
expect(predicted?.soh).toBe(60)
|
|
expect(predicted?.sohSource).toBe('prediction')
|
|
expect(predicted?.soh30d).toBe(58)
|
|
expect(predicted?.soh90d).toBe(52)
|
|
expect(predicted?.soh60d).toBeNull()
|
|
expect(predicted?.cycles).toBe(6)
|
|
expect(predicted?.firmware).toBe('v3.8.2')
|
|
expect(predicted?.status).toBe(DEVICE_STATUS.WARNING)
|
|
expect(predicted?.temperature).toBe(0)
|
|
expect(predicted?.chargeEfficiency).toBe(0)
|
|
expect(snapshot.soh.history).toHaveLength(0)
|
|
expect(snapshot.soh.forecast).toHaveLength(3)
|
|
expect(snapshot.soh.forecast[0]).toEqual({ month: '当前', value: 60 })
|
|
expect(snapshot.soh.forecast[1]).toEqual({ month: '30天', value: 58 })
|
|
expect(snapshot.soh.forecast[2]).toEqual({ month: '90天', value: 52 })
|
|
})
|
|
})
|