Add unit tests for Router configuration and transport layers
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled

- Implemented tests for RouterConfig, RoutingOptions, StaticInstanceConfig, and RouterConfigOptions to ensure default values are set correctly.
- Added tests for RouterConfigProvider to validate configurations and ensure defaults are returned when no file is specified.
- Created tests for ConfigValidationResult to check success and error scenarios.
- Developed tests for ServiceCollectionExtensions to verify service registration for RouterConfig.
- Introduced UdpTransportTests to validate serialization, connection, request-response, and error handling in UDP transport.
- Added scripts for signing authority gaps and hashing DevPortal SDK snippets.
This commit is contained in:
StellaOps Bot
2025-12-05 08:01:47 +02:00
parent 635c70e828
commit 6a299d231f
294 changed files with 28434 additions and 1329 deletions

View File

@@ -8,6 +8,5 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Diagnostics.DiagnosticSource" Version="10.0.0-rc.2.25502.107" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,40 @@
using Microsoft.Extensions.Logging;
using StellaOps.Infrastructure.Postgres.Connections;
using StellaOps.Infrastructure.Postgres.Options;
namespace StellaOps.IssuerDirectory.Storage.Postgres;
/// <summary>
/// PostgreSQL data source for the IssuerDirectory module.
/// Manages connection pooling, tenant context, and session configuration.
/// </summary>
public sealed class IssuerDirectoryDataSource : DataSourceBase
{
private readonly ILogger<IssuerDirectoryDataSource> _logger;
/// <summary>
/// Creates a new IssuerDirectory data source.
/// </summary>
/// <param name="options">PostgreSQL connection options.</param>
/// <param name="logger">Logger for diagnostics.</param>
public IssuerDirectoryDataSource(PostgresOptions options, ILogger<IssuerDirectoryDataSource> logger)
: base(options, logger)
{
_logger = logger;
}
/// <inheritdoc />
protected override string ModuleName => "IssuerDirectory";
/// <inheritdoc />
protected override void OnConnectionOpened(string role)
{
_logger.LogDebug("IssuerDirectory connection opened with role {Role}.", role);
}
/// <inheritdoc />
protected override void OnConnectionClosed(string role)
{
_logger.LogDebug("IssuerDirectory connection closed for role {Role}.", role);
}
}

View File

