sln build fix (again), tests fixes, audit work and doctors work
This commit is contained in:
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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 |
|
||||
| --- | --- | --- |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
Reference in New Issue
Block a user