Complete IoT monitoring platform for Acuvim II power meters via ESP32. Firmware (Phases 1-7): - ESP32-WROVER-B (TTGO T-Call v1.4) with RS485 Modbus RTU - WiFi STA+AP concurrent mode with GSM/GPRS failover - Transport abstraction layer with 4 priority modes - MQTT protocol with 20 commands, LWT, QoS, exponential backoff - SD card offline buffering with JSONL rotation and non-blocking drain - OTA firmware updates with dual partition rollback protection - Watchdog timer, crash loop detection, Acuvim health monitoring - Captive portal provisioning with AP mode Console backend (Phase 8): - .NET 10 minimal API with PostgreSQL + EF Core - JWT authentication, SignalR real-time updates - MQTTnet 5.x bridge service with health monitoring - Device, telemetry, firmware, alert, group management - Rate limiting, security headers, Swagger/OpenAPI Frontend (Phase 9): - React 18 + TypeScript + Vite with Ant Design 5 - ECharts telemetry visualization, TanStack Query - SignalR live updates, device management UI - Dashboard, fleet management, firmware deployment Testing & Production (Phase 10): - 28 firmware unit tests (Modbus, JSON, config, version) - 23 xUnit backend tests (device, telemetry, command, alert) - Docker Compose with nginx, TLS MQTT, PostgreSQL - Production deployment, commissioning, and troubleshooting docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
130 lines
4.0 KiB
C#
130 lines
4.0 KiB
C#
using System.Text.Json;
|
|
using Tau.Acuvim.Console.DTOs;
|
|
using Tau.Acuvim.Console.Models;
|
|
using Tau.Acuvim.Console.Services;
|
|
|
|
namespace Tau.Acuvim.Console.Tests;
|
|
|
|
public class DeviceServiceTests
|
|
{
|
|
private DeviceService CreateService(Data.AppDbContext db)
|
|
=> new(db, TestHelpers.CreateLogger<DeviceService>());
|
|
|
|
[Fact]
|
|
public async Task RegisterAsync_CreatesNewDevice()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
var payload = JsonSerializer.Serialize(new
|
|
{
|
|
device_id = "DEV-001",
|
|
mac = "AA:BB:CC:DD:EE:FF",
|
|
firmware = "1.0.0",
|
|
hardware = "rev-A",
|
|
imei = "123456789012345",
|
|
ts = 1700000000L
|
|
});
|
|
|
|
var device = await svc.RegisterAsync(payload);
|
|
|
|
Assert.Equal("DEV-001", device.DeviceId);
|
|
Assert.Equal("AA:BB:CC:DD:EE:FF", device.MacAddress);
|
|
Assert.Equal("1.0.0", device.FirmwareVersion);
|
|
Assert.Equal("rev-A", device.Hardware);
|
|
Assert.Equal("123456789012345", device.Imei);
|
|
Assert.Equal("online", device.Status);
|
|
Assert.Single(db.Devices);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RegisterAsync_UpdatesExistingDevice()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
var payload1 = JsonSerializer.Serialize(new { device_id = "DEV-001", firmware = "1.0.0" });
|
|
await svc.RegisterAsync(payload1);
|
|
|
|
var payload2 = JsonSerializer.Serialize(new { device_id = "DEV-001", firmware = "2.0.0" });
|
|
var device = await svc.RegisterAsync(payload2);
|
|
|
|
Assert.Equal("2.0.0", device.FirmwareVersion);
|
|
Assert.Single(db.Devices);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetAllAsync_FiltersByStatus()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
db.Devices.AddRange(
|
|
new Device { Id = Guid.NewGuid(), DeviceId = "D1", Status = "online" },
|
|
new Device { Id = Guid.NewGuid(), DeviceId = "D2", Status = "offline" },
|
|
new Device { Id = Guid.NewGuid(), DeviceId = "D3", Status = "online" }
|
|
);
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await svc.GetAllAsync(status: "online", search: null, page: 1, pageSize: 10);
|
|
|
|
Assert.Equal(2, result.Count);
|
|
Assert.All(result, r => Assert.Equal("online", r.Status));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetAllAsync_SearchesByName()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
db.Devices.AddRange(
|
|
new Device { Id = Guid.NewGuid(), DeviceId = "D1", Name = "Sensor Alpha" },
|
|
new Device { Id = Guid.NewGuid(), DeviceId = "D2", Name = "Sensor Beta" },
|
|
new Device { Id = Guid.NewGuid(), DeviceId = "D3", Name = "Motor Gamma" }
|
|
);
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await svc.GetAllAsync(status: null, search: "Sensor", page: 1, pageSize: 10);
|
|
|
|
Assert.Equal(2, result.Count);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetByDeviceIdAsync_ReturnsNull_WhenNotFound()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
var result = await svc.GetByDeviceIdAsync("NONEXISTENT");
|
|
|
|
Assert.Null(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeleteAsync_RemovesDevice()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
db.Devices.Add(new Device { Id = Guid.NewGuid(), DeviceId = "DEV-DEL" });
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await svc.DeleteAsync("DEV-DEL");
|
|
|
|
Assert.True(result);
|
|
Assert.Empty(db.Devices);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeleteAsync_ReturnsFalse_WhenNotFound()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
var result = await svc.DeleteAsync("NONEXISTENT");
|
|
|
|
Assert.False(result);
|
|
}
|
|
}
|