@@ -0,0 +1,129 @@
-- Migration: 001_initial_schema
-- Category: startup
-- Description: Initial schema for IssuerDirectory PostgreSQL storage
-- Source: docs/db/schemas/issuer.sql
CREATE SCHEMA IF NOT EXISTS issuer;
-- Issuers (tenant or global)
CREATE TABLE IF NOT EXISTS issuer.issuers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL, -- use @global GUID for seed publishers
name TEXT NOT NULL, -- logical issuer name (slug)
display_name TEXT NOT NULL,
description TEXT,
endpoints JSONB DEFAULT '{}'::jsonb, -- CSAF feeds, OIDC issuer URLs, contact links
contact JSONB DEFAULT '{}'::jsonb, -- Contact information
metadata JSONB DEFAULT '{}'::jsonb, -- Domain metadata (CVE org ID, CSAF publisher ID, etc.)
tags TEXT[] DEFAULT '{}',
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','revoked','deprecated')),
is_system_seed BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_by TEXT,
UNIQUE (tenant_id, name)
);
-- Keys
CREATE TABLE IF NOT EXISTS issuer.issuer_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issuer_id UUID NOT NULL REFERENCES issuer.issuers(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL,
key_id TEXT NOT NULL, -- stable key identifier
key_type TEXT NOT NULL CHECK (key_type IN ('ed25519','x509','dsse','kms','hsm','fido2')),
public_key TEXT NOT NULL, -- PEM / base64
fingerprint TEXT NOT NULL, -- canonical fingerprint for dedupe
not_before TIMESTAMPTZ,
not_after TIMESTAMPTZ,
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','retired','revoked')),
replaces_key_id TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_by TEXT,
retired_at TIMESTAMPTZ,
revoked_at TIMESTAMPTZ,
revoke_reason TEXT,
metadata JSONB DEFAULT '{}'::jsonb,
UNIQUE (issuer_id, key_id),
UNIQUE (fingerprint)
);
-- Trust overrides (tenant-scoped weights)
CREATE TABLE IF NOT EXISTS issuer.trust_overrides (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
issuer_id UUID NOT NULL REFERENCES issuer.issuers(id) ON DELETE CASCADE,
tenant_id UUID NOT NULL, -- consumer tenant applying the override
weight NUMERIC(5,2) NOT NULL CHECK (weight >= -10 AND weight <= 10),
rationale TEXT,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by TEXT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_by TEXT,
UNIQUE (issuer_id, tenant_id)
);
-- Audit log (issuer-domain specific)
CREATE TABLE IF NOT EXISTS issuer.audit (
id BIGSERIAL PRIMARY KEY,
tenant_id UUID NOT NULL,
actor TEXT,
action TEXT NOT NULL, -- create_issuer, update_issuer, delete_issuer, add_key, rotate_key, revoke_key, set_trust, delete_trust, seed_csaf
issuer_id UUID,
key_id TEXT,
trust_override_id UUID,
reason TEXT,
details JSONB DEFAULT '{}'::jsonb,
correlation_id TEXT,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Schema migrations tracking
CREATE TABLE IF NOT EXISTS issuer.schema_migrations (
migration_name TEXT PRIMARY KEY,
category TEXT NOT NULL DEFAULT 'startup',
checksum TEXT NOT NULL,
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
applied_by TEXT,
duration_ms INT,
CONSTRAINT valid_category CHECK (category IN ('startup', 'release', 'seed', 'data'))
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_issuers_tenant ON issuer.issuers(tenant_id);
CREATE INDEX IF NOT EXISTS idx_issuers_status ON issuer.issuers(status);
CREATE INDEX IF NOT EXISTS idx_issuers_slug ON issuer.issuers(name);
CREATE INDEX IF NOT EXISTS idx_keys_issuer ON issuer.issuer_keys(issuer_id);
CREATE INDEX IF NOT EXISTS idx_keys_status ON issuer.issuer_keys(status);
CREATE INDEX IF NOT EXISTS idx_keys_tenant ON issuer.issuer_keys(tenant_id);
CREATE INDEX IF NOT EXISTS idx_trust_tenant ON issuer.trust_overrides(tenant_id);
CREATE INDEX IF NOT EXISTS idx_audit_tenant_time ON issuer.audit(tenant_id, occurred_at DESC);
CREATE INDEX IF NOT EXISTS idx_audit_issuer ON issuer.audit(issuer_id);
CREATE INDEX IF NOT EXISTS idx_schema_migrations_applied_at ON issuer.schema_migrations(applied_at DESC);
-- Updated-at trigger for issuers/trust overrides
CREATE OR REPLACE FUNCTION issuer.update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trg_issuers_updated_at ON issuer.issuers;
CREATE TRIGGER trg_issuers_updated_at
BEFORE UPDATE ON issuer.issuers
FOR EACH ROW EXECUTE FUNCTION issuer.update_updated_at();
DROP TRIGGER IF EXISTS trg_keys_updated_at ON issuer.issuer_keys;
CREATE TRIGGER trg_keys_updated_at
BEFORE UPDATE ON issuer.issuer_keys
FOR EACH ROW EXECUTE FUNCTION issuer.update_updated_at();
DROP TRIGGER IF EXISTS trg_trust_updated_at ON issuer.trust_overrides;
CREATE TRIGGER trg_trust_updated_at
BEFORE UPDATE ON issuer.trust_overrides
FOR EACH ROW EXECUTE FUNCTION issuer.update_updated_at();

View File

@@ -0,0 +1,66 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Infrastructure.Postgres.Options;
namespace StellaOps.IssuerDirectory.Storage.Postgres;
/// <summary>
/// Extension methods for registering IssuerDirectory PostgreSQL storage services.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Registers the IssuerDirectory PostgreSQL data source.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="configureOptions">Options configuration delegate.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddIssuerDirectoryPostgresStorage(
this IServiceCollection services,
Action<PostgresOptions> configureOptions)
{
ArgumentNullException.ThrowIfNull(configureOptions);
var options = new PostgresOptions
{
ConnectionString = string.Empty,
SchemaName = "issuer"
};
configureOptions(options);
services.AddSingleton(sp =>
{
var logger = sp.GetRequiredService<ILogger<IssuerDirectoryDataSource>>();
return new IssuerDirectoryDataSource(options, logger);
});
return services;
}
/// <summary>
/// Registers the IssuerDirectory PostgreSQL data source with provided options.
/// </summary>
/// <param name="services">Service collection.</param>
/// <param name="options">PostgreSQL options.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddIssuerDirectoryPostgresStorage(
this IServiceCollection services,
PostgresOptions options)
{
ArgumentNullException.ThrowIfNull(options);
// Ensure schema is set for issuer module
if (string.IsNullOrWhiteSpace(options.SchemaName))
{
options.SchemaName = "issuer";
}
services.AddSingleton(sp =>
{
var logger = sp.GetRequiredService<ILogger<IssuerDirectoryDataSource>>();
return new IssuerDirectoryDataSource(options, logger);
});
return services;
}
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RootNamespace>StellaOps.IssuerDirectory.Storage.Postgres</RootNamespace>
<AssemblyName>StellaOps.IssuerDirectory.Storage.Postgres</AssemblyName>
<Description>PostgreSQL storage implementation for IssuerDirectory module</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0-rc.2.25502.107" />
<PackageReference Include="Npgsql" Version="9.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.IssuerDirectory.Core\StellaOps.IssuerDirectory.Core.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Infrastructure.Postgres\StellaOps.Infrastructure.Postgres.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Migrations\*.sql" LogicalName="%(Filename)%(Extension)" />
</ItemGroup>
</Project>

View File

@@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.IssuerDirectory.W
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.IssuerDirectory.Core.Tests", "StellaOps.IssuerDirectory.Core.Tests\StellaOps.IssuerDirectory.Core.Tests.csproj", "{22842BC6-D909-4919-8FB1-B2C3ED7E4DDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StellaOps.IssuerDirectory.Storage.Postgres", "StellaOps.IssuerDirectory.Storage.Postgres\StellaOps.IssuerDirectory.Storage.Postgres.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -33,6 +35,10 @@ Global
{22842BC6-D909-4919-8FB1-B2C3ED7E4DDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22842BC6-D909-4919-8FB1-B2C3ED7E4DDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22842BC6-D909-4919-8FB1-B2C3ED7E4DDE}.Release|Any CPU.Build.0 = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE