5-improve-encrypted-storage #6

Merged
eelke merged 17 commits from 5-improve-encrypted-storage into main 2026-02-27 17:57:44 +00:00
3 changed files with 196 additions and 2 deletions
Showing only changes of commit cd2ec646fd - Show all commits

View file

@ -0,0 +1,154 @@
using IdentityShroud.Core.Contracts;
using IdentityShroud.Core.Model;
using IdentityShroud.Core.Services;
using IdentityShroud.Core.Tests.Fixtures;
using IdentityShroud.TestUtils.Substitutes;
using Microsoft.EntityFrameworkCore;
namespace IdentityShroud.Core.Tests.Services;
public class ClientServiceTests : IClassFixture<DbFixture>
{
private readonly DbFixture _dbFixture;
private readonly IEncryptionService _encryptionService = EncryptionServiceSubstitute.CreatePassthrough();
private readonly IClock _clock = Substitute.For<IClock>();
private readonly Guid _realmId = new("a1b2c3d4-0000-0000-0000-000000000001");
public ClientServiceTests(DbFixture dbFixture)
{
_dbFixture = dbFixture;
using Db db = dbFixture.CreateDbContext();
if (!db.Database.EnsureCreated())
TruncateTables(db);
EnsureRealm(db);
}
private void TruncateTables(Db db)
{
db.Database.ExecuteSqlRaw("TRUNCATE client CASCADE;");
db.Database.ExecuteSqlRaw("TRUNCATE realm CASCADE;");
}
private void EnsureRealm(Db db)
{
if (!db.Realms.Any(r => r.Id == _realmId))
{
db.Realms.Add(new() { Id = _realmId, Slug = "test-realm", Name = "Test Realm" });
db.SaveChanges();
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Create(bool allowClientCredentialsFlow)
{
// Setup
DateTime now = DateTime.UtcNow;
_clock.UtcNow().Returns(now);
Client val;
await using (var db = _dbFixture.CreateDbContext())
{
// Act
ClientService sut = new(db, _encryptionService, _clock);
var response = await sut.Create(
_realmId,
new ClientCreateRequest
{
ClientId = "test-client",
Name = "Test Client",
Description = "A test client",
AllowClientCredentialsFlow = allowClientCredentialsFlow,
},
TestContext.Current.CancellationToken);
// Verify
val = ResultAssert.Success(response);
Assert.Equal(_realmId, val.RealmId);
Assert.Equal("test-client", val.ClientId);
Assert.Equal("Test Client", val.Name);
Assert.Equal("A test client", val.Description);
Assert.Equal(allowClientCredentialsFlow, val.AllowClientCredentialsFlow);
Assert.Equal(now, val.CreatedAt);
}
await using (var db = _dbFixture.CreateDbContext())
{
var dbRecord = await db.Clients
.Include(e => e.Secrets)
.SingleAsync(e => e.Id == val.Id, TestContext.Current.CancellationToken);
if (allowClientCredentialsFlow)
Assert.Single(dbRecord.Secrets);
else
Assert.Empty(dbRecord.Secrets);
}
}
[Theory]
[InlineData("existing-client", true)]
[InlineData("missing-client", false)]
public async Task GetByClientId(string clientId, bool shouldFind)
{
// Setup
_clock.UtcNow().Returns(DateTime.UtcNow);
await using (var setupContext = _dbFixture.CreateDbContext())
{
setupContext.Clients.Add(new()
{
RealmId = _realmId,
ClientId = "existing-client",
CreatedAt = DateTime.UtcNow,
});
await setupContext.SaveChangesAsync(TestContext.Current.CancellationToken);
}
await using var actContext = _dbFixture.CreateDbContext();
// Act
ClientService sut = new(actContext, _encryptionService, _clock);
Client? result = await sut.GetByClientId(clientId, TestContext.Current.CancellationToken);
// Verify
if (shouldFind)
Assert.NotNull(result);
else
Assert.Null(result);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task FindById(bool shouldFind)
{
// Setup
_clock.UtcNow().Returns(DateTime.UtcNow);
int existingId;
await using (var setupContext = _dbFixture.CreateDbContext())
{
Client client = new()
{
RealmId = _realmId,
ClientId = "find-by-id-client",
CreatedAt = DateTime.UtcNow,
};
setupContext.Clients.Add(client);
await setupContext.SaveChangesAsync(TestContext.Current.CancellationToken);
existingId = client.Id;
}
int searchId = shouldFind ? existingId : existingId + 9999;
await using var actContext = _dbFixture.CreateDbContext();
// Act
ClientService sut = new(actContext, _encryptionService, _clock);
Client? result = await sut.FindById(searchId, TestContext.Current.CancellationToken);
// Verify
if (shouldFind)
Assert.NotNull(result);
else
Assert.Null(result);
}
}

View file

@ -91,10 +91,47 @@ public class RealmServiceTests : IClassFixture<DbFixture>
} }
await using var actContext = _dbFixture.CreateDbContext(); await using var actContext = _dbFixture.CreateDbContext();
RealmService sut = new(actContext, _keyService);
// Act // Act
RealmService sut = new(actContext, _keyService);
var result = await sut.FindBySlug(slug, TestContext.Current.CancellationToken); var result = await sut.FindBySlug(slug, TestContext.Current.CancellationToken);
// Verify
Assert.Equal(name, result?.Name); Assert.Equal(name, result?.Name);
} }
[Theory]
[InlineData("b0423bba-2411-497b-a5b6-c5adf404b862", true)]
[InlineData("65ac9dba-6d43-4fa4-b57f-133ed639fbcb", false)]
public async Task FindById(string idString, bool shouldFind)
{
Guid id = new(idString);
await using (var setupContext = _dbFixture.CreateDbContext())
{
setupContext.Realms.Add(new()
{
Id = new("b0423bba-2411-497b-a5b6-c5adf404b862"),
Slug = "foo",
Name = "Foo",
});
setupContext.Realms.Add(new()
{
Id = new("d4ffc7d0-7b2c-4f02-82b9-a74610435b0d"),
Slug = "bar",
Name = "Bar",
});
await setupContext.SaveChangesAsync(TestContext.Current.CancellationToken);
}
await using var actContext = _dbFixture.CreateDbContext();
// Act
RealmService sut = new(actContext, _keyService);
Realm? result = await sut.FindById(id, TestContext.Current.CancellationToken);
// Verify
if (shouldFind)
Assert.NotNull(result);
else
Assert.Null(result);
}
} }

