- 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>
81 lines
3.8 KiB
C#
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);
|
|
}
|
|
}
|