157 lines
5.3 KiB
Rust
157 lines
5.3 KiB
Rust
// Integration tests for the onboarding backfill migration.
|
|
//
|
|
// Requires MongoDB (set TEST_MONGODB_URI if not at the default).
|
|
// Not run in CI (which is `--lib` only) — run locally:
|
|
// cargo test -p compliance-agent --test e2e migration
|
|
|
|
use compliance_agent::database::{Database, DatabasePool};
|
|
use compliance_agent::migrate::onboarding;
|
|
use compliance_core::models::{
|
|
ArtifactKind, DastTarget, DastTargetType, TargetType, TrackedRepository,
|
|
};
|
|
use mongodb::bson::{doc, Document};
|
|
|
|
async fn fresh_db() -> (DatabasePool, String, Database) {
|
|
let uri = std::env::var("TEST_MONGODB_URI")
|
|
.unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
|
|
// Prefix must fit the pool's 30-char cap (`<prefix>_<32 hex>` <= 63).
|
|
let prefix = format!("t_{}", &uuid::Uuid::new_v4().simple().to_string()[..16]);
|
|
let pool = DatabasePool::connect(&uri, &prefix)
|
|
.await
|
|
.expect("connect mongo");
|
|
let db = pool.for_tenant_id("t1").await.expect("tenant db");
|
|
(pool, prefix, db)
|
|
}
|
|
|
|
async fn cleanup(pool: &DatabasePool, prefix: &str) {
|
|
if let Ok(names) = pool.client().list_database_names().await {
|
|
for n in names {
|
|
if n.starts_with(prefix) {
|
|
pool.client().database(&n).drop().await.ok();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn backfill_folds_relinks_is_idempotent_and_reversible() {
|
|
let (pool, prefix, db) = fresh_db().await;
|
|
|
|
// Seed a repo.
|
|
let repo = TrackedRepository::new("acme".into(), "https://git/acme.git".into());
|
|
let repo_id = db
|
|
.repositories()
|
|
.insert_one(repo)
|
|
.await
|
|
.expect("insert repo")
|
|
.inserted_id
|
|
.as_object_id()
|
|
.expect("repo oid");
|
|
|
|
// A DAST target linked to the repo (folds + promotes to WebApp + relinks).
|
|
let mut linked = DastTarget::new(
|
|
"acme-web".into(),
|
|
"https://acme.example.com".into(),
|
|
DastTargetType::WebApp,
|
|
);
|
|
linked.repo_id = Some(repo_id.to_hex());
|
|
let linked_id = db
|
|
.dast_targets()
|
|
.insert_one(linked)
|
|
.await
|
|
.expect("insert linked dast")
|
|
.inserted_id
|
|
.as_object_id()
|
|
.expect("linked oid");
|
|
|
|
// A repo-less DAST target (standalone).
|
|
let standalone = DastTarget::new(
|
|
"acme-api".into(),
|
|
"https://api.acme.com".into(),
|
|
DastTargetType::RestApi,
|
|
);
|
|
let standalone_id = db
|
|
.dast_targets()
|
|
.insert_one(standalone)
|
|
.await
|
|
.expect("insert standalone dast")
|
|
.inserted_id
|
|
.as_object_id()
|
|
.expect("standalone oid");
|
|
|
|
// A DAST scan run pointing at the linked target — should be relinked to the repo.
|
|
db.collection_named::<Document>("dast_scan_runs")
|
|
.insert_one(doc! { "target_id": linked_id.to_hex(), "status": "completed" })
|
|
.await
|
|
.expect("insert dast run");
|
|
|
|
// --- Backfill ---
|
|
assert!(!onboarding::already_applied(&db).await.unwrap());
|
|
let report = onboarding::backfill_onboarded_targets(&db, false)
|
|
.await
|
|
.expect("backfill");
|
|
assert_eq!(report.repos_migrated, 1);
|
|
assert_eq!(report.dast_targets_folded, 1);
|
|
assert_eq!(report.dast_targets_standalone, 1);
|
|
assert!(onboarding::already_applied(&db).await.unwrap());
|
|
|
|
// Repo target: preserved _id, has git + folded live-url, promoted to WebApp.
|
|
let repo_target = db
|
|
.onboarded_targets()
|
|
.find_one(doc! { "_id": repo_id })
|
|
.await
|
|
.unwrap()
|
|
.expect("repo target");
|
|
assert!(repo_target.has(ArtifactKind::GitRepo));
|
|
assert!(repo_target.has(ArtifactKind::LiveUrl));
|
|
assert_eq!(repo_target.target_type, TargetType::WebApp);
|
|
|
|
// Standalone target: preserved _id, live-url, backend service.
|
|
let standalone_target = db
|
|
.onboarded_targets()
|
|
.find_one(doc! { "_id": standalone_id })
|
|
.await
|
|
.unwrap()
|
|
.expect("standalone target");
|
|
assert!(standalone_target.has(ArtifactKind::LiveUrl));
|
|
assert_eq!(standalone_target.target_type, TargetType::BackendService);
|
|
|
|
// The DAST run was relinked from the old dast id to the repo (unified) id.
|
|
let run = db
|
|
.collection_named::<Document>("dast_scan_runs")
|
|
.find_one(doc! {})
|
|
.await
|
|
.unwrap()
|
|
.expect("run");
|
|
assert_eq!(run.get_str("target_id").unwrap(), repo_id.to_hex());
|
|
|
|
// --- Idempotent: re-run migrates nothing new ---
|
|
let again = onboarding::backfill_onboarded_targets(&db, false)
|
|
.await
|
|
.expect("backfill again");
|
|
assert_eq!(again.repos_migrated, 0);
|
|
assert_eq!(again.dast_targets_folded, 0);
|
|
assert_eq!(again.dast_targets_standalone, 0);
|
|
assert!(again.skipped_existing >= 2);
|
|
|
|
// --- Revert: onboarded targets gone, relink undone, marker cleared ---
|
|
onboarding::revert(&db).await.expect("revert");
|
|
assert_eq!(
|
|
db.onboarded_targets()
|
|
.count_documents(doc! {})
|
|
.await
|
|
.unwrap(),
|
|
0
|
|
);
|
|
let run_after = db
|
|
.collection_named::<Document>("dast_scan_runs")
|
|
.find_one(doc! {})
|
|
.await
|
|
.unwrap()
|
|
.expect("run");
|
|
assert_eq!(run_after.get_str("target_id").unwrap(), linked_id.to_hex());
|
|
assert!(!onboarding::already_applied(&db).await.unwrap());
|
|
|
|
cleanup(&pool, &prefix).await;
|
|
}
|