tests fixes

This commit is contained in:
master
2026-01-27 08:23:42 +02:00
parent c305d05d32
commit 82caceba56
58 changed files with 651 additions and 312 deletions

View File

@@ -71,7 +71,8 @@ public sealed class PostgresExceptionObjectRepository : RepositoryBase<PolicyDat
created_at, updated_at, approved_at, expires_at,
reason_code, rationale, evidence_refs, compensating_controls,
metadata, ticket_ref,
recheck_policy_id, last_recheck_result, last_recheck_at
recheck_policy_id, last_recheck_result, last_recheck_at,
name, reason
)
VALUES (
@exception_id, @version, @status, @type,
@@ -81,7 +82,8 @@ public sealed class PostgresExceptionObjectRepository : RepositoryBase<PolicyDat
@created_at, @updated_at, @approved_at, @expires_at,
@reason_code, @rationale, @evidence_refs::jsonb, @compensating_controls::jsonb,
@metadata::jsonb, @ticket_ref,
@recheck_policy_id, @last_recheck_result::jsonb, @last_recheck_at
@recheck_policy_id, @last_recheck_result::jsonb, @last_recheck_at,
@name, @reason
)
RETURNING id
""";
@@ -464,16 +466,17 @@ public sealed class PostgresExceptionObjectRepository : RepositoryBase<PolicyDat
AddParameter(command, "vulnerability_id", (object?)exception.Scope.VulnerabilityId ?? DBNull.Value);
AddParameter(command, "policy_rule_id", (object?)exception.Scope.PolicyRuleId ?? DBNull.Value);
AddTextArrayParameter(command, "environments", exception.Scope.Environments.ToArray());
AddParameter(command, "tenant_id", (object?)exception.Scope.TenantId ?? DBNull.Value);
AddParameter(command, "owner_id", exception.OwnerId);
AddParameter(command, "requester_id", exception.RequesterId);
// tenant_id is stored as TEXT in the database
AddParameter(command, "tenant_id", exception.Scope.TenantId?.ToString() ?? (object)DBNull.Value);
AddParameter(command, "owner_id", (object?)exception.OwnerId ?? DBNull.Value);
AddParameter(command, "requester_id", (object?)exception.RequesterId ?? DBNull.Value);
AddTextArrayParameter(command, "approver_ids", exception.ApproverIds.ToArray());
AddParameter(command, "created_at", exception.CreatedAt);
AddParameter(command, "updated_at", exception.UpdatedAt);
AddParameter(command, "approved_at", (object?)exception.ApprovedAt ?? DBNull.Value);
AddParameter(command, "expires_at", exception.ExpiresAt);
AddParameter(command, "reason_code", ReasonToString(exception.ReasonCode));
AddParameter(command, "rationale", exception.Rationale);
AddParameter(command, "rationale", (object?)exception.Rationale ?? DBNull.Value);
AddJsonbParameter(command, "evidence_refs", JsonSerializer.Serialize(exception.EvidenceRefs, JsonOptions));
AddJsonbParameter(command, "compensating_controls", JsonSerializer.Serialize(exception.CompensatingControls, JsonOptions));
AddJsonbParameter(command, "metadata", JsonSerializer.Serialize(exception.Metadata, JsonOptions));
@@ -481,6 +484,9 @@ public sealed class PostgresExceptionObjectRepository : RepositoryBase<PolicyDat
AddParameter(command, "recheck_policy_id", (object?)(exception.RecheckPolicyId ?? exception.RecheckPolicy?.PolicyId) ?? DBNull.Value);
AddJsonbParameter(command, "last_recheck_result", SerializeRecheckResult(exception.LastRecheckResult));
AddParameter(command, "last_recheck_at", (object?)exception.LastRecheckAt ?? DBNull.Value);
// Legacy columns required by schema (name TEXT NOT NULL, reason TEXT NOT NULL)
AddParameter(command, "name", exception.ExceptionId); // Use exception_id as name
AddParameter(command, "reason", exception.Rationale ?? ReasonToString(exception.ReasonCode)); // Use rationale or reason_code as reason
}
private async Task InsertEventAsync(
@@ -612,6 +618,12 @@ public sealed class PostgresExceptionObjectRepository : RepositoryBase<PolicyDat
private static ExceptionObject MapException(NpgsqlDataReader reader)
{
// tenant_id is stored as TEXT in the database but needs to be parsed as Guid
var tenantIdText = GetNullableString(reader, reader.GetOrdinal("tenant_id"));
Guid? tenantId = !string.IsNullOrEmpty(tenantIdText) && Guid.TryParse(tenantIdText, out var parsedTenantId)
? parsedTenantId
: null;
return new ExceptionObject
{
ExceptionId = reader.GetString(reader.GetOrdinal("exception_id")),
@@ -625,17 +637,17 @@ public sealed class PostgresExceptionObjectRepository : RepositoryBase<PolicyDat
VulnerabilityId = GetNullableString(reader, reader.GetOrdinal("vulnerability_id")),
PolicyRuleId = GetNullableString(reader, reader.GetOrdinal("policy_rule_id")),
Environments = GetStringArray(reader, reader.GetOrdinal("environments")),
TenantId = GetNullableGuid(reader, reader.GetOrdinal("tenant_id"))
TenantId = tenantId
},
OwnerId = reader.GetString(reader.GetOrdinal("owner_id")),
RequesterId = reader.GetString(reader.GetOrdinal("requester_id")),
OwnerId = GetNullableString(reader, reader.GetOrdinal("owner_id")) ?? string.Empty,
RequesterId = GetNullableString(reader, reader.GetOrdinal("requester_id")) ?? string.Empty,
ApproverIds = GetStringArray(reader, reader.GetOrdinal("approver_ids")),
CreatedAt = reader.GetFieldValue<DateTimeOffset>(reader.GetOrdinal("created_at")),
UpdatedAt = reader.GetFieldValue<DateTimeOffset>(reader.GetOrdinal("updated_at")),
ApprovedAt = GetNullableDateTimeOffset(reader, reader.GetOrdinal("approved_at")),
ExpiresAt = reader.GetFieldValue<DateTimeOffset>(reader.GetOrdinal("expires_at")),
ReasonCode = ParseReason(reader.GetString(reader.GetOrdinal("reason_code"))),
Rationale = reader.GetString(reader.GetOrdinal("rationale")),
Rationale = GetNullableString(reader, reader.GetOrdinal("rationale")) ?? string.Empty,
EvidenceRefs = ParseJsonArray(reader.GetString(reader.GetOrdinal("evidence_refs"))),
CompensatingControls = ParseJsonArray(reader.GetString(reader.GetOrdinal("compensating_controls"))),
Metadata = ParseJsonDictionary(reader.GetString(reader.GetOrdinal("metadata"))),

View File

@@ -415,32 +415,37 @@ public sealed class UnknownsRepository : IUnknownsRepository
#region Row Mapping
private sealed record UnknownRow(
Guid id,
Guid tenant_id,
string package_id,
string package_version,
string band,
decimal score,
decimal uncertainty_factor,
decimal exploit_pressure,
string? reason_code,
string? remediation_hint,
string? evidence_refs,
string? assumptions,
int? blast_radius_dependents,
bool? blast_radius_net_facing,
string? blast_radius_privilege,
string? containment_seccomp,
string? containment_fs_mode,
string? containment_network_policy,
DateTimeOffset first_seen_at,
DateTimeOffset last_evaluated_at,
string? resolution_reason,
DateTimeOffset? resolved_at,
DateTimeOffset created_at,
DateTimeOffset updated_at)
/// <summary>
/// Internal row class for Dapper materialization. Uses parameterless constructor
/// with property setters for compatibility with Dapper's deserialization.
/// </summary>
private sealed class UnknownRow
{
public Guid id { get; set; }
public Guid tenant_id { get; set; }
public string package_id { get; set; } = string.Empty;
public string package_version { get; set; } = string.Empty;
public string band { get; set; } = string.Empty;
public decimal score { get; set; }
public decimal uncertainty_factor { get; set; }
public decimal exploit_pressure { get; set; }
public string? reason_code { get; set; }
public string? remediation_hint { get; set; }
public string? evidence_refs { get; set; }
public string? assumptions { get; set; }
public int? blast_radius_dependents { get; set; }
public bool? blast_radius_net_facing { get; set; }
public string? blast_radius_privilege { get; set; }
public string? containment_seccomp { get; set; }
public string? containment_fs_mode { get; set; }
public string? containment_network_policy { get; set; }
public DateTime first_seen_at { get; set; }
public DateTime last_evaluated_at { get; set; }
public string? resolution_reason { get; set; }
public DateTime? resolved_at { get; set; }
public DateTime created_at { get; set; }
public DateTime updated_at { get; set; }
public Unknown ToModel() => new()
{
Id = id,
@@ -475,16 +480,25 @@ public sealed class UnknownsRepository : IUnknownsRepository
NetworkPolicy = containment_network_policy
}
: null,
FirstSeenAt = first_seen_at,
LastEvaluatedAt = last_evaluated_at,
FirstSeenAt = new DateTimeOffset(first_seen_at, TimeSpan.Zero),
LastEvaluatedAt = new DateTimeOffset(last_evaluated_at, TimeSpan.Zero),
ResolutionReason = resolution_reason,
ResolvedAt = resolved_at,
CreatedAt = created_at,
UpdatedAt = updated_at
ResolvedAt = resolved_at.HasValue ? new DateTimeOffset(resolved_at.Value, TimeSpan.Zero) : null,
CreatedAt = new DateTimeOffset(created_at, TimeSpan.Zero),
UpdatedAt = new DateTimeOffset(updated_at, TimeSpan.Zero)
};
}
private sealed record SummaryRow(int hot_count, int warm_count, int cold_count, int resolved_count);
/// <summary>
/// Internal row class for summary query Dapper materialization.
/// </summary>
private sealed class SummaryRow
{
public int hot_count { get; set; }
public int warm_count { get; set; }
public int cold_count { get; set; }
public int resolved_count { get; set; }
}
private static readonly JsonSerializerOptions JsonOptions = new()
{