2026-09-16 17:12:03 -03:00
|
|
|
using System.Security.Claims;
|
2026-07-24 17:35:34 -03:00
|
|
|
using Data.SeaHavenIndustries;
|
|
|
|
|
using FluentAssertions;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
|
using SeaHaven.DataServices.Implementation;
|
2026-09-16 17:12:03 -03:00
|
|
|
using SeaHaven.Services.Exceptions;
|
|
|
|
|
using SeaHaven.Services.Helpers;
|
2026-07-24 17:35:34 -03:00
|
|
|
using SeaHaven.Services.Implementation;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
|
|
|
|
namespace Api.SeaHavenIndustries.Tests;
|
|
|
|
|
|
|
|
|
|
public class DashboardServiceTests
|
|
|
|
|
{
|
|
|
|
|
private static ApplicationDbContext NewContext()
|
|
|
|
|
{
|
|
|
|
|
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
|
|
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
|
|
|
.Options;
|
|
|
|
|
return new ApplicationDbContext(options);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-16 17:12:03 -03:00
|
|
|
private static DashboardService NewService(ApplicationDbContext ctx)
|
|
|
|
|
{
|
|
|
|
|
var resolver = new WorkOrderAccountResolver(
|
|
|
|
|
new AccountDataService(ctx),
|
|
|
|
|
new LocationDataService(ctx));
|
|
|
|
|
return new DashboardService(new DashboardDataService(ctx), resolver);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static ClaimsPrincipal AccountUser(int accountId)
|
|
|
|
|
{
|
|
|
|
|
var claims = new[]
|
|
|
|
|
{
|
|
|
|
|
new Claim(SeaHavenClaimTypes.AccountId, accountId.ToString()),
|
|
|
|
|
new Claim(ClaimTypes.Role, "Dispatcher")
|
|
|
|
|
};
|
|
|
|
|
return new ClaimsPrincipal(new ClaimsIdentity(claims, "test"));
|
|
|
|
|
}
|
2026-07-24 17:35:34 -03:00
|
|
|
|
2026-09-16 17:12:03 -03:00
|
|
|
private static ClaimsPrincipal OrgWideUser()
|
|
|
|
|
{
|
|
|
|
|
var claims = new[]
|
|
|
|
|
{
|
|
|
|
|
new Claim(SeaHavenClaimTypes.OrgScope, SeaHavenClaimTypes.OrgScopeAll),
|
|
|
|
|
new Claim(ClaimTypes.Role, "Admin")
|
|
|
|
|
};
|
|
|
|
|
return new ClaimsPrincipal(new ClaimsIdentity(claims, "test"));
|
|
|
|
|
}
|
2026-07-24 17:35:34 -03:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetStatsAsync_CountsByStatusExcludingTemplates()
|
|
|
|
|
{
|
|
|
|
|
using var ctx = NewContext();
|
|
|
|
|
ctx.workOrders.AddRange(
|
2026-09-16 17:12:03 -03:00
|
|
|
new WorkOrder { AccountId = 1, Status = "Open", istemplate = false },
|
|
|
|
|
new WorkOrder { AccountId = 1, Status = "Open", istemplate = false, AssignTo = "u1" },
|
|
|
|
|
new WorkOrder { AccountId = 1, Status = "In Progress" },
|
|
|
|
|
new WorkOrder { AccountId = 1, Status = "On Hold" },
|
|
|
|
|
new WorkOrder { AccountId = 1, Status = "Done" },
|
|
|
|
|
new WorkOrder { AccountId = 1, Status = "Done" },
|
|
|
|
|
new WorkOrder { AccountId = 1, Status = "Open", istemplate = true },
|
|
|
|
|
new WorkOrder { AccountId = 1, Status = "Cancelled" },
|
|
|
|
|
new WorkOrder { AccountId = 2, Status = "Open" },
|
|
|
|
|
new WorkOrder { Status = "Open" }
|
2026-07-24 17:35:34 -03:00
|
|
|
);
|
|
|
|
|
ctx.SaveChanges();
|
|
|
|
|
|
2026-09-16 17:12:03 -03:00
|
|
|
var stats = await NewService(ctx).GetStatsAsync(AccountUser(1), CancellationToken.None);
|
2026-07-24 17:35:34 -03:00
|
|
|
|
|
|
|
|
stats.Total.Should().Be(7);
|
|
|
|
|
stats.Open.Should().Be(4);
|
|
|
|
|
stats.NotDispatched.Should().Be(1);
|
|
|
|
|
stats.Completed.Should().Be(2);
|
|
|
|
|
}
|
2026-09-16 17:12:03 -03:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetStatsAsync_OrgWideUserSeesAllNonTemplateOrders()
|
|
|
|
|
{
|
|
|
|
|
using var ctx = NewContext();
|
|
|
|
|
ctx.workOrders.AddRange(
|
|
|
|
|
new WorkOrder { AccountId = 1, Status = "Open" },
|
|
|
|
|
new WorkOrder { AccountId = 2, Status = "Done" },
|
|
|
|
|
new WorkOrder { Status = "Open", istemplate = true });
|
|
|
|
|
ctx.SaveChanges();
|
|
|
|
|
|
|
|
|
|
var stats = await NewService(ctx).GetStatsAsync(OrgWideUser(), CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
stats.Total.Should().Be(2);
|
|
|
|
|
stats.Open.Should().Be(1);
|
|
|
|
|
stats.Completed.Should().Be(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async Task GetStatsAsync_MissingScopeFailsClosed()
|
|
|
|
|
{
|
|
|
|
|
using var ctx = NewContext();
|
|
|
|
|
var user = new ClaimsPrincipal(new ClaimsIdentity(new[]
|
|
|
|
|
{
|
|
|
|
|
new Claim(ClaimTypes.Role, "Dispatcher")
|
|
|
|
|
}, "test"));
|
|
|
|
|
|
|
|
|
|
var act = () => NewService(ctx).GetStatsAsync(user, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
var exception = await act.Should().ThrowAsync<WorkOrderBoardValidationException>();
|
|
|
|
|
|
|
|
|
|
exception.Which.Code.Should().Be("Forbidden");
|
|
|
|
|
}
|
2026-07-24 17:35:34 -03:00
|
|
|
}
|