using Bit.Seeder.Data; using Bit.Seeder.Data.Distributions; using Bit.Seeder.Data.Enums; using Bit.Seeder.Options; using Xunit; namespace Bit.SeederApi.IntegrationTest; public class GeneratorContextTests { [Fact] public void FromOptions_SameDomain_ProducesSameSeed() { var options1 = CreateOptions("acme.com", ciphers: 100); var options2 = CreateOptions("acme.com", ciphers: 100); var ctx1 = GeneratorContext.FromOptions(options1); var ctx2 = GeneratorContext.FromOptions(options2); Assert.Equal(ctx1.Seed, ctx2.Seed); } [Fact] public void FromOptions_DifferentDomains_ProduceDifferentSeeds() { var ctx1 = GeneratorContext.FromOptions(CreateOptions("acme.com")); var ctx2 = GeneratorContext.FromOptions(CreateOptions("contoso.com")); Assert.NotEqual(ctx1.Seed, ctx2.Seed); } [Fact] public void FromOptions_ExplicitSeed_OverridesDomainHash() { var options = new OrganizationVaultOptions { Name = "Test Org", Domain = "example.com", Users = 10, Ciphers = 100, Seed = 42 }; var ctx = GeneratorContext.FromOptions(options); Assert.Equal(42, ctx.Seed); } [Fact] public void Username_SameSeed_ProducesSameOutput() { var options = CreateOptions("test.com", ciphers: 100); var ctx1 = GeneratorContext.FromOptions(options); var ctx2 = GeneratorContext.FromOptions(options); for (int i = 0; i < 50; i++) { var username1 = ctx1.Username.GenerateByIndex(i, totalHint: 100, domain: "test.com"); var username2 = ctx2.Username.GenerateByIndex(i, totalHint: 100, domain: "test.com"); Assert.Equal(username1, username2); } } [Fact] public void Username_DifferentSeeds_ProducesDifferentOutput() { var ctx1 = GeneratorContext.FromOptions(CreateOptions("alpha.com")); var ctx2 = GeneratorContext.FromOptions(CreateOptions("beta.com")); var username1 = ctx1.Username.GenerateByIndex(0, domain: "alpha.com"); var username2 = ctx2.Username.GenerateByIndex(0, domain: "beta.com"); Assert.NotEqual(username1, username2); } [Fact] public void Folder_SameSeed_ProducesSameOutput() { var options = CreateOptions("test.com"); var ctx1 = GeneratorContext.FromOptions(options); var ctx2 = GeneratorContext.FromOptions(options); for (int i = 0; i < 20; i++) { var folder1 = ctx1.Folder.GetFolderName(i); var folder2 = ctx2.Folder.GetFolderName(i); Assert.Equal(folder1, folder2); } } [Fact] public void Card_SameSeed_ProducesSameOutput() { var options = CreateOptions("test.com"); var ctx1 = GeneratorContext.FromOptions(options); var ctx2 = GeneratorContext.FromOptions(options); for (int i = 0; i < 20; i++) { var card1 = ctx1.Card.GenerateByIndex(i); var card2 = ctx2.Card.GenerateByIndex(i); Assert.Equal(card1.CardholderName, card2.CardholderName); Assert.Equal(card1.Number, card2.Number); Assert.Equal(card1.ExpMonth, card2.ExpMonth); Assert.Equal(card1.ExpYear, card2.ExpYear); Assert.Equal(card1.Code, card2.Code); } } [Fact] public void Identity_SameSeed_ProducesSameOutput() { var options = CreateOptions("test.com"); var ctx1 = GeneratorContext.FromOptions(options); var ctx2 = GeneratorContext.FromOptions(options); for (int i = 0; i < 20; i++) { var identity1 = ctx1.Identity.GenerateByIndex(i); var identity2 = ctx2.Identity.GenerateByIndex(i); Assert.Equal(identity1.FirstName, identity2.FirstName); Assert.Equal(identity1.LastName, identity2.LastName); Assert.Equal(identity1.Email, identity2.Email); } } /// /// Limited to 5 iterations to avoid a Bogus.Password() infinite loop bug /// that occurs with certain seed/index combinations in WiFi/Database note categories. /// The workaround is a known test workaround that doesn't affect production code. /// [Fact] public void SecureNote_SameSeed_ProducesSameOutput() { var options = CreateOptions("test.com"); var ctx1 = GeneratorContext.FromOptions(options); var ctx2 = GeneratorContext.FromOptions(options); for (var i = 0; i < 5; i++) { var (title1, content1) = ctx1.SecureNote.GenerateByIndex(i); var (title2, content2) = ctx2.SecureNote.GenerateByIndex(i); Assert.Equal(title1, title2); Assert.Equal(content1, content2); } } [Fact] public void CipherCount_ReflectsOptionsValue() { var options = CreateOptions("test.com", ciphers: 500); var ctx = GeneratorContext.FromOptions(options); Assert.Equal(500, ctx.CipherCount); } [Fact] public void Username_WithCorporatePattern_AppliesCorrectFormat() { var options = new OrganizationVaultOptions { Name = "Test Org", Domain = "corp.com", Users = 10, Ciphers = 100, UsernamePattern = UsernamePatternType.FDotLast, UsernameDistribution = new Distribution( (UsernameCategory.CorporateEmail, 1.0) ) }; var ctx = GeneratorContext.FromOptions(options); var username = ctx.Username.GenerateByIndex(0, domain: "corp.com"); Assert.Matches(@"^[a-z]\.[a-z]+@corp\.com$", username); } [Fact] public void Username_WithRegion_ProducesCulturallyAppropriateNames() { var europeOptions = new OrganizationVaultOptions { Name = "Euro Corp", Domain = "euro.com", Users = 10, Ciphers = 100, Region = GeographicRegion.Europe, UsernameDistribution = new Distribution( (UsernameCategory.CorporateEmail, 1.0) ) }; var ctx = GeneratorContext.FromOptions(europeOptions); var username = ctx.Username.GenerateByIndex(0, domain: "euro.com"); Assert.Contains("@euro.com", username); Assert.Matches(@"^[\p{L}]+\.[\p{L}]+@euro\.com$", username); } [Fact] public void Generators_AreLazilyInitialized() { var options = CreateOptions("test.com"); var ctx = GeneratorContext.FromOptions(options); _ = ctx.Seed; _ = ctx.Username.GenerateByIndex(0); Assert.NotNull(ctx.Username); Assert.NotNull(ctx.Folder); Assert.NotNull(ctx.Card); Assert.NotNull(ctx.Identity); } [Fact] public void AllGenerators_ProduceDifferentOutputForDifferentIndices() { var ctx = GeneratorContext.FromOptions(CreateOptions("test.com", ciphers: 100)); var usernames = Enumerable.Range(0, 50) .Select(i => ctx.Username.GenerateByIndex(i, domain: "test.com")) .ToHashSet(); Assert.True(usernames.Count > 40, "Should generate mostly unique usernames"); var folders = Enumerable.Range(0, 50) .Select(i => ctx.Folder.GetFolderName(i)) .ToHashSet(); Assert.True(folders.Count > 30, "Should generate diverse folder names"); var cards = Enumerable.Range(0, 50) .Select(i => ctx.Card.GenerateByIndex(i).Number) .ToHashSet(); Assert.True(cards.Count > 40, "Should generate mostly unique card numbers"); var identities = Enumerable.Range(0, 50) .Select(i => ctx.Identity.GenerateByIndex(i).Email) .ToHashSet(); Assert.True(identities.Count > 40, "Should generate mostly unique identity emails"); } private static OrganizationVaultOptions CreateOptions(string domain, int ciphers = 100) { return new OrganizationVaultOptions { Name = "Test Org", Domain = domain, Users = 10, Ciphers = ciphers }; } }