Tests fixes, audit progress, UI completions

This commit is contained in:
StellaOps Bot
2025-12-30 09:03:22 +02:00
parent 7a5210e2aa
commit 82e55c206a
318 changed files with 7232 additions and 1256 deletions

View File

@@ -0,0 +1,22 @@
# Auth Abstractions Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: unit coverage for scopes, claims, principal builder, network masks, and problem responses.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs, stable ordering).
- Use explicit assertions for scope lists and network mask behavior.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit.
- Add edge-case coverage for parsing and canonicalization.

View File

@@ -0,0 +1,10 @@
# Auth Abstractions Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0079-M | DONE | Maintainability audit for StellaOps.Auth.Abstractions.Tests. |
| AUDIT-0079-T | DONE | Test coverage audit for StellaOps.Auth.Abstractions.Tests. |
| AUDIT-0079-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Auth Abstractions AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Auth.Abstractions/`.
- Roles: backend engineer, QA automation.
- Focus: shared auth scopes, claim types, problem responses, and network mask utilities.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep auth identifiers deterministic and stable (no implicit ordering changes).
- Preserve offline posture (no network calls).
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit.
- Cover scope normalization, network masks, principal builder behavior, and problem responses.

View File

@@ -0,0 +1,10 @@
# Auth Abstractions Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0078-M | DONE | Maintainability audit for StellaOps.Auth.Abstractions. |
| AUDIT-0078-T | DONE | Test coverage audit for StellaOps.Auth.Abstractions. |
| AUDIT-0078-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Auth Client Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Auth.Client.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: unit coverage for auth client options, caches, and auth handlers.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs, stable ordering).
- Avoid live network calls and file system leakage; clean temp artifacts.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit.
- Cover token caches, discovery/JWKS fallback, and bearer handler modes.

View File

@@ -77,7 +77,7 @@ public class ServiceCollectionExtensionsTests
Assert.Equal(new Uri("https://authority.test/connect/token"), configuration.TokenEndpoint);
Assert.Equal(2, attemptCount);
Assert.NotEmpty(recordedHandlers);
Assert.Contains(recordedHandlers, handler => handler.GetType().Name.Contains("PolicyHttpMessageHandler", StringComparison.Ordinal));
Assert.Contains(recordedHandlers, handler => handler.GetType().Name.Contains("ResilienceHandler", StringComparison.Ordinal));
}
[Trait("Category", TestCategories.Unit)]

View File

@@ -0,0 +1,10 @@
# Auth Client Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0081-M | DONE | Maintainability audit for StellaOps.Auth.Client.Tests. |
| AUDIT-0081-T | DONE | Test coverage audit for StellaOps.Auth.Client.Tests. |
| AUDIT-0081-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,23 @@
# Auth Client AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Auth.Client/`.
- Roles: backend engineer, QA automation.
- Focus: token acquisition, discovery/JWKS caching, and auth handler integration.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep token flows deterministic and time-aware (TimeProvider, skew handling).
- Respect offline/air-gap posture and egress policy checks.
- Avoid leaking sensitive credentials in logs.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit.
- Cover discovery/JWKS caches, token client error paths, and auth handler behavior.

View File

@@ -0,0 +1,10 @@
# Auth Client Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0080-M | DONE | Maintainability audit for StellaOps.Auth.Client. |
| AUDIT-0080-T | DONE | Test coverage audit for StellaOps.Auth.Client. |
| AUDIT-0080-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Auth Server Integration Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: resource server options, policy registration, bypass evaluation, and scope authorization behavior.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs, stable ordering).
- Avoid live network calls; use fakes for metadata/JWKS retrieval.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit.
- Cover options validation, bypass deny paths, scope normalization, and audit event emission.

View File

@@ -0,0 +1,10 @@
# Auth Server Integration Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0084-M | DONE | Maintainability audit for StellaOps.Auth.ServerIntegration.Tests. |
| AUDIT-0084-T | DONE | Test coverage audit for StellaOps.Auth.ServerIntegration.Tests. |
| AUDIT-0084-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,23 @@
# Auth Server Integration AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Auth.ServerIntegration/`.
- Roles: backend engineer, QA automation.
- Focus: ASP.NET Core resource server auth configuration, scope policies, and authorization audit events.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep auth decisions deterministic and time-aware (TimeProvider).
- Preserve offline/air-gap posture with resilient metadata/JWKS caching.
- Avoid logging sensitive claims; use classified strings.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit.
- Cover options normalization, bypass evaluation, metadata/JWKS caching, scope decisions, and audit event emission.

View File

@@ -0,0 +1,10 @@
# Auth Server Integration Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0083-M | DONE | Maintainability audit for StellaOps.Auth.ServerIntegration. |
| AUDIT-0083-T | DONE | Test coverage audit for StellaOps.Auth.ServerIntegration. |
| AUDIT-0083-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority LDAP Plugin Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Ldap.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: LDAP plugin test coverage, fixtures, and determinism.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs); avoid real LDAP network access.
- Prefer exercising production code paths over duplicated test-only logic.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit fakes.
- Tag integration/snapshot tests appropriately and keep fixtures stable.

View File

@@ -0,0 +1,10 @@
# Authority LDAP Plugin Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0091-M | DONE | Maintainability audit for StellaOps.Authority.Plugin.Ldap.Tests. |
| AUDIT-0091-T | DONE | Test coverage audit for StellaOps.Authority.Plugin.Ldap.Tests. |
| AUDIT-0091-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority LDAP Plugin AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Ldap/`.
- Roles: backend engineer, QA automation.
- Focus: LDAP identity provider plugin, connection factory, claims enrichment, and client provisioning.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Preserve TLS and credential handling guarantees; avoid weakening defaults.
- Keep timeouts and bind flows configurable; avoid hidden sync-over-async paths.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit fakes; avoid real LDAP network access.
- Cover health checks, capability probing, and error/timeout paths deterministically.

View File

@@ -0,0 +1,10 @@
# Authority LDAP Plugin Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0090-M | DONE | Maintainability audit for StellaOps.Authority.Plugin.Ldap. |
| AUDIT-0090-T | DONE | Test coverage audit for StellaOps.Authority.Plugin.Ldap. |
| AUDIT-0090-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority OIDC Plugin Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Oidc.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: OIDC plugin test coverage, fixtures, and determinism.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs); avoid live OIDC metadata calls.
- Prefer exercising production code paths over test-only simulations.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + local fixtures.
- Tag snapshot and security tests appropriately and keep fixtures stable.

View File

@@ -0,0 +1,10 @@
# Authority OIDC Plugin Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0093-M | DONE | Maintainability audit for StellaOps.Authority.Plugin.Oidc.Tests. |
| AUDIT-0093-T | DONE | Test coverage audit for StellaOps.Authority.Plugin.Oidc.Tests. |
| AUDIT-0093-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority OIDC Plugin AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Oidc/`.
- Roles: backend engineer, QA automation.
- Focus: OIDC identity provider plugin, token validation, metadata retrieval, and claims mapping.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Preserve offline/air-gap posture; avoid implicit external calls without explicit configuration.
- Use IHttpClientFactory and configurable timeouts; avoid new HttpClient per call.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + deterministic token fixtures.
- Avoid live OIDC network calls; mock metadata retrieval and token validation.

