more audit work
This commit is contained in:
23
src/__Tests/Determinism/AGENTS.md
Normal file
23
src/__Tests/Determinism/AGENTS.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Determinism Tests Charter
|
||||
|
||||
## Mission
|
||||
Validate CGS determinism and cross-platform hash stability.
|
||||
|
||||
## Responsibilities
|
||||
- Maintain determinism tests for VerdictBuilder and CGS hashing.
|
||||
- Keep fixtures deterministic and offline-friendly.
|
||||
- Track sprint tasks in `TASKS.md` and update the sprint tracker.
|
||||
|
||||
## Key Paths
|
||||
- `CgsDeterminismTests.cs`
|
||||
- `README.md`
|
||||
|
||||
## Required Reading
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
|
||||
|
||||
## Working Agreement
|
||||
- 1. Use fixed time and IDs; avoid Guid.NewGuid or DateTimeOffset.UtcNow in fixtures.
|
||||
- 2. Keep evidence JSON deterministic and aligned with canonicalization rules.
|
||||
- 3. Update `TASKS.md` and sprint statuses when work changes.
|
||||
10
src/__Tests/Determinism/TASKS.md
Normal file
10
src/__Tests/Determinism/TASKS.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Determinism Tests Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| AUDIT-0787-M | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0787-T | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0787-A | DONE | Waived (test project; revalidated 2026-01-07). |
|
||||
27
src/__Tests/Tools/FixtureHarvester/AGENTS.md
Normal file
27
src/__Tests/Tools/FixtureHarvester/AGENTS.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Fixture Harvester Charter
|
||||
|
||||
## Mission
|
||||
Maintain the fixture harvester CLI and tests for deterministic fixture capture.
|
||||
|
||||
## Responsibilities
|
||||
- Maintain fixture harvesting, validation, and metadata commands.
|
||||
- Keep fixture outputs deterministic and offline-friendly.
|
||||
- Track sprint tasks in `TASKS.md` and update the sprint tracker.
|
||||
|
||||
## Key Paths
|
||||
- `Program.cs`
|
||||
- `Commands/*.cs`
|
||||
- `FixtureHarvester.csproj`
|
||||
- `FixtureHarvester.Tests.csproj`
|
||||
- `*Tests.cs`
|
||||
|
||||
## Required Reading
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
|
||||
|
||||
## Working Agreement
|
||||
- 1. Use fixed time/ID sources for fixture metadata and sample content.
|
||||
- 2. Prefer offline defaults; gate live downloads behind explicit flags.
|
||||
- 3. Keep CLI output ASCII-only and deterministic.
|
||||
- 4. Update `TASKS.md` and sprint statuses when work changes.
|
||||
13
src/__Tests/Tools/FixtureHarvester/TASKS.md
Normal file
13
src/__Tests/Tools/FixtureHarvester/TASKS.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Fixture Harvester Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| AUDIT-0788-M | DONE | Revalidated 2026-01-07 (test project). |
|
||||
| AUDIT-0788-T | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0788-A | DONE | Waived (test project; revalidated 2026-01-07). |
|
||||
| AUDIT-0789-M | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0789-T | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0789-A | TODO | Open findings (determinism, HttpClientFactory, ASCII output, path validation, test coverage). |
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"id": "acr-event-001",
|
||||
"timestamp": "2024-12-29T12:00:00.0000000Z",
|
||||
"action": "push",
|
||||
@@ -6,12 +6,12 @@
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"size": 3028,
|
||||
"digest": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
|
||||
"repository": "library/myapp",
|
||||
"tag": "v1.0.0"
|
||||
"repository": "stellaops/api-gateway",
|
||||
"tag": "1.0.0"
|
||||
},
|
||||
"request": {
|
||||
"id": "req-001",
|
||||
"host": "myregistry.azurecr.io",
|
||||
"host": "stellaops.azurecr.io",
|
||||
"method": "PUT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
{
|
||||
{
|
||||
"push_data": {
|
||||
"pushed_at": 1703854800,
|
||||
"images": [],
|
||||
"tag": "v1.0.0",
|
||||
"tag": "v2.0.0",
|
||||
"pusher": "stellaops"
|
||||
},
|
||||
"callback_url": "https://registry.hub.docker.com/u/stellaops/myapp/hook/callback",
|
||||
"callback_url": "https://registry.hub.docker.com/u/stellaops/scanner/hook/callback",
|
||||
"repository": {
|
||||
"status": "Active",
|
||||
"description": "StellaOps application image",
|
||||
"is_trusted": true,
|
||||
"repo_name": "stellaops/myapp",
|
||||
"name": "myapp",
|
||||
"repo_name": "stellaops/scanner",
|
||||
"name": "scanner",
|
||||
"namespace": "stellaops"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"version": "0",
|
||||
"id": "ecr-event-001",
|
||||
"detail-type": "ECR Image Action",
|
||||
@@ -9,8 +9,8 @@
|
||||
"detail": {
|
||||
"action-type": "PUSH",
|
||||
"result": "SUCCESS",
|
||||
"repository-name": "library/myapp",
|
||||
"repository-name": "stellaops/scanner",
|
||||
"image-digest": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
|
||||
"image-tag": "v1.0.0"
|
||||
"image-tag": "v3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
{
|
||||
{
|
||||
"action": "published",
|
||||
"package": {
|
||||
"id": 12345,
|
||||
"name": "myapp",
|
||||
"name": "stellaops-cli",
|
||||
"namespace": "stellaops",
|
||||
"ecosystem": "container",
|
||||
"package_type": "container",
|
||||
"package_version": {
|
||||
"id": 67890,
|
||||
"version": "v1.0.0",
|
||||
"version": "v4.0.0",
|
||||
"container_metadata": {
|
||||
"tag": {
|
||||
"digest": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
|
||||
"name": "v1.0.0"
|
||||
"name": "v4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"id": 111222,
|
||||
"name": "myapp",
|
||||
"full_name": "stellaops/myapp"
|
||||
"name": "stellaops-cli",
|
||||
"full_name": "stellaops/stellaops-cli"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
{
|
||||
"type": "PUSH_ARTIFACT",
|
||||
"occur_at": 1703854800,
|
||||
"operator": "admin",
|
||||
@@ -6,15 +6,15 @@
|
||||
"resources": [
|
||||
{
|
||||
"digest": "sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4",
|
||||
"tag": "v1.0.0",
|
||||
"resource_url": "harbor.example.com/library/myapp:v1.0.0"
|
||||
"tag": "v1.2.3",
|
||||
"resource_url": "harbor.example.com/library/nginx:v1.2.3"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"date_created": 1703850000,
|
||||
"name": "myapp",
|
||||
"name": "nginx",
|
||||
"namespace": "library",
|
||||
"repo_full_name": "library/myapp",
|
||||
"repo_full_name": "library/nginx",
|
||||
"repo_type": "public"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
{
|
||||
{
|
||||
"secret": "",
|
||||
"ref": "refs/heads/main",
|
||||
"before": "0000000000000000000000000000000000000000",
|
||||
"after": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"compare_url": "https://gitea.example.com/stellaops/myapp/compare/main...feature",
|
||||
"after": "abc123def456789012345678901234567890abcd",
|
||||
"compare_url": "https://gitea.example.com/stellaops-org/stellaops/compare/main...feature",
|
||||
"commits": [
|
||||
{
|
||||
"id": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"message": "feat: add new feature",
|
||||
"id": "abc123def456789012345678901234567890abcd",
|
||||
"message": "feat: add Python wheel analyzer",
|
||||
"timestamp": "2024-12-29T12:00:00Z",
|
||||
"author": {
|
||||
"name": "StellaOps",
|
||||
"email": "dev@stellaops.org",
|
||||
"username": "stellaops"
|
||||
"name": "Developer",
|
||||
"email": "developer@stellaops.org",
|
||||
"username": "developer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"id": 123456789,
|
||||
"name": "myapp",
|
||||
"full_name": "stellaops/myapp",
|
||||
"name": "stellaops",
|
||||
"full_name": "stellaops-org/stellaops",
|
||||
"default_branch": "main",
|
||||
"html_url": "https://gitea.example.com/stellaops/myapp",
|
||||
"html_url": "https://gitea.example.com/stellaops-org/stellaops",
|
||||
"owner": {
|
||||
"id": 123456,
|
||||
"login": "stellaops",
|
||||
"login": "stellaops-org",
|
||||
"email": "org@stellaops.org"
|
||||
}
|
||||
},
|
||||
"pusher": {
|
||||
"id": 123456,
|
||||
"login": "stellaops",
|
||||
"email": "dev@stellaops.org"
|
||||
"login": "developer",
|
||||
"email": "developer@stellaops.org"
|
||||
},
|
||||
"sender": {
|
||||
"id": 123456,
|
||||
"login": "stellaops",
|
||||
"email": "dev@stellaops.org"
|
||||
"login": "developer",
|
||||
"email": "developer@stellaops.org"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,34 @@
|
||||
{
|
||||
{
|
||||
"action": "opened",
|
||||
"number": 42,
|
||||
"pull_request": {
|
||||
"id": 1234567,
|
||||
"number": 42,
|
||||
"state": "open",
|
||||
"title": "feat: add new feature",
|
||||
"title": "feat: add Python wheel analyzer",
|
||||
"user": {
|
||||
"login": "stellaops",
|
||||
"id": 123456,
|
||||
"login": "developer",
|
||||
"id": 987654,
|
||||
"type": "User"
|
||||
},
|
||||
"head": {
|
||||
"sha": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"ref": "feature-branch"
|
||||
"sha": "abc123def456789012345678901234567890abcd",
|
||||
"ref": "feature/python-wheel"
|
||||
},
|
||||
"base": {
|
||||
"sha": "b4fe06dafc13gge79dee0ge95517791bf04e744d",
|
||||
"sha": "def456789012345678901234567890abc123def4",
|
||||
"ref": "main"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"id": 123456789,
|
||||
"name": "myapp",
|
||||
"full_name": "stellaops/myapp",
|
||||
"name": "stellaops",
|
||||
"full_name": "stellaops-org/stellaops",
|
||||
"default_branch": "main"
|
||||
},
|
||||
"sender": {
|
||||
"login": "stellaops",
|
||||
"id": 123456,
|
||||
"login": "developer",
|
||||
"id": 987654,
|
||||
"type": "User"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
{
|
||||
{
|
||||
"ref": "refs/heads/main",
|
||||
"before": "0000000000000000000000000000000000000000",
|
||||
"after": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"after": "abc123def456789012345678901234567890abcd",
|
||||
"repository": {
|
||||
"id": 123456789,
|
||||
"name": "myapp",
|
||||
"full_name": "stellaops/myapp",
|
||||
"name": "stellaops",
|
||||
"full_name": "stellaops-org/stellaops",
|
||||
"default_branch": "main",
|
||||
"html_url": "https://github.com/stellaops/myapp"
|
||||
"html_url": "https://github.com/stellaops-org/stellaops"
|
||||
},
|
||||
"pusher": {
|
||||
"name": "stellaops",
|
||||
"email": "ci@stellaops.org"
|
||||
"name": "developer",
|
||||
"email": "developer@stellaops.org"
|
||||
},
|
||||
"sender": {
|
||||
"login": "stellaops",
|
||||
"login": "developer",
|
||||
"id": 123456,
|
||||
"type": "User"
|
||||
},
|
||||
"head_commit": {
|
||||
"id": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"message": "feat: add new feature",
|
||||
"id": "abc123def456789012345678901234567890abcd",
|
||||
"message": "feat: add Python wheel analyzer",
|
||||
"timestamp": "2024-12-29T12:00:00Z",
|
||||
"author": {
|
||||
"name": "StellaOps",
|
||||
"email": "dev@stellaops.org"
|
||||
"name": "Developer",
|
||||
"email": "developer@stellaops.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
{
|
||||
"action": "completed",
|
||||
"workflow_run": {
|
||||
"id": 1234567890,
|
||||
"name": "CI",
|
||||
"name": "StellaOps CI",
|
||||
"node_id": "WFR_kwDOGPQW8c8AAAAB",
|
||||
"head_branch": "main",
|
||||
"head_sha": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"head_sha": "abc123def456789012345678901234567890abcd",
|
||||
"path": ".github/workflows/ci.yml",
|
||||
"run_number": 42,
|
||||
"run_number": 123,
|
||||
"event": "push",
|
||||
"status": "completed",
|
||||
"conclusion": "success",
|
||||
@@ -27,13 +27,13 @@
|
||||
},
|
||||
"workflow": {
|
||||
"id": 12345,
|
||||
"name": "CI",
|
||||
"name": "StellaOps CI",
|
||||
"path": ".github/workflows/ci.yml"
|
||||
},
|
||||
"repository": {
|
||||
"id": 123456789,
|
||||
"name": "myapp",
|
||||
"full_name": "stellaops/myapp",
|
||||
"name": "stellaops",
|
||||
"full_name": "stellaops-org/stellaops",
|
||||
"default_branch": "main"
|
||||
},
|
||||
"sender": {
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
{
|
||||
{
|
||||
"object_kind": "push",
|
||||
"event_name": "push",
|
||||
"before": "0000000000000000000000000000000000000000",
|
||||
"after": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"after": "abc123def456789012345678901234567890abcd",
|
||||
"ref": "refs/heads/main",
|
||||
"checkout_sha": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"checkout_sha": "abc123def456789012345678901234567890abcd",
|
||||
"user_id": 123456,
|
||||
"user_name": "StellaOps",
|
||||
"user_username": "stellaops",
|
||||
"user_email": "dev@stellaops.org",
|
||||
"user_name": "Developer",
|
||||
"user_username": "developer",
|
||||
"user_email": "developer@stellaops.org",
|
||||
"project": {
|
||||
"id": 123456789,
|
||||
"name": "myapp",
|
||||
"path_with_namespace": "stellaops/myapp",
|
||||
"name": "stellaops",
|
||||
"path_with_namespace": "stellaops-org/stellaops",
|
||||
"default_branch": "main",
|
||||
"web_url": "https://gitlab.com/stellaops/myapp"
|
||||
"web_url": "https://gitlab.com/stellaops-org/stellaops"
|
||||
},
|
||||
"repository": {
|
||||
"name": "myapp",
|
||||
"url": "git@gitlab.com:stellaops/myapp.git"
|
||||
"name": "stellaops",
|
||||
"url": "git@gitlab.com:stellaops-org/stellaops.git"
|
||||
},
|
||||
"commits": [
|
||||
{
|
||||
"id": "a3ed95caeb02ffe68cdd9fd84406680ae93d633c",
|
||||
"message": "feat: add new feature",
|
||||
"id": "abc123def456789012345678901234567890abcd",
|
||||
"message": "feat: add Python wheel analyzer",
|
||||
"timestamp": "2024-12-29T12:00:00Z",
|
||||
"author": {
|
||||
"name": "StellaOps",
|
||||
"email": "dev@stellaops.org"
|
||||
"name": "Developer",
|
||||
"email": "developer@stellaops.org"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -13,7 +13,11 @@ public sealed class DeterminismManifestReader
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
|
||||
Converters =
|
||||
{
|
||||
new JsonStringEnumConverter(JsonNamingPolicy.CamelCase),
|
||||
new ObjectValueConverter()
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -225,6 +229,127 @@ public sealed class DeterminismManifestReader
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class ObjectValueConverter : JsonConverter<object?>
|
||||
{
|
||||
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
=> ReadValue(ref reader, options);
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, object? value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
writer.WriteNullValue();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case string text:
|
||||
writer.WriteStringValue(text);
|
||||
return;
|
||||
case bool flag:
|
||||
writer.WriteBooleanValue(flag);
|
||||
return;
|
||||
case int intValue:
|
||||
writer.WriteNumberValue(intValue);
|
||||
return;
|
||||
case long longValue:
|
||||
writer.WriteNumberValue(longValue);
|
||||
return;
|
||||
case float floatValue:
|
||||
writer.WriteNumberValue(floatValue);
|
||||
return;
|
||||
case double doubleValue:
|
||||
writer.WriteNumberValue(doubleValue);
|
||||
return;
|
||||
case decimal decimalValue:
|
||||
writer.WriteNumberValue(decimalValue);
|
||||
return;
|
||||
case byte[] bytes:
|
||||
writer.WriteBase64StringValue(bytes);
|
||||
return;
|
||||
case IDictionary<string, object?> dictionary:
|
||||
writer.WriteStartObject();
|
||||
foreach (var (key, itemValue) in dictionary)
|
||||
{
|
||||
writer.WritePropertyName(key);
|
||||
Write(writer, itemValue, options);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
return;
|
||||
case IEnumerable<object?> list:
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in list)
|
||||
{
|
||||
Write(writer, item, options);
|
||||
}
|
||||
writer.WriteEndArray();
|
||||
return;
|
||||
default:
|
||||
JsonSerializer.Serialize(writer, value, value.GetType(), options);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static IDictionary<string, object?> ReadObject(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
{
|
||||
var values = new Dictionary<string, object?>(StringComparer.Ordinal);
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.EndObject)
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
if (reader.TokenType != JsonTokenType.PropertyName)
|
||||
{
|
||||
throw new JsonException("Expected property name.");
|
||||
}
|
||||
|
||||
var propertyName = reader.GetString() ?? string.Empty;
|
||||
if (!reader.Read())
|
||||
{
|
||||
throw new JsonException("Unexpected end of JSON.");
|
||||
}
|
||||
|
||||
values[propertyName] = ReadValue(ref reader, options);
|
||||
}
|
||||
|
||||
throw new JsonException("Unexpected end of JSON.");
|
||||
}
|
||||
|
||||
private static IList<object?> ReadArray(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
{
|
||||
var values = new List<object?>();
|
||||
while (reader.Read())
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.EndArray)
|
||||
{
|
||||
return values;
|
||||
}
|
||||
|
||||
values.Add(ReadValue(ref reader, options));
|
||||
}
|
||||
|
||||
throw new JsonException("Unexpected end of JSON.");
|
||||
}
|
||||
|
||||
private static object? ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.String => reader.GetString(),
|
||||
JsonTokenType.Number => reader.TryGetInt64(out var number) ? number : reader.GetDouble(),
|
||||
JsonTokenType.True => true,
|
||||
JsonTokenType.False => false,
|
||||
JsonTokenType.Null => null,
|
||||
JsonTokenType.StartObject => ReadObject(ref reader, options),
|
||||
JsonTokenType.StartArray => ReadArray(ref reader, options),
|
||||
_ => throw new JsonException("Unsupported JSON token.")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSupportedHashAlgorithm(string algorithm)
|
||||
{
|
||||
return algorithm switch
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Net.Http.Json;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace StellaOps.Chaos.Router.Tests.Fixtures;
|
||||
|
||||
@@ -57,10 +58,18 @@ public class RouterTestFixture : IAsyncLifetime
|
||||
return JsonContent.Create(request);
|
||||
}
|
||||
|
||||
public ValueTask InitializeAsync()
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
// Verify router is reachable
|
||||
return ValueTask.CompletedTask;
|
||||
try
|
||||
{
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
|
||||
_ = await _client.GetAsync("/", cts.Token);
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException || ex is TaskCanceledException || ex is OperationCanceledException)
|
||||
{
|
||||
throw SkipException.ForSkip(
|
||||
$"Router not reachable at '{_routerUrl}'. Set ROUTER_URL or start the router service to run chaos tests.");
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
|
||||
32
src/__Tests/e2e/Integrations/AGENTS.md
Normal file
32
src/__Tests/e2e/Integrations/AGENTS.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Integration E2E Integrations Charter
|
||||
|
||||
## Mission
|
||||
Validate integration flows for registry webhooks, SCM webhooks, CI templates, offline mode, and facet admission.
|
||||
|
||||
## Responsibilities
|
||||
- Maintain E2E integration fixtures and helpers.
|
||||
- Keep outputs deterministic and offline-friendly.
|
||||
- Track sprint tasks in `TASKS.md` and update the sprint tracker.
|
||||
|
||||
## Key Paths
|
||||
- `CiTemplateTests.cs`
|
||||
- `DeterminismTests.cs`
|
||||
- `FacetSealAdmissionE2ETests.cs`
|
||||
- `OfflineModeTests.cs`
|
||||
- `RegistryWebhookTests.cs`
|
||||
- `ScmWebhookTests.cs`
|
||||
- `Helpers/*.cs`
|
||||
- `Fixtures/*.cs`
|
||||
|
||||
## Required Reading
|
||||
- `docs/07_HIGH_LEVEL_ARCHITECTURE.md`
|
||||
- `docs/modules/platform/architecture-overview.md`
|
||||
- `docs/modules/sbom-service/architecture.md`
|
||||
- `docs/modules/signals/architecture.md`
|
||||
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
|
||||
|
||||
## Working Agreement
|
||||
- 1. Use fixed time and IDs in fixtures and test data.
|
||||
- 2. Keep templates and outputs deterministic (canonical JSON, stable ordering).
|
||||
- 3. Keep tests offline; do not require network access for fixtures.
|
||||
- 4. Update `TASKS.md` and sprint statuses when work changes.
|
||||
@@ -4,6 +4,7 @@
|
||||
// Description: Utility class for webhook testing operations
|
||||
// =============================================================================
|
||||
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
@@ -357,6 +358,18 @@ public static class WebhookTestHelper
|
||||
|
||||
foreach (var part in jsonPath.Split('.'))
|
||||
{
|
||||
if (element.ValueKind == JsonValueKind.Array &&
|
||||
int.TryParse(part, NumberStyles.None, CultureInfo.InvariantCulture, out var index))
|
||||
{
|
||||
if (index < 0 || index >= element.GetArrayLength())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
element = element[index];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!element.TryGetProperty(part, out element))
|
||||
{
|
||||
return null;
|
||||
@@ -411,6 +424,18 @@ public static class WebhookTestHelper
|
||||
var current = element;
|
||||
foreach (var part in path)
|
||||
{
|
||||
if (current.ValueKind == JsonValueKind.Array &&
|
||||
int.TryParse(part, NumberStyles.None, CultureInfo.InvariantCulture, out var index))
|
||||
{
|
||||
if (index < 0 || index >= current.GetArrayLength())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
current = current[index];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (current.ValueKind != JsonValueKind.Object ||
|
||||
!current.TryGetProperty(part, out current))
|
||||
{
|
||||
|
||||
10
src/__Tests/e2e/Integrations/TASKS.md
Normal file
10
src/__Tests/e2e/Integrations/TASKS.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Integration E2E Integrations Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| AUDIT-0790-M | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0790-T | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0790-A | DONE | Waived (test project; revalidated 2026-01-07). |
|
||||
26
src/__Tests/e2e/ReplayableVerdict/AGENTS.md
Normal file
26
src/__Tests/e2e/ReplayableVerdict/AGENTS.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Replayable Verdict E2E Charter
|
||||
|
||||
## Mission
|
||||
Validate replayable verdict bundles and determinism across the verdict pipeline.
|
||||
|
||||
## Responsibilities
|
||||
- Maintain replayable verdict bundle fixtures and tests.
|
||||
- Keep bundle metadata and hashing deterministic.
|
||||
- Track sprint tasks in `TASKS.md` and update the sprint tracker.
|
||||
|
||||
## Key Paths
|
||||
- `ReplayableVerdictE2ETests.cs`
|
||||
- `README.md`
|
||||
- `../fixtures/e2e/`
|
||||
|
||||
## Required Reading
|
||||
- `docs/modules/replay/architecture.md`
|
||||
- `docs/modules/vex-lens/architecture.md`
|
||||
- `docs/modules/scanner/architecture.md`
|
||||
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
|
||||
|
||||
## Working Agreement
|
||||
- 1. Use fixed time and IDs in fixtures and manifest data.
|
||||
- 2. Keep bundle hashes and outputs deterministic (canonical JSON, stable ordering).
|
||||
- 3. Avoid non-ASCII glyphs in docs and test output.
|
||||
- 4. Update `TASKS.md` and sprint statuses when work changes.
|
||||
10
src/__Tests/e2e/ReplayableVerdict/TASKS.md
Normal file
10
src/__Tests/e2e/ReplayableVerdict/TASKS.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# Replayable Verdict E2E Task Board
|
||||
|
||||
This board mirrors active sprint tasks for this module.
|
||||
Source of truth: `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
|
||||
|
||||
| Task ID | Status | Notes |
|
||||
| --- | --- | --- |
|
||||
| AUDIT-0791-M | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0791-T | DONE | Revalidated 2026-01-07. |
|
||||
| AUDIT-0791-A | DONE | Waived (test project; revalidated 2026-01-07). |
|
||||
@@ -27,7 +27,7 @@ namespace StellaOps.ScannerSignals.IntegrationTests;
|
||||
public sealed class ScannerToSignalsReachabilityTests
|
||||
{
|
||||
private static readonly string RepoRoot = LocateRepoRoot();
|
||||
private static readonly string FixtureRoot = Path.Combine(RepoRoot, "tests", "reachability", "fixtures", "reachbench-2025-expanded", "cases");
|
||||
private static readonly string FixtureRoot = Path.Combine(RepoRoot, "__Tests", "reachability", "fixtures", "reachbench-2025-expanded", "cases");
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
|
||||
@@ -56,22 +56,16 @@ public class AuditPackBuilderTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PackDigest_IsComputedCorrectly()
|
||||
public async Task PackDigest_IsComputedCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var pack = new AuditPack
|
||||
{
|
||||
PackId = "test-pack",
|
||||
Name = "Test Pack",
|
||||
CreatedAt = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero),
|
||||
RunManifest = new RunManifest("scan-1", DateTimeOffset.UtcNow),
|
||||
EvidenceIndex = new EvidenceIndex([]),
|
||||
Verdict = new Verdict("verdict-1", "pass"),
|
||||
OfflineBundle = new BundleManifest("bundle-1", "1.0"),
|
||||
Contents = new PackContents()
|
||||
};
|
||||
var scanResult = new ScanResult("scan-1");
|
||||
var builder = new AuditPackBuilder();
|
||||
|
||||
// Act - digest should be set during build
|
||||
pack.PackDigest.Should().NotBeNull();
|
||||
// Act
|
||||
var pack = await builder.BuildAsync(scanResult, new AuditPackOptions());
|
||||
|
||||
// Assert
|
||||
pack.PackDigest.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,16 +159,18 @@ public class ReplayAttestationServiceTests
|
||||
public async Task VerifyAsync_ValidAttestation_ReturnsValid()
|
||||
{
|
||||
// Arrange
|
||||
var service = new ReplayAttestationService(new FakeSigner(), new AcceptAllVerifier());
|
||||
var manifest = CreateTestManifest();
|
||||
var result = CreateTestResult();
|
||||
var attestation = await _service.GenerateAsync(manifest, result);
|
||||
var attestation = await service.GenerateAsync(manifest, result);
|
||||
|
||||
// Act
|
||||
var verificationResult = await _service.VerifyAsync(attestation);
|
||||
var verificationResult = await service.VerifyAsync(attestation);
|
||||
|
||||
// Assert
|
||||
verificationResult.IsValid.Should().BeTrue();
|
||||
verificationResult.Errors.Should().BeEmpty();
|
||||
verificationResult.SignatureVerified.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -255,4 +257,27 @@ public class ReplayAttestationServiceTests
|
||||
verificationResult.IsValid.Should().BeFalse();
|
||||
verificationResult.Errors.Should().Contain(e => e.Contains("payload digest"));
|
||||
}
|
||||
|
||||
private sealed class FakeSigner : IReplayAttestationSigner
|
||||
{
|
||||
public Task<DsseSignatureResult> SignAsync(byte[] payload, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(new DsseSignatureResult
|
||||
{
|
||||
KeyId = "test-key",
|
||||
Signature = Convert.ToBase64String(payload.Take(8).ToArray())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AcceptAllVerifier : IReplayAttestationSignatureVerifier
|
||||
{
|
||||
public Task<ReplayAttestationSignatureVerification> VerifyAsync(
|
||||
ReplayDsseEnvelope envelope,
|
||||
byte[] payload,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(new ReplayAttestationSignatureVerification { Verified = true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user