fix: format and lint
CI / Format (push) Successful in 48s
CI / Test (push) Failing after 7m9s
CI / Check (push) Successful in 12m6s
CI / Clippy (push) Successful in 12m10s

This commit is contained in:
Guillaume GRABÉ
2026-06-08 23:51:29 +02:00
parent 6400518beb
commit 1302ce412c
27 changed files with 563 additions and 395 deletions
+35 -24
View File
@@ -95,17 +95,18 @@ impl ProductHandler {
let mapped = self.field_mapper.map_all(entity).await?;
// Extract name/description from mapped fields or directly from entity metadata
let name = mapped.get("name").cloned()
let name = mapped
.get("name")
.cloned()
.unwrap_or_else(|| json!(self.extract_name(entity)));
let description = mapped.get("description").cloned()
.unwrap_or_else(|| {
let desc = self.extract_description(entity);
if desc.is_empty() {
json!(format!("Managed by Charybdis (entity: {})", entity.id))
} else {
json!(desc)
}
});
let description = mapped.get("description").cloned().unwrap_or_else(|| {
let desc = self.extract_description(entity);
if desc.is_empty() {
json!(format!("Managed by Charybdis (entity: {})", entity.id))
} else {
json!(desc)
}
});
// Build DefectDojo product payload
let mut payload = json!({
@@ -315,7 +316,10 @@ impl ProductHandler {
match &group.spec {
Some(Spec::GroupSpec(s)) => s.members.clone(),
_ => {
warn!("Group '{}' has no GroupSpec — cannot resolve members", owner);
warn!(
"Group '{}' has no GroupSpec — cannot resolve members",
owner
);
return Ok(resolved_users);
}
}
@@ -382,11 +386,7 @@ impl ProductHandler {
}
/// Assign resolved users as product members in DefectDojo
async fn assign_owner_to_product(
&self,
product_id: i32,
owner: &str,
) -> Result<Vec<i32>> {
async fn assign_owner_to_product(&self, product_id: i32, owner: &str) -> Result<Vec<i32>> {
let resolved = self.resolve_owner_to_users(owner).await?;
if resolved.is_empty() {
@@ -471,9 +471,7 @@ impl ProductHandler {
fn extract_owner(&self, entity: &Entity) -> Option<String> {
use charybdis::charybdis::entities::entity;
match &entity.spec {
Some(entity::Spec::ComponentSpec(s)) if !s.owner.is_empty() => {
Some(s.owner.clone())
}
Some(entity::Spec::ComponentSpec(s)) if !s.owner.is_empty() => Some(s.owner.clone()),
_ => None,
}
}
@@ -501,7 +499,10 @@ impl ResourceHandler for ProductHandler {
// Build annotations to store DefectDojo product ID
let mut annotations = HashMap::new();
annotations.insert("defectdojo.com/product-id".to_string(), product_id.to_string());
annotations.insert(
"defectdojo.com/product-id".to_string(),
product_id.to_string(),
);
// Auto-create engagement if enabled
if self.config.default_engagement.auto_create {
@@ -516,7 +517,10 @@ impl ResourceHandler for ProductHandler {
.await
{
Ok(engagement_id) => {
annotations.insert("defectdojo.com/engagement-id".to_string(), engagement_id.to_string());
annotations.insert(
"defectdojo.com/engagement-id".to_string(),
engagement_id.to_string(),
);
info!(
"Auto-created engagement {} for product {} (entity: {})",
engagement_id, product_id, entity.id
@@ -552,7 +556,9 @@ impl ResourceHandler for ProductHandler {
}
// Update only annotations in Charybdis
self.repository.update_annotations(&entity.id, annotations).await?;
self.repository
.update_annotations(&entity.id, annotations)
.await?;
// Don't create a new entity, just return None
Ok(None)
@@ -571,8 +577,13 @@ impl ResourceHandler for ProductHandler {
// Update only annotations with product ID
let mut annotations = HashMap::new();
annotations.insert("defectdojo.com/product-id".to_string(), product_id.to_string());
self.repository.update_annotations(&entity.id, annotations).await?;
annotations.insert(
"defectdojo.com/product-id".to_string(),
product_id.to_string(),
);
self.repository
.update_annotations(&entity.id, annotations)
.await?;
}
Ok(())
+70 -61
View File
@@ -8,7 +8,9 @@ use charybdis::charybdis::entities::entity::{Metadata, Spec};
use charybdis::charybdis::entities::Entity;
use charybdis::database::{ensure_schema, EntityRepository};
use charybdis::plugins::ResourceHandler;
use charybdis_defectdojo::{DefectDojoClient, DefectDojoConfig, EngagementConfig, OwnerResolutionConfig};
use charybdis_defectdojo::{
DefectDojoClient, DefectDojoConfig, EngagementConfig, OwnerResolutionConfig,
};
use serde_json::json;
use sqlx::PgPool;
use std::collections::HashMap;
@@ -17,9 +19,11 @@ use wiremock::matchers::{method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
async fn setup_db() -> PgPool {
let url = std::env::var("DATABASE_URL")
.expect("DATABASE_URL must be set for integration tests");
let pool = PgPool::connect(&url).await.expect("Failed to connect to test database");
let url =
std::env::var("DATABASE_URL").expect("DATABASE_URL must be set for integration tests");
let pool = PgPool::connect(&url)
.await
.expect("Failed to connect to test database");
ensure_schema(&pool).await.expect("Failed to create schema");
// Clean up from previous test runs
@@ -118,13 +122,11 @@ async fn test_product_creation_on_component_create() {
config.owner_resolution.assign_all_members = false;
let client = DefectDojoClient::new(mock_server.uri(), "test-token".to_string()).unwrap();
let engagement_handler = Arc::new(
charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
),
);
let engagement_handler = Arc::new(charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
));
let handler = charybdis_defectdojo::handlers::ProductHandler::new(
client,
@@ -136,7 +138,10 @@ async fn test_product_creation_on_component_create() {
// Create the entity in the database first (returns entity with real UUID)
let entity = make_component_entity("", "payment-api", "team-payments");
let entity = repository.create(&entity).await.expect("Failed to create entity");
let entity = repository
.create(&entity)
.await
.expect("Failed to create entity");
// Trigger handler
let result = handler.handle_create(&entity).await;
@@ -178,13 +183,11 @@ async fn test_product_update_with_existing_product_id() {
let config = make_config(&mock_server.uri());
let client = DefectDojoClient::new(mock_server.uri(), "test-token".to_string()).unwrap();
let engagement_handler = Arc::new(
charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
),
);
let engagement_handler = Arc::new(charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
));
let handler = charybdis_defectdojo::handlers::ProductHandler::new(
client,
@@ -196,11 +199,13 @@ async fn test_product_update_with_existing_product_id() {
// Create entity with existing product-id annotation
let mut entity = make_component_entity("", "payment-api", "team-payments");
entity.annotations.insert(
"defectdojo.com/product-id".to_string(),
"42".to_string(),
);
let entity = repository.create(&entity).await.expect("Failed to create entity");
entity
.annotations
.insert("defectdojo.com/product-id".to_string(), "42".to_string());
let entity = repository
.create(&entity)
.await
.expect("Failed to create entity");
// Trigger update handler
let result = handler.handle_update(&entity).await;
@@ -228,13 +233,11 @@ async fn test_product_deletion() {
let config = make_config(&mock_server.uri());
let client = DefectDojoClient::new(mock_server.uri(), "test-token".to_string()).unwrap();
let engagement_handler = Arc::new(
charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
),
);
let engagement_handler = Arc::new(charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
));
let handler = charybdis_defectdojo::handlers::ProductHandler::new(
client,
@@ -246,11 +249,13 @@ async fn test_product_deletion() {
// Entity with product-id annotation
let mut entity = make_component_entity("", "payment-api", "team-payments");
entity.annotations.insert(
"defectdojo.com/product-id".to_string(),
"42".to_string(),
);
let entity = repository.create(&entity).await.expect("Failed to create entity");
entity
.annotations
.insert("defectdojo.com/product-id".to_string(), "42".to_string());
let entity = repository
.create(&entity)
.await
.expect("Failed to create entity");
// Trigger delete handler
let result = handler.handle_delete(&entity).await;
@@ -423,7 +428,10 @@ async fn test_owner_resolution_assigns_product_members() {
member_of: vec!["backend-team".to_string()],
})),
};
alice.annotations.insert("keycloak.com/email".to_string(), "alice@example.com".to_string());
alice.annotations.insert(
"keycloak.com/email".to_string(),
"alice@example.com".to_string(),
);
repository.create(&alice).await.unwrap();
// Create User "bob" with keycloak email annotation
@@ -448,18 +456,19 @@ async fn test_owner_resolution_assigns_product_members() {
member_of: vec!["backend-team".to_string()],
})),
};
bob.annotations.insert("keycloak.com/email".to_string(), "bob@example.com".to_string());
bob.annotations.insert(
"keycloak.com/email".to_string(),
"bob@example.com".to_string(),
);
repository.create(&bob).await.unwrap();
// Create handlers
let client = DefectDojoClient::new(mock_server.uri(), "test-token".to_string()).unwrap();
let engagement_handler = Arc::new(
charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
),
);
let engagement_handler = Arc::new(charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
));
let handler = charybdis_defectdojo::handlers::ProductHandler::new(
client,
@@ -489,7 +498,9 @@ async fn test_owner_resolution_assigns_product_members() {
);
// Owner member IDs should be set
assert!(
updated.annotations.contains_key("defectdojo.com/owner-member-ids"),
updated
.annotations
.contains_key("defectdojo.com/owner-member-ids"),
"Expected owner-member-ids annotation"
);
}
@@ -529,13 +540,11 @@ async fn test_product_creation_without_engagement() {
config.default_engagement.auto_create = false;
let client = DefectDojoClient::new(mock_server.uri(), "test-token".to_string()).unwrap();
let engagement_handler = Arc::new(
charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
),
);
let engagement_handler = Arc::new(charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
));
let handler = charybdis_defectdojo::handlers::ProductHandler::new(
client,
@@ -557,7 +566,9 @@ async fn test_product_creation_without_engagement() {
updated.annotations.get("defectdojo.com/product-id"),
Some(&"55".to_string())
);
assert!(!updated.annotations.contains_key("defectdojo.com/engagement-id"));
assert!(!updated
.annotations
.contains_key("defectdojo.com/engagement-id"));
}
// ──────────────────────────────────────────────────────────────
@@ -583,13 +594,11 @@ async fn test_product_creation_handles_api_error() {
let config = make_config(&mock_server.uri());
let client = DefectDojoClient::new(mock_server.uri(), "test-token".to_string()).unwrap();
let engagement_handler = Arc::new(
charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
),
);
let engagement_handler = Arc::new(charybdis_defectdojo::handlers::EngagementHandler::new(
client.clone(),
repository.clone(),
HashMap::new(),
));
let handler = charybdis_defectdojo::handlers::ProductHandler::new(
client,
+14 -4
View File
@@ -179,7 +179,8 @@ impl KeycloakClient {
pub async fn authenticate(&self) -> Result<()> {
info!("Authenticating with Keycloak (client credentials grant)");
let response = self.raw_client
let response = self
.raw_client
.post(&self.token_url)
.form(&[
("grant_type", "client_credentials"),
@@ -222,7 +223,8 @@ impl KeycloakClient {
let url = format!("{}/{}", self.http.base_url(), path.trim_start_matches('/'));
let response = self.raw_client
let response = self
.raw_client
.get(&url)
.header("Authorization", format!("Bearer {}", token))
.header("Accept", "application/json")
@@ -258,7 +260,11 @@ impl KeycloakClient {
first += page_size;
}
info!("Fetched {} users from Keycloak realm '{}'", all_users.len(), self.realm);
info!(
"Fetched {} users from Keycloak realm '{}'",
all_users.len(),
self.realm
);
Ok(all_users)
}
@@ -266,7 +272,11 @@ impl KeycloakClient {
pub async fn fetch_groups(&self) -> Result<Vec<KeycloakGroup>> {
let response = self.get("groups?briefRepresentation=false").await?;
let groups: Vec<KeycloakGroup> = serde_json::from_value(response)?;
info!("Fetched {} top-level groups from Keycloak realm '{}'", groups.len(), self.realm);
info!(
"Fetched {} top-level groups from Keycloak realm '{}'",
groups.len(),
self.realm
);
Ok(groups)
}
+1 -4
View File
@@ -194,10 +194,7 @@ fn build_group_entity(
let mut annotations = HashMap::new();
annotations.insert("keycloak.com/group-id".to_string(), kc_group.id.clone());
annotations.insert(
"keycloak.com/group-path".to_string(),
kc_group.path.clone(),
);
annotations.insert("keycloak.com/group-path".to_string(), kc_group.path.clone());
annotations.insert("keycloak.com/realm".to_string(), config.realm.clone());
annotations.insert(
"keycloak.com/member-count".to_string(),