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>
120 lines
4.0 KiB
C#
120 lines
4.0 KiB
C#
using System.Text.Json;
|
|
using Tau.Acuvim.Console.Models;
|
|
using Tau.Acuvim.Console.Services;
|
|
|
|
namespace Tau.Acuvim.Console.Tests;
|
|
|
|
public class AlertServiceTests
|
|
{
|
|
private AlertService CreateService(Data.AppDbContext db)
|
|
=> new(db, TestHelpers.CreateLogger<AlertService>());
|
|
|
|
[Fact]
|
|
public async Task ProcessAlertAsync_CreatesAlert()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
var payload = JsonSerializer.Serialize(new
|
|
{
|
|
alert = "overvoltage",
|
|
severity = "critical",
|
|
message = "Voltage exceeded 250V",
|
|
value = 255.3,
|
|
threshold = 250.0
|
|
});
|
|
|
|
await svc.ProcessAlertAsync("DEV-A1", payload);
|
|
|
|
Assert.Single(db.Alerts);
|
|
var alert = db.Alerts.First();
|
|
Assert.Equal("DEV-A1", alert.DeviceId);
|
|
Assert.Equal("overvoltage", alert.AlertType);
|
|
Assert.Equal("critical", alert.Severity);
|
|
Assert.Equal("Voltage exceeded 250V", alert.Message);
|
|
Assert.Equal(255.3, alert.Value);
|
|
Assert.Equal(250.0, alert.Threshold);
|
|
Assert.False(alert.Acknowledged);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetAllAsync_FiltersBySeverity()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
db.Alerts.AddRange(
|
|
new Alert { DeviceId = "D1", AlertType = "temp", Severity = "critical", Message = "hot" },
|
|
new Alert { DeviceId = "D1", AlertType = "voltage", Severity = "warning", Message = "low" },
|
|
new Alert { DeviceId = "D2", AlertType = "temp", Severity = "critical", Message = "hot2" }
|
|
);
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await svc.GetAllAsync(
|
|
deviceId: null, severity: "critical", acknowledged: null, page: 1, pageSize: 10);
|
|
|
|
Assert.Equal(2, result.Count);
|
|
Assert.All(result, a => Assert.Equal("critical", a.Severity));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AcknowledgeAsync_MarksAlertAcknowledged()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
var alert = new Alert
|
|
{
|
|
DeviceId = "D1",
|
|
AlertType = "temp",
|
|
Severity = "warning",
|
|
Message = "test"
|
|
};
|
|
db.Alerts.Add(alert);
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await svc.AcknowledgeAsync(alert.Id, "operator1");
|
|
|
|
Assert.True(result);
|
|
var updated = db.Alerts.First(a => a.Id == alert.Id);
|
|
Assert.True(updated.Acknowledged);
|
|
Assert.Equal("operator1", updated.AcknowledgedBy);
|
|
Assert.NotNull(updated.AcknowledgedAt);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AcknowledgeAsync_ReturnsFalse_WhenNotFound()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
var result = await svc.AcknowledgeAsync(999, "operator1");
|
|
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetActiveCountAsync_CountsUnacknowledged()
|
|
{
|
|
using var db = TestHelpers.CreateDbContext();
|
|
var svc = CreateService(db);
|
|
|
|
db.Alerts.AddRange(
|
|
new Alert { DeviceId = "D1", AlertType = "a", Severity = "info", Message = "m1",
|
|
Acknowledged = false, ResolvedAt = null },
|
|
new Alert { DeviceId = "D1", AlertType = "b", Severity = "info", Message = "m2",
|
|
Acknowledged = true, AcknowledgedBy = "op" },
|
|
new Alert { DeviceId = "D2", AlertType = "c", Severity = "info", Message = "m3",
|
|
Acknowledged = false, ResolvedAt = DateTime.UtcNow },
|
|
new Alert { DeviceId = "D2", AlertType = "d", Severity = "info", Message = "m4",
|
|
Acknowledged = false, ResolvedAt = null }
|
|
);
|
|
await db.SaveChangesAsync();
|
|
|
|
var count = await svc.GetActiveCountAsync();
|
|
|
|
// Only alerts where Acknowledged == false AND ResolvedAt == null
|
|
Assert.Equal(2, count);
|
|
}
|
|
}
|