View File

@@ -0,0 +1,10 @@
# Authority OIDC Plugin Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0092-M | DONE | Maintainability audit for StellaOps.Authority.Plugin.Oidc. |
| AUDIT-0092-T | DONE | Test coverage audit for StellaOps.Authority.Plugin.Oidc. |
| AUDIT-0092-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority SAML Plugin Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Saml.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: SAML plugin test coverage, fixtures, and determinism.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs); avoid live IdP metadata calls.
- Prefer exercising production code paths over test-only simulations.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + fixture XML.
- Tag snapshot and security tests appropriately and keep fixtures stable.

View File

@@ -0,0 +1,10 @@
# Authority SAML Plugin Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0095-M | DONE | Maintainability audit for StellaOps.Authority.Plugin.Saml.Tests. |
| AUDIT-0095-T | DONE | Test coverage audit for StellaOps.Authority.Plugin.Saml.Tests. |
| AUDIT-0095-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,23 @@
# Authority SAML Plugin AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Saml/`.
- Roles: backend engineer, QA automation.
- Focus: SAML identity provider plugin, assertion validation, metadata/certificate handling, and claims mapping.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Preserve offline/air-gap posture; avoid implicit external calls without explicit configuration.
- Use IHttpClientFactory and configurable timeouts; avoid new HttpClient per call.
- Harden XML parsing (no DTD/XXE) and keep assertion validation deterministic.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions with deterministic fixture XML.
- Avoid live IdP network calls; mock metadata/cert retrieval.

View File