View file

@ -22,9 +22,12 @@
<s:String x:Key="/Default/dotCover/Editor/HighlightingSourceSnapshotLocation/@EntryValue">/home/eelke/.cache/JetBrains/Rider2025.3/resharper-host/temp/Rider/vAny/CoverageData/_IdentityShroud.-1277985570/Snapshot/snapshot.utdcvr</s:String> <s:String x:Key="/Default/dotCover/Editor/HighlightingSourceSnapshotLocation/@EntryValue">/home/eelke/.cache/JetBrains/Rider2025.3/resharper-host/temp/Rider/vAny/CoverageData/_IdentityShroud.-1277985570/Snapshot/snapshot.utdcvr</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue">/home/eelke/.dotnet/dotnet</s:String> <s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue">/home/eelke/.dotnet/dotnet</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">/home/eelke/.dotnet/sdk/10.0.102/MSBuild.dll</s:String> <s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">/home/eelke/.dotnet/sdk/10.0.102/MSBuild.dll</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=4bf578c0_002Dc8f9_002D46e4_002D9bdc_002D38da0a3f253a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt; <s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=4bf578c0_002Dc8f9_002D46e4_002D9bdc_002D38da0a3f253a/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Solution /&gt; &lt;Solution /&gt;
&lt;/SessionState&gt;</s:String> &lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=7ce84f90_002D6ae2_002D4e9e_002D860e_002Deb90f45871f3/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="Junie Session" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;ProjectFile&gt;DC887623-8680-4D3B-B23A-D54F7DA91891/d:Services/f:ClientServiceTests.cs&lt;/ProjectFile&gt;
&lt;/SessionState&gt;</s:String>