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 }) }) })