diff --git a/ManagerService.Tests/Controllers/UserControllerTests.cs b/ManagerService.Tests/Controllers/UserControllerTests.cs index 10b0ec7..aaee9d8 100644 --- a/ManagerService.Tests/Controllers/UserControllerTests.cs +++ b/ManagerService.Tests/Controllers/UserControllerTests.cs @@ -157,6 +157,75 @@ namespace ManagerService.Tests.Controllers Assert.Null(db.Users.Single().PasswordTokenHash); } + // ── PLAFOND DE 5 UTILISATEURS ──────────────────────────────────────── + + private static void SeedUsers(MyInfoMateDbContext db, int count, string instanceId) + { + for (var i = 0; i < count; i++) + db.Users.Add(new User + { + Id = $"{instanceId}-u{i}", + Email = $"{instanceId}-{i}@a.be", + Password = "x", + LastName = "A", + Token = $"{instanceId}-t{i}", + InstanceId = instanceId + }); + db.SaveChanges(); + } + + [Fact] + public async Task CreateUser_AtCap_Returns422() + { + // Le compteur de manager-app est un garde-fou d'interface : un POST direct + // passait toujours. C'est ce trou-là que le 422 ferme. + using var db = DbContextFactory.Create(); + SeedUsers(db, 5, "inst-test"); + + var result = await BuildController(db, "Manager.instanceadmin").CreateUser(MakeNewUser("sixth@a.be")); + + Assert.Equal(422, result.StatusCode); + Assert.Equal(5, db.Users.Count(u => u.InstanceId == "inst-test")); + } + + [Fact] + public async Task CreateUser_UnderCap_Succeeds() + { + using var db = DbContextFactory.Create(); + SeedUsers(db, 4, "inst-test"); + + var result = await BuildController(db, "Manager.instanceadmin").CreateUser(MakeNewUser("fifth@a.be")); + + Assert.IsType(result); + Assert.Equal(5, db.Users.Count(u => u.InstanceId == "inst-test")); + } + + [Fact] + public async Task CreateUser_CapIsPerInstance() + { + // Une autre instance pleine ne doit pas bloquer celle de l'appelant. + using var db = DbContextFactory.Create(); + SeedUsers(db, 5, "other-inst"); + + var result = await BuildController(db, "Manager.instanceadmin").CreateUser(MakeNewUser("first@a.be")); + + Assert.IsType(result); + } + + [Fact] + public async Task CreateUser_SuperAdmin_IsNotCapped() + { + // Porte de service interne : tant qu'aucun champ ne permet de relever la + // limite d'un client, le SuperAdmin doit pouvoir ajouter un siège. + using var db = DbContextFactory.Create(); + SeedUsers(db, 5, "inst-test"); + + var result = await BuildController(db, "Manager.superadmin").CreateUser(MakeNewUser("sixth@a.be")); + + Assert.IsType(result); + Assert.Equal(6, db.Users.Count(u => u.InstanceId == "inst-test")); + } + // ── UPDATE ─────────────────────────────────────────────────────────── [Fact] diff --git a/ManagerService/Controllers/UserController.cs b/ManagerService/Controllers/UserController.cs index 7c692d7..2fe25c0 100644 --- a/ManagerService/Controllers/UserController.cs +++ b/ManagerService/Controllers/UserController.cs @@ -30,6 +30,18 @@ namespace ManagerService.Controllers private readonly IConfiguration _configuration; IHexIdGeneratorService idService = new HexIdGeneratorService(); + /// + /// Plafond d'utilisateurs par instance, tous plans confondus. + /// + /// + /// ⚠️ Dette assumee pour la V1 : le chiffre est en dur. Le faire varier par plan + /// demanderait une colonne sur SubscriptionPlan, qui n'en porte aucune sur les + /// utilisateurs, donc une migration apres le gel du schema (lot B). + /// Le SuperAdmin n'est pas soumis au plafond : c'est la porte de service interne + /// tant qu'aucun champ ne permet de relever la limite d'un client. + /// + private const int MaxUsersPerInstance = 5; + public UserController(ILogger logger, UserDatabaseService userService, ProfileLogic profileLogic, MyInfoMateDbContext myInfoMateDbContext, IEmailService emailService, IConfiguration configuration) { _logger = logger; @@ -113,6 +125,7 @@ namespace ManagerService.Controllers [ProducesResponseType(typeof(UserDetailDTO), 200)] [ProducesResponseType(typeof(string), 400)] [ProducesResponseType(typeof(string), 409)] + [ProducesResponseType(typeof(string), 422)] [ProducesResponseType(typeof(string), 500)] [HttpPost] public async Task CreateUser([FromBody] UserDetailDTO newUserDTO) @@ -131,6 +144,9 @@ namespace ManagerService.Controllers User newUser = new User(); newUser.InstanceId = IsSuperAdmin() ? newUserDTO.instanceId : GetCallerInstanceId(); + + if (!IsSuperAdmin() && _myInfoMateDbContext.Users.Count(u => u.InstanceId == newUser.InstanceId) >= MaxUsersPerInstance) + return UnprocessableEntity($"This instance has reached its limit of {MaxUsersPerInstance} users"); newUser.Email = newUserDTO.email; newUser.FirstName = newUserDTO.firstName; newUser.LastName = newUserDTO.lastName;