using System.Diagnostics.Metrics; using Jiaowu.Api.Infrastructure.Observability; using Jiaowu.Api.Infrastructure.Persistence; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging.Abstractions; namespace Jiaowu.Api.Tests; public sealed class DatabaseCommandTelemetryInterceptorTests { [Fact] public void Query_name_uses_tag_without_exposing_statement_text() { const string sql = "-- Timetable.LoadMixedExamEntries\n" + "SELECT * FROM ExamRooms WHERE SecretValue = @p0"; var queryName = DatabaseCommandTelemetryInterceptor.GetQueryName(sql); Assert.Equal("Timetable.LoadMixedExamEntries", queryName); Assert.DoesNotContain("SecretValue", queryName); Assert.DoesNotContain("@p0", queryName); } [Fact] public void Untagged_query_name_is_stable_hash_not_statement_text() { const string sql = "SELECT * FROM Students WHERE StudentNumber = @studentNumber"; var first = DatabaseCommandTelemetryInterceptor.GetQueryName(sql); var second = DatabaseCommandTelemetryInterceptor.GetQueryName(sql); Assert.Equal(first, second); Assert.StartsWith("select:", first); Assert.DoesNotContain("Students", first); Assert.DoesNotContain("StudentNumber", first); } [Fact] public async Task Ef_command_records_duration_metric() { await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); var setupOptions = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using (var setupDb = new AppDbContext(setupOptions)) await setupDb.Database.EnsureCreatedAsync(); double? recordedDuration = null; using var listener = new MeterListener { InstrumentPublished = (instrument, meterListener) => { if (instrument.Meter.Name == DatabaseCommandTelemetryInterceptor.MeterName && instrument.Name == "jiaowu.db.command.duration") { meterListener.EnableMeasurementEvents(instrument); } } }; listener.SetMeasurementEventCallback( (_, measurement, _, _) => recordedDuration = measurement); listener.Start(); var interceptor = new DatabaseCommandTelemetryInterceptor( new ObservabilityOptions(), NullLogger.Instance); var queryOptions = new DbContextOptionsBuilder() .UseSqlite(connection) .AddInterceptors(interceptor) .Options; await using var db = new AppDbContext(queryOptions); await db.AcademicTerms .TagWith("Observability.Tests.TermCount") .CountAsync(); Assert.NotNull(recordedDuration); Assert.True(recordedDuration >= 0); } }