sqlinsight-pro/tests/SQLInsight.IntegrationTests/HubSupportEndToEndTests.cs
Søren Eggert Lundsteen Olsen 50bc878776 Customers file support cases in SQLInsight Web and follow their status
- Repository migration 0005: insight.SupportCases. The web UI has only
  db_datareader, so INSERT on this one table is granted to public.
- Web: /support (menu "Support") lists the customer's cases and, with
  Hub sync on and a Professional/Enterprise license, a form (title,
  description, contact; contact prefilled from the Windows user).
  /support?fund=<id> prefills from a finding: instance, rule, severity,
  explanation and non-sensitive measurements.
- "Opret supportsag" on findings: right-click a finding link or a
  finding's heading (support-menu.js, a static file within the CSP;
  Shift+F10/context key, Escape closes), and a visible button on the
  finding page that works without JavaScript. Only when support is
  available.
- Agent: sends pending requests to POST /api/v1/cases, stores the case
  number, backs off on failures and gives up on 400/401/403/422; fetches
  the customer's cases every 5 minutes (at once after sending).
- Docs: support.md (also on the community site), databehandling.md
  (contact details are sent to the hub).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 11:34:06 +02:00

81 lines
3.8 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using SQLInsight.Agent.Hub;
using SQLInsight.Hub.Data;
using SQLInsight.Repository;
namespace SQLInsight.IntegrationTests;
/// <summary>A support case from SQLInsight Web, through the agent to the hub and its status back again.</summary>
[Collection(SqlServerTestGroup.Name)]
[Trait("Category", "Integration")]
public class HubSupportEndToEndTests(SqlServerFixture sql, HubFixture hub) : IClassFixture<HubFixture>, IAsyncLifetime
{
private readonly string _database = "SQLInsightSupE2E_" + Guid.NewGuid().ToString("N")[..8];
private InsightRepository _repository = null!;
public async Task InitializeAsync()
{
await sql.ExecuteAsSaAsync($"CREATE DATABASE [{_database}];");
_repository = new InsightRepository(sql.DatabaseConnectionString(_database));
await _repository.MigrateAsync();
}
public Task DisposeAsync() => sql.ExecuteAsSaAsync($"ALTER DATABASE [{_database}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [{_database}];");
private sealed class TestServerClients(Func<HttpClient> create) : IHttpClientFactory
{
public HttpClient CreateClient(string name) => create();
}
private HubSyncService Sync(string license)
{
var options = Options.Create(new HubOptions { Enabled = true, Url = new Uri("http://localhost/"), License = license });
options.Value.TrustedKeys[HubFixture.KeyId] = hub.PublicKey;
return new HubSyncService(options, _repository, new TestServerClients(() => hub.Factory.CreateClient()), TimeProvider.System, NullLogger<HubSyncService>.Instance);
}
[Fact]
public async Task Case_filed_in_web_gets_a_number_and_its_closing_comes_back()
{
var customer = $"Support E2E {Guid.NewGuid():N}"[..28];
var license = hub.License(customer);
await _repository.AddSupportRequestAsync(new NewSupportRequest(Guid.NewGuid(), "Langsom backup", "Siden mandag", "Anna", "anna@kunde.dk", null, "KUNDE\\anna"), DateTimeOffset.UtcNow);
await Sync(license).RunOnceAsync(CancellationToken.None);
var filed = Assert.Single(await _repository.GetSupportCasesAsync());
Assert.Equal(("SUP-000001", SupportCaseStatus.Open), (filed.Reference, filed.Status));
await using (var scope = hub.Factory.Services.CreateAsyncScope())
{
var db = scope.ServiceProvider.GetRequiredService<HubDbContext>();
var stored = await db.SupportCases.SingleAsync(c => c.Customer!.Name == customer);
stored.IsOpen = false;
stored.ClosedAt = DateTime.UtcNow;
await db.SaveChangesAsync();
}
await Sync(license).RunOnceAsync(CancellationToken.None);
var closed = Assert.Single(await _repository.GetSupportCasesAsync());
Assert.Equal(SupportCaseStatus.Closed, closed.Status);
Assert.NotNull(closed.ClosedAt);
}
[Fact]
public async Task Request_the_license_does_not_cover_is_given_up()
{
var customer = $"Community {Guid.NewGuid():N}"[..24];
await _repository.AddSupportRequestAsync(new NewSupportRequest(Guid.NewGuid(), "Hjælp", null, "Anna", "anna@kunde.dk", null, null), DateTimeOffset.UtcNow);
var sync = Sync(hub.License(customer));
await sync.SyncSupportCasesAsync(hub.Factory.CreateClient(), new HubOptions { Url = new Uri("http://localhost/"), License = hub.License(customer, SQLInsight.Licensing.LicensePlan.Community) }, CancellationToken.None);
var failed = Assert.Single(await _repository.GetSupportCasesAsync());
Assert.Equal(SupportCaseStatus.Failed, failed.Status);
Assert.Contains("403", failed.LastError, StringComparison.Ordinal);
}
}