using Hangfire; using Manager.Services; using Manager.Interfaces.Models; using ManagerService.Controllers; using ManagerService.Helpers; using ManagerService.Data; using ManagerService.DTOs; using ManagerService.Services; using ManagerService.Tests.Infrastructure; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging.Abstractions; using Moq; using Xunit; namespace ManagerService.Tests.Controllers { /// /// L'add-on « Contenu immersif » — §6 de DOCS/v2/vr-quest-unity-plan.md. /// /// ⚠️ Ces tests existent pour une raison précise : la migration pose un /// defaultValue, ce qui amène EF Core à traiter la colonne comme /// ValueGeneratedOnAddread-only après insert. Le piège est déjà /// documenté dans MyInfoMateDbContext pour les quotas, et il a coûté une /// migration corrigée à la main sur Device.AppType. Sans /// ValueGeneratedNever, l'add-on ne s'activerait jamais — silencieusement. /// public class InstanceAddonTests { private InstanceController BuildController(MyInfoMateDbContext db) { var cfg = FakeMongoConfig.Create(); return new InstanceController( NullLogger.Instance, new InstanceDatabaseService(cfg), new UserDatabaseService(cfg), new ProfileLogic(NullLogger.Instance), db, new ApiKeyDatabaseService(db), new Mock().Object); } [Fact] public void ImmersiveContent_IsOffByDefault() { using var db = DbContextFactory.Create(); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow }); db.SaveChanges(); Assert.False(db.Instances.First().HasImmersiveContent); } [Fact] public void ImmersiveContent_CanBeTurnedOn() { using var db = DbContextFactory.Create(); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow }); db.SaveChanges(); var result = BuildController(db).Updateinstance( new InstanceDTO { id = "i1", hasImmersiveContent = true }); Assert.IsType(result); Assert.True(db.Instances.First().HasImmersiveContent); } [Fact] public void ImmersiveContent_AbsentFromPayload_KeepsPreviousValue() { using var db = DbContextFactory.Create(); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow, HasImmersiveContent = true }); db.SaveChanges(); // Un écran qui ne parle pas de l'add-on ne doit pas le désactiver au passage : // le contrôleur applique `?? valeur actuelle`, comme pour les autres drapeaux. BuildController(db).Updateinstance(new InstanceDTO { id = "i1", name = "Musée" }); Assert.True(db.Instances.First().HasImmersiveContent); } private const long Envelope = 100L * 1024 * 1024 * 1024; private const long PlanQuota = 10L * 1024 * 1024 * 1024; private static MyInfoMateDbContext WithPlan(long quota = PlanQuota) { var db = DbContextFactory.Create(); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "pro", Name = "Pro", StorageQuotaBytes = quota }); db.Instances.Add(new Instance { Id = "i1", Name = "Musée", DateCreation = DateTime.UtcNow, StorageQuotaBytes = quota }); db.SaveChanges(); return db; } [Fact] public void ActivatingImmersive_RaisesStorageQuota() { using var db = WithPlan(); BuildController(db).Updateinstance( new InstanceDTO { id = "i1", hasImmersiveContent = true }); // Sans ce relèvement, un client Pro activait la 360 et saturait son quota à la // première vidéo : c'est le point de marge du §6 du plan. Assert.Equal(PlanQuota + Envelope, db.Instances.First().StorageQuotaBytes); } [Fact] public void DeactivatingImmersive_LowersStorageQuotaBack() { using var db = WithPlan(); var controller = BuildController(db); controller.Updateinstance(new InstanceDTO { id = "i1", hasImmersiveContent = true }); controller.Updateinstance(new InstanceDTO { id = "i1", hasImmersiveContent = false }); Assert.Equal(PlanQuota, db.Instances.First().StorageQuotaBytes); } [Fact] public void ActivatingImmersiveTwice_DoesNotStackEnvelopes() { using var db = WithPlan(); var controller = BuildController(db); controller.Updateinstance(new InstanceDTO { id = "i1", hasImmersiveContent = true }); controller.Updateinstance(new InstanceDTO { id = "i1", hasImmersiveContent = true }); // Un écran qui renvoie tout le formulaire à chaque enregistrement repasse `true` // sans rien changer : le quota ne doit pas grimper d'une enveloppe par clic. Assert.Equal(PlanQuota + Envelope, db.Instances.First().StorageQuotaBytes); } [Fact] public void ChangingPlan_KeepsTheImmersiveEnvelope() { using var db = WithPlan(); db.SubscriptionPlans.Add(new SubscriptionPlan { Id = "premium", Name = "Premium", StorageQuotaBytes = 50L * 1024 * 1024 * 1024 }); db.SaveChanges(); var controller = BuildController(db); controller.Updateinstance(new InstanceDTO { id = "i1", hasImmersiveContent = true }); controller.Updateinstance(new InstanceDTO { id = "i1", subscriptionPlanId = "premium" }); // ApplyPlanQuotas réécrit le quota depuis le plan : sans reposer l'enveloppe // par-dessus, faire monter un client en gamme lui retirerait son add-on. Assert.Equal(50L * 1024 * 1024 * 1024 + Envelope, db.Instances.First().StorageQuotaBytes); } } }