@@ -0,0 +1,10 @@
# Authority SAML Plugin Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0094-M | DONE | Maintainability audit for StellaOps.Authority.Plugin.Saml. |
| AUDIT-0094-T | DONE | Test coverage audit for StellaOps.Authority.Plugin.Saml. |
| AUDIT-0094-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,21 @@
# Authority Standard Plugin Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugin.Standard.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: Standard plugin test coverage, credential flows, and determinism.
## Required Reading (treat as read before DOING)
- `docs/modules/authority/architecture.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/dev/31_AUTHORITY_PLUGIN_DEVELOPER_GUIDE.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs); avoid external network calls.
- Prefer exercising production code paths over test-only simulations.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit helpers.
- Cover credential flows, lockouts, bootstrap behavior, and client provisioning.

View File

@@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Persistence.Postgres.Repositories;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard;
using StellaOps.Authority.Plugin.Standard.Bootstrap;
@@ -16,16 +17,16 @@ using StellaOps.Authority.Plugin.Standard.Storage;
using StellaOps.Authority.Persistence.Documents;
using StellaOps.Authority.Persistence.InMemory.Stores;
using StellaOps.Authority.Persistence.Sessions;
using StellaOps.Cryptography;
using StellaOps.Cryptography.Audit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugin.Standard.Tests;
public class StandardPluginRegistrarTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task Register_ConfiguresIdentityProviderAndSeedsBootstrapUser()
{
var client = new InMemoryClient();
@@ -58,10 +59,11 @@ public class StandardPluginRegistrarTests
"standard.yaml");
var pluginContext = new AuthorityPluginContext(manifest, configuration);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database, configuration);
var registrar = new StandardPluginRegistrar();
registrar.Register(new AuthorityPluginRegistrationContext(services, pluginContext, configuration));
services.AddSingleton<ICryptoProvider>(new DefaultCryptoProvider());
using var provider = services.BuildServiceProvider();
var hostedServices = provider.GetServices<IHostedService>();
@@ -88,7 +90,7 @@ public class StandardPluginRegistrarTests
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public void Register_LogsWarning_WhenPasswordPolicyWeaker()
{
var client = new InMemoryClient();
@@ -116,12 +118,13 @@ public class StandardPluginRegistrarTests
"standard.yaml");
var pluginContext = new AuthorityPluginContext(manifest, configuration);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database, configuration);
var loggerProvider = new CapturingLoggerProvider();
services.AddLogging(builder => builder.AddProvider(loggerProvider));
var registrar = new StandardPluginRegistrar();
registrar.Register(new AuthorityPluginRegistrationContext(services, pluginContext, configuration));
services.AddSingleton<ICryptoProvider>(new DefaultCryptoProvider());
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
@@ -134,7 +137,7 @@ public class StandardPluginRegistrarTests
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public void Register_ForcesPasswordCapability_WhenManifestMissing()
{
var client = new InMemoryClient();
@@ -152,10 +155,11 @@ public class StandardPluginRegistrarTests
"standard.yaml");
var pluginContext = new AuthorityPluginContext(manifest, configuration);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database, configuration);
var registrar = new StandardPluginRegistrar();
registrar.Register(new AuthorityPluginRegistrationContext(services, pluginContext, configuration));
services.AddSingleton<ICryptoProvider>(new DefaultCryptoProvider());
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
@@ -167,7 +171,7 @@ public class StandardPluginRegistrarTests
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public void Register_Throws_WhenBootstrapConfigurationIncomplete()
{
var client = new InMemoryClient();
@@ -191,10 +195,11 @@ public class StandardPluginRegistrarTests
"standard.yaml");
var pluginContext = new AuthorityPluginContext(manifest, configuration);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database, configuration);
var registrar = new StandardPluginRegistrar();
registrar.Register(new AuthorityPluginRegistrationContext(services, pluginContext, configuration));
services.AddSingleton<ICryptoProvider>(new DefaultCryptoProvider());
using var provider = services.BuildServiceProvider();
using var scope = provider.CreateScope();
@@ -202,7 +207,7 @@ public class StandardPluginRegistrarTests
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public void Register_NormalizesTokenSigningKeyDirectory()
{
var client = new InMemoryClient();
@@ -232,11 +237,12 @@ public class StandardPluginRegistrarTests
configPath);
var pluginContext = new AuthorityPluginContext(manifest, configuration);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database);
var services = StandardPluginRegistrarTestHelpers.CreateServiceCollection(database, configuration);
services.AddSingleton(TimeProvider.System);
var registrar = new StandardPluginRegistrar();
registrar.Register(new AuthorityPluginRegistrationContext(services, pluginContext, configuration));
services.AddSingleton<ICryptoProvider>(new DefaultCryptoProvider());
using var provider = services.BuildServiceProvider();
var optionsMonitor = provider.GetRequiredService<IOptionsMonitor<StandardPluginOptions>>();
@@ -398,6 +404,7 @@ internal static class StandardPluginRegistrarTestHelpers
{
public static ServiceCollection CreateServiceCollection(
IDatabase database,
IConfiguration? configuration = null,
IAuthEventSink? authEventSink = null,
IAuthorityCredentialAuditContextAccessor? auditContextAccessor = null)
{
@@ -405,10 +412,12 @@ internal static class StandardPluginRegistrarTestHelpers
var services = new ServiceCollection();
services.AddLogging();
services.AddSingleton(configuration ?? new ConfigurationBuilder().Build());
services.AddSingleton(database);
services.AddSingleton<IAuthorityClientStore>(new InMemoryClientStore());
services.AddSingleton<IAuthorityRevocationStore>(new StubRevocationStore());
services.AddSingleton<IAuthorityLoginAttemptStore>(new InMemoryLoginAttemptStore());
services.AddSingleton<IUserRepository>(new InMemoryUserRepository());
services.AddSingleton(TimeProvider.System);
services.AddSingleton<IAuthorityCredentialAuditContextAccessor>(
auditContextAccessor ?? new TestAuthorityCredentialAuditContextAccessor());

View File

@@ -2,12 +2,11 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using StellaOps.Authority.InMemoryDriver;
using StellaOps.Authority.Persistence.Postgres.Repositories;
using StellaOps.Authority.Persistence.Postgres.Models;
using StellaOps.Authority.Plugins.Abstractions;
using StellaOps.Authority.Plugin.Standard.Security;
using StellaOps.Authority.Plugin.Standard.Storage;
@@ -15,20 +14,18 @@ using StellaOps.Cryptography;
using StellaOps.Cryptography.Audit;
using StellaOps.TestKit;
namespace StellaOps.Authority.Plugin.Standard.Tests;
public class StandardUserCredentialStoreTests : IAsyncLifetime
{
private readonly IDatabase database;
private readonly InMemoryUserRepository userRepository;
private readonly StandardPluginOptions options;
private readonly StandardUserCredentialStore store;
private readonly TestAuditLogger auditLogger;
private readonly Mock<IUserRepository> userRepositoryMock;
public StandardUserCredentialStoreTests()
{
var client = new InMemoryClient();
database = client.GetDatabase("authority-tests");
options = new StandardPluginOptions
{
PasswordPolicy = new PasswordPolicyOptions
@@ -55,11 +52,11 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
};
var cryptoProvider = new DefaultCryptoProvider();
auditLogger = new TestAuditLogger();
userRepositoryMock = new Mock<IUserRepository>();
userRepository = new InMemoryUserRepository();
store = new StandardUserCredentialStore(
"standard",
"test-tenant",
userRepositoryMock.Object,
userRepository,
options,
new CryptoPasswordHasher(options, cryptoProvider),
auditLogger,
@@ -67,7 +64,7 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task VerifyPasswordAsync_ReturnsSuccess_ForValidCredentials()
{
auditLogger.Reset();
@@ -95,7 +92,7 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task VerifyPasswordAsync_EnforcesLockout_AfterRepeatedFailures()
{
auditLogger.Reset();
@@ -144,7 +141,7 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task VerifyPasswordAsync_RehashesLegacyHashesToArgon2()
{
auditLogger.Reset();
@@ -156,19 +153,24 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
Iterations = 160_000
});
var document = new StandardUserDocument
await userRepository.CreateAsync(new UserEntity
{
Id = Guid.NewGuid(),
TenantId = "test-tenant",
Username = "legacy",
NormalizedUsername = "legacy",
Email = "legacy@local",
DisplayName = "Legacy",
PasswordHash = legacyHash,
Roles = new List<string>(),
Attributes = new Dictionary<string, string?>(),
CreatedAt = DateTimeOffset.UtcNow.AddDays(-1),
UpdatedAt = DateTimeOffset.UtcNow.AddDays(-1)
};
await database.GetCollection<StandardUserDocument>("authority_users_standard")
.InsertOneAsync(document);
PasswordSalt = "",
Enabled = true,
Metadata = JsonSerializer.Serialize(new Dictionary<string, object?>
{
["subjectId"] = "legacy",
["roles"] = new List<string>(),
["attributes"] = new Dictionary<string, string?>(),
["requirePasswordReset"] = false
})
});
var result = await store.VerifyPasswordAsync("legacy", "Legacy1!", CancellationToken.None);
@@ -180,16 +182,14 @@ public class StandardUserCredentialStoreTests : IAsyncLifetime
Assert.True(auditEntry.Success);
Assert.Equal("legacy", auditEntry.Username);
var results = await database.GetCollection<StandardUserDocument>("authority_users_standard")
.FindAsync(u => u.NormalizedUsername == "legacy");
var updated = results.FirstOrDefault();
var updated = await userRepository.GetByUsernameAsync("test-tenant", "legacy", CancellationToken.None);
Assert.NotNull(updated);
Assert.StartsWith("$argon2id$", updated!.PasswordHash, StringComparison.Ordinal);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
[Fact]
public async Task VerifyPasswordAsync_RecordsAudit_ForUnknownUser()
{
auditLogger.Reset();

View File

@@ -0,0 +1,10 @@
# Authority Standard Plugin Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0097-M | DONE | Maintainability audit for StellaOps.Authority.Plugin.Standard.Tests. |
| AUDIT-0097-T | DONE | Test coverage audit for StellaOps.Authority.Plugin.Standard.Tests. |
| AUDIT-0097-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,281 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Authority.Persistence.Postgres.Models;
using StellaOps.Authority.Persistence.Postgres.Repositories;
namespace StellaOps.Authority.Plugin.Standard.Tests;
internal sealed class InMemoryUserRepository : IUserRepository
{
private readonly Dictionary<Guid, UserEntity> users = new();
private readonly Dictionary<string, Guid> byUsername = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, Guid> byEmail = new(StringComparer.OrdinalIgnoreCase);
public Task<UserEntity> CreateAsync(UserEntity user, CancellationToken cancellationToken = default)
{
var now = DateTimeOffset.UtcNow;
var created = new UserEntity
{
Id = user.Id,
TenantId = user.TenantId,
Username = user.Username,
Email = user.Email,
DisplayName = user.DisplayName,
PasswordHash = user.PasswordHash,
PasswordSalt = user.PasswordSalt,
Enabled = user.Enabled,
EmailVerified = user.EmailVerified,
MfaEnabled = user.MfaEnabled,
MfaSecret = user.MfaSecret,
MfaBackupCodes = user.MfaBackupCodes,
FailedLoginAttempts = user.FailedLoginAttempts,
LockedUntil = user.LockedUntil,
LastLoginAt = user.LastLoginAt,
PasswordChangedAt = user.PasswordChangedAt,
Settings = string.IsNullOrWhiteSpace(user.Settings) ? "{}" : user.Settings,
Metadata = string.IsNullOrWhiteSpace(user.Metadata) ? "{}" : user.Metadata,
CreatedAt = user.CreatedAt == default ? now : user.CreatedAt,
UpdatedAt = user.UpdatedAt == default ? now : user.UpdatedAt,
CreatedBy = user.CreatedBy
};
users[created.Id] = created;
byUsername[GetUsernameKey(created.TenantId, created.Username)] = created.Id;
byEmail[GetEmailKey(created.TenantId, created.Email)] = created.Id;
return Task.FromResult(created);
}
public Task<UserEntity?> GetByIdAsync(string tenantId, Guid id, CancellationToken cancellationToken = default)
{
if (users.TryGetValue(id, out var user) && string.Equals(user.TenantId, tenantId, StringComparison.Ordinal))
{
return Task.FromResult<UserEntity?>(user);
}
return Task.FromResult<UserEntity?>(null);
}
public Task<UserEntity?> GetByUsernameAsync(string tenantId, string username, CancellationToken cancellationToken = default)
{
var key = GetUsernameKey(tenantId, username);
if (byUsername.TryGetValue(key, out var id) && users.TryGetValue(id, out var user))
{
return Task.FromResult<UserEntity?>(user);
}
return Task.FromResult<UserEntity?>(null);
}
public Task<UserEntity?> GetByEmailAsync(string tenantId, string email, CancellationToken cancellationToken = default)
{
var key = GetEmailKey(tenantId, email);
if (byEmail.TryGetValue(key, out var id) && users.TryGetValue(id, out var user))
{
return Task.FromResult<UserEntity?>(user);
}
return Task.FromResult<UserEntity?>(null);
}
public Task<IReadOnlyList<UserEntity>> GetAllAsync(
string tenantId,
bool? enabled = null,
int limit = 100,
int offset = 0,
CancellationToken cancellationToken = default)
{
var results = users.Values
.Where(u => string.Equals(u.TenantId, tenantId, StringComparison.Ordinal))
.Where(u => enabled is null || u.Enabled == enabled.Value)
.OrderBy(u => u.Username, StringComparer.OrdinalIgnoreCase)
.Skip(offset)
.Take(limit)
.ToList();
return Task.FromResult<IReadOnlyList<UserEntity>>(results);
}
public Task<bool> UpdateAsync(UserEntity user, CancellationToken cancellationToken = default)
{
if (!users.TryGetValue(user.Id, out var existing))
{
return Task.FromResult(false);
}
var now = DateTimeOffset.UtcNow;
var updated = new UserEntity
{
Id = user.Id,
TenantId = user.TenantId,
Username = user.Username,
Email = user.Email,
DisplayName = user.DisplayName,
PasswordHash = user.PasswordHash,
PasswordSalt = user.PasswordSalt,
Enabled = user.Enabled,
EmailVerified = user.EmailVerified,
MfaEnabled = user.MfaEnabled,
MfaSecret = user.MfaSecret,
MfaBackupCodes = user.MfaBackupCodes,
FailedLoginAttempts = user.FailedLoginAttempts,
LockedUntil = user.LockedUntil,
LastLoginAt = user.LastLoginAt,
PasswordChangedAt = user.PasswordChangedAt,
Settings = string.IsNullOrWhiteSpace(user.Settings) ? existing.Settings : user.Settings,
Metadata = string.IsNullOrWhiteSpace(user.Metadata) ? existing.Metadata : user.Metadata,
CreatedAt = existing.CreatedAt,
UpdatedAt = now,
CreatedBy = user.CreatedBy ?? existing.CreatedBy
};
users[updated.Id] = updated;
byUsername[GetUsernameKey(updated.TenantId, updated.Username)] = updated.Id;
byEmail[GetEmailKey(updated.TenantId, updated.Email)] = updated.Id;
return Task.FromResult(true);
}
public Task<bool> DeleteAsync(string tenantId, Guid id, CancellationToken cancellationToken = default)
{
if (users.TryGetValue(id, out var user) && string.Equals(user.TenantId, tenantId, StringComparison.Ordinal))
{
users.Remove(id);
byUsername.Remove(GetUsernameKey(user.TenantId, user.Username));
byEmail.Remove(GetEmailKey(user.TenantId, user.Email));
return Task.FromResult(true);
}
return Task.FromResult(false);
}
public Task<bool> UpdatePasswordAsync(
string tenantId,
Guid userId,
string passwordHash,
string passwordSalt,
CancellationToken cancellationToken = default)
{
if (!users.TryGetValue(userId, out var existing) || !string.Equals(existing.TenantId, tenantId, StringComparison.Ordinal))
{
return Task.FromResult(false);
}
var now = DateTimeOffset.UtcNow;
var updated = new UserEntity
{
Id = existing.Id,
TenantId = existing.TenantId,
Username = existing.Username,
Email = existing.Email,
DisplayName = existing.DisplayName,
PasswordHash = passwordHash,
PasswordSalt = passwordSalt,
Enabled = existing.Enabled,
EmailVerified = existing.EmailVerified,
MfaEnabled = existing.MfaEnabled,
MfaSecret = existing.MfaSecret,
MfaBackupCodes = existing.MfaBackupCodes,
FailedLoginAttempts = existing.FailedLoginAttempts,
LockedUntil = existing.LockedUntil,
LastLoginAt = existing.LastLoginAt,
PasswordChangedAt = now,
Settings = existing.Settings,
Metadata = existing.Metadata,
CreatedAt = existing.CreatedAt,
UpdatedAt = now,
CreatedBy = existing.CreatedBy
};
users[updated.Id] = updated;
return Task.FromResult(true);
}
public Task<int> RecordFailedLoginAsync(
string tenantId,
Guid userId,
DateTimeOffset? lockUntil = null,
CancellationToken cancellationToken = default)
{
if (!users.TryGetValue(userId, out var existing) || !string.Equals(existing.TenantId, tenantId, StringComparison.Ordinal))
{
return Task.FromResult(0);
}
var now = DateTimeOffset.UtcNow;
var attempts = existing.FailedLoginAttempts + 1;
var updated = new UserEntity
{
Id = existing.Id,
TenantId = existing.TenantId,
Username = existing.Username,
Email = existing.Email,
DisplayName = existing.DisplayName,
PasswordHash = existing.PasswordHash,
PasswordSalt = existing.PasswordSalt,
Enabled = existing.Enabled,
EmailVerified = existing.EmailVerified,
MfaEnabled = existing.MfaEnabled,
MfaSecret = existing.MfaSecret,
MfaBackupCodes = existing.MfaBackupCodes,
FailedLoginAttempts = attempts,
LockedUntil = lockUntil,
LastLoginAt = existing.LastLoginAt,
PasswordChangedAt = existing.PasswordChangedAt,
Settings = existing.Settings,
Metadata = existing.Metadata,
CreatedAt = existing.CreatedAt,
UpdatedAt = now,
CreatedBy = existing.CreatedBy
};
users[updated.Id] = updated;
return Task.FromResult(attempts);
}
public Task RecordSuccessfulLoginAsync(string tenantId, Guid userId, CancellationToken cancellationToken = default)
{
if (!users.TryGetValue(userId, out var existing) || !string.Equals(existing.TenantId, tenantId, StringComparison.Ordinal))
{
return Task.CompletedTask;
}
var now = DateTimeOffset.UtcNow;
var updated = new UserEntity
{
Id = existing.Id,
TenantId = existing.TenantId,
Username = existing.Username,
Email = existing.Email,
DisplayName = existing.DisplayName,
PasswordHash = existing.PasswordHash,
PasswordSalt = existing.PasswordSalt,
Enabled = existing.Enabled,
EmailVerified = existing.EmailVerified,
MfaEnabled = existing.MfaEnabled,
MfaSecret = existing.MfaSecret,
MfaBackupCodes = existing.MfaBackupCodes,
FailedLoginAttempts = 0,
LockedUntil = null,
LastLoginAt = now,
PasswordChangedAt = existing.PasswordChangedAt,
Settings = existing.Settings,
Metadata = existing.Metadata,
CreatedAt = existing.CreatedAt,
UpdatedAt = now,
CreatedBy = existing.CreatedBy
};
users[updated.Id] = updated;
return Task.CompletedTask;
}
private static string GetUsernameKey(string tenantId, string username)
=> $"{tenantId}::{username}".ToLowerInvariant();
private static string GetEmailKey(string tenantId, string email)
=> $"{tenantId}::{email}".ToLowerInvariant();
}

View File

@@ -0,0 +1,10 @@
# Authority Standard Plugin Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0096-M | DONE | Maintainability audit for StellaOps.Authority.Plugin.Standard. |
| AUDIT-0096-T | DONE | Test coverage audit for StellaOps.Authority.Plugin.Standard. |
| AUDIT-0096-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority Plugin Abstractions Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugins.Abstractions.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: contract type validation, normalization, and edge-case behavior.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs); avoid external network calls.
- Prefer explicit assertions over documentation-only tests.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + TestKit.
- Cover capability parsing, normalization logic, and validation failures.

View File

@@ -0,0 +1,10 @@
# Authority Plugin Abstractions Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0099-M | DONE | Maintainability audit for StellaOps.Authority.Plugins.Abstractions.Tests. |
| AUDIT-0099-T | DONE | Test coverage audit for StellaOps.Authority.Plugins.Abstractions.Tests. |
| AUDIT-0099-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,21 @@
# Authority Plugin Abstractions AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority.Plugins.Abstractions/`.
- Roles: backend engineer, QA automation.
- Focus: Authority plugin contracts, identity provider abstractions, and shared metadata types.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep contracts deterministic (stable ordering, normalized data).
- Avoid breaking changes without coordinating downstream plugin implementations.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + TestKit; cover normalization, validation, and edge cases for contract types.

