using Hangfire; using Manager.Services; using Manager.Interfaces.Models; using ManagerService.Controllers; using ManagerService.Data; using ManagerService.DTOs; using ManagerService.Helpers; using ManagerService.Services; using ManagerService.Tests.Infrastructure; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; namespace ManagerService.Tests.Controllers { public class InstanceControllerTests { private InstanceController BuildController(MyInfoMateDbContext db, Mock jobs = null) { var cfg = FakeMongoConfig.Create(); var instanceService = new InstanceDatabaseService(cfg); var userService = new UserDatabaseService(cfg); var profileLogic = new ProfileLogic(NullLogger.Instance); var apiKeyService = new ApiKeyDatabaseService(db); return new InstanceController( NullLogger.Instance, instanceService, userService, profileLogic, db, apiKeyService, (jobs ?? new Mock()).Object); } // ── CREATE ─────────────────────────────────────────────────────────── [Fact] public void CreateInstance_DuplicateName_Returns409() { using var db = DbContextFactory.Create(); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow }); db.SaveChanges(); var result = BuildController(db).CreateInstance(new InstanceDTO { name = "Musée" }); Assert.IsType(result); } [Fact] public void CreateInstance_ValidDto_PersistsAndReturnsDto() { using var db = DbContextFactory.Create(); var result = BuildController(db).CreateInstance(new InstanceDTO { name = "Nouveau" }); Assert.IsType(result); Assert.Equal(1, db.Instances.Count()); } [Fact] public void CreateInstance_NullDto_Returns400() { using var db = DbContextFactory.Create(); var result = BuildController(db).CreateInstance(null!); Assert.IsType(result); } // ── UPDATE ─────────────────────────────────────────────────────────── [Fact] public void Updateinstance_ClearSubscriptionPlan_SetsNull() { using var db = DbContextFactory.Create(); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "p1", Name = "Starter" }); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", SubscriptionPlanId = "p1", DateCreation = DateTime.UtcNow }); db.SaveChanges(); // subscriptionPlanId = "" → doit effacer le plan var result = BuildController(db).Updateinstance(new InstanceDTO { id = "i1", subscriptionPlanId = "" }); Assert.IsType(result); Assert.Null(db.Instances.First().SubscriptionPlanId); } [Fact] public void Updateinstance_SetSubscriptionPlan_Updates() { using var db = DbContextFactory.Create(); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "p1", Name = "Starter" }); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow }); db.SaveChanges(); var result = BuildController(db).Updateinstance(new InstanceDTO { id = "i1", subscriptionPlanId = "p1" }); Assert.IsType(result); Assert.Equal("p1", db.Instances.First().SubscriptionPlanId); } [Fact] public void Updateinstance_ChangingPlan_CopiesPlanQuotasOntoInstance() { using var db = DbContextFactory.Create(); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "p-premium", Name = "Premium", StorageQuotaBytes = 50_000, AiTokensPerMonth = 20_000, HasStats = true, HasAdvancedStats = true }); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow }); db.SaveChanges(); BuildController(db).Updateinstance(new InstanceDTO { id = "i1", subscriptionPlanId = "p-premium" }); // CreateInstance recopiait les quotas du plan, pas Update : un client passé à un plan // payant gardait 0 jeton IA, donc pas de guide, tout en payant pour. var instance = db.Instances.First(); Assert.Equal(20_000, instance.AiTokensPerMonth); Assert.Equal(50_000, instance.StorageQuotaBytes); Assert.True(instance.HasAdvancedStats); } [Fact] public void Updateinstance_GainingAiQuota_EnqueuesBackfill() { using var db = DbContextFactory.Create(); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "p-premium", Name = "Premium", AiTokensPerMonth = 20_000 }); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow, AiTokensPerMonth = 0 }); db.SaveChanges(); var jobs = new Mock(); BuildController(db, jobs).Updateinstance(new InstanceDTO { id = "i1", subscriptionPlanId = "p-premium" }); // Rien n'a été indexé tant que l'instance n'avait pas droit à l'IA : sans ce // rattrapage le client paie un guide qui ne connaît rien de son contenu. jobs.Verify(j => j.Create(It.IsAny(), It.IsAny()), Times.Once); } [Fact] public void Updateinstance_PlanUnchanged_DoesNotEnqueueBackfill() { using var db = DbContextFactory.Create(); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "p-premium", Name = "Premium", AiTokensPerMonth = 20_000 }); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow, SubscriptionPlanId = "p-premium", AiTokensPerMonth = 20_000 }); db.SaveChanges(); var jobs = new Mock(); BuildController(db, jobs).Updateinstance(new InstanceDTO { id = "i1", name = "Musée d'Ixelles" }); jobs.Verify(j => j.Create(It.IsAny(), It.IsAny()), Times.Never); } [Fact] public void Updateinstance_UnknownId_Returns404() { using var db = DbContextFactory.Create(); var result = BuildController(db).Updateinstance(new InstanceDTO { id = "unknown", name = "X" }); Assert.IsType(result); } // ── GET QUOTA ──────────────────────────────────────────────────────── [Fact] public void GetQuota_UnknownInstance_Returns404() { using var db = DbContextFactory.Create(); var result = BuildController(db).GetQuota("unknown"); Assert.IsType(result); } [Fact] public void GetQuota_NoSubscriptionPlan_ReturnsZeroQuotas() { using var db = DbContextFactory.Create(); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow }); db.SaveChanges(); var result = BuildController(db).GetQuota("i1"); var ok = Assert.IsType(result); var dto = Assert.IsType(ok.Value); Assert.Equal(0, dto.storageQuotaBytes); Assert.Equal(0, dto.aiTokensPerMonth); } [Fact] public void GetQuota_WithPlan_ReturnsQuotaFromPlan() { using var db = DbContextFactory.Create(); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "p1", Name = "Standard", StorageQuotaBytes = 10_000_000_000L, AiTokensPerMonth = 100 }); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", SubscriptionPlanId = "p1", DateCreation = DateTime.UtcNow }); db.SaveChanges(); var result = BuildController(db).GetQuota("i1"); var ok = Assert.IsType(result); var dto = Assert.IsType(ok.Value); Assert.Equal(10_000_000_000L, dto.storageQuotaBytes); Assert.Equal(100, dto.aiTokensPerMonth); } [Fact] public void GetQuota_SumsResourceSizeBytes() { using var db = DbContextFactory.Create(); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow }); db.Resources.AddRange( new Resource { Id = "r1", InstanceId = "i1", Label = "img1", Type = ResourceType.Image, SizeBytes = 1000 }, new Resource { Id = "r2", InstanceId = "i1", Label = "img2", Type = ResourceType.Image, SizeBytes = 2000 }, new Resource { Id = "r3", InstanceId = "other", Label = "img3", Type = ResourceType.Image, SizeBytes = 9999 } ); db.SaveChanges(); var result = BuildController(db).GetQuota("i1"); var ok = Assert.IsType(result); var dto = Assert.IsType(ok.Value); Assert.Equal(3000, dto.storageUsedBytes); } [Fact] public void GetQuota_AiUsage_CurrentMonth_ReturnsCount() { using var db = DbContextFactory.Create(); var monthKey = DateTime.UtcNow.ToString("yyyy-MM"); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow, AiTokensThisMonth = 7, AiUsageMonthKey = monthKey }); db.SaveChanges(); var result = BuildController(db).GetQuota("i1"); var ok = Assert.IsType(result); var dto = Assert.IsType(ok.Value); Assert.Equal(7, dto.aiTokensUsed); } [Fact] public void GetQuota_AiUsage_PreviousMonth_ReturnsZero() { using var db = DbContextFactory.Create(); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow, AiTokensThisMonth = 7, AiUsageMonthKey = "2020-01" }); db.SaveChanges(); var result = BuildController(db).GetQuota("i1"); var ok = Assert.IsType(result); var dto = Assert.IsType(ok.Value); Assert.Equal(0, dto.aiTokensUsed); } // ── DELETE ─────────────────────────────────────────────────────────── [Fact] public void DeleteInstance_CascadesUsers() { using var db = DbContextFactory.Create(); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow }); db.Users.AddRange( new User { Id = "u1", Email = "a@test.be", Password = "x", LastName = "A", Token = "t1", InstanceId = "i1" }, new User { Id = "u2", Email = "b@test.be", Password = "x", LastName = "B", Token = "t2", InstanceId = "i1" } ); db.SaveChanges(); var result = BuildController(db).DeleteInstance("i1"); var status = Assert.IsType(result); Assert.Equal(202, status.StatusCode); Assert.Equal(0, db.Instances.Count()); Assert.Equal(0, db.Users.Count()); } [Fact] public void DeleteInstance_UnknownId_Returns404() { using var db = DbContextFactory.Create(); var result = BuildController(db).DeleteInstance("unknown"); Assert.IsType(result); } } }