mirror of
https://github.com/bitwarden/server
synced 2026-02-14 15:33:35 +00:00
Enhance seeder with additional cipher types and architectural refactorings (#6935)
This commit is contained in:
@@ -8,6 +8,7 @@ using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Api.Models.Request;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Seeder.Recipes;
|
||||
using Bit.Seeder.Services;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
@@ -31,7 +32,8 @@ public class GroupsControllerPerformanceTests(ITestOutputHelper testOutputHelper
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = new NoOpManglerService();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var collectionsSeeder = new CollectionsRecipe(db);
|
||||
var groupsSeeder = new GroupsRecipe(db);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Seeder.Recipes;
|
||||
using Bit.Seeder.Services;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
@@ -33,7 +34,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var collectionsSeeder = new CollectionsRecipe(db);
|
||||
var groupsSeeder = new GroupsRecipe(db);
|
||||
|
||||
@@ -71,7 +73,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var collectionsSeeder = new CollectionsRecipe(db);
|
||||
var groupsSeeder = new GroupsRecipe(db);
|
||||
|
||||
@@ -107,7 +110,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var groupsSeeder = new GroupsRecipe(db);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
@@ -141,7 +145,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: 1);
|
||||
@@ -176,7 +181,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
var orgId = orgSeeder.Seed(
|
||||
@@ -226,7 +232,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount);
|
||||
@@ -268,7 +275,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
var orgId = orgSeeder.Seed(
|
||||
@@ -314,7 +322,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
var orgId = orgSeeder.Seed(
|
||||
@@ -360,7 +369,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var domainSeeder = new OrganizationDomainRecipe(db);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
@@ -407,7 +417,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var collectionsSeeder = new CollectionsRecipe(db);
|
||||
var groupsSeeder = new GroupsRecipe(db);
|
||||
|
||||
@@ -459,7 +470,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
var orgId = orgSeeder.Seed(name: "Org", domain: domain, users: userCount);
|
||||
@@ -498,7 +510,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var domainSeeder = new OrganizationDomainRecipe(db);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
@@ -541,7 +554,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var collectionsSeeder = new CollectionsRecipe(db);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
@@ -591,7 +605,8 @@ public class OrganizationUsersControllerPerformanceTests(ITestOutputHelper testO
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = factory.GetService<IManglerService>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
|
||||
var domain = OrganizationTestHelpers.GenerateRandomDomain();
|
||||
var orgId = orgSeeder.Seed(
|
||||
|
||||
@@ -11,6 +11,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Seeder.Recipes;
|
||||
using Bit.Seeder.Services;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
@@ -34,7 +35,8 @@ public class OrganizationsControllerPerformanceTests(ITestOutputHelper testOutpu
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = new NoOpManglerService();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var collectionsSeeder = new CollectionsRecipe(db);
|
||||
var groupsSeeder = new GroupsRecipe(db);
|
||||
|
||||
@@ -84,7 +86,8 @@ public class OrganizationsControllerPerformanceTests(ITestOutputHelper testOutpu
|
||||
var db = factory.GetDatabaseContext();
|
||||
var mapper = factory.GetService<IMapper>();
|
||||
var passwordHasher = factory.GetService<IPasswordHasher<User>>();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher);
|
||||
var manglerService = new NoOpManglerService();
|
||||
var orgSeeder = new OrganizationWithUsersRecipe(db, mapper, passwordHasher, manglerService);
|
||||
var collectionsSeeder = new CollectionsRecipe(db);
|
||||
var groupsSeeder = new GroupsRecipe(db);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using Bit.Core.Platform.PushRegistration.Internal;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Infrastructure.EntityFramework.Repositories;
|
||||
using Bit.Seeder.Services;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
@@ -190,6 +191,9 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
|
||||
TestDatabase.Migrate(services);
|
||||
}
|
||||
|
||||
// Register NoOpManglerService for test data seeding (no mangling in tests)
|
||||
services.TryAddSingleton<IManglerService, NoOpManglerService>();
|
||||
|
||||
// QUESTION: The normal licensing service should run fine on developer machines but not in CI
|
||||
// should we have a fork here to leave the normal service for developers?
|
||||
// TODO: Eventually add the license file to CI
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Identity\Identity.csproj" />
|
||||
<ProjectReference Include="..\..\util\Migrator\Migrator.csproj" />
|
||||
<ProjectReference Include="..\..\util\Seeder\Seeder.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
175
test/SeederApi.IntegrationTest/DistributionTests.cs
Normal file
175
test/SeederApi.IntegrationTest/DistributionTests.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using Bit.Seeder.Data.Distributions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.SeederApi.IntegrationTest;
|
||||
|
||||
public class DistributionTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_PercentagesSumToOne_Succeeds()
|
||||
{
|
||||
var distribution = new Distribution<string>(
|
||||
("A", 0.50),
|
||||
("B", 0.30),
|
||||
("C", 0.20)
|
||||
);
|
||||
|
||||
Assert.NotNull(distribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_PercentagesDoNotSumToOne_ThrowsArgumentException()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentException>(() => new Distribution<int>(
|
||||
(1, 0.50),
|
||||
(2, 0.40)
|
||||
));
|
||||
|
||||
Assert.Contains("must sum to 1.0", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_PercentagesExceedOne_ThrowsArgumentException()
|
||||
{
|
||||
var exception = Assert.Throws<ArgumentException>(() => new Distribution<string>(
|
||||
("X", 0.60),
|
||||
("Y", 0.60)
|
||||
));
|
||||
|
||||
Assert.Contains("must sum to 1.0", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithinToleranceOf001_Succeeds()
|
||||
{
|
||||
var distribution = new Distribution<string>(
|
||||
("A", 0.333),
|
||||
("B", 0.333),
|
||||
("C", 0.333)
|
||||
);
|
||||
|
||||
Assert.NotNull(distribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Select_ReturnsCorrectBuckets_ForEvenSplit()
|
||||
{
|
||||
var distribution = new Distribution<string>(
|
||||
("A", 0.50),
|
||||
("B", 0.50)
|
||||
);
|
||||
|
||||
Assert.Equal("A", distribution.Select(0, 100));
|
||||
Assert.Equal("A", distribution.Select(49, 100));
|
||||
Assert.Equal("B", distribution.Select(50, 100));
|
||||
Assert.Equal("B", distribution.Select(99, 100));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Select_ReturnsCorrectBuckets_ForThreeWaySplit()
|
||||
{
|
||||
var distribution = new Distribution<int>(
|
||||
(1, 0.60),
|
||||
(2, 0.30),
|
||||
(3, 0.10)
|
||||
);
|
||||
|
||||
Assert.Equal(1, distribution.Select(0, 100));
|
||||
Assert.Equal(1, distribution.Select(59, 100));
|
||||
Assert.Equal(2, distribution.Select(60, 100));
|
||||
Assert.Equal(2, distribution.Select(89, 100));
|
||||
Assert.Equal(3, distribution.Select(90, 100));
|
||||
Assert.Equal(3, distribution.Select(99, 100));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Select_IndexBeyondTotal_ReturnsLastBucket()
|
||||
{
|
||||
var distribution = new Distribution<string>(
|
||||
("A", 0.50),
|
||||
("B", 0.50)
|
||||
);
|
||||
|
||||
Assert.Equal("B", distribution.Select(150, 100));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Select_SmallTotal_HandlesRoundingGracefully()
|
||||
{
|
||||
var distribution = new Distribution<string>(
|
||||
("A", 0.33),
|
||||
("B", 0.33),
|
||||
("C", 0.34)
|
||||
);
|
||||
|
||||
Assert.Equal("A", distribution.Select(0, 10));
|
||||
Assert.Equal("A", distribution.Select(2, 10));
|
||||
Assert.Equal("B", distribution.Select(3, 10));
|
||||
Assert.Equal("C", distribution.Select(9, 10));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCounts_ReturnsCorrectCounts_ForEvenSplit()
|
||||
{
|
||||
var distribution = new Distribution<string>(
|
||||
("X", 0.50),
|
||||
("Y", 0.50)
|
||||
);
|
||||
|
||||
var counts = distribution.GetCounts(100).ToList();
|
||||
|
||||
Assert.Equal(2, counts.Count);
|
||||
Assert.Equal(("X", 50), counts[0]);
|
||||
Assert.Equal(("Y", 50), counts[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCounts_LastBucketReceivesRemainder()
|
||||
{
|
||||
var distribution = new Distribution<string>(
|
||||
("A", 0.33),
|
||||
("B", 0.33),
|
||||
("C", 0.34)
|
||||
);
|
||||
|
||||
var counts = distribution.GetCounts(100).ToList();
|
||||
|
||||
Assert.Equal(3, counts.Count);
|
||||
Assert.Equal(33, counts[0].Count);
|
||||
Assert.Equal(33, counts[1].Count);
|
||||
Assert.Equal(34, counts[2].Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetCounts_TotalCountsMatchInput()
|
||||
{
|
||||
var distribution = new Distribution<int>(
|
||||
(1, 0.25),
|
||||
(2, 0.25),
|
||||
(3, 0.25),
|
||||
(4, 0.25)
|
||||
);
|
||||
|
||||
var counts = distribution.GetCounts(1000).ToList();
|
||||
var total = counts.Sum(c => c.Count);
|
||||
|
||||
Assert.Equal(1000, total);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Select_IsDeterministic_SameInputSameOutput()
|
||||
{
|
||||
var distribution = new Distribution<string>(
|
||||
("Alpha", 0.40),
|
||||
("Beta", 0.35),
|
||||
("Gamma", 0.25)
|
||||
);
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var first = distribution.Select(i, 100);
|
||||
var second = distribution.Select(i, 100);
|
||||
Assert.Equal(first, second);
|
||||
}
|
||||
}
|
||||
}
|
||||
262
test/SeederApi.IntegrationTest/GeneratorContextTests.cs
Normal file
262
test/SeederApi.IntegrationTest/GeneratorContextTests.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[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>(
|
||||
(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>(
|
||||
(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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -110,7 +110,7 @@ public class RustSdkCipherTests
|
||||
},
|
||||
Fields =
|
||||
[
|
||||
new FieldViewDto { Name = "API Key", Value = "sk-secret-api-key-12345", Type = 1 },
|
||||
new FieldViewDto { Name = "API Key", Value = "sk_test_FAKE_api_key_12345", Type = 1 },
|
||||
new FieldViewDto { Name = "Client ID", Value = "client-id-xyz", Type = 0 }
|
||||
]
|
||||
};
|
||||
@@ -128,7 +128,7 @@ public class RustSdkCipherTests
|
||||
Assert.NotNull(decrypted?.Fields);
|
||||
Assert.Equal(2, decrypted.Fields.Count);
|
||||
Assert.Equal("API Key", decrypted.Fields[0].Name);
|
||||
Assert.Equal("sk-secret-api-key-12345", decrypted.Fields[0].Value);
|
||||
Assert.Equal("sk_test_FAKE_api_key_12345", decrypted.Fields[0].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -138,10 +138,10 @@ public class RustSdkCipherTests
|
||||
var orgId = Guid.NewGuid();
|
||||
|
||||
// Create cipher using the seeder
|
||||
var cipher = CipherSeeder.CreateOrganizationLoginCipher(
|
||||
orgId,
|
||||
var cipher = LoginCipherSeeder.Create(
|
||||
orgKeys.Key,
|
||||
name: "GitHub Account",
|
||||
organizationId: orgId,
|
||||
username: "developer@example.com",
|
||||
password: "SecureP@ss123!",
|
||||
uri: "https://github.com",
|
||||
@@ -175,16 +175,16 @@ public class RustSdkCipherTests
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
|
||||
var cipher = CipherSeeder.CreateOrganizationLoginCipherWithFields(
|
||||
Guid.NewGuid(),
|
||||
var cipher = LoginCipherSeeder.Create(
|
||||
orgKeys.Key,
|
||||
name: "API Service",
|
||||
organizationId: Guid.NewGuid(),
|
||||
username: "service@example.com",
|
||||
password: "SvcP@ss!",
|
||||
uri: "https://api.example.com",
|
||||
fields: [
|
||||
("API Key", "sk-live-abc123", 1), // Hidden field
|
||||
("Environment", "production", 0) // Text field
|
||||
("API Key", "sk_test_FAKE_abc123", 1),
|
||||
("Environment", "production", 0)
|
||||
]);
|
||||
|
||||
var loginData = JsonSerializer.Deserialize<CipherLoginData>(cipher.Data);
|
||||
@@ -204,7 +204,7 @@ public class RustSdkCipherTests
|
||||
Assert.Equal(Core.Vault.Enums.FieldType.Text, fields[1].Type);
|
||||
|
||||
Assert.DoesNotContain("API Key", cipher.Data);
|
||||
Assert.DoesNotContain("sk-live-abc123", cipher.Data);
|
||||
Assert.DoesNotContain("sk_test_FAKE_abc123", cipher.Data);
|
||||
}
|
||||
|
||||
private static CipherViewDto CreateTestLoginCipher()
|
||||
@@ -223,4 +223,268 @@ public class RustSdkCipherTests
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncryptDecrypt_CardCipher_RoundtripPreservesPlaintext()
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
|
||||
var originalCipher = new CipherViewDto
|
||||
{
|
||||
Name = "My Visa Card",
|
||||
Notes = "Primary card for online purchases",
|
||||
Type = CipherTypes.Card,
|
||||
Card = new CardViewDto
|
||||
{
|
||||
CardholderName = "John Doe",
|
||||
Brand = "Visa",
|
||||
Number = "4111111111111111",
|
||||
ExpMonth = "12",
|
||||
ExpYear = "2028",
|
||||
Code = "123"
|
||||
}
|
||||
};
|
||||
|
||||
var originalJson = JsonSerializer.Serialize(originalCipher, SdkJsonOptions);
|
||||
var encryptedJson = RustSdkService.EncryptCipher(originalJson, orgKeys.Key);
|
||||
|
||||
Assert.DoesNotContain("\"error\"", encryptedJson);
|
||||
Assert.DoesNotContain("4111111111111111", encryptedJson);
|
||||
Assert.DoesNotContain("John Doe", encryptedJson);
|
||||
|
||||
var decryptedJson = RustSdkService.DecryptCipher(encryptedJson, orgKeys.Key);
|
||||
var decrypted = JsonSerializer.Deserialize<CipherViewDto>(decryptedJson, SdkJsonOptions);
|
||||
|
||||
Assert.NotNull(decrypted?.Card);
|
||||
Assert.Equal("4111111111111111", decrypted.Card.Number);
|
||||
Assert.Equal("John Doe", decrypted.Card.CardholderName);
|
||||
Assert.Equal("123", decrypted.Card.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CipherSeeder_CardCipher_ProducesServerCompatibleFormat()
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
var orgId = Guid.NewGuid();
|
||||
|
||||
var card = new CardViewDto
|
||||
{
|
||||
CardholderName = "Jane Smith",
|
||||
Brand = "Mastercard",
|
||||
Number = "5500000000000004",
|
||||
ExpMonth = "06",
|
||||
ExpYear = "2027",
|
||||
Code = "456"
|
||||
};
|
||||
|
||||
var cipher = CardCipherSeeder.Create(orgKeys.Key, name: "Business Card", card: card, organizationId: orgId, notes: "Company expenses");
|
||||
|
||||
Assert.Equal(orgId, cipher.OrganizationId);
|
||||
Assert.Equal(Core.Vault.Enums.CipherType.Card, cipher.Type);
|
||||
|
||||
var cardData = JsonSerializer.Deserialize<CipherCardData>(cipher.Data);
|
||||
Assert.NotNull(cardData);
|
||||
|
||||
var encStringPrefix = "2.";
|
||||
Assert.StartsWith(encStringPrefix, cardData.Name);
|
||||
Assert.StartsWith(encStringPrefix, cardData.CardholderName);
|
||||
Assert.StartsWith(encStringPrefix, cardData.Number);
|
||||
Assert.StartsWith(encStringPrefix, cardData.Code);
|
||||
|
||||
Assert.DoesNotContain("5500000000000004", cipher.Data);
|
||||
Assert.DoesNotContain("Jane Smith", cipher.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncryptDecrypt_IdentityCipher_RoundtripPreservesPlaintext()
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
|
||||
var originalCipher = new CipherViewDto
|
||||
{
|
||||
Name = "Personal Identity",
|
||||
Type = CipherTypes.Identity,
|
||||
Identity = new IdentityViewDto
|
||||
{
|
||||
Title = "Mr",
|
||||
FirstName = "John",
|
||||
MiddleName = "Robert",
|
||||
LastName = "Doe",
|
||||
Email = "john.doe@example.com",
|
||||
Phone = "+1-555-123-4567",
|
||||
SSN = "123-45-6789",
|
||||
Address1 = "123 Main Street",
|
||||
City = "Anytown",
|
||||
State = "CA",
|
||||
PostalCode = "90210",
|
||||
Country = "US"
|
||||
}
|
||||
};
|
||||
|
||||
var originalJson = JsonSerializer.Serialize(originalCipher, SdkJsonOptions);
|
||||
var encryptedJson = RustSdkService.EncryptCipher(originalJson, orgKeys.Key);
|
||||
|
||||
Assert.DoesNotContain("\"error\"", encryptedJson);
|
||||
Assert.DoesNotContain("123-45-6789", encryptedJson);
|
||||
Assert.DoesNotContain("john.doe@example.com", encryptedJson);
|
||||
|
||||
var decryptedJson = RustSdkService.DecryptCipher(encryptedJson, orgKeys.Key);
|
||||
var decrypted = JsonSerializer.Deserialize<CipherViewDto>(decryptedJson, SdkJsonOptions);
|
||||
|
||||
Assert.NotNull(decrypted?.Identity);
|
||||
Assert.Equal("John", decrypted.Identity.FirstName);
|
||||
Assert.Equal("123-45-6789", decrypted.Identity.SSN);
|
||||
Assert.Equal("john.doe@example.com", decrypted.Identity.Email);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CipherSeeder_IdentityCipher_ProducesServerCompatibleFormat()
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
var orgId = Guid.NewGuid();
|
||||
|
||||
var identity = new IdentityViewDto
|
||||
{
|
||||
Title = "Dr",
|
||||
FirstName = "Alice",
|
||||
LastName = "Johnson",
|
||||
Email = "alice@company.com",
|
||||
SSN = "987-65-4321",
|
||||
PassportNumber = "X12345678"
|
||||
};
|
||||
|
||||
var cipher = IdentityCipherSeeder.Create(orgKeys.Key, name: "Dr. Alice Johnson", identity: identity, organizationId: orgId);
|
||||
|
||||
Assert.Equal(orgId, cipher.OrganizationId);
|
||||
Assert.Equal(Core.Vault.Enums.CipherType.Identity, cipher.Type);
|
||||
|
||||
var identityData = JsonSerializer.Deserialize<CipherIdentityData>(cipher.Data);
|
||||
Assert.NotNull(identityData);
|
||||
|
||||
var encStringPrefix = "2.";
|
||||
Assert.StartsWith(encStringPrefix, identityData.Name);
|
||||
Assert.StartsWith(encStringPrefix, identityData.FirstName);
|
||||
Assert.StartsWith(encStringPrefix, identityData.SSN);
|
||||
|
||||
Assert.DoesNotContain("987-65-4321", cipher.Data);
|
||||
Assert.DoesNotContain("Alice", cipher.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncryptDecrypt_SecureNoteCipher_RoundtripPreservesPlaintext()
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
|
||||
var originalCipher = new CipherViewDto
|
||||
{
|
||||
Name = "API Secrets",
|
||||
Notes = "sk_test_FAKE_abc123xyz789key",
|
||||
Type = CipherTypes.SecureNote,
|
||||
SecureNote = new SecureNoteViewDto { Type = 0 }
|
||||
};
|
||||
|
||||
var originalJson = JsonSerializer.Serialize(originalCipher, SdkJsonOptions);
|
||||
var encryptedJson = RustSdkService.EncryptCipher(originalJson, orgKeys.Key);
|
||||
|
||||
Assert.DoesNotContain("\"error\"", encryptedJson);
|
||||
Assert.DoesNotContain("sk_test_FAKE_abc123xyz789key", encryptedJson);
|
||||
|
||||
var decryptedJson = RustSdkService.DecryptCipher(encryptedJson, orgKeys.Key);
|
||||
var decrypted = JsonSerializer.Deserialize<CipherViewDto>(decryptedJson, SdkJsonOptions);
|
||||
|
||||
Assert.NotNull(decrypted);
|
||||
Assert.Equal("API Secrets", decrypted.Name);
|
||||
Assert.Equal("sk_test_FAKE_abc123xyz789key", decrypted.Notes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CipherSeeder_SecureNoteCipher_ProducesServerCompatibleFormat()
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
var orgId = Guid.NewGuid();
|
||||
|
||||
var cipher = SecureNoteCipherSeeder.Create(
|
||||
orgKeys.Key,
|
||||
name: "Production Secrets",
|
||||
organizationId: orgId,
|
||||
notes: "DATABASE_URL=postgres://user:FAKE_secret@db.example.com/prod");
|
||||
|
||||
Assert.Equal(orgId, cipher.OrganizationId);
|
||||
Assert.Equal(Core.Vault.Enums.CipherType.SecureNote, cipher.Type);
|
||||
|
||||
var noteData = JsonSerializer.Deserialize<CipherSecureNoteData>(cipher.Data);
|
||||
Assert.NotNull(noteData);
|
||||
Assert.Equal(Core.Vault.Enums.SecureNoteType.Generic, noteData.Type);
|
||||
|
||||
var encStringPrefix = "2.";
|
||||
Assert.StartsWith(encStringPrefix, noteData.Name);
|
||||
Assert.StartsWith(encStringPrefix, noteData.Notes);
|
||||
|
||||
Assert.DoesNotContain("postgres://", cipher.Data);
|
||||
Assert.DoesNotContain("secret", cipher.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EncryptDecrypt_SshKeyCipher_RoundtripPreservesPlaintext()
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
|
||||
var originalCipher = new CipherViewDto
|
||||
{
|
||||
Name = "Dev Server Key",
|
||||
Type = CipherTypes.SshKey,
|
||||
SshKey = new SshKeyViewDto
|
||||
{
|
||||
PrivateKey = "-----BEGIN FAKE RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA...\n-----END FAKE RSA PRIVATE KEY-----",
|
||||
PublicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQ... user@host",
|
||||
Fingerprint = "SHA256:nThbg6kXUpJWGl7E1IGOCspRomTxdCARLviKw6E5SY8"
|
||||
}
|
||||
};
|
||||
|
||||
var originalJson = JsonSerializer.Serialize(originalCipher, SdkJsonOptions);
|
||||
var encryptedJson = RustSdkService.EncryptCipher(originalJson, orgKeys.Key);
|
||||
|
||||
Assert.DoesNotContain("\"error\"", encryptedJson);
|
||||
Assert.DoesNotContain("BEGIN FAKE RSA PRIVATE KEY", encryptedJson);
|
||||
Assert.DoesNotContain("ssh-rsa AAAAB3", encryptedJson);
|
||||
|
||||
var decryptedJson = RustSdkService.DecryptCipher(encryptedJson, orgKeys.Key);
|
||||
var decrypted = JsonSerializer.Deserialize<CipherViewDto>(decryptedJson, SdkJsonOptions);
|
||||
|
||||
Assert.NotNull(decrypted?.SshKey);
|
||||
Assert.Contains("BEGIN FAKE RSA PRIVATE KEY", decrypted.SshKey.PrivateKey);
|
||||
Assert.StartsWith("ssh-rsa", decrypted.SshKey.PublicKey);
|
||||
Assert.StartsWith("SHA256:", decrypted.SshKey.Fingerprint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CipherSeeder_SshKeyCipher_ProducesServerCompatibleFormat()
|
||||
{
|
||||
var orgKeys = RustSdkService.GenerateOrganizationKeys();
|
||||
var orgId = Guid.NewGuid();
|
||||
|
||||
var sshKey = new SshKeyViewDto
|
||||
{
|
||||
PrivateKey = "-----BEGIN FAKE OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAA...\n-----END FAKE OPENSSH PRIVATE KEY-----",
|
||||
PublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample test@machine",
|
||||
Fingerprint = "SHA256:examplefingerprint123"
|
||||
};
|
||||
|
||||
var cipher = SshKeyCipherSeeder.Create(orgKeys.Key, name: "Production Deploy Key", sshKey: sshKey, organizationId: orgId);
|
||||
|
||||
Assert.Equal(orgId, cipher.OrganizationId);
|
||||
Assert.Equal(Core.Vault.Enums.CipherType.SSHKey, cipher.Type);
|
||||
|
||||
var sshData = JsonSerializer.Deserialize<CipherSSHKeyData>(cipher.Data);
|
||||
Assert.NotNull(sshData);
|
||||
|
||||
var encStringPrefix = "2.";
|
||||
Assert.StartsWith(encStringPrefix, sshData.Name);
|
||||
Assert.StartsWith(encStringPrefix, sshData.PrivateKey);
|
||||
Assert.StartsWith(encStringPrefix, sshData.PublicKey);
|
||||
Assert.StartsWith(encStringPrefix, sshData.KeyFingerprint);
|
||||
|
||||
Assert.DoesNotContain("BEGIN FAKE OPENSSH PRIVATE KEY", cipher.Data);
|
||||
Assert.DoesNotContain("ssh-ed25519", cipher.Data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user