View File

@@ -0,0 +1,10 @@
# Authority Plugin Abstractions Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0098-M | DONE | Maintainability audit for StellaOps.Authority.Plugins.Abstractions. |
| AUDIT-0098-T | DONE | Test coverage audit for StellaOps.Authority.Plugins.Abstractions. |
| AUDIT-0098-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,29 @@
# Authority Tests Charter
## Mission
Own the Authority test suite for the Authority web service and shared components. Ensure coverage for auth flows, policy enforcement, and deterministic behavior.
## Responsibilities
- Maintain `StellaOps.Authority.Tests` and supporting test utilities.
- Keep tests deterministic and offline-friendly; avoid external dependencies unless explicitly approved.
- Surface open work on `TASKS.md`; update statuses (TODO/DOING/DONE/BLOCKED/REVIEW).
## Key Paths
- `Infrastructure/AuthorityWebApplicationFactory.cs`
- `OpenIddict/*`, `Auth/*`, `Bootstrap/*`, `Notifications/*`, `Observability/*`
- `docs/modules/authority/architecture.md`
## Coordination
- Authority Core and Security guilds for auth flows and crypto.
- Observability guild for trace/metrics assertions.
## Required Reading
- `docs/modules/authority/architecture.md`
- `docs/modules/platform/architecture-overview.md`
## Working Agreement
- 1. Update task status to `DOING`/`DONE` in both corresponding sprint file `/docs/implplan/SPRINT_*.md` and the local `TASKS.md` when you start or finish work.
- 2. Review this charter and the Required Reading documents before coding; confirm prerequisites are met.
- 3. Keep changes deterministic (stable ordering, timestamps, hashes) and align with offline/air-gap expectations.
- 4. Coordinate doc updates, tests, and cross-guild communication whenever contracts or workflows change.
- 5. Revert to `TODO` if you pause the task without shipping changes; leave notes in commit/PR descriptions for context.

