using System.Net; using System.Net.Http.Json; using MeterVault.Core.Domain; using MeterVault.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; namespace MeterVault.Integration.Tests; [Collection("Timescale")] public sealed class ApiTests(TimescaleFixture fx) { private sealed record ReadingPush(int MeterId, DateTimeOffset Time, double Value); [Fact] public async Task Readings_push_requires_key_and_writes_when_authorized() { int meterId; await using (var db = fx.CreateContext()) { await DatabaseSeeder.SeedAsync(db); var type = await db.EnergyTypes.FirstAsync(t => t.Key == "electricity"); var meter = new Meter { Name = $"api-{Guid.NewGuid():N}", EnergyTypeId = type.Id, Mode = MeterMode.CumulativeCounter, Unit = "kWh" }; db.Meters.Add(meter); await db.SaveChangesAsync(); meterId = meter.Id; } using var factory = new MeterVaultAppFactory(fx.ConnectionString); using var client = factory.CreateClient(); var push = new[] { new ReadingPush(meterId, new DateTimeOffset(2024, 5, 1, 0, 0, 0, TimeSpan.Zero), 1500) }; // Without the key → 401. var unauthorized = await client.PostAsJsonAsync("/api/v1/readings", push); Assert.Equal(HttpStatusCode.Unauthorized, unauthorized.StatusCode); // With the key → 200 and the reading is persisted. using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/readings") { Content = JsonContent.Create(push), }; request.Headers.Add("X-Api-Key", MeterVaultAppFactory.ApiKey); var authorized = await client.SendAsync(request); authorized.EnsureSuccessStatusCode(); await using (var db = fx.CreateContext()) { var reading = await db.Readings.SingleAsync(r => r.MeterId == meterId); Assert.Equal(1500, reading.Value, 3); await db.Readings.Where(r => r.MeterId == meterId).ExecuteDeleteAsync(); await db.Meters.Where(m => m.Id == meterId).ExecuteDeleteAsync(); } } private sealed record EventPush(int MeterId, DateTimeOffset Time, string Type, double? Amount, double? PrevValue, double? NewValue, string? Unit, string? Notes); [Fact] public async Task Api_is_closed_when_no_keys_are_configured() { using var factory = new MeterVaultAppFactory(fx.ConnectionString, configureApiKey: false); using var client = factory.CreateClient(); var response = await client.PostAsJsonAsync("/api/v1/readings", new[] { new ReadingPush(1, DateTimeOffset.UtcNow, 1) }); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } [Fact] public async Task Events_for_a_missing_meter_return_404_not_500() { using var factory = new MeterVaultAppFactory(fx.ConnectionString); using var client = factory.CreateClient(); using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/events") { Content = JsonContent.Create(new EventPush(999999, DateTimeOffset.UtcNow, "Delivery", 100, null, null, "L", null)), }; request.Headers.Add("X-Api-Key", MeterVaultAppFactory.ApiKey); var response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } [Fact] public async Task Meters_endpoint_and_swagger_are_available() { using var factory = new MeterVaultAppFactory(fx.ConnectionString); using var client = factory.CreateClient(); using var metersRequest = new HttpRequestMessage(HttpMethod.Get, "/api/v1/meters"); metersRequest.Headers.Add("X-Api-Key", MeterVaultAppFactory.ApiKey); var meters = await client.SendAsync(metersRequest); meters.EnsureSuccessStatusCode(); // Swagger document is served (no API key required). var swagger = await client.GetAsync(new Uri("/swagger/v1/swagger.json", UriKind.Relative)); swagger.EnsureSuccessStatusCode(); Assert.Contains("MeterVault API", await swagger.Content.ReadAsStringAsync(), StringComparison.Ordinal); } }