sln build fix (again), tests fixes, audit work and doctors work

This commit is contained in:
master
2026-01-12 22:15:51 +02:00
parent 9873f80830
commit 9330c64349
812 changed files with 48051 additions and 3891 deletions

View File

@@ -1,7 +1,7 @@
# Concelier WebService 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Analyzer 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Merge Analyzers 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -21,7 +21,7 @@ Implement and maintain the Astra Linux advisory connector (OVAL fetch/parse/map)
- `docs/modules/concelier/architecture.md`
- `docs/modules/concelier/link-not-merge-schema.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
- `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
## Working Agreement
- 1. Update task status to `DOING`/`DONE` in both corresponding sprint file and local `TASKS.md`.

View File

@@ -1,7 +1,7 @@
# Concelier Astra Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -18,7 +18,7 @@ Define and maintain backport proof logic for Concelier evidence pipelines.
- `docs/modules/concelier/architecture.md`
- `docs/modules/concelier/link-not-merge-schema.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
- `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
## Working Agreement
- 1. Update task status to `DOING`/`DONE` in both corresponding sprint file and local `TASKS.md`.

View File

@@ -1,7 +1,7 @@
# Concelier BackportProof 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Valkey Cache 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# ACSC Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CCCS Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CERT-Bund Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CERT/CC Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CERT-FR Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CERT-In Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Connector Common 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CVE Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Alpine Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Debian Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Red Hat Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# SUSE Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Ubuntu Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# EPSS Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# GHSA Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# ICS CISA Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# ICS Kaspersky Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# JVN Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# KEV Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# KISA Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# NVD Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# OSV Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# RU-BDU Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# RU-NKCKI Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# StellaOps Mirror Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Adobe Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Apple Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Chromium Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Cisco Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# MSRC Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Oracle Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# VMware Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Json Exporter 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier TrivyDb Exporter 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Federation 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Interest 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Merge 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Models 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Normalization 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier ProofService Postgres 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier ProofService 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier RawModels 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier SbomIntegration 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier SourceIntel 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Analyzer 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Valkey Cache 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# ACSC Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -18,7 +18,7 @@ Validate Astra connector configuration, plugin registration, and mapping scaffol
- `docs/modules/concelier/architecture.md`
- `docs/modules/concelier/link-not-merge-schema.md`
- `docs/modules/platform/architecture-overview.md`
- `docs/implplan/permament/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
- `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`
## Working Agreement
- 1. Update task status to `DOING`/`DONE` in both corresponding sprint file and local `TASKS.md`.

View File

@@ -1,7 +1,7 @@
# Concelier Astra Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CCCS Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CERT-Bund Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CERT/CC Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CERT-FR Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CERT-In Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Connector Common 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# CVE Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Alpine Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Debian Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Red Hat Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# SUSE Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Ubuntu Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# EPSS Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# GHSA Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# ICS CISA Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# ICS Kaspersky Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# JVN Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# KEV Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# KISA Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# NVD Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# OSV Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# RU-BDU Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# RU-NKCKI Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# StellaOps Mirror Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Adobe Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Apple Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Chromium Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Cisco Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# MSRC Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Oracle Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# VMware Connector 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier Json Exporter 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -1,7 +1,7 @@
# Concelier TrivyDb Exporter 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

View File

@@ -0,0 +1,392 @@
// -----------------------------------------------------------------------------
// CrossRegionLatencyTests.cs
// Sprint: Testing Enhancement Advisory - Phase 2.3
// Description: Tests for cross-region latency scenarios in federation
// -----------------------------------------------------------------------------
using FluentAssertions;
using StellaOps.Concelier.Federation.Tests.MultiSite.Fixtures;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Concelier.Federation.Tests.MultiSite;
/// <summary>
/// Tests for federation behavior under various network latency conditions
/// simulating cross-region and intercontinental communication.
/// </summary>
[Trait("Category", TestCategories.Integration)]
[Trait("Category", TestCategories.Federation)]
[Trait("Category", TestCategories.Latency)]
public class CrossRegionLatencyTests : IClassFixture<FederationClusterFixture>
{
private readonly FederationClusterFixture _fixture;
public CrossRegionLatencyTests(FederationClusterFixture fixture)
{
_fixture = fixture;
// Reset network between tests
_fixture.Network.Reset();
}
#region Standard Cross-Region Latency Tests
[Fact]
public async Task CrossRegion_ModerateLatency_SyncCompletes()
{
// Arrange - Set 100ms latency between regions (typical US-EU)
_fixture.SetCrossRegionLatency(TimeSpan.FromMilliseconds(100));
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-2024-LATENCY",
Version = "1.0",
Content = "Latency test"
});
// Act
var syncResult = await _fixture.SynchronizeAllAsync();
// Assert
syncResult.Failed.Should().Be(0);
syncResult.Successful.Should().BeGreaterThan(0);
_fixture.SiteB.GetAdvisory("CVE-2024-LATENCY").Should().NotBeNull();
_fixture.SiteC.GetAdvisory("CVE-2024-LATENCY").Should().NotBeNull();
}
[Fact]
public async Task CrossRegion_HighLatency_500ms_SyncCompletes()
{
// Arrange - Set 500ms latency (typical US-Asia Pacific)
_fixture.SetCrossRegionLatency(TimeSpan.FromMilliseconds(500), jitterPercent: 30);
for (var i = 1; i <= 5; i++)
{
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = $"CVE-2024-HL-{i}",
Version = "1.0",
Content = $"High latency test {i}"
});
}
// Act
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromMinutes(1),
checkInterval: TimeSpan.FromSeconds(5));
// Assert
convergence.Converged.Should().BeTrue();
_fixture.SiteA.AdvisoryCount.Should().Be(5);
_fixture.SiteB.AdvisoryCount.Should().Be(5);
_fixture.SiteC.AdvisoryCount.Should().Be(5);
}
[Fact]
public async Task CrossRegion_ExtremeLatency_2000ms_SyncWithRetry()
{
// Arrange - Set 2000ms latency (extreme case, satellite/constrained network)
_fixture.SetLatency(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId, TimeSpan.FromMilliseconds(2000));
_fixture.SetLatency(_fixture.SiteC.SiteId, _fixture.SiteA.SiteId, TimeSpan.FromMilliseconds(2000));
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-2024-EXTREME",
Version = "1.0",
Content = "Extreme latency test"
});
// Act - Multiple sync rounds needed due to high latency
for (var i = 0; i < 5; i++)
{
await _fixture.SynchronizeAllAsync();
}
// Assert
_fixture.SiteC.GetAdvisory("CVE-2024-EXTREME").Should().NotBeNull();
}
#endregion
#region Jitter Tests
[Fact]
public async Task HighJitter_50Percent_MaintainsOrdering()
{
// Arrange - High jitter environment
_fixture.SetCrossRegionLatency(TimeSpan.FromMilliseconds(200), jitterPercent: 50);
// Add advisories in order
for (var i = 1; i <= 10; i++)
{
_fixture.TimeProvider.Advance(TimeSpan.FromSeconds(1));
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = $"CVE-2024-JITTER-{i:D2}",
Version = "1.0",
Content = $"Jitter test {i}"
});
}
// Act
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromMinutes(1),
checkInterval: TimeSpan.FromSeconds(2));
// Assert - All data should arrive eventually
convergence.Converged.Should().BeTrue();
_fixture.SiteB.AdvisoryCount.Should().Be(10);
_fixture.SiteC.AdvisoryCount.Should().Be(10);
}
[Fact]
public async Task VariableJitter_OrderingMaintained()
{
// Arrange - Different jitter for each path
_fixture.SetLatency(_fixture.SiteA.SiteId, _fixture.SiteB.SiteId, TimeSpan.FromMilliseconds(50), jitterPercent: 10);
_fixture.SetLatency(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId, TimeSpan.FromMilliseconds(200), jitterPercent: 60);
_fixture.SetLatency(_fixture.SiteB.SiteId, _fixture.SiteC.SiteId, TimeSpan.FromMilliseconds(100), jitterPercent: 30);
// Concurrent updates with timestamps
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A", Version = "1.0", Content = "A" });
_fixture.TimeProvider.Advance(TimeSpan.FromMilliseconds(100));
_fixture.SiteB.AddAdvisory(new Advisory { Id = "CVE-B", Version = "1.0", Content = "B" });
_fixture.TimeProvider.Advance(TimeSpan.FromMilliseconds(100));
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C", Version = "1.0", Content = "C" });
// Act
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromMinutes(1),
checkInterval: TimeSpan.FromSeconds(1));
// Assert
convergence.Converged.Should().BeTrue();
}
#endregion
#region Asymmetric Latency Tests
[Fact]
public async Task AsymmetricLatency_DifferentUploadDownload()
{
// Arrange - Common in satellite links: fast download, slow upload
_fixture.SetLatency(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId, TimeSpan.FromMilliseconds(500)); // Upload slow
_fixture.SetLatency(_fixture.SiteC.SiteId, _fixture.SiteA.SiteId, TimeSpan.FromMilliseconds(100)); // Download fast
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-ASYM-A", Version = "1.0", Content = "From A" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-ASYM-C", Version = "1.0", Content = "From C" });
// Act
await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(2));
// Assert - Both should eventually sync
_fixture.SiteA.GetAdvisory("CVE-ASYM-C").Should().NotBeNull();
_fixture.SiteC.GetAdvisory("CVE-ASYM-A").Should().NotBeNull();
}
#endregion
#region Latency + Partition Combined Tests
[Fact]
public async Task LatencyAndPartition_Combined()
{
// Arrange
_fixture.SetCrossRegionLatency(TimeSpan.FromMilliseconds(300), jitterPercent: 20);
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-BEFORE", Version = "1.0", Content = "Before partition" });
await _fixture.SynchronizeAllAsync();
// Create partition while under high latency
_fixture.PartitionSite(_fixture.SiteC.SiteId);
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-DURING", Version = "1.0", Content = "During partition" });
await _fixture.SynchronizeAllAsync();
// Verify C is isolated
_fixture.SiteC.GetAdvisory("CVE-DURING").Should().BeNull();
// Heal partition
_fixture.HealAllPartitions();
// Act
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromMinutes(1),
checkInterval: TimeSpan.FromSeconds(2));
// Assert
convergence.Converged.Should().BeTrue();
_fixture.SiteC.GetAdvisory("CVE-DURING").Should().NotBeNull();
}
#endregion
#region Latency Degradation Tests
[Fact]
public async Task ProgressiveLatencyDegradation_StillConverges()
{
// Arrange - Start with good latency, progressively degrade
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-DEGRADE", Version = "1.0", Content = "Test" });
// Act - Sync with increasing latency
var latencies = new[] { 50, 100, 200, 400, 800 };
foreach (var latencyMs in latencies)
{
_fixture.SetCrossRegionLatency(TimeSpan.FromMilliseconds(latencyMs));
await _fixture.SynchronizeAllAsync();
}
// Assert
_fixture.SiteB.GetAdvisory("CVE-DEGRADE").Should().NotBeNull();
_fixture.SiteC.GetAdvisory("CVE-DEGRADE").Should().NotBeNull();
}
[Fact]
public async Task LatencyImprovement_SyncSpeedsUp()
{
// Arrange - Start slow, get faster
_fixture.SetCrossRegionLatency(TimeSpan.FromMilliseconds(1000));
for (var i = 1; i <= 5; i++)
{
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = $"CVE-IMPROVE-{i}",
Version = "1.0",
Content = $"Test {i}"
});
}
// Improve latency mid-sync
await _fixture.SynchronizeAllAsync();
_fixture.SetCrossRegionLatency(TimeSpan.FromMilliseconds(100));
// Act
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(1));
// Assert
convergence.Converged.Should().BeTrue();
}
#endregion
#region Realistic Cross-Region Scenarios
[Fact]
public async Task Scenario_USEast_EUWest_APSouth()
{
// Arrange - Realistic cross-region latencies
// US-East to EU-West: ~80-100ms
// US-East to AP-South: ~200-250ms
// EU-West to AP-South: ~150-180ms
_fixture.SetLatency("site-a", "site-b", TimeSpan.FromMilliseconds(90), jitterPercent: 15);
_fixture.SetLatency("site-b", "site-a", TimeSpan.FromMilliseconds(90), jitterPercent: 15);
_fixture.SetLatency("site-a", "site-c", TimeSpan.FromMilliseconds(225), jitterPercent: 20);
_fixture.SetLatency("site-c", "site-a", TimeSpan.FromMilliseconds(225), jitterPercent: 20);
_fixture.SetLatency("site-b", "site-c", TimeSpan.FromMilliseconds(165), jitterPercent: 18);
_fixture.SetLatency("site-c", "site-b", TimeSpan.FromMilliseconds(165), jitterPercent: 18);
// Simulate real traffic
for (var i = 1; i <= 20; i++)
{
var site = (i % 3) switch
{
0 => _fixture.SiteA,
1 => _fixture.SiteB,
_ => _fixture.SiteC
};
site.AddAdvisory(new Advisory
{
Id = $"CVE-2024-{i:D4}",
Version = "1.0",
Content = $"Advisory from {site.Region}"
});
_fixture.TimeProvider.Advance(TimeSpan.FromMilliseconds(500));
}
// Act
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromMinutes(2),
checkInterval: TimeSpan.FromSeconds(5));
// Assert
convergence.Converged.Should().BeTrue();
_fixture.SiteA.AdvisoryCount.Should().Be(20);
_fixture.SiteB.AdvisoryCount.Should().Be(20);
_fixture.SiteC.AdvisoryCount.Should().Be(20);
}
#endregion
#region Latency Timeout Tests
[Fact]
public async Task SyncTimeout_HighLatency_HandledGracefully()
{
// Arrange - Set very high latency to simulate near-timeout conditions
_fixture.SetCrossRegionLatency(TimeSpan.FromSeconds(5), jitterPercent: 40);
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-TIMEOUT",
Version = "1.0",
Content = "Timeout test"
});
// Act - Try sync with short timeout expectation
var syncResult = await _fixture.SynchronizeAllAsync();
// Assert - Sync should still work (simulated latency doesn't cause actual timeout)
syncResult.Failed.Should().Be(0);
}
#endregion
#region Network Condition Tests
[Fact]
public async Task DegradedNetwork_MultipleFactor()
{
// Arrange - Degraded network condition
_fixture.Network.SetCondition(
_fixture.SiteA.SiteId,
_fixture.SiteC.SiteId,
NetworkCondition.Degraded);
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-DEGRADED",
Version = "1.0",
Content = "Degraded network test"
});
// Act
// May need multiple attempts due to packet loss
for (var attempt = 0; attempt < 5; attempt++)
{
await _fixture.SynchronizeAllAsync();
}
// Assert
// With 5% packet loss, should eventually succeed
var convergence = _fixture.VerifyConvergence();
convergence.Converged.Should().BeTrue();
}
#endregion
}

View File

@@ -0,0 +1,359 @@
// -----------------------------------------------------------------------------
// FederationPartitionTests.cs
// Sprint: Testing Enhancement Advisory - Phase 2.2
// Description: Tests for federation behavior under network partition scenarios
// -----------------------------------------------------------------------------
using FluentAssertions;
using StellaOps.Concelier.Federation.Tests.MultiSite.Fixtures;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Concelier.Federation.Tests.MultiSite;
/// <summary>
/// Tests for federation behavior during network partitions including
/// split-brain scenarios, partition healing, and data reconciliation.
/// </summary>
[Trait("Category", TestCategories.Integration)]
[Trait("Category", TestCategories.Federation)]
[Trait("Category", TestCategories.Chaos)]
public class FederationPartitionTests : IClassFixture<FederationClusterFixture>
{
private readonly FederationClusterFixture _fixture;
public FederationPartitionTests(FederationClusterFixture fixture)
{
_fixture = fixture;
// Reset network between tests
_fixture.Network.Reset();
}
#region Full Partition Tests
[Fact]
public async Task FullPartition_SingleSite_SyncBlocked()
{
// Arrange
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-2024-0001",
Version = "1.0",
Content = "From isolated site"
});
// Act - Isolate Site A
_fixture.PartitionSite(_fixture.SiteA.SiteId);
var syncResult = await _fixture.SynchronizeAllAsync();
// Assert - Syncs from/to Site A should be blocked
syncResult.PartitionBlocked.Should().BeGreaterThan(0);
// Site B and C should not have A's advisory
_fixture.SiteB.GetAdvisory("CVE-2024-0001").Should().BeNull();
_fixture.SiteC.GetAdvisory("CVE-2024-0001").Should().BeNull();
}
[Fact]
public async Task FullPartition_TwoSites_FormIsland()
{
// Arrange
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A", Version = "1.0", Content = "A" });
_fixture.SiteB.AddAdvisory(new Advisory { Id = "CVE-B", Version = "1.0", Content = "B" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C", Version = "1.0", Content = "C" });
// Act - Partition Site C from A and B (A-B can still communicate)
_fixture.PartitionSite(_fixture.SiteC.SiteId);
await _fixture.SynchronizeAllAsync();
// Assert - A and B should sync, C should be isolated
_fixture.SiteA.GetAdvisory("CVE-B").Should().NotBeNull();
_fixture.SiteB.GetAdvisory("CVE-A").Should().NotBeNull();
_fixture.SiteC.GetAdvisory("CVE-A").Should().BeNull();
_fixture.SiteC.GetAdvisory("CVE-B").Should().BeNull();
_fixture.SiteA.GetAdvisory("CVE-C").Should().BeNull();
_fixture.SiteB.GetAdvisory("CVE-C").Should().BeNull();
}
#endregion
#region Partial Partition Tests
[Fact]
public async Task PartialPartition_AB_Syncs_C_Isolated()
{
// Arrange
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A", Version = "1.0", Content = "A" });
_fixture.SiteB.AddAdvisory(new Advisory { Id = "CVE-B", Version = "1.0", Content = "B" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C", Version = "1.0", Content = "C" });
// Act - Create partition: A-B can communicate, C is isolated
_fixture.PartitionSites(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId);
_fixture.PartitionSites(_fixture.SiteB.SiteId, _fixture.SiteC.SiteId);
await _fixture.SynchronizeAllAsync();
// Assert
_fixture.SiteA.GetAdvisory("CVE-B").Should().NotBeNull();
_fixture.SiteB.GetAdvisory("CVE-A").Should().NotBeNull();
// C is isolated - no sync
_fixture.SiteC.AdvisoryCount.Should().Be(1);
_fixture.SiteA.GetAdvisory("CVE-C").Should().BeNull();
}
[Fact]
public async Task PartialPartition_HubSpoke_CenterReachable()
{
// Arrange - B is hub, A and C can only reach B
_fixture.PartitionSites(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId);
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A", Version = "1.0", Content = "A" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C", Version = "1.0", Content = "C" });
// Act - Multiple sync rounds for propagation through B
for (var i = 0; i < 3; i++)
{
await _fixture.SynchronizeAllAsync();
}
// Assert - Both A and C should eventually get each other's data via B
_fixture.SiteA.GetAdvisory("CVE-C").Should().NotBeNull();
_fixture.SiteC.GetAdvisory("CVE-A").Should().NotBeNull();
_fixture.SiteB.AdvisoryCount.Should().Be(2);
}
#endregion
#region Partition Healing Tests
[Fact]
public async Task PartitionHealing_FullRecovery()
{
// Arrange
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A", Version = "1.0", Content = "A" });
// Create partition
_fixture.PartitionSite(_fixture.SiteA.SiteId);
await _fixture.SynchronizeAllAsync();
// Verify A is isolated
_fixture.SiteB.GetAdvisory("CVE-A").Should().BeNull();
// Act - Heal partition
_fixture.HealAllPartitions();
await _fixture.SynchronizeAllAsync();
// Assert - Now B and C should have A's data
_fixture.SiteB.GetAdvisory("CVE-A").Should().NotBeNull();
_fixture.SiteC.GetAdvisory("CVE-A").Should().NotBeNull();
}
[Fact]
public async Task PartitionHealing_DataFromBothSides_Merged()
{
// Arrange - Initial state
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-INITIAL", Version = "1.0", Content = "Before partition" });
await _fixture.SynchronizeAllAsync();
// Create partition
_fixture.PartitionSite(_fixture.SiteC.SiteId);
// Both sides add data during partition
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-DURING-AB", Version = "1.0", Content = "During partition A-B" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-DURING-C", Version = "1.0", Content = "During partition C" });
await _fixture.SynchronizeAllAsync();
// Verify partition state
_fixture.SiteB.GetAdvisory("CVE-DURING-AB").Should().NotBeNull();
_fixture.SiteC.GetAdvisory("CVE-DURING-AB").Should().BeNull();
// Act - Heal partition
_fixture.HealAllPartitions();
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(1));
// Assert - All data merged
convergence.Converged.Should().BeTrue();
_fixture.SiteA.AdvisoryCount.Should().Be(3);
_fixture.SiteB.AdvisoryCount.Should().Be(3);
_fixture.SiteC.AdvisoryCount.Should().Be(3);
}
[Fact]
public async Task PartitionHealing_ConflictingEdits_LWWApplied()
{
// Arrange - Same CVE edited on both sides of partition
var cveId = "CVE-2024-CONFLICT";
_fixture.SiteA.AddAdvisory(new Advisory { Id = cveId, Version = "1.0", Content = "Original" });
await _fixture.SynchronizeAllAsync();
// Create partition
_fixture.PartitionSite(_fixture.SiteC.SiteId);
// Both sides edit the same CVE
_fixture.TimeProvider.Advance(TimeSpan.FromSeconds(1));
_fixture.SiteA.AddAdvisory(new Advisory { Id = cveId, Version = "2.0", Content = "Updated on A-B side" });
_fixture.TimeProvider.Advance(TimeSpan.FromSeconds(2));
_fixture.SiteC.AddAdvisory(new Advisory { Id = cveId, Version = "3.0", Content = "Updated on C side (latest)" });
await _fixture.SynchronizeAllAsync();
// Act - Heal and sync
_fixture.HealAllPartitions();
await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(1));
// Assert - Latest version (from C) should win
_fixture.SiteA.GetAdvisory(cveId)!.Version.Should().Be("3.0");
_fixture.SiteB.GetAdvisory(cveId)!.Version.Should().Be("3.0");
_fixture.SiteC.GetAdvisory(cveId)!.Version.Should().Be("3.0");
}
#endregion
#region Split Brain Tests
[Fact]
public async Task SplitBrain_TwoPartitions_BothMakeProgress()
{
// Arrange - Create two islands: [A, B] and [C]
_fixture.PartitionSites(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId);
_fixture.PartitionSites(_fixture.SiteB.SiteId, _fixture.SiteC.SiteId);
// Both partitions make progress
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-AB-1", Version = "1.0", Content = "AB partition" });
_fixture.SiteB.AddAdvisory(new Advisory { Id = "CVE-AB-2", Version = "1.0", Content = "AB partition" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C-1", Version = "1.0", Content = "C partition" });
// Act - Sync within partitions
await _fixture.SynchronizeAllAsync();
// Assert - Each partition has its own data
_fixture.SiteA.AdvisoryCount.Should().Be(2);
_fixture.SiteB.AdvisoryCount.Should().Be(2);
_fixture.SiteC.AdvisoryCount.Should().Be(1);
// A and B share data
_fixture.SiteA.GetAdvisory("CVE-AB-2").Should().NotBeNull();
_fixture.SiteB.GetAdvisory("CVE-AB-1").Should().NotBeNull();
}
[Fact]
public async Task SplitBrain_Healed_NoDataLoss()
{
// Arrange - Create split brain
_fixture.PartitionSites(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId);
_fixture.PartitionSites(_fixture.SiteB.SiteId, _fixture.SiteC.SiteId);
// Add unique data to each partition
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-AB", Version = "1.0", Content = "AB" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C", Version = "1.0", Content = "C" });
await _fixture.SynchronizeAllAsync();
// Act - Heal and resync
_fixture.HealAllPartitions();
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(1));
// Assert - No data loss
convergence.Converged.Should().BeTrue();
// All sites have all data
foreach (var site in _fixture.Sites.Values)
{
site.GetAdvisory("CVE-AB").Should().NotBeNull($"{site.SiteId} should have CVE-AB");
site.GetAdvisory("CVE-C").Should().NotBeNull($"{site.SiteId} should have CVE-C");
}
}
#endregion
#region Asymmetric Partition Tests
[Fact]
public async Task AsymmetricPartition_OneWayBlock()
{
// Arrange - A can't reach C, but C can reach A
_fixture.Network.PartitionOneWay(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId);
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A", Version = "1.0", Content = "A" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C", Version = "1.0", Content = "C" });
// Act
await _fixture.SynchronizeAllAsync();
await _fixture.SynchronizeAllAsync();
// Assert
// C should get A's data (via B or direct from A if C initiates)
// This depends on sync direction semantics
_fixture.SiteB.GetAdvisory("CVE-A").Should().NotBeNull();
_fixture.SiteB.GetAdvisory("CVE-C").Should().NotBeNull();
}
#endregion
#region Flapping Partition Tests
[Fact]
public async Task FlappingPartition_IntermittentConnectivity()
{
// Arrange
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-FLAP", Version = "1.0", Content = "Flapping test" });
// Act - Simulate flapping connectivity
for (var i = 0; i < 5; i++)
{
// Partition
_fixture.PartitionSite(_fixture.SiteC.SiteId);
await _fixture.SynchronizeAllAsync();
// Heal
_fixture.HealAllPartitions();
await _fixture.SynchronizeAllAsync();
}
// Assert - Eventually all should sync
var convergence = _fixture.VerifyConvergence();
convergence.Converged.Should().BeTrue();
}
#endregion
#region Partition Detection Tests
[Fact]
public void PartitionState_ReportsCorrectly()
{
// Arrange
_fixture.PartitionSites(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId);
_fixture.PartitionSite(_fixture.SiteB.SiteId);
// Act
var state = _fixture.Network.GetPartitionState();
// Assert
state.Should().ContainKey(_fixture.SiteA.SiteId);
state.Should().ContainKey(_fixture.SiteB.SiteId);
_fixture.Network.IsPartitioned(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId).Should().BeTrue();
_fixture.Network.IsPartitioned(_fixture.SiteB.SiteId, _fixture.SiteA.SiteId).Should().BeTrue();
}
#endregion
}

View File

@@ -0,0 +1,499 @@
// -----------------------------------------------------------------------------
// FederationClusterFixture.cs
// Sprint: Testing Enhancement Advisory - Phase 2.2
// Description: Test fixture for multi-site federation testing (3+ sites)
// -----------------------------------------------------------------------------
using System.Collections.Concurrent;
using System.Collections.Immutable;
using Microsoft.Extensions.Time.Testing;
using StellaOps.Concelier.Federation.Models;
using Xunit;
namespace StellaOps.Concelier.Federation.Tests.MultiSite.Fixtures;
/// <summary>
/// Test fixture managing a cluster of 3+ federated sites for integration testing.
/// Simulates multi-site federation with configurable network conditions.
/// </summary>
public sealed class FederationClusterFixture : IAsyncLifetime
{
private readonly Dictionary<string, FederationSite> _sites = [];
private readonly NetworkConditionSimulator _network = new();
private readonly FakeTimeProvider _timeProvider = new();
/// <summary>
/// Gets Site A (primary site in most test scenarios).
/// </summary>
public FederationSite SiteA { get; private set; } = null!;
/// <summary>
/// Gets Site B (secondary site).
/// </summary>
public FederationSite SiteB { get; private set; } = null!;
/// <summary>
/// Gets Site C (tertiary site).
/// </summary>
public FederationSite SiteC { get; private set; } = null!;
/// <summary>
/// Gets the network condition simulator.
/// </summary>
public NetworkConditionSimulator Network => _network;
/// <summary>
/// Gets the shared time provider for all sites.
/// </summary>
public FakeTimeProvider TimeProvider => _timeProvider;
/// <summary>
/// Gets all registered sites.
/// </summary>
public IReadOnlyDictionary<string, FederationSite> Sites => _sites;
/// <inheritdoc />
public ValueTask InitializeAsync()
{
// Create default 3-site cluster
SiteA = CreateSite("site-a", "US-East");
SiteB = CreateSite("site-b", "EU-West");
SiteC = CreateSite("site-c", "AP-South");
return ValueTask.CompletedTask;
}
/// <inheritdoc />
public ValueTask DisposeAsync()
{
foreach (var site in _sites.Values)
{
site.Dispose();
}
_sites.Clear();
return ValueTask.CompletedTask;
}
/// <summary>
/// Creates a new federation site.
/// </summary>
public FederationSite CreateSite(string siteId, string region)
{
ArgumentException.ThrowIfNullOrWhiteSpace(siteId);
ArgumentException.ThrowIfNullOrWhiteSpace(region);
var site = new FederationSite(siteId, region, _timeProvider, _network);
_sites[siteId] = site;
return site;
}
/// <summary>
/// Gets a site by ID.
/// </summary>
public FederationSite GetSite(string siteId)
{
return _sites.TryGetValue(siteId, out var site)
? site
: throw new ArgumentException($"Site '{siteId}' not found", nameof(siteId));
}
/// <summary>
/// Isolates a site from all other sites.
/// </summary>
public void PartitionSite(string siteId)
{
_network.IsolateNode(siteId);
}
/// <summary>
/// Partitions communication between two specific sites.
/// </summary>
public void PartitionSites(string siteA, string siteB)
{
_network.PartitionNodes(siteA, siteB);
}
/// <summary>
/// Heals all partitions (restores full connectivity).
/// </summary>
public void HealAllPartitions()
{
_network.HealAll();
}
/// <summary>
/// Sets latency between two sites.
/// </summary>
public void SetLatency(string fromSite, string toSite, TimeSpan latency, double jitterPercent = 0)
{
_network.SetLatency(fromSite, toSite, latency, jitterPercent);
}
/// <summary>
/// Sets high latency for cross-region communication.
/// </summary>
public void SetCrossRegionLatency(TimeSpan latency, double jitterPercent = 20)
{
foreach (var siteA in _sites.Values)
{
foreach (var siteB in _sites.Values)
{
if (siteA.SiteId != siteB.SiteId && siteA.Region != siteB.Region)
{
_network.SetLatency(siteA.SiteId, siteB.SiteId, latency, jitterPercent);
}
}
}
}
/// <summary>
/// Advances time for all sites.
/// </summary>
public void AdvanceTime(TimeSpan duration)
{
_timeProvider.Advance(duration);
}
/// <summary>
/// Synchronizes sites by exchanging bundles (simulates federation sync).
/// </summary>
public async Task<FederationSyncResult> SynchronizeAllAsync(CancellationToken ct = default)
{
var results = new List<SiteSyncResult>();
// Each site exports to each other site
foreach (var source in _sites.Values)
{
foreach (var target in _sites.Values)
{
if (source.SiteId == target.SiteId) continue;
var syncResult = await SynchronizePairAsync(source, target, ct);
results.Add(syncResult);
}
}
return new FederationSyncResult(
TotalPairs: results.Count,
Successful: results.Count(r => r.Success),
Failed: results.Count(r => !r.Success),
PartitionBlocked: results.Count(r => r.PartitionBlocked),
Results: [.. results]);
}
/// <summary>
/// Synchronizes a specific pair of sites.
/// </summary>
public async Task<SiteSyncResult> SynchronizePairAsync(
FederationSite source,
FederationSite target,
CancellationToken ct = default)
{
// Check network partition
if (_network.IsPartitioned(source.SiteId, target.SiteId))
{
return new SiteSyncResult(
SourceSite: source.SiteId,
TargetSite: target.SiteId,
Success: false,
PartitionBlocked: true,
ItemsTransferred: 0,
Duration: TimeSpan.Zero);
}
// Apply network latency
var latency = _network.GetLatency(source.SiteId, target.SiteId);
if (latency.BaseLatency > TimeSpan.Zero)
{
_timeProvider.Advance(latency.ComputeDelay());
}
var sw = System.Diagnostics.Stopwatch.StartNew();
try
{
// Export from source
var bundle = await source.ExportBundleAsync(target.LastCursor(source.SiteId), ct);
// Import to target
var imported = await target.ImportBundleAsync(source.SiteId, bundle, ct);
sw.Stop();
return new SiteSyncResult(
SourceSite: source.SiteId,
TargetSite: target.SiteId,
Success: true,
PartitionBlocked: false,
ItemsTransferred: imported,
Duration: sw.Elapsed);
}
catch (Exception ex)
{
sw.Stop();
return new SiteSyncResult(
SourceSite: source.SiteId,
TargetSite: target.SiteId,
Success: false,
PartitionBlocked: false,
ItemsTransferred: 0,
Duration: sw.Elapsed,
Error: ex.Message);
}
}
/// <summary>
/// Verifies that all sites have converged to the same state.
/// </summary>
public ConvergenceResult VerifyConvergence()
{
var siteStates = _sites.Values
.Select(s => (s.SiteId, State: s.GetStateDigest()))
.ToList();
var uniqueStates = siteStates.Select(s => s.State).Distinct().ToList();
var converged = uniqueStates.Count == 1;
var divergentSites = new List<(string SiteId, string State)>();
if (!converged && siteStates.Count > 0)
{
var expectedState = siteStates.First().State;
divergentSites = siteStates.Where(s => s.State != expectedState).ToList();
}
return new ConvergenceResult(
Converged: converged,
UniqueStates: uniqueStates.Count,
SiteStates: [.. siteStates],
DivergentSites: [.. divergentSites]);
}
/// <summary>
/// Waits for convergence with timeout.
/// </summary>
public async Task<ConvergenceResult> WaitForConvergenceAsync(
TimeSpan timeout,
TimeSpan checkInterval,
CancellationToken ct = default)
{
var deadline = _timeProvider.GetUtcNow() + timeout;
while (_timeProvider.GetUtcNow() < deadline)
{
ct.ThrowIfCancellationRequested();
// Sync all sites
await SynchronizeAllAsync(ct);
// Check convergence
var result = VerifyConvergence();
if (result.Converged)
{
return result;
}
// Advance time
_timeProvider.Advance(checkInterval);
}
return VerifyConvergence();
}
}
/// <summary>
/// Represents a single federated site in the test cluster.
/// </summary>
public sealed class FederationSite : IDisposable
{
private readonly ConcurrentDictionary<string, Advisory> _advisories = new();
private readonly ConcurrentDictionary<string, string> _cursors = new();
private readonly FakeTimeProvider _timeProvider;
private readonly NetworkConditionSimulator _network;
private long _sequenceNumber;
public FederationSite(
string siteId,
string region,
FakeTimeProvider timeProvider,
NetworkConditionSimulator network)
{
SiteId = siteId;
Region = region;
_timeProvider = timeProvider;
_network = network;
}
/// <summary>
/// Gets the site identifier.
/// </summary>
public string SiteId { get; }
/// <summary>
/// Gets the region of this site.
/// </summary>
public string Region { get; }
/// <summary>
/// Gets the count of advisories stored.
/// </summary>
public int AdvisoryCount => _advisories.Count;
/// <summary>
/// Adds an advisory to this site.
/// </summary>
public void AddAdvisory(Advisory advisory)
{
var seq = Interlocked.Increment(ref _sequenceNumber);
advisory = advisory with { Sequence = seq, LastModified = _timeProvider.GetUtcNow() };
_advisories[advisory.Id] = advisory;
}
/// <summary>
/// Gets an advisory by ID.
/// </summary>
public Advisory? GetAdvisory(string id)
{
return _advisories.TryGetValue(id, out var advisory) ? advisory : null;
}
/// <summary>
/// Gets all advisories.
/// </summary>
public IReadOnlyCollection<Advisory> GetAllAdvisories()
{
return _advisories.Values.ToList();
}
/// <summary>
/// Gets the last cursor for a source site.
/// </summary>
public string? LastCursor(string sourceSiteId)
{
return _cursors.TryGetValue(sourceSiteId, out var cursor) ? cursor : null;
}
/// <summary>
/// Exports a bundle of advisories since the given cursor.
/// </summary>
public Task<FederationBundle> ExportBundleAsync(string? sinceCursor, CancellationToken ct)
{
var sinceSeq = string.IsNullOrEmpty(sinceCursor) ? 0 : long.Parse(sinceCursor);
var items = _advisories.Values
.Where(a => a.Sequence > sinceSeq)
.OrderBy(a => a.Sequence)
.ToList();
var maxSeq = items.Count > 0 ? items.Max(a => a.Sequence) : sinceSeq;
var bundle = new FederationBundle(
SourceSite: SiteId,
SinceCursor: sinceCursor,
Cursor: maxSeq.ToString(),
Items: [.. items],
ExportedAt: _timeProvider.GetUtcNow());
return Task.FromResult(bundle);
}
/// <summary>
/// Imports a bundle from another site.
/// </summary>
public Task<int> ImportBundleAsync(string sourceSiteId, FederationBundle bundle, CancellationToken ct)
{
var imported = 0;
foreach (var advisory in bundle.Items)
{
// LWW (Last Writer Wins) conflict resolution
if (!_advisories.TryGetValue(advisory.Id, out var existing) ||
advisory.LastModified > existing.LastModified)
{
var seq = Interlocked.Increment(ref _sequenceNumber);
_advisories[advisory.Id] = advisory with { Sequence = seq };
imported++;
}
}
// Update cursor
_cursors[sourceSiteId] = bundle.Cursor;
return Task.FromResult(imported);
}
/// <summary>
/// Gets a digest representing the current state (for convergence checking).
/// </summary>
public string GetStateDigest()
{
var sortedIds = _advisories.Keys.OrderBy(k => k).ToList();
var combined = string.Join("|", sortedIds.Select(id =>
{
var a = _advisories[id];
return $"{a.Id}:{a.Version}:{a.Content}";
}));
using var sha256 = System.Security.Cryptography.SHA256.Create();
var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(combined));
return Convert.ToHexString(hash).ToLowerInvariant();
}
public void Dispose()
{
_advisories.Clear();
_cursors.Clear();
}
}
/// <summary>
/// Represents an advisory in the federation system.
/// </summary>
public sealed record Advisory
{
public required string Id { get; init; }
public required string Version { get; init; }
public required string Content { get; init; }
public DateTimeOffset LastModified { get; init; }
public long Sequence { get; init; }
}
/// <summary>
/// Bundle of advisories for federation transfer.
/// </summary>
public sealed record FederationBundle(
string SourceSite,
string? SinceCursor,
string Cursor,
ImmutableArray<Advisory> Items,
DateTimeOffset ExportedAt);
/// <summary>
/// Result of a federation sync operation.
/// </summary>
public sealed record FederationSyncResult(
int TotalPairs,
int Successful,
int Failed,
int PartitionBlocked,
ImmutableArray<SiteSyncResult> Results);
/// <summary>
/// Result of syncing between two sites.
/// </summary>
public sealed record SiteSyncResult(
string SourceSite,
string TargetSite,
bool Success,
bool PartitionBlocked,
int ItemsTransferred,
TimeSpan Duration,
string? Error = null);
/// <summary>
/// Result of convergence verification.
/// </summary>
public sealed record ConvergenceResult(
bool Converged,
int UniqueStates,
ImmutableArray<(string SiteId, string State)> SiteStates,
ImmutableArray<(string SiteId, string State)> DivergentSites);

View File

@@ -0,0 +1,394 @@
// -----------------------------------------------------------------------------
// NetworkConditionSimulator.cs
// Sprint: Testing Enhancement Advisory - Phase 2.2
// Description: Simulates network conditions for multi-site federation testing
// -----------------------------------------------------------------------------
using System.Collections.Concurrent;
using System.Collections.Immutable;
namespace StellaOps.Concelier.Federation.Tests.MultiSite.Fixtures;
/// <summary>
/// Simulates network conditions between federation sites including partitions,
/// latency, packet loss, and bandwidth constraints.
/// </summary>
public sealed class NetworkConditionSimulator
{
private readonly ConcurrentDictionary<string, HashSet<string>> _partitions = new();
private readonly ConcurrentDictionary<(string From, string To), LatencyConfig> _latencies = new();
private readonly ConcurrentDictionary<(string From, string To), NetworkCondition> _conditions = new();
private readonly object _lock = new();
private readonly Random _random = new();
/// <summary>
/// Isolates a site from all other sites (full partition).
/// </summary>
public void IsolateNode(string siteId)
{
ArgumentException.ThrowIfNullOrWhiteSpace(siteId);
lock (_lock)
{
_partitions[siteId] = ["*"]; // Full isolation marker
}
}
/// <summary>
/// Partitions communication between two specific sites.
/// </summary>
public void PartitionNodes(string siteA, string siteB)
{
ArgumentException.ThrowIfNullOrWhiteSpace(siteA);
ArgumentException.ThrowIfNullOrWhiteSpace(siteB);
lock (_lock)
{
if (!_partitions.TryGetValue(siteA, out var aPartitions))
{
aPartitions = [];
_partitions[siteA] = aPartitions;
}
aPartitions.Add(siteB);
if (!_partitions.TryGetValue(siteB, out var bPartitions))
{
bPartitions = [];
_partitions[siteB] = bPartitions;
}
bPartitions.Add(siteA);
}
}
/// <summary>
/// Creates an asymmetric partition (A can't reach B, but B can reach A).
/// </summary>
public void PartitionOneWay(string from, string to)
{
ArgumentException.ThrowIfNullOrWhiteSpace(from);
ArgumentException.ThrowIfNullOrWhiteSpace(to);
lock (_lock)
{
if (!_partitions.TryGetValue(from, out var partitions))
{
partitions = [];
_partitions[from] = partitions;
}
partitions.Add(to);
}
}
/// <summary>
/// Heals the partition for a specific site (restores connectivity).
/// </summary>
public void HealNode(string siteId)
{
ArgumentException.ThrowIfNullOrWhiteSpace(siteId);
lock (_lock)
{
_partitions.TryRemove(siteId, out _);
// Also remove this site from other sites' partition lists
foreach (var kvp in _partitions)
{
kvp.Value.Remove(siteId);
}
}
}
/// <summary>
/// Heals partition between two specific sites.
/// </summary>
public void HealPartition(string siteA, string siteB)
{
lock (_lock)
{
if (_partitions.TryGetValue(siteA, out var aPartitions))
{
aPartitions.Remove(siteB);
}
if (_partitions.TryGetValue(siteB, out var bPartitions))
{
bPartitions.Remove(siteA);
}
}
}
/// <summary>
/// Heals all partitions (restores full connectivity).
/// </summary>
public void HealAll()
{
lock (_lock)
{
_partitions.Clear();
}
}
/// <summary>
/// Checks if communication between two sites is blocked.
/// </summary>
public bool IsPartitioned(string fromSite, string toSite)
{
lock (_lock)
{
// Check if fromSite is fully isolated
if (_partitions.TryGetValue(fromSite, out var fromPartitions))
{
if (fromPartitions.Contains("*") || fromPartitions.Contains(toSite))
{
return true;
}
}
// Check if toSite is fully isolated
if (_partitions.TryGetValue(toSite, out var toPartitions))
{
if (toPartitions.Contains("*") || toPartitions.Contains(fromSite))
{
return true;
}
}
return false;
}
}
/// <summary>
/// Sets simulated latency between two sites.
/// </summary>
public void SetLatency(string fromSite, string toSite, TimeSpan baseLatency, double jitterPercent = 0)
{
_latencies[(fromSite, toSite)] = new LatencyConfig(baseLatency, jitterPercent);
}
/// <summary>
/// Gets the configured latency between two sites.
/// </summary>
public LatencyConfig GetLatency(string fromSite, string toSite)
{
if (_latencies.TryGetValue((fromSite, toSite), out var config))
{
return config;
}
return LatencyConfig.Default;
}
/// <summary>
/// Sets comprehensive network condition between two sites.
/// </summary>
public void SetCondition(string fromSite, string toSite, NetworkCondition condition)
{
_conditions[(fromSite, toSite)] = condition;
}
/// <summary>
/// Gets the network condition between two sites.
/// </summary>
public NetworkCondition GetCondition(string fromSite, string toSite)
{
if (_conditions.TryGetValue((fromSite, toSite), out var condition))
{
return condition;
}
return NetworkCondition.Perfect;
}
/// <summary>
/// Simulates a temporary network glitch (brief partition then heal).
/// </summary>
public async Task SimulateGlitchAsync(string siteA, string siteB, TimeSpan duration)
{
PartitionNodes(siteA, siteB);
await Task.Delay(duration);
HealPartition(siteA, siteB);
}
/// <summary>
/// Creates a scenario where only specific sites can communicate.
/// </summary>
public void CreateIsland(IEnumerable<string> islandSites, IEnumerable<string> allSites)
{
var island = islandSites.ToHashSet();
var others = allSites.Where(s => !island.Contains(s)).ToList();
foreach (var islandSite in island)
{
foreach (var otherSite in others)
{
PartitionNodes(islandSite, otherSite);
}
}
}
/// <summary>
/// Clears all latency configurations.
/// </summary>
public void ClearLatencies()
{
_latencies.Clear();
}
/// <summary>
/// Clears all network conditions.
/// </summary>
public void ClearConditions()
{
_conditions.Clear();
}
/// <summary>
/// Resets all network configurations to default (full connectivity, no latency).
/// </summary>
public void Reset()
{
HealAll();
ClearLatencies();
ClearConditions();
}
/// <summary>
/// Gets the current partition state for diagnostic purposes.
/// </summary>
public ImmutableDictionary<string, ImmutableHashSet<string>> GetPartitionState()
{
lock (_lock)
{
return _partitions.ToImmutableDictionary(
kvp => kvp.Key,
kvp => kvp.Value.ToImmutableHashSet());
}
}
/// <summary>
/// Checks if a message should be dropped based on packet loss configuration.
/// </summary>
public bool ShouldDropMessage(string fromSite, string toSite)
{
var condition = GetCondition(fromSite, toSite);
if (condition.PacketLossPercent <= 0)
{
return false;
}
lock (_lock)
{
return _random.NextDouble() * 100 < condition.PacketLossPercent;
}
}
}
/// <summary>
/// Configuration for simulated network latency.
/// </summary>
public sealed record LatencyConfig
{
public static readonly LatencyConfig Default = new(TimeSpan.Zero, 0);
public TimeSpan BaseLatency { get; }
public double JitterPercent { get; }
public LatencyConfig(TimeSpan baseLatency, double jitterPercent)
{
BaseLatency = baseLatency;
JitterPercent = Math.Clamp(jitterPercent, 0, 100);
}
/// <summary>
/// Calculates the actual delay including jitter.
/// </summary>
public TimeSpan ComputeDelay(Random? random = null)
{
if (BaseLatency <= TimeSpan.Zero)
{
return TimeSpan.Zero;
}
if (JitterPercent <= 0)
{
return BaseLatency;
}
random ??= Random.Shared;
var jitterFactor = 1.0 + ((random.NextDouble() * 2 - 1) * JitterPercent / 100);
return TimeSpan.FromTicks((long)(BaseLatency.Ticks * jitterFactor));
}
}
/// <summary>
/// Comprehensive network condition configuration.
/// </summary>
public sealed record NetworkCondition
{
public static readonly NetworkCondition Perfect = new();
/// <summary>
/// Base network latency.
/// </summary>
public TimeSpan Latency { get; init; }
/// <summary>
/// Latency jitter as percentage of base latency.
/// </summary>
public double JitterPercent { get; init; }
/// <summary>
/// Packet loss percentage (0-100).
/// </summary>
public double PacketLossPercent { get; init; }
/// <summary>
/// Bandwidth limit in bytes per second (0 = unlimited).
/// </summary>
public long BandwidthBytesPerSecond { get; init; }
/// <summary>
/// Whether the connection is currently available.
/// </summary>
public bool IsAvailable { get; init; } = true;
/// <summary>
/// Creates a high latency condition (typical cross-continent).
/// </summary>
public static NetworkCondition HighLatency(TimeSpan latency) => new()
{
Latency = latency,
JitterPercent = 20
};
/// <summary>
/// Creates a lossy connection condition.
/// </summary>
public static NetworkCondition Lossy(double packetLossPercent) => new()
{
PacketLossPercent = packetLossPercent
};
/// <summary>
/// Creates a degraded connection with multiple issues.
/// </summary>
public static NetworkCondition Degraded => new()
{
Latency = TimeSpan.FromMilliseconds(200),
JitterPercent = 50,
PacketLossPercent = 5
};
}
/// <summary>
/// Exception thrown when a network partition prevents communication.
/// </summary>
public sealed class NetworkPartitionException : Exception
{
public string FromSite { get; }
public string ToSite { get; }
public NetworkPartitionException(string fromSite, string toSite)
: base($"Network partition: {fromSite} cannot communicate with {toSite}")
{
FromSite = fromSite;
ToSite = toSite;
}
}

View File

@@ -0,0 +1,361 @@
// -----------------------------------------------------------------------------
// ThreeSiteFederationTests.cs
// Sprint: Testing Enhancement Advisory - Phase 2.2
// Description: Tests for 3-site federation convergence and synchronization
// -----------------------------------------------------------------------------
using FluentAssertions;
using StellaOps.Concelier.Federation.Tests.MultiSite.Fixtures;
using StellaOps.TestKit;
using Xunit;
namespace StellaOps.Concelier.Federation.Tests.MultiSite;
/// <summary>
/// Tests for 3-site federation scenarios including convergence,
/// synchronization, and conflict resolution.
/// </summary>
[Trait("Category", TestCategories.Integration)]
[Trait("Category", TestCategories.Federation)]
public class ThreeSiteFederationTests : IClassFixture<FederationClusterFixture>
{
private readonly FederationClusterFixture _fixture;
public ThreeSiteFederationTests(FederationClusterFixture fixture)
{
_fixture = fixture;
// Reset network between tests
_fixture.Network.Reset();
}
#region Basic Convergence Tests
[Fact]
public async Task ThreeSite_FullSync_AllSitesConverge()
{
// Arrange - Add different advisories to each site
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-2024-0001",
Version = "1.0",
Content = "Advisory from Site A"
});
_fixture.SiteB.AddAdvisory(new Advisory
{
Id = "CVE-2024-0002",
Version = "1.0",
Content = "Advisory from Site B"
});
_fixture.SiteC.AddAdvisory(new Advisory
{
Id = "CVE-2024-0003",
Version = "1.0",
Content = "Advisory from Site C"
});
// Act - Synchronize all sites
var syncResult = await _fixture.SynchronizeAllAsync();
// Continue syncing until convergence (may need multiple rounds)
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(1));
// Assert
syncResult.Failed.Should().Be(0);
convergence.Converged.Should().BeTrue();
// All sites should have all 3 advisories
_fixture.SiteA.AdvisoryCount.Should().Be(3);
_fixture.SiteB.AdvisoryCount.Should().Be(3);
_fixture.SiteC.AdvisoryCount.Should().Be(3);
// Verify each site has all advisories
_fixture.SiteA.GetAdvisory("CVE-2024-0001").Should().NotBeNull();
_fixture.SiteA.GetAdvisory("CVE-2024-0002").Should().NotBeNull();
_fixture.SiteA.GetAdvisory("CVE-2024-0003").Should().NotBeNull();
}
[Fact]
public async Task ThreeSite_EmptySites_SyncCompletes()
{
// Arrange - All sites start empty
// Act
var syncResult = await _fixture.SynchronizeAllAsync();
// Assert
syncResult.Failed.Should().Be(0);
syncResult.Successful.Should().Be(6); // 3 sites * 2 directions each
var convergence = _fixture.VerifyConvergence();
convergence.Converged.Should().BeTrue();
}
[Fact]
public async Task ThreeSite_SingleSource_PropagatesCorrectly()
{
// Arrange - Only Site A has data
for (var i = 1; i <= 10; i++)
{
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = $"CVE-2024-{i:D4}",
Version = "1.0",
Content = $"Advisory {i}"
});
}
// Act - Multiple sync rounds
await _fixture.SynchronizeAllAsync();
await _fixture.SynchronizeAllAsync();
// Assert
_fixture.SiteA.AdvisoryCount.Should().Be(10);
_fixture.SiteB.AdvisoryCount.Should().Be(10);
_fixture.SiteC.AdvisoryCount.Should().Be(10);
var convergence = _fixture.VerifyConvergence();
convergence.Converged.Should().BeTrue();
}
#endregion
#region Conflict Resolution Tests
[Fact]
public async Task ThreeSite_ConflictingUpdates_LWWResolution()
{
// Arrange - Same CVE on all sites with different timestamps
var cveId = "CVE-2024-CONFLICT";
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = cveId,
Version = "1.0",
Content = "Version from A"
});
_fixture.TimeProvider.Advance(TimeSpan.FromSeconds(1));
_fixture.SiteB.AddAdvisory(new Advisory
{
Id = cveId,
Version = "2.0",
Content = "Version from B"
});
_fixture.TimeProvider.Advance(TimeSpan.FromSeconds(1));
_fixture.SiteC.AddAdvisory(new Advisory
{
Id = cveId,
Version = "3.0",
Content = "Version from C (latest)"
});
// Act
await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(1));
// Assert - All sites should have the latest version (from C)
var advisoryA = _fixture.SiteA.GetAdvisory(cveId);
var advisoryB = _fixture.SiteB.GetAdvisory(cveId);
var advisoryC = _fixture.SiteC.GetAdvisory(cveId);
advisoryA!.Version.Should().Be("3.0");
advisoryA.Content.Should().Be("Version from C (latest)");
advisoryB!.Version.Should().Be("3.0");
advisoryC!.Version.Should().Be("3.0");
}
[Fact]
public async Task ThreeSite_ConcurrentUpdates_AllPreserved()
{
// Arrange - Each site adds different CVEs simultaneously
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A-1", Version = "1.0", Content = "A1" });
_fixture.SiteB.AddAdvisory(new Advisory { Id = "CVE-B-1", Version = "1.0", Content = "B1" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C-1", Version = "1.0", Content = "C1" });
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A-2", Version = "1.0", Content = "A2" });
_fixture.SiteB.AddAdvisory(new Advisory { Id = "CVE-B-2", Version = "1.0", Content = "B2" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C-2", Version = "1.0", Content = "C2" });
// Act
await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(1));
// Assert - All 6 unique CVEs should be on all sites
_fixture.SiteA.AdvisoryCount.Should().Be(6);
_fixture.SiteB.AdvisoryCount.Should().Be(6);
_fixture.SiteC.AdvisoryCount.Should().Be(6);
}
#endregion
#region Partial Sync Tests
[Fact]
public async Task ThreeSite_IncrementalSync_OnlyDeltaTransferred()
{
// Arrange - Initial sync
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-2024-0001",
Version = "1.0",
Content = "Initial advisory"
});
await _fixture.SynchronizeAllAsync();
// Add more data after initial sync
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-2024-0002",
Version = "1.0",
Content = "Second advisory"
});
// Act - Sync again (should only transfer delta)
var syncResult = await _fixture.SynchronizeAllAsync();
// Assert
syncResult.Failed.Should().Be(0);
// Verify both advisories are present
_fixture.SiteB.GetAdvisory("CVE-2024-0001").Should().NotBeNull();
_fixture.SiteB.GetAdvisory("CVE-2024-0002").Should().NotBeNull();
}
[Fact]
public async Task ThreeSite_ChainSync_PropagatesThroughIntermediate()
{
// Arrange - A -> B -> C chain (A and C never sync directly)
_fixture.Network.PartitionNodes(_fixture.SiteA.SiteId, _fixture.SiteC.SiteId);
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-2024-CHAIN",
Version = "1.0",
Content = "Chain test"
});
// Act - Multiple sync rounds to propagate through B
for (var i = 0; i < 3; i++)
{
await _fixture.SynchronizeAllAsync();
}
// Assert - Site C should have the advisory via B
_fixture.SiteB.GetAdvisory("CVE-2024-CHAIN").Should().NotBeNull();
_fixture.SiteC.GetAdvisory("CVE-2024-CHAIN").Should().NotBeNull();
}
#endregion
#region Large Dataset Tests
[Fact]
public async Task ThreeSite_LargeDataset_ConvergesCorrectly()
{
// Arrange - Add 100 advisories to each site
for (var i = 1; i <= 100; i++)
{
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = $"CVE-A-{i:D4}",
Version = "1.0",
Content = $"Advisory A-{i}"
});
_fixture.SiteB.AddAdvisory(new Advisory
{
Id = $"CVE-B-{i:D4}",
Version = "1.0",
Content = $"Advisory B-{i}"
});
_fixture.SiteC.AddAdvisory(new Advisory
{
Id = $"CVE-C-{i:D4}",
Version = "1.0",
Content = $"Advisory C-{i}"
});
}
// Act
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromMinutes(1),
checkInterval: TimeSpan.FromSeconds(2));
// Assert
convergence.Converged.Should().BeTrue();
_fixture.SiteA.AdvisoryCount.Should().Be(300);
_fixture.SiteB.AdvisoryCount.Should().Be(300);
_fixture.SiteC.AdvisoryCount.Should().Be(300);
}
#endregion
#region Idempotency Tests
[Fact]
public async Task ThreeSite_RepeatedSync_Idempotent()
{
// Arrange
_fixture.SiteA.AddAdvisory(new Advisory
{
Id = "CVE-2024-IDEMP",
Version = "1.0",
Content = "Idempotency test"
});
// Act - Sync multiple times
await _fixture.SynchronizeAllAsync();
var stateAfterFirst = _fixture.VerifyConvergence();
await _fixture.SynchronizeAllAsync();
await _fixture.SynchronizeAllAsync();
await _fixture.SynchronizeAllAsync();
var stateAfterMultiple = _fixture.VerifyConvergence();
// Assert - State should be identical
stateAfterFirst.SiteStates.Should().BeEquivalentTo(stateAfterMultiple.SiteStates);
_fixture.SiteA.AdvisoryCount.Should().Be(1);
_fixture.SiteB.AdvisoryCount.Should().Be(1);
_fixture.SiteC.AdvisoryCount.Should().Be(1);
}
#endregion
#region Four+ Site Tests
[Fact]
public async Task FourSite_AllConverge()
{
// Arrange - Add a fourth site
var siteD = _fixture.CreateSite("site-d", "SA-East");
_fixture.SiteA.AddAdvisory(new Advisory { Id = "CVE-A", Version = "1.0", Content = "A" });
_fixture.SiteB.AddAdvisory(new Advisory { Id = "CVE-B", Version = "1.0", Content = "B" });
_fixture.SiteC.AddAdvisory(new Advisory { Id = "CVE-C", Version = "1.0", Content = "C" });
siteD.AddAdvisory(new Advisory { Id = "CVE-D", Version = "1.0", Content = "D" });
// Act
var convergence = await _fixture.WaitForConvergenceAsync(
timeout: TimeSpan.FromSeconds(30),
checkInterval: TimeSpan.FromSeconds(1));
// Assert
convergence.Converged.Should().BeTrue();
_fixture.Sites.Values.Should().AllSatisfy(s => s.AdvisoryCount.Should().Be(4));
}
#endregion
}

View File

@@ -6,15 +6,19 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Federation/StellaOps.Concelier.Federation.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Models/StellaOps.Concelier.Models.csproj" />
<ProjectReference Include="../../__Libraries/StellaOps.Concelier.Persistence/StellaOps.Concelier.Persistence.csproj" />
<!-- Test packages inherited from Directory.Build.props -->
<ProjectReference Include="../../../__Libraries/StellaOps.TestKit/StellaOps.TestKit.csproj" />
<!-- Test packages inherited from Directory.Build.props for .Tests projects -->
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Moq" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,7 @@
# Concelier Federation 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`.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |

Some files were not shown because too many files have changed in this diff Show More