View File

@@ -0,0 +1,10 @@
# Authority Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0100-M | DONE | Maintainability audit for StellaOps.Authority.Tests. |
| AUDIT-0100-T | DONE | Test coverage audit for StellaOps.Authority.Tests. |
| AUDIT-0100-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,25 @@
# Authority Service AGENTS
## Purpose & Scope
- Working directory: `src/Authority/StellaOps.Authority/StellaOps.Authority/`.
- Roles: backend engineer, QA automation.
- Focus: Authority web service composition, OpenIddict flows, plugins, storage adapters, and security controls.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- `docs/architecture/console-admin-rbac.md`
- `docs/architecture/console-branding.md`
- Relevant sprint files.
## Working Agreements
- Keep auth flows deterministic (TimeProvider/ID generators where feasible).
- Preserve offline/air-gap posture and avoid new hard network dependencies.
- Audit events must stay structured and avoid leaking secrets.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + TestKit.
- Cover OpenIddict handlers, auth audit sinks, storage adapters, and policy enforcement.

View File

@@ -0,0 +1,10 @@
# Authority Service Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0085-M | DONE | Maintainability audit for StellaOps.Authority. |
| AUDIT-0085-T | DONE | Test coverage audit for StellaOps.Authority. |
| AUDIT-0085-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority Core AGENTS
## Purpose & Scope
- Working directory: `src/Authority/__Libraries/StellaOps.Authority.Core/`.
- Roles: backend engineer, QA automation.
- Focus: verdict manifests, replay verification, manifest signing interfaces, and deterministic serialization.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Preserve deterministic ordering and timestamps (TimeProvider where possible).
- Keep manifests replayable with explicit inputs and stable serialization.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions.
- Cover manifest builder/serializer, replay verification, and store pagination.

