diff --git a/test/SeederApi.IntegrationTest/Factories/OrganizationSeederTests.cs b/test/SeederApi.IntegrationTest/Factories/OrganizationSeederTests.cs index cafa3abe1156..18728a6dcffc 100644 --- a/test/SeederApi.IntegrationTest/Factories/OrganizationSeederTests.cs +++ b/test/SeederApi.IntegrationTest/Factories/OrganizationSeederTests.cs @@ -126,10 +126,23 @@ public void Create_WithoutSecretsManager_LeavesSeatsUnprovisioned() Assert.False(free.UseSecretsManager); } + [Fact] + public void Create_FreeWithSecretsManager_SetsFreeTierDefaults() + { + var free = OrganizationSeeder.Create( + Seed() with { PlanType = PlanType.Free, EnableSecretsManager = true }, + new NoOpManglerService()); + + Assert.True(free.UseSecretsManager); + Assert.Equal(2, free.SmSeats); // Free tier base seats + Assert.Equal(3, free.SmServiceAccounts); // Free tier base service accounts + } + [Fact] public void Create_SecretsManagerOnUnsupportedPlan_Throws() { - var seed = Seed() with { PlanType = PlanType.Free, EnableSecretsManager = true }; + // Families has no Secrets Manager tier, so enabling it must still throw. + var seed = Seed() with { PlanType = PlanType.FamiliesAnnually, EnableSecretsManager = true }; Assert.Throws(() => OrganizationSeeder.Create(seed, new NoOpManglerService())); } diff --git a/util/Seeder/Factories/PlanFeatures.cs b/util/Seeder/Factories/PlanFeatures.cs index 3fd220db6846..261ac3527639 100644 --- a/util/Seeder/Factories/PlanFeatures.cs +++ b/util/Seeder/Factories/PlanFeatures.cs @@ -119,18 +119,20 @@ internal static void ApplyOrganizationOverrides(Organization org, OrganizationOv /// internal static void EnableSecretsManager(Organization org, int? smSeats, int? smServiceAccounts) { - var baseServiceAccounts = org.PlanType switch + var (baseSeats, baseServiceAccounts) = org.PlanType switch { PlanType.EnterpriseMonthly or PlanType.EnterpriseAnnually - or PlanType.TeamsAnnually => 50, - PlanType.TeamsMonthly or PlanType.TeamsStarter => 20, + or PlanType.TeamsAnnually => (org.Seats, 50), + PlanType.TeamsMonthly or PlanType.TeamsStarter => (org.Seats, 20), + // Free Secrets Manager tier: 2 seats, 3 service accounts (see FreePlan mock). + PlanType.Free => (2, 3), _ => throw new ArgumentException( $"PlanType '{org.PlanType}' does not support Secrets Manager. " + - "Supported: TeamsMonthly, TeamsAnnually, TeamsStarter, EnterpriseMonthly, EnterpriseAnnually.") + "Supported: Free, TeamsMonthly, TeamsAnnually, TeamsStarter, EnterpriseMonthly, EnterpriseAnnually.") }; org.UseSecretsManager = true; - org.SmSeats = smSeats ?? org.Seats; + org.SmSeats = smSeats ?? baseSeats; org.SmServiceAccounts = smServiceAccounts ?? baseServiceAccounts; } diff --git a/util/Seeder/Models/OrganizationSeed.cs b/util/Seeder/Models/OrganizationSeed.cs index b322c9eb919b..0ff2bfa53b33 100644 --- a/util/Seeder/Models/OrganizationSeed.cs +++ b/util/Seeder/Models/OrganizationSeed.cs @@ -27,7 +27,7 @@ internal record OrganizationSeed public required int Seats { get; init; } /// - /// Drives ~25 feature flags through PlanFeatures.Apply. Free and Families reject Secrets Manager. + /// Drives ~25 feature flags through PlanFeatures.Apply. Families rejects Secrets Manager. /// public PlanType PlanType { get; init; } = PlanType.EnterpriseAnnually; @@ -62,17 +62,18 @@ internal record OrganizationSeed public string? GatewaySubscriptionId { get; init; } /// - /// Throws for plans without a Secrets Manager tier (Free, Families). + /// Throws for plans without a Secrets Manager tier (e.g. Families). /// public bool EnableSecretsManager { get; init; } /// - /// Defaults to . Ignored unless . + /// Defaults to the plan's base seats: for paid plans, 2 for Free. + /// Ignored unless . /// public int? SmSeats { get; init; } /// - /// Defaults to the plan's base allotment: 50 for Enterprise and Teams-Annual, 20 for Teams. + /// Defaults to the plan's base allotment: 50 for Enterprise and Teams-Annual, 20 for Teams, 3 for Free. /// Ignored unless . /// public int? SmServiceAccounts { get; init; }