Some checks failed
CI / Check (pull_request) Failing after 9m4s
CI / Detect Changes (pull_request) Has been skipped
CI / Deploy Agent (pull_request) Has been skipped
CI / Deploy Dashboard (pull_request) Has been skipped
CI / Deploy Docs (pull_request) Has been skipped
CI / Deploy MCP (pull_request) Has been skipped
E2E Tests: - 17 integration tests covering: health, repos CRUD, findings lifecycle, cascade delete (SAST + DAST + pentest), DAST targets, stats overview - TestServer harness: spins up agent API on random port with isolated MongoDB database per test, auto-cleanup - Added lib.rs to expose agent internals for integration tests - Nightly CI workflow with MongoDB service container (3 AM UTC) Tests verify: - Repository add/list/delete + duplicate rejection + invalid ID handling - Finding creation, filtering by severity/repo, status updates, bulk updates - Cascade delete: repo deletion removes all DAST targets, pentest sessions, attack chain nodes, DAST findings, SAST findings, and SBOM entries - DAST target CRUD and empty finding list - Stats overview accuracy with zero and populated data Also: - Fix Dockerfile.dashboard: bump dioxus-cli 0.7.3 → 0.7.4 (compile fix) - Fix clippy: allow new_without_default for pattern scanners Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
222 lines
7.9 KiB
Rust
222 lines
7.9 KiB
Rust
use crate::common::TestServer;
|
|
use serde_json::json;
|
|
|
|
/// Insert a DAST target directly into MongoDB linked to a repo.
|
|
async fn insert_dast_target(server: &TestServer, repo_id: &str, name: &str) -> String {
|
|
let mongodb_uri = std::env::var("TEST_MONGODB_URI")
|
|
.unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
|
|
let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
|
|
let db = client.database(&server.db_name());
|
|
|
|
let result = db
|
|
.collection::<mongodb::bson::Document>("dast_targets")
|
|
.insert_one(mongodb::bson::doc! {
|
|
"name": name,
|
|
"base_url": format!("https://{name}.example.com"),
|
|
"target_type": "webapp",
|
|
"repo_id": repo_id,
|
|
"rate_limit": 10,
|
|
"allow_destructive": false,
|
|
"created_at": mongodb::bson::DateTime::now(),
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
result.inserted_id.as_object_id().unwrap().to_hex()
|
|
}
|
|
|
|
/// Insert a pentest session linked to a target.
|
|
async fn insert_pentest_session(server: &TestServer, target_id: &str, repo_id: &str) -> String {
|
|
let mongodb_uri = std::env::var("TEST_MONGODB_URI")
|
|
.unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
|
|
let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
|
|
let db = client.database(&server.db_name());
|
|
|
|
let result = db
|
|
.collection::<mongodb::bson::Document>("pentest_sessions")
|
|
.insert_one(mongodb::bson::doc! {
|
|
"target_id": target_id,
|
|
"repo_id": repo_id,
|
|
"strategy": "comprehensive",
|
|
"status": "completed",
|
|
"findings_count": 1_i32,
|
|
"exploitable_count": 0_i32,
|
|
"created_at": mongodb::bson::DateTime::now(),
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
result.inserted_id.as_object_id().unwrap().to_hex()
|
|
}
|
|
|
|
/// Insert an attack chain node linked to a session.
|
|
async fn insert_attack_node(server: &TestServer, session_id: &str) {
|
|
let mongodb_uri = std::env::var("TEST_MONGODB_URI")
|
|
.unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
|
|
let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
|
|
let db = client.database(&server.db_name());
|
|
|
|
db.collection::<mongodb::bson::Document>("attack_chain_nodes")
|
|
.insert_one(mongodb::bson::doc! {
|
|
"session_id": session_id,
|
|
"node_id": "node-1",
|
|
"tool_name": "recon",
|
|
"status": "completed",
|
|
"created_at": mongodb::bson::DateTime::now(),
|
|
})
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
/// Insert a DAST finding linked to a target.
|
|
async fn insert_dast_finding(server: &TestServer, target_id: &str, session_id: &str) {
|
|
let mongodb_uri = std::env::var("TEST_MONGODB_URI")
|
|
.unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
|
|
let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
|
|
let db = client.database(&server.db_name());
|
|
|
|
db.collection::<mongodb::bson::Document>("dast_findings")
|
|
.insert_one(mongodb::bson::doc! {
|
|
"scan_run_id": "run-1",
|
|
"target_id": target_id,
|
|
"vuln_type": "xss",
|
|
"title": "Reflected XSS",
|
|
"description": "XSS in search param",
|
|
"severity": "high",
|
|
"endpoint": "https://example.com/search",
|
|
"method": "GET",
|
|
"exploitable": true,
|
|
"evidence": [],
|
|
"session_id": session_id,
|
|
"created_at": mongodb::bson::DateTime::now(),
|
|
})
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
/// Helper to count documents in a collection
|
|
async fn count_docs(server: &TestServer, collection: &str) -> u64 {
|
|
let mongodb_uri = std::env::var("TEST_MONGODB_URI")
|
|
.unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
|
|
let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
|
|
let db = client.database(&server.db_name());
|
|
db.collection::<mongodb::bson::Document>(collection)
|
|
.count_documents(mongodb::bson::doc! {})
|
|
.await
|
|
.unwrap()
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delete_repo_cascades_to_dast_and_pentest_data() {
|
|
let server = TestServer::start().await;
|
|
|
|
// Create a repo
|
|
let resp = server
|
|
.post(
|
|
"/api/v1/repositories",
|
|
&json!({
|
|
"name": "cascade-test",
|
|
"git_url": "https://github.com/example/cascade-test.git",
|
|
}),
|
|
)
|
|
.await;
|
|
let body: serde_json::Value = resp.json().await.unwrap();
|
|
let repo_id = body["data"]["id"].as_str().unwrap().to_string();
|
|
|
|
// Insert DAST target linked to repo
|
|
let target_id = insert_dast_target(&server, &repo_id, "cascade-target").await;
|
|
|
|
// Insert pentest session linked to target
|
|
let session_id = insert_pentest_session(&server, &target_id, &repo_id).await;
|
|
|
|
// Insert downstream data
|
|
insert_attack_node(&server, &session_id).await;
|
|
insert_dast_finding(&server, &target_id, &session_id).await;
|
|
|
|
// Verify data exists
|
|
assert_eq!(count_docs(&server, "dast_targets").await, 1);
|
|
assert_eq!(count_docs(&server, "pentest_sessions").await, 1);
|
|
assert_eq!(count_docs(&server, "attack_chain_nodes").await, 1);
|
|
assert_eq!(count_docs(&server, "dast_findings").await, 1);
|
|
|
|
// Delete the repo
|
|
let resp = server
|
|
.delete(&format!("/api/v1/repositories/{repo_id}"))
|
|
.await;
|
|
assert_eq!(resp.status(), 200);
|
|
|
|
// All downstream data should be gone
|
|
assert_eq!(count_docs(&server, "dast_targets").await, 0);
|
|
assert_eq!(count_docs(&server, "pentest_sessions").await, 0);
|
|
assert_eq!(count_docs(&server, "attack_chain_nodes").await, 0);
|
|
assert_eq!(count_docs(&server, "dast_findings").await, 0);
|
|
|
|
server.cleanup().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn delete_repo_cascades_sast_findings_and_sbom() {
|
|
let server = TestServer::start().await;
|
|
|
|
// Create a repo
|
|
let resp = server
|
|
.post(
|
|
"/api/v1/repositories",
|
|
&json!({
|
|
"name": "sast-cascade",
|
|
"git_url": "https://github.com/example/sast-cascade.git",
|
|
}),
|
|
)
|
|
.await;
|
|
let body: serde_json::Value = resp.json().await.unwrap();
|
|
let repo_id = body["data"]["id"].as_str().unwrap().to_string();
|
|
|
|
// Insert SAST finding and SBOM entry
|
|
let mongodb_uri = std::env::var("TEST_MONGODB_URI")
|
|
.unwrap_or_else(|_| "mongodb://root:example@localhost:27017/?authSource=admin".into());
|
|
let client = mongodb::Client::with_uri_str(&mongodb_uri).await.unwrap();
|
|
let db = client.database(&server.db_name());
|
|
let now = mongodb::bson::DateTime::now();
|
|
|
|
db.collection::<mongodb::bson::Document>("findings")
|
|
.insert_one(mongodb::bson::doc! {
|
|
"repo_id": &repo_id,
|
|
"fingerprint": "fp-test-1",
|
|
"scanner": "semgrep",
|
|
"scan_type": "sast",
|
|
"title": "SQL Injection",
|
|
"description": "desc",
|
|
"severity": "critical",
|
|
"status": "open",
|
|
"created_at": now,
|
|
"updated_at": now,
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
db.collection::<mongodb::bson::Document>("sbom_entries")
|
|
.insert_one(mongodb::bson::doc! {
|
|
"repo_id": &repo_id,
|
|
"name": "lodash",
|
|
"version": "4.17.20",
|
|
"package_manager": "npm",
|
|
"known_vulnerabilities": [],
|
|
})
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(count_docs(&server, "findings").await, 1);
|
|
assert_eq!(count_docs(&server, "sbom_entries").await, 1);
|
|
|
|
// Delete repo
|
|
server
|
|
.delete(&format!("/api/v1/repositories/{repo_id}"))
|
|
.await;
|
|
|
|
// Both should be gone
|
|
assert_eq!(count_docs(&server, "findings").await, 0);
|
|
assert_eq!(count_docs(&server, "sbom_entries").await, 0);
|
|
|
|
server.cleanup().await;
|
|
}
|