View File

@@ -0,0 +1,10 @@
# Authority Core Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0086-M | DONE | Maintainability audit for StellaOps.Authority.Core. |
| AUDIT-0086-T | DONE | Test coverage audit for StellaOps.Authority.Core. |
| AUDIT-0086-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority Persistence AGENTS
## Purpose & Scope
- Working directory: `src/Authority/__Libraries/StellaOps.Authority.Persistence/`.
- Roles: backend engineer, QA automation.
- Focus: Authority persistence layer (Postgres repositories, in-memory stores, migrations, and EF Core scaffolding).
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Preserve deterministic ordering and timestamps; avoid implicit NOW()/UtcNow for testable paths.
- Keep schema usage consistent with configured Postgres options.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + Moq.
- Cover repository CRUD, pagination, schema overrides, and in-memory store behavior.

View File

@@ -2,8 +2,6 @@
-- Consolidated from migrations 001-005 (pre_1.0 archived)
-- Creates the complete authority schema for IAM, tenants, users, tokens, RLS, and audit
BEGIN;
-- ============================================================================
-- SECTION 1: Schema Creation
-- ============================================================================
@@ -78,15 +76,20 @@ CREATE TABLE IF NOT EXISTS authority.users (
display_name TEXT,
password_hash TEXT,
password_salt TEXT,
enabled BOOLEAN NOT NULL DEFAULT TRUE,
password_algorithm TEXT DEFAULT 'argon2id',
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'locked', 'deleted')),
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
mfa_enabled BOOLEAN NOT NULL DEFAULT FALSE,
mfa_secret TEXT,
mfa_backup_codes TEXT,
failed_login_attempts INT NOT NULL DEFAULT 0,
locked_until TIMESTAMPTZ,
last_login_at TIMESTAMPTZ,
password_changed_at TIMESTAMPTZ,
last_password_change_at TIMESTAMPTZ,
password_expires_at TIMESTAMPTZ,
settings JSONB NOT NULL DEFAULT '{}',
metadata JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
@@ -606,4 +609,3 @@ BEGIN
END
$$;
COMMIT;

View File

@@ -121,7 +121,7 @@ public sealed class TenantRepository : RepositoryBase<AuthorityDataSource>, ITen
public async Task<bool> UpdateAsync(TenantEntity tenant, CancellationToken cancellationToken = default)
{
const string sql = """
UPDATE auth.tenants
UPDATE authority.tenants
SET name = @name,
description = @description,
contact_email = @contact_email,
@@ -152,7 +152,7 @@ public sealed class TenantRepository : RepositoryBase<AuthorityDataSource>, ITen
/// <inheritdoc />
public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
const string sql = "DELETE FROM auth.tenants WHERE id = @id";
const string sql = "DELETE FROM authority.tenants WHERE id = @id";
var rows = await ExecuteAsync(
SystemTenantId,
@@ -166,7 +166,7 @@ public sealed class TenantRepository : RepositoryBase<AuthorityDataSource>, ITen
/// <inheritdoc />
public async Task<bool> SlugExistsAsync(string slug, CancellationToken cancellationToken = default)
{
const string sql = "SELECT EXISTS(SELECT 1 FROM auth.tenants WHERE slug = @slug)";
const string sql = "SELECT EXISTS(SELECT 1 FROM authority.tenants WHERE slug = @slug)";
var result = await ExecuteScalarAsync<bool>(
SystemTenantId,

View File

@@ -22,7 +22,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
public async Task<UserEntity> CreateAsync(UserEntity user, CancellationToken cancellationToken = default)
{
const string sql = """
INSERT INTO auth.users (
INSERT INTO authority.users (
id, tenant_id, username, email, display_name, password_hash, password_salt,
enabled, email_verified, mfa_enabled, mfa_secret, mfa_backup_codes,
settings, metadata, created_by
@@ -58,7 +58,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
enabled, email_verified, mfa_enabled, mfa_secret, mfa_backup_codes,
failed_login_attempts, locked_until, last_login_at, password_changed_at,
settings::text, metadata::text, created_at, updated_at, created_by
FROM auth.users
FROM authority.users
WHERE tenant_id = @tenant_id AND id = @id
""";
@@ -82,7 +82,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
enabled, email_verified, mfa_enabled, mfa_secret, mfa_backup_codes,
failed_login_attempts, locked_until, last_login_at, password_changed_at,
settings::text, metadata::text, created_at, updated_at, created_by
FROM auth.users
FROM authority.users
WHERE tenant_id = @tenant_id AND username = @username
""";
@@ -106,7 +106,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
enabled, email_verified, mfa_enabled, mfa_secret, mfa_backup_codes,
failed_login_attempts, locked_until, last_login_at, password_changed_at,
settings::text, metadata::text, created_at, updated_at, created_by
FROM auth.users
FROM authority.users
WHERE tenant_id = @tenant_id AND email = @email
""";
@@ -135,7 +135,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
enabled, email_verified, mfa_enabled, mfa_secret, mfa_backup_codes,
failed_login_attempts, locked_until, last_login_at, password_changed_at,
settings::text, metadata::text, created_at, updated_at, created_by
FROM auth.users
FROM authority.users
WHERE tenant_id = @tenant_id
""";
@@ -167,7 +167,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
public async Task<bool> UpdateAsync(UserEntity user, CancellationToken cancellationToken = default)
{
const string sql = """
UPDATE auth.users
UPDATE authority.users
SET username = @username,
email = @email,
display_name = @display_name,
@@ -207,7 +207,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
/// <inheritdoc />
public async Task<bool> DeleteAsync(string tenantId, Guid id, CancellationToken cancellationToken = default)
{
const string sql = "DELETE FROM auth.users WHERE tenant_id = @tenant_id AND id = @id";
const string sql = "DELETE FROM authority.users WHERE tenant_id = @tenant_id AND id = @id";
var rows = await ExecuteAsync(
tenantId,
@@ -231,7 +231,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
CancellationToken cancellationToken = default)
{
const string sql = """
UPDATE auth.users
UPDATE authority.users
SET password_hash = @password_hash,
password_salt = @password_salt,
password_changed_at = NOW()
@@ -261,7 +261,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
CancellationToken cancellationToken = default)
{
const string sql = """
UPDATE auth.users
UPDATE authority.users
SET failed_login_attempts = failed_login_attempts + 1,
locked_until = @locked_until
WHERE tenant_id = @tenant_id AND id = @id
@@ -289,7 +289,7 @@ public sealed class UserRepository : RepositoryBase<AuthorityDataSource>, IUserR
CancellationToken cancellationToken = default)
{
const string sql = """
UPDATE auth.users
UPDATE authority.users
SET failed_login_attempts = 0,
locked_until = NULL,
last_login_at = NOW()

View File

@@ -0,0 +1,10 @@
# Authority Persistence Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0088-M | DONE | Maintainability audit for StellaOps.Authority.Persistence. |
| AUDIT-0088-T | DONE | Test coverage audit for StellaOps.Authority.Persistence. |
| AUDIT-0088-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority Core Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/__Tests/StellaOps.Authority.Core.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: verdict manifests, serialization, replay verification, and store behaviors.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs, stable ordering).
- Avoid live network calls; use fakes for signing and evaluation.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + Moq.
- Cover manifest builder/serializer, replay verification, and store pagination/filters.

View File

@@ -0,0 +1,10 @@
# Authority Core Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0087-M | DONE | Maintainability audit for StellaOps.Authority.Core.Tests. |
| AUDIT-0087-T | DONE | Test coverage audit for StellaOps.Authority.Core.Tests. |
| AUDIT-0087-A | TODO | Pending approval for changes. |

View File

@@ -0,0 +1,22 @@
# Authority Persistence Tests AGENTS
## Purpose & Scope
- Working directory: `src/Authority/__Tests/StellaOps.Authority.Persistence.Tests/`.
- Roles: QA automation, backend engineer.
- Focus: PostgreSQL repository behavior, migrations, concurrency, and storage correctness.
## Required Reading (treat as read before DOING)
- `docs/README.md`
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/modules/authority/architecture.md`
- Relevant sprint files.
## Working Agreements
- Keep tests deterministic (fixed time/IDs); tag integration tests correctly.
- Avoid network calls beyond local Postgres fixtures.
- Update `docs/implplan/SPRINT_*.md` and local `TASKS.md` when starting or completing work.
## Testing
- Use xUnit + FluentAssertions + Moq + TestKit fixtures.
- Cover repository CRUD, migrations, concurrency, and pagination edge cases.

View File

@@ -31,6 +31,7 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private ApiKeyRepository _repository = null!;
private AuthorityDataSource? _dataSource;
private NpgsqlDataSource _npgsqlDataSource = null!;
private readonly string _tenantId = Guid.NewGuid().ToString();
private readonly Guid _userId = Guid.NewGuid();
@@ -44,10 +45,9 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
{
await _fixture.TruncateAllTablesAsync();
var options = _fixture.Fixture.CreateOptions();
options.SchemaName = _fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new ApiKeyRepository(dataSource, NullLogger<ApiKeyRepository>.Instance);
var options = _fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new ApiKeyRepository(_dataSource, NullLogger<ApiKeyRepository>.Instance);
_npgsqlDataSource = NpgsqlDataSource.Create(_fixture.ConnectionString);
await SeedTenantAsync();
@@ -57,6 +57,10 @@ public sealed class ApiKeyConcurrencyTests : IAsyncLifetime
public async ValueTask DisposeAsync()
{
await _npgsqlDataSource.DisposeAsync();
if (_dataSource is not null)
{
await _dataSource.DisposeAsync();
}
}
[Trait("Category", TestCategories.Unit)]

View File

@@ -31,6 +31,7 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private ApiKeyRepository _repository = null!;
private AuthorityDataSource? _dataSource;
private NpgsqlDataSource _npgsqlDataSource = null!;
private readonly string _tenantId = Guid.NewGuid().ToString();
private readonly Guid _userId = Guid.NewGuid();
@@ -44,10 +45,9 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
{
await _fixture.TruncateAllTablesAsync();
var options = _fixture.Fixture.CreateOptions();
options.SchemaName = _fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new ApiKeyRepository(dataSource, NullLogger<ApiKeyRepository>.Instance);
var options = _fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new ApiKeyRepository(_dataSource, NullLogger<ApiKeyRepository>.Instance);
_npgsqlDataSource = NpgsqlDataSource.Create(_fixture.ConnectionString);
await SeedTenantAsync();
@@ -57,6 +57,10 @@ public sealed class ApiKeyIdempotencyTests : IAsyncLifetime
public async ValueTask DisposeAsync()
{
await _npgsqlDataSource.DisposeAsync();
if (_dataSource is not null)
{
await _dataSource.DisposeAsync();
}
}
[Trait("Category", TestCategories.Unit)]

View File

@@ -12,6 +12,7 @@ namespace StellaOps.Authority.Persistence.Tests;
public sealed class ApiKeyRepositoryTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly AuthorityDataSource _dataSource;
private readonly ApiKeyRepository _repository;
private readonly string _tenantId = Guid.NewGuid().ToString();
@@ -19,10 +20,9 @@ public sealed class ApiKeyRepositoryTests : IAsyncLifetime
{
_fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new ApiKeyRepository(dataSource, NullLogger<ApiKeyRepository>.Instance);
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new ApiKeyRepository(_dataSource, NullLogger<ApiKeyRepository>.Instance);
}
public async ValueTask InitializeAsync()
@@ -31,7 +31,7 @@ public sealed class ApiKeyRepositoryTests : IAsyncLifetime
await SeedTenantAsync();
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]

View File

@@ -12,6 +12,7 @@ namespace StellaOps.Authority.Persistence.Tests;
public sealed class AuditRepositoryTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly AuthorityDataSource _dataSource;
private readonly AuditRepository _repository;
private readonly string _tenantId = Guid.NewGuid().ToString();
@@ -19,14 +20,13 @@ public sealed class AuditRepositoryTests : IAsyncLifetime
{
_fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new AuditRepository(dataSource, NullLogger<AuditRepository>.Instance);
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new AuditRepository(_dataSource, NullLogger<AuditRepository>.Instance);
}
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]

View File

@@ -8,6 +8,7 @@
using System.Reflection;
using StellaOps.Authority.Persistence.Postgres;
using StellaOps.Infrastructure.Postgres.Testing;
using StellaOps.Infrastructure.Postgres.Options;
using StellaOps.TestKit;
using StellaOps.TestKit.Fixtures;
using Xunit;
@@ -28,6 +29,15 @@ public sealed class AuthorityPostgresFixture : PostgresIntegrationFixture, IColl
=> typeof(AuthorityDataSource).Assembly;
protected override string GetModuleName() => "Authority";
public PostgresOptions CreateOptions()
{
var options = Fixture.CreateOptions();
options.SchemaName = SchemaName;
options.MaxPoolSize = 10;
options.MinPoolSize = 0;
return options;
}
}
/// <summary>

View File

@@ -13,19 +13,19 @@ public sealed class OfflineKitAuditRepositoryTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly OfflineKitAuditRepository _repository;
private readonly AuthorityDataSource _dataSource;
public OfflineKitAuditRepositoryTests(AuthorityPostgresFixture fixture)
{
_fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new OfflineKitAuditRepository(dataSource, NullLogger<OfflineKitAuditRepository>.Instance);
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new OfflineKitAuditRepository(_dataSource, NullLogger<OfflineKitAuditRepository>.Instance);
}
public ValueTask InitializeAsync() => new(_fixture.TruncateAllTablesAsync());
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]

View File

@@ -12,6 +12,7 @@ namespace StellaOps.Authority.Persistence.Tests;
public sealed class PermissionRepositoryTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly AuthorityDataSource _dataSource;
private readonly PermissionRepository _repository;
private readonly string _tenantId = Guid.NewGuid().ToString();
@@ -19,10 +20,9 @@ public sealed class PermissionRepositoryTests : IAsyncLifetime
{
_fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new PermissionRepository(dataSource, NullLogger<PermissionRepository>.Instance);
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new PermissionRepository(_dataSource, NullLogger<PermissionRepository>.Instance);
}
public async ValueTask InitializeAsync()
@@ -31,7 +31,7 @@ public sealed class PermissionRepositoryTests : IAsyncLifetime
await SeedTenantAsync();
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]

View File

@@ -13,6 +13,7 @@ namespace StellaOps.Authority.Persistence.Tests;
public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly AuthorityDataSource _dataSource;
private readonly RefreshTokenRepository _repository;
private readonly string _tenantId = Guid.NewGuid().ToString();
@@ -20,10 +21,9 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
{
_fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new RefreshTokenRepository(dataSource, NullLogger<RefreshTokenRepository>.Instance);
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new RefreshTokenRepository(_dataSource, NullLogger<RefreshTokenRepository>.Instance);
}
public async ValueTask InitializeAsync()
@@ -32,7 +32,7 @@ public sealed class RefreshTokenRepositoryTests : IAsyncLifetime
await SeedTenantAsync();
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]

View File

@@ -28,6 +28,7 @@ namespace StellaOps.Authority.Persistence.Tests;
public sealed class RoleBasedAccessTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private AuthorityDataSource? _dataSource;
private RoleRepository _roleRepository = null!;
private PermissionRepository _permissionRepository = null!;
private UserRepository _userRepository = null!;
@@ -42,18 +43,23 @@ public sealed class RoleBasedAccessTests : IAsyncLifetime
{
await _fixture.TruncateAllTablesAsync();
var options = _fixture.Fixture.CreateOptions();
options.SchemaName = _fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
var options = _fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_roleRepository = new RoleRepository(dataSource, NullLogger<RoleRepository>.Instance);
_permissionRepository = new PermissionRepository(dataSource, NullLogger<PermissionRepository>.Instance);
_userRepository = new UserRepository(dataSource, NullLogger<UserRepository>.Instance);
_roleRepository = new RoleRepository(_dataSource, NullLogger<RoleRepository>.Instance);
_permissionRepository = new PermissionRepository(_dataSource, NullLogger<PermissionRepository>.Instance);
_userRepository = new UserRepository(_dataSource, NullLogger<UserRepository>.Instance);
await SeedTenantAsync();
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public async ValueTask DisposeAsync()
{
if (_dataSource is not null)
{
await _dataSource.DisposeAsync();
}
}
#region User-Role Assignment Tests

View File

@@ -12,6 +12,7 @@ namespace StellaOps.Authority.Persistence.Tests;
public sealed class RoleRepositoryTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly AuthorityDataSource _dataSource;
private readonly RoleRepository _repository;
private readonly string _tenantId = Guid.NewGuid().ToString();
@@ -19,10 +20,9 @@ public sealed class RoleRepositoryTests : IAsyncLifetime
{
_fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new RoleRepository(dataSource, NullLogger<RoleRepository>.Instance);
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new RoleRepository(_dataSource, NullLogger<RoleRepository>.Instance);
}
public async ValueTask InitializeAsync()
@@ -31,7 +31,7 @@ public sealed class RoleRepositoryTests : IAsyncLifetime
await SeedTenantAsync();
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]

View File

@@ -12,6 +12,7 @@ namespace StellaOps.Authority.Persistence.Tests;
public sealed class SessionRepositoryTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly AuthorityDataSource _dataSource;
private readonly SessionRepository _repository;
private readonly string _tenantId = Guid.NewGuid().ToString();
@@ -19,10 +20,9 @@ public sealed class SessionRepositoryTests : IAsyncLifetime
{
_fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new SessionRepository(dataSource, NullLogger<SessionRepository>.Instance);
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new SessionRepository(_dataSource, NullLogger<SessionRepository>.Instance);
}
public async ValueTask InitializeAsync()
@@ -31,7 +31,7 @@ public sealed class SessionRepositoryTests : IAsyncLifetime
await SeedTenantAsync();
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]

View File

@@ -0,0 +1,10 @@
# Authority Persistence Tests Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0089-M | DONE | Maintainability audit for StellaOps.Authority.Persistence.Tests. |
| AUDIT-0089-T | DONE | Test coverage audit for StellaOps.Authority.Persistence.Tests. |
| AUDIT-0089-A | TODO | Pending approval for changes. |

View File

@@ -13,6 +13,7 @@ namespace StellaOps.Authority.Persistence.Tests;
public sealed class TokenRepositoryTests : IAsyncLifetime
{
private readonly AuthorityPostgresFixture _fixture;
private readonly AuthorityDataSource _dataSource;
private readonly TokenRepository _repository;
private readonly string _tenantId = Guid.NewGuid().ToString();
@@ -20,10 +21,9 @@ public sealed class TokenRepositoryTests : IAsyncLifetime
{
_fixture = fixture;
var options = fixture.Fixture.CreateOptions();
options.SchemaName = fixture.SchemaName;
var dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new TokenRepository(dataSource, NullLogger<TokenRepository>.Instance);
var options = fixture.CreateOptions();
_dataSource = new AuthorityDataSource(Options.Create(options), NullLogger<AuthorityDataSource>.Instance);
_repository = new TokenRepository(_dataSource, NullLogger<TokenRepository>.Instance);
}
public async ValueTask InitializeAsync()
@@ -31,7 +31,7 @@ public sealed class TokenRepositoryTests : IAsyncLifetime
await _fixture.TruncateAllTablesAsync();
await SeedTenantAsync();
}
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
public ValueTask DisposeAsync() => _dataSource.DisposeAsync();
[Trait("Category", TestCategories.Unit)]
[Fact]