From 459689b4e48a1e77f06b9fcc0f67d8861ead6ffb Mon Sep 17 00:00:00 2001 From: "bxf12315@gmail.com" Date: Mon, 3 Nov 2025 22:33:46 +0800 Subject: [PATCH 1/6] TC-3071 scale test for put and patch advisory lables --- scenarios/full-20250604.json5 | 4 ++- src/main.rs | 4 +++ src/restapi.rs | 54 ++++++++++++++++++++++++++++++++++- src/scenario/mod.rs | 35 +++++++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) diff --git a/scenarios/full-20250604.json5 b/scenarios/full-20250604.json5 index 5ab8e87..77ff498 100644 --- a/scenarios/full-20250604.json5 +++ b/scenarios/full-20250604.json5 @@ -113,5 +113,7 @@ "urn:uuid:01973122-3bdd-78a3-898a-fb67c06387ed" ], "download_advisory": "24ae57c3-4b57-4f4e-82c1-83ae26059a89", - "get_advisory": "24ae57c3-4b57-4f4e-82c1-83ae26059a89" + "get_advisory": "24ae57c3-4b57-4f4e-82c1-83ae26059a89", + "put_advisory_lables": "urn:uuid:24ae57c3-4b57-4f4e-82c1-83ae26059a89", + "patch_advisory_lables": "urn:uuid:9ffb1a1b-14e0-4abb-943a-76906daa54aa" } diff --git a/src/main.rs b/src/main.rs index b11c30d..15c3bee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -192,6 +192,10 @@ async fn main() -> Result<(), anyhow::Error> { tx!(s.post_vulnerability_analyze?(scenario.analyze_purl.clone())); tx!(s.get_purl_details?(scenario.get_purl_details.clone())); tx!(s.get_recommendations?(scenario.get_recommendations.clone())); + tx!(s.put_advisory_labels?(scenario.put_advisory_lables.clone())); + tx!(s.patch_advisory_labels?( + scenario.patch_advisory_lables.clone() + )); tx!(s.download_advisory?(scenario.download_advisory.clone())); tx!(s.get_advisory?(scenario.get_advisory.clone())); diff --git a/src/restapi.rs b/src/restapi.rs index a99de09..5f74b27 100644 --- a/src/restapi.rs +++ b/src/restapi.rs @@ -1,5 +1,7 @@ use crate::utils::DisplayVec; -use goose::goose::{GooseUser, TransactionResult}; +use goose::goose::{GooseMethod, GooseRequest, GooseUser, TransactionResult}; +use reqwest::{Client, RequestBuilder}; + use serde_json::json; use std::sync::{ Arc, @@ -67,6 +69,56 @@ pub async fn search_advisory(user: &mut GooseUser) -> TransactionResult { Ok(()) } +async fn send_advisory_label_request( + advisory_id: String, + user: &mut GooseUser, + method: GooseMethod, + source: &str, + client_method: fn(&Client, String) -> RequestBuilder, +) -> TransactionResult { + let path = format!("/api/v2/advisory/{}/label", advisory_id); + let json = json!({ + "source": source, + "foo": "bar", + "space": "with space", + "empty": "", + }); + + let url = user.build_url(&path)?; + + let reqwest_request_builder = client_method(&user.client, url); + let goose_request = GooseRequest::builder() + .method(method) + .path(path.as_str()) + .set_request_builder(reqwest_request_builder.json(&json)) + .build(); + let _response = user.request(goose_request).await?; + + Ok(()) +} + +pub async fn put_advisory_labels(advisory_id: String, user: &mut GooseUser) -> TransactionResult { + send_advisory_label_request( + advisory_id, + user, + GooseMethod::Put, + "It's a put request", + Client::put, + ) + .await +} + +pub async fn patch_advisory_labels(advisory_id: String, user: &mut GooseUser) -> TransactionResult { + send_advisory_label_request( + advisory_id, + user, + GooseMethod::Patch, + "It's a patch request", + Client::patch, + ) + .await +} + pub async fn list_importer(user: &mut GooseUser) -> TransactionResult { let _response = user.get("/api/v2/importer").await?; diff --git a/src/scenario/mod.rs b/src/scenario/mod.rs index a370d2b..34def0c 100644 --- a/src/scenario/mod.rs +++ b/src/scenario/mod.rs @@ -102,6 +102,11 @@ pub(crate) struct Scenario { #[serde(with = "required")] pub get_advisory: Option, + + pub put_advisory_lables: Option, + + #[serde(with = "required")] + pub patch_advisory_lables: Option, } impl Scenario { @@ -144,6 +149,12 @@ impl Scenario { let download_advisory = Some(loader.download_advisory().await?); let get_advisory = Some(loader.download_advisory().await?); + let put_advisory_lables = Some(format!("urn:uuid:{}", loader.put_advisory_lables().await?)); + let patch_advisory_lables = Some(format!( + "urn:uuid:{}", + loader.patch_advisory_lables().await? + )); + Ok(Self { get_sbom: large_sbom_digest.clone(), get_sbom_advisories: large_sbom_digest.clone(), @@ -160,6 +171,8 @@ impl Scenario { delete_sbom_pool, download_advisory, get_advisory, + put_advisory_lables, + patch_advisory_lables, }) } } @@ -376,6 +389,28 @@ FROM public.advisory order by modified desc limit 1;"#, ) .await } + + /// A advisory ID for put labels + pub async fn put_advisory_lables(&self) -> anyhow::Result { + self.find( + r#" +SELECT id::text as result +FROM public.advisory where labels is not null order by modified desc limit 1; +"#, + ) + .await + } + + /// A advisory ID for patch labels + pub async fn patch_advisory_lables(&self) -> anyhow::Result { + self.find( + r#" +SELECT id::text as result +FROM public.advisory where labels is not null order by modified desc OFFSET 1 limit 1; +"#, + ) + .await + } } #[cfg(test)] From 7e12e20111e070c740945500f861a86957e0e27f Mon Sep 17 00:00:00 2001 From: "bxf12315@gmail.com" Date: Wed, 12 Nov 2025 13:59:04 +0800 Subject: [PATCH 2/6] Add a feature to randomly retrieve IDs from the ID pool. --- scenarios/full-20250604.json5 | 46 ++++++++++++++++++++++- src/main.rs | 23 +++++++++--- src/restapi.rs | 19 ++++++++-- src/scenario/mod.rs | 70 +++++++++++++++++++++-------------- 4 files changed, 120 insertions(+), 38 deletions(-) diff --git a/scenarios/full-20250604.json5 b/scenarios/full-20250604.json5 index 77ff498..699efda 100644 --- a/scenarios/full-20250604.json5 +++ b/scenarios/full-20250604.json5 @@ -114,6 +114,48 @@ ], "download_advisory": "24ae57c3-4b57-4f4e-82c1-83ae26059a89", "get_advisory": "24ae57c3-4b57-4f4e-82c1-83ae26059a89", - "put_advisory_lables": "urn:uuid:24ae57c3-4b57-4f4e-82c1-83ae26059a89", - "patch_advisory_lables": "urn:uuid:9ffb1a1b-14e0-4abb-943a-76906daa54aa" + "put_advisory_lables": [ + "urn:uuid:24ae57c3-4b57-4f4e-82c1-83ae26059a89", + "urn:uuid:9ffb1a1b-14e0-4abb-943a-76906daa54aa", + "urn:uuid:7c932cc3-aa83-4bb4-bbaf-6030a198e271", + "urn:uuid:cac4862f-c87a-4a7d-8d67-f2e0db8f0273", + "urn:uuid:f80b5b7e-f2eb-4921-b254-a4873d69a9e9", + "urn:uuid:858a53a2-347a-491e-a86a-e24b9a4c1436", + "urn:uuid:15637501-2a0b-4076-aec3-d92fa2b959bf", + "urn:uuid:5364258d-418e-4cd8-add7-248207c4836f", + "urn:uuid:5066569d-c5a3-4d90-8cdc-d8680ee52db0", + "urn:uuid:869b1646-2f27-4b7a-b4e0-f6783651135d", + "urn:uuid:04313720-b5eb-40bc-b545-1f813f6e53b9", + "urn:uuid:2c6d41c1-f39d-47df-b37f-12b08af41577", + "urn:uuid:99bac490-3ff3-4c10-8466-2cc4947ac29c", + "urn:uuid:90a53b25-118d-4c2a-9b8d-a38ac1a85ab0", + "urn:uuid:4d3fd5be-eeb5-46f7-a55d-871be40c9837", + "urn:uuid:fd88f7cd-8b9e-4bba-b92b-726732545e82", + "urn:uuid:06a769a4-1f30-4fe5-9113-72080301e8ed", + "urn:uuid:871263d1-e7c3-4f4e-98b2-04aedeae048c", + "urn:uuid:35dc833f-fbb7-43f0-852a-9db14cd3a0f4", + "urn:uuid:acfbea2f-08d0-4f2f-aa22-12e635956dce" + ], + "patch_advisory_lables": [ + "urn:uuid:8a28d2fc-3e91-4989-add8-b9dd78fcf37d", + "urn:uuid:9e3eee98-a032-424d-9d0d-1645faca9d24", + "urn:uuid:66dde1a5-9575-46a4-8f7a-f6ab9904f0d1", + "urn:uuid:2070a289-c6bb-4a6c-bdb2-6fc951743e55", + "urn:uuid:59751bb1-b205-4ea4-9832-a48ab69e39e6", + "urn:uuid:0dd5f452-af2b-4740-905e-a00f22fffa7e", + "urn:uuid:08467200-8c82-4d38-9920-3cbf6c2d0568", + "urn:uuid:be6b4acf-fe7b-46a2-9f4c-fdd497d43188", + "urn:uuid:6ddb2cc0-bb10-4f23-96bf-cc409263d80d", + "urn:uuid:3973cb57-325a-4574-a60c-1910a30b44ac", + "urn:uuid:074881e9-d328-402c-8bd8-13df42a590e5", + "urn:uuid:2e8008f1-e3d2-401d-8ef6-87bad7d328f0", + "urn:uuid:d30949a9-1f9b-4005-93a8-288a3578e7f7", + "urn:uuid:8aa4555d-3723-4c4f-aee8-69c4007ec5fd", + "urn:uuid:6076bbcb-b352-4bcb-b2e3-8889e0310724", + "urn:uuid:5390d139-c00c-4d3a-858a-833d4b571d9a", + "urn:uuid:f1479e4b-839e-4d53-929c-d08b6b907430", + "urn:uuid:96c1b100-ec62-46c0-9156-819c37bd0445", + "urn:uuid:148db4ee-55ca-4292-b55d-a0c0a9d83b9c", + "urn:uuid:e98e73ab-4cfe-4861-8b46-06df5a84198f" + ] } diff --git a/src/main.rs b/src/main.rs index 15c3bee..0d07c1a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -192,10 +192,22 @@ async fn main() -> Result<(), anyhow::Error> { tx!(s.post_vulnerability_analyze?(scenario.analyze_purl.clone())); tx!(s.get_purl_details?(scenario.get_purl_details.clone())); tx!(s.get_recommendations?(scenario.get_recommendations.clone())); - tx!(s.put_advisory_labels?(scenario.put_advisory_lables.clone())); - tx!(s.patch_advisory_labels?( - scenario.patch_advisory_lables.clone() - )); + + // Register put Advisory labels transaction if pool is available + let put_advisory_labels_counter = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + if let Some(_advisory_ids) = scenario.put_advisory_lables.clone() { + tx!(s.put_advisory_labels?(scenario.put_advisory_lables.clone(), + put_advisory_labels_counter.clone()), + name: format!("put_advisory_labels")); + } + + // Register patch Advisory labels transaction if pool is available + let patch_advisory_labels_counter = Arc::new(std::sync::atomic::AtomicUsize::new(0)); + if let Some(_advisory_ids) = scenario.patch_advisory_lables.clone() { + tx!(s.patch_advisory_labels?(scenario.patch_advisory_lables.clone(), + patch_advisory_labels_counter.clone()), + name: format!("patch_advisory_labels")); + } tx!(s.download_advisory?(scenario.download_advisory.clone())); tx!(s.get_advisory?(scenario.get_advisory.clone())); @@ -228,7 +240,8 @@ async fn main() -> Result<(), anyhow::Error> { tx!(s.delete_sbom_from_pool_sequential?( scenario.delete_sbom_pool.clone(), delete_counter.clone() - ), name: format!("delete_sbom_from_pool_sequential[{} SBOMs]", pool.len())) + ), + name: format!("delete_sbom_from_pool_sequential[{} SBOMs]", pool.len())) } s }) diff --git a/src/restapi.rs b/src/restapi.rs index 5f74b27..d3bdfad 100644 --- a/src/restapi.rs +++ b/src/restapi.rs @@ -69,6 +69,7 @@ pub async fn search_advisory(user: &mut GooseUser) -> TransactionResult { Ok(()) } +/// Send Advisory labels request async fn send_advisory_label_request( advisory_id: String, user: &mut GooseUser, @@ -97,9 +98,14 @@ async fn send_advisory_label_request( Ok(()) } -pub async fn put_advisory_labels(advisory_id: String, user: &mut GooseUser) -> TransactionResult { +/// Send Advisory labels request using PUT method +pub async fn put_advisory_labels( + advisory_ids: Vec, + counter: Arc, + user: &mut GooseUser, +) -> TransactionResult { send_advisory_label_request( - advisory_id, + advisory_ids[counter.fetch_add(1, Ordering::Relaxed) % advisory_ids.len()].clone(), user, GooseMethod::Put, "It's a put request", @@ -108,9 +114,14 @@ pub async fn put_advisory_labels(advisory_id: String, user: &mut GooseUser) -> T .await } -pub async fn patch_advisory_labels(advisory_id: String, user: &mut GooseUser) -> TransactionResult { +/// Send Advisory labels request using PATCH method +pub async fn patch_advisory_labels( + advisory_ids: Vec, + counter: Arc, + user: &mut GooseUser, +) -> TransactionResult { send_advisory_label_request( - advisory_id, + advisory_ids[counter.fetch_add(1, Ordering::Relaxed) % advisory_ids.len()].clone(), user, GooseMethod::Patch, "It's a patch request", diff --git a/src/scenario/mod.rs b/src/scenario/mod.rs index 34def0c..01f8f54 100644 --- a/src/scenario/mod.rs +++ b/src/scenario/mod.rs @@ -103,10 +103,11 @@ pub(crate) struct Scenario { #[serde(with = "required")] pub get_advisory: Option, - pub put_advisory_lables: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub put_advisory_lables: Option>, - #[serde(with = "required")] - pub patch_advisory_lables: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub patch_advisory_lables: Option>, } impl Scenario { @@ -149,11 +150,22 @@ impl Scenario { let download_advisory = Some(loader.download_advisory().await?); let get_advisory = Some(loader.download_advisory().await?); - let put_advisory_lables = Some(format!("urn:uuid:{}", loader.put_advisory_lables().await?)); - let patch_advisory_lables = Some(format!( - "urn:uuid:{}", - loader.patch_advisory_lables().await? - )); + let put_advisory_lables = Some( + loader + .put_advisory_lables() + .await? + .iter() + .map(|advisory_id| format!("urn:uuid:{advisory_id}")) + .collect(), + ); + let patch_advisory_lables = Some( + loader + .patch_advisory_lables() + .await? + .iter() + .map(|advisory_id| format!("urn:uuid:{advisory_id}")) + .collect(), + ); Ok(Self { get_sbom: large_sbom_digest.clone(), @@ -389,27 +401,31 @@ FROM public.advisory order by modified desc limit 1;"#, ) .await } - - /// A advisory ID for put labels - pub async fn put_advisory_lables(&self) -> anyhow::Result { - self.find( - r#" -SELECT id::text as result -FROM public.advisory where labels is not null order by modified desc limit 1; -"#, - ) - .await + + /// Advisory IDs for put labels + pub async fn put_advisory_lables(&self) -> anyhow::Result> { + let mut db = crate::db::connect(&self.db).await?; + + let rows = sqlx::query("SELECT id::text as id FROM public.advisory where labels is not null order by modified desc limit 20;") + .fetch_all(&mut db) + .await?; + Ok(rows + .into_iter() + .map(|row| row.get::("id")) + .collect()) } - /// A advisory ID for patch labels - pub async fn patch_advisory_lables(&self) -> anyhow::Result { - self.find( - r#" -SELECT id::text as result -FROM public.advisory where labels is not null order by modified desc OFFSET 1 limit 1; -"#, - ) - .await + /// Advisory IDs for patch labels + pub async fn patch_advisory_lables(&self) -> anyhow::Result> { + let mut db = crate::db::connect(&self.db).await?; + + let rows = sqlx::query("SELECT id::text as id FROM public.advisory where labels is not null order by modified desc OFFSET 20 limit 20;") + .fetch_all(&mut db) + .await?; + Ok(rows + .into_iter() + .map(|row| row.get::("id")) + .collect()) } } From e195f7d11a06ed2bf5d513b371c83af80ef18750 Mon Sep 17 00:00:00 2001 From: "bxf12315@gmail.com" Date: Thu, 27 Nov 2025 22:56:05 +0800 Subject: [PATCH 3/6] =?UTF-8?q?TC-3071=20randomly=20fetch=20advisory=20ID?= =?UTF-8?q?=EF=BB=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + Cargo.toml | 1 + scenarios/full-20250604.json5 | 46 +---------------- src/main.rs | 20 ++------ src/restapi.rs | 96 ++++++++++++++++++++++++++++++----- src/scenario/mod.rs | 51 ------------------- 6 files changed, 89 insertions(+), 126 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e81a9e..f433bbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1353,6 +1353,7 @@ dependencies = [ "log", "openid", "packageurl", + "rand 0.8.5", "reqwest", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index e48a30f..7e8a700 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ sqlx = { version = "0.8", features = ["runtime-tokio", "tls-native-tls", "uuid", tokio = { version = "1.43.1", features = ["sync"] } urlencoding = "2" packageurl = "0.4.2" +rand = "0.8" [features] default = ["postgres"] diff --git a/scenarios/full-20250604.json5 b/scenarios/full-20250604.json5 index 699efda..5ab8e87 100644 --- a/scenarios/full-20250604.json5 +++ b/scenarios/full-20250604.json5 @@ -113,49 +113,5 @@ "urn:uuid:01973122-3bdd-78a3-898a-fb67c06387ed" ], "download_advisory": "24ae57c3-4b57-4f4e-82c1-83ae26059a89", - "get_advisory": "24ae57c3-4b57-4f4e-82c1-83ae26059a89", - "put_advisory_lables": [ - "urn:uuid:24ae57c3-4b57-4f4e-82c1-83ae26059a89", - "urn:uuid:9ffb1a1b-14e0-4abb-943a-76906daa54aa", - "urn:uuid:7c932cc3-aa83-4bb4-bbaf-6030a198e271", - "urn:uuid:cac4862f-c87a-4a7d-8d67-f2e0db8f0273", - "urn:uuid:f80b5b7e-f2eb-4921-b254-a4873d69a9e9", - "urn:uuid:858a53a2-347a-491e-a86a-e24b9a4c1436", - "urn:uuid:15637501-2a0b-4076-aec3-d92fa2b959bf", - "urn:uuid:5364258d-418e-4cd8-add7-248207c4836f", - "urn:uuid:5066569d-c5a3-4d90-8cdc-d8680ee52db0", - "urn:uuid:869b1646-2f27-4b7a-b4e0-f6783651135d", - "urn:uuid:04313720-b5eb-40bc-b545-1f813f6e53b9", - "urn:uuid:2c6d41c1-f39d-47df-b37f-12b08af41577", - "urn:uuid:99bac490-3ff3-4c10-8466-2cc4947ac29c", - "urn:uuid:90a53b25-118d-4c2a-9b8d-a38ac1a85ab0", - "urn:uuid:4d3fd5be-eeb5-46f7-a55d-871be40c9837", - "urn:uuid:fd88f7cd-8b9e-4bba-b92b-726732545e82", - "urn:uuid:06a769a4-1f30-4fe5-9113-72080301e8ed", - "urn:uuid:871263d1-e7c3-4f4e-98b2-04aedeae048c", - "urn:uuid:35dc833f-fbb7-43f0-852a-9db14cd3a0f4", - "urn:uuid:acfbea2f-08d0-4f2f-aa22-12e635956dce" - ], - "patch_advisory_lables": [ - "urn:uuid:8a28d2fc-3e91-4989-add8-b9dd78fcf37d", - "urn:uuid:9e3eee98-a032-424d-9d0d-1645faca9d24", - "urn:uuid:66dde1a5-9575-46a4-8f7a-f6ab9904f0d1", - "urn:uuid:2070a289-c6bb-4a6c-bdb2-6fc951743e55", - "urn:uuid:59751bb1-b205-4ea4-9832-a48ab69e39e6", - "urn:uuid:0dd5f452-af2b-4740-905e-a00f22fffa7e", - "urn:uuid:08467200-8c82-4d38-9920-3cbf6c2d0568", - "urn:uuid:be6b4acf-fe7b-46a2-9f4c-fdd497d43188", - "urn:uuid:6ddb2cc0-bb10-4f23-96bf-cc409263d80d", - "urn:uuid:3973cb57-325a-4574-a60c-1910a30b44ac", - "urn:uuid:074881e9-d328-402c-8bd8-13df42a590e5", - "urn:uuid:2e8008f1-e3d2-401d-8ef6-87bad7d328f0", - "urn:uuid:d30949a9-1f9b-4005-93a8-288a3578e7f7", - "urn:uuid:8aa4555d-3723-4c4f-aee8-69c4007ec5fd", - "urn:uuid:6076bbcb-b352-4bcb-b2e3-8889e0310724", - "urn:uuid:5390d139-c00c-4d3a-858a-833d4b571d9a", - "urn:uuid:f1479e4b-839e-4d53-929c-d08b6b907430", - "urn:uuid:96c1b100-ec62-46c0-9156-819c37bd0445", - "urn:uuid:148db4ee-55ca-4292-b55d-a0c0a9d83b9c", - "urn:uuid:e98e73ab-4cfe-4861-8b46-06df5a84198f" - ] + "get_advisory": "24ae57c3-4b57-4f4e-82c1-83ae26059a89" } diff --git a/src/main.rs b/src/main.rs index 0d07c1a..90e5fef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -180,7 +180,9 @@ async fn main() -> Result<(), anyhow::Error> { .register_transaction(tx!(list_sboms_paginated)) .register_transaction(tx!(get_analysis_status)) .register_transaction(tx!(get_analysis_latest_cpe)) - .register_transaction(tx!(list_advisory_labels)); + .register_transaction(tx!(list_advisory_labels)) + .register_transaction(tx!(patch_advisory_labels)) + .register_transaction(tx!(put_advisory_labels)); tx!(s.get_sbom?(scenario.get_sbom.clone())); tx!(s.get_sbom_advisories?(scenario.get_sbom_advisories.clone())); @@ -193,22 +195,6 @@ async fn main() -> Result<(), anyhow::Error> { tx!(s.get_purl_details?(scenario.get_purl_details.clone())); tx!(s.get_recommendations?(scenario.get_recommendations.clone())); - // Register put Advisory labels transaction if pool is available - let put_advisory_labels_counter = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - if let Some(_advisory_ids) = scenario.put_advisory_lables.clone() { - tx!(s.put_advisory_labels?(scenario.put_advisory_lables.clone(), - put_advisory_labels_counter.clone()), - name: format!("put_advisory_labels")); - } - - // Register patch Advisory labels transaction if pool is available - let patch_advisory_labels_counter = Arc::new(std::sync::atomic::AtomicUsize::new(0)); - if let Some(_advisory_ids) = scenario.patch_advisory_lables.clone() { - tx!(s.patch_advisory_labels?(scenario.patch_advisory_lables.clone(), - patch_advisory_labels_counter.clone()), - name: format!("patch_advisory_labels")); - } - tx!(s.download_advisory?(scenario.download_advisory.clone())); tx!(s.get_advisory?(scenario.get_advisory.clone())); s diff --git a/src/restapi.rs b/src/restapi.rs index d3bdfad..186b885 100644 --- a/src/restapi.rs +++ b/src/restapi.rs @@ -1,14 +1,19 @@ use crate::utils::DisplayVec; -use goose::goose::{GooseMethod, GooseRequest, GooseUser, TransactionResult}; +use goose::goose::{GooseMethod, GooseRequest, GooseUser, TransactionError, TransactionResult}; use reqwest::{Client, RequestBuilder}; +use rand::Rng; use serde_json::json; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; +use tokio::sync::OnceCell; use urlencoding::encode; +// Static variable to store advisory total count +static ADVISORY_TOTAL: OnceCell = OnceCell::const_new(); + pub async fn get_advisory(id: String, user: &mut GooseUser) -> TransactionResult { let uri = format!("/api/v2/advisory/{}", encode(&format!("urn:uuid:{}", id))); @@ -46,6 +51,46 @@ pub async fn list_advisory(user: &mut GooseUser) -> TransactionResult { Ok(()) } +/// Get advisory total count and store it in static OnceCell +async fn get_advisory_total(user: &mut GooseUser) -> Result> { + let response = user.get("/api/v2/advisory").await?; + let json_data = response.response?.json::().await?; + + // Extract total from the response + if let Some(total) = json_data.get("total").and_then(|t| t.as_u64()) { + log::info!("Advisory total count: {}", total); + return Ok(total); + } + + Err(Box::new(TransactionError::Custom( + "Failed to get advisory total count".to_string(), + ))) +} + +/// Get cached advisory total count, fetch if not available using get_or_init +async fn get_cached_advisory_total(user: &mut GooseUser) -> Result> { + // Try to get from cache first + if let Some(&total) = ADVISORY_TOTAL.get() { + return Ok(total); + } + + // If not cached, fetch it and handle errors properly + match get_advisory_total(user).await { + Ok(total) => { + // Store in cache for future use + let _ = ADVISORY_TOTAL.set(total); + Ok(total) + } + Err(e) => { + // Propagate the error with context instead of silently returning 0 + Err(Box::new(TransactionError::Custom(format!( + "Failed to get advisory total count: {}", + e + )))) + } + } +} + pub async fn list_advisory_paginated(user: &mut GooseUser) -> TransactionResult { let _response = user.get("/api/v2/advisory?offset=100&limit=10").await?; @@ -69,6 +114,37 @@ pub async fn search_advisory(user: &mut GooseUser) -> TransactionResult { Ok(()) } +/// List advisory with random offset and limit=1, return advisory ID +async fn list_advisory_random_single( + user: &mut GooseUser, +) -> Result> { + let total = get_cached_advisory_total(user).await?; + // Generate random offset + let offset = rand::thread_rng().gen_range(0..=total); + let url = format!("/api/v2/advisory?offset={}&limit=1", offset); + + let response = user.get(&url).await?; + let json_data = response.response?.json::().await?; + + // Extract advisory ID from the response + if let Some(items) = json_data.get("items").and_then(|i| i.as_array()) { + if let Some(first_item) = items.first() { + if let Some(id) = first_item.get("uuid").and_then(|u| u.as_str()) { + log::info!("Listing advisory with offset {}: {}", offset, id); + return Ok(id.to_string()); + } + } + } + + // Return error if no advisory found + Err(Box::new(TransactionError::Custom(format!( + "No advisory found at offset: {}", + offset + )))) +} + +// + /// Send Advisory labels request async fn send_advisory_label_request( advisory_id: String, @@ -99,13 +175,10 @@ async fn send_advisory_label_request( } /// Send Advisory labels request using PUT method -pub async fn put_advisory_labels( - advisory_ids: Vec, - counter: Arc, - user: &mut GooseUser, -) -> TransactionResult { +pub async fn put_advisory_labels(user: &mut GooseUser) -> TransactionResult { + let advisory_id = list_advisory_random_single(user).await?; send_advisory_label_request( - advisory_ids[counter.fetch_add(1, Ordering::Relaxed) % advisory_ids.len()].clone(), + advisory_id, user, GooseMethod::Put, "It's a put request", @@ -115,13 +188,10 @@ pub async fn put_advisory_labels( } /// Send Advisory labels request using PATCH method -pub async fn patch_advisory_labels( - advisory_ids: Vec, - counter: Arc, - user: &mut GooseUser, -) -> TransactionResult { +pub async fn patch_advisory_labels(user: &mut GooseUser) -> TransactionResult { + let advisory_id = list_advisory_random_single(user).await?; send_advisory_label_request( - advisory_ids[counter.fetch_add(1, Ordering::Relaxed) % advisory_ids.len()].clone(), + advisory_id, user, GooseMethod::Patch, "It's a patch request", diff --git a/src/scenario/mod.rs b/src/scenario/mod.rs index 01f8f54..a370d2b 100644 --- a/src/scenario/mod.rs +++ b/src/scenario/mod.rs @@ -102,12 +102,6 @@ pub(crate) struct Scenario { #[serde(with = "required")] pub get_advisory: Option, - - #[serde(default, skip_serializing_if = "Option::is_none")] - pub put_advisory_lables: Option>, - - #[serde(default, skip_serializing_if = "Option::is_none")] - pub patch_advisory_lables: Option>, } impl Scenario { @@ -150,23 +144,6 @@ impl Scenario { let download_advisory = Some(loader.download_advisory().await?); let get_advisory = Some(loader.download_advisory().await?); - let put_advisory_lables = Some( - loader - .put_advisory_lables() - .await? - .iter() - .map(|advisory_id| format!("urn:uuid:{advisory_id}")) - .collect(), - ); - let patch_advisory_lables = Some( - loader - .patch_advisory_lables() - .await? - .iter() - .map(|advisory_id| format!("urn:uuid:{advisory_id}")) - .collect(), - ); - Ok(Self { get_sbom: large_sbom_digest.clone(), get_sbom_advisories: large_sbom_digest.clone(), @@ -183,8 +160,6 @@ impl Scenario { delete_sbom_pool, download_advisory, get_advisory, - put_advisory_lables, - patch_advisory_lables, }) } } @@ -401,32 +376,6 @@ FROM public.advisory order by modified desc limit 1;"#, ) .await } - - /// Advisory IDs for put labels - pub async fn put_advisory_lables(&self) -> anyhow::Result> { - let mut db = crate::db::connect(&self.db).await?; - - let rows = sqlx::query("SELECT id::text as id FROM public.advisory where labels is not null order by modified desc limit 20;") - .fetch_all(&mut db) - .await?; - Ok(rows - .into_iter() - .map(|row| row.get::("id")) - .collect()) - } - - /// Advisory IDs for patch labels - pub async fn patch_advisory_lables(&self) -> anyhow::Result> { - let mut db = crate::db::connect(&self.db).await?; - - let rows = sqlx::query("SELECT id::text as id FROM public.advisory where labels is not null order by modified desc OFFSET 20 limit 20;") - .fetch_all(&mut db) - .await?; - Ok(rows - .into_iter() - .map(|row| row.get::("id")) - .collect()) - } } #[cfg(test)] From bd3bdbbaaba0655b458fb37039a0d9d2619f4cf0 Mon Sep 17 00:00:00 2001 From: "bxf12315@gmail.com" Date: Sun, 14 Dec 2025 20:48:44 +0800 Subject: [PATCH 4/6] Use GooseUserData to pass data --- src/main.rs | 23 +++++-- src/restapi.rs | 179 ++++++++++++++++++++++++++++--------------------- src/utils.rs | 6 ++ 3 files changed, 126 insertions(+), 82 deletions(-) diff --git a/src/main.rs b/src/main.rs index 90e5fef..d2969e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -180,9 +180,7 @@ async fn main() -> Result<(), anyhow::Error> { .register_transaction(tx!(list_sboms_paginated)) .register_transaction(tx!(get_analysis_status)) .register_transaction(tx!(get_analysis_latest_cpe)) - .register_transaction(tx!(list_advisory_labels)) - .register_transaction(tx!(patch_advisory_labels)) - .register_transaction(tx!(put_advisory_labels)); + .register_transaction(tx!(list_advisory_labels)); tx!(s.get_sbom?(scenario.get_sbom.clone())); tx!(s.get_sbom_advisories?(scenario.get_sbom_advisories.clone())); @@ -216,7 +214,7 @@ async fn main() -> Result<(), anyhow::Error> { "RestAPIUserDelete", wait_time_from, wait_time_to, - custom_client, + custom_client.clone(), )? .set_weight(1)? // With 100 SBOM IDs this ensure they all delete something in the sequential situation @@ -231,6 +229,19 @@ async fn main() -> Result<(), anyhow::Error> { } s }) + .register_scenario({ + create_scenario( + "RestAdvisoryLableUser", + wait_time_from, + wait_time_to, + custom_client, + )? + .set_weight(5)? + .register_transaction(tx!(get_advisory_total).set_on_start()) + .register_transaction(tx!(list_advisory_random_single)) + .register_transaction(tx!(put_advisory_labels)) + .register_transaction(tx!(patch_advisory_labels)) + }) // .register_scenario( // scenario!("GraphQLUser") // // .set_weight(1)? @@ -241,13 +252,13 @@ async fn main() -> Result<(), anyhow::Error> { // Duration::from_secs(wait_time_to), // )? // .register_transaction(tx!(g_get_advisories)) - // .register_transaction(tx!(g_get_advisory_by_id)) + // .register_transaction(transactiontx!(g_get_advisory_by_id)) // .register_transaction(tx!(g_get_organization_by_name)) // .register_transaction(tx!(g_get_sbom_by_id)) // .register_transaction(tx!(g_get_sbom_by_labels)) // .register_transaction(tx!(g_cves_by_sbom)) // .register_transaction(tx!(g_get_vulnerability_by_id)) - // .register_transaction(tx!(g_get_vulnerabilities)), + // .register_(tx!(g_get_vulnerabilities)), // ) .execute() .await?; diff --git a/src/restapi.rs b/src/restapi.rs index 186b885..11b68ed 100644 --- a/src/restapi.rs +++ b/src/restapi.rs @@ -2,17 +2,87 @@ use crate::utils::DisplayVec; use goose::goose::{GooseMethod, GooseRequest, GooseUser, TransactionError, TransactionResult}; use reqwest::{Client, RequestBuilder}; +use crate::utils::GooseUserData; use rand::Rng; use serde_json::json; use std::sync::{ Arc, atomic::{AtomicUsize, Ordering}, }; -use tokio::sync::OnceCell; use urlencoding::encode; -// Static variable to store advisory total count -static ADVISORY_TOTAL: OnceCell = OnceCell::const_new(); +/// Get advisory total count +pub async fn get_advisory_total(user: &mut GooseUser) -> TransactionResult { + let response = user.get("/api/v2/advisory").await?; + let json_data = response.response?.json::().await?; + + // Extract total from the response + if let Some(total) = json_data.get("total").and_then(|t| t.as_u64()) { + log::info!("Advisory total count: {}", total); + user.set_session_data(GooseUserData { + total_advisories: Some(total), + advisory_id: None, + }); + return Ok(()); + } + Err(Box::new(TransactionError::Custom( + "Failed to get advisory total count".to_string(), + ))) +} + +/// List advisory with random offset and limit=1 +pub async fn list_advisory_random_single(user: &mut GooseUser) -> TransactionResult { + // Get the total count first + let total = { + let goose_user_data = user + .get_session_data_mut::() + .ok_or_else(|| { + Box::new(TransactionError::Custom( + "No GooseUserData found, please initialize user data first".to_string(), + )) + })?; + + goose_user_data.total_advisories.ok_or_else(|| { + Box::new(TransactionError::Custom( + "No total_advisories found in GooseUserData".to_string(), + )) + })? + }; + + // Generate random offset + let offset = rand::thread_rng().gen_range(0..=total); + let url = format!("/api/v2/advisory?offset={}&limit=1", offset); + + let response = user.get(&url).await?; + let json_data = response.response?.json::().await?; + + // Extract advisory ID from the response + if let Some(items) = json_data.get("items").and_then(|i| i.as_array()) { + if let Some(first_item) = items.first() { + if let Some(id) = first_item.get("uuid").and_then(|u| u.as_str()) { + log::info!("Listing advisory with offset {}: {}", offset, id); + + let goose_user_data = + user.get_session_data_mut::() + .ok_or_else(|| { + Box::new(TransactionError::Custom( + "No GooseUserData found, please initialize user data first" + .to_string(), + )) + })?; + goose_user_data.advisory_id = Some(id.to_string()); + + return Ok(()); + } + } + } + + // Return error if no advisory found + Err(Box::new(TransactionError::Custom(format!( + "No advisory found at offset: {}", + offset + )))) +} pub async fn get_advisory(id: String, user: &mut GooseUser) -> TransactionResult { let uri = format!("/api/v2/advisory/{}", encode(&format!("urn:uuid:{}", id))); @@ -51,46 +121,6 @@ pub async fn list_advisory(user: &mut GooseUser) -> TransactionResult { Ok(()) } -/// Get advisory total count and store it in static OnceCell -async fn get_advisory_total(user: &mut GooseUser) -> Result> { - let response = user.get("/api/v2/advisory").await?; - let json_data = response.response?.json::().await?; - - // Extract total from the response - if let Some(total) = json_data.get("total").and_then(|t| t.as_u64()) { - log::info!("Advisory total count: {}", total); - return Ok(total); - } - - Err(Box::new(TransactionError::Custom( - "Failed to get advisory total count".to_string(), - ))) -} - -/// Get cached advisory total count, fetch if not available using get_or_init -async fn get_cached_advisory_total(user: &mut GooseUser) -> Result> { - // Try to get from cache first - if let Some(&total) = ADVISORY_TOTAL.get() { - return Ok(total); - } - - // If not cached, fetch it and handle errors properly - match get_advisory_total(user).await { - Ok(total) => { - // Store in cache for future use - let _ = ADVISORY_TOTAL.set(total); - Ok(total) - } - Err(e) => { - // Propagate the error with context instead of silently returning 0 - Err(Box::new(TransactionError::Custom(format!( - "Failed to get advisory total count: {}", - e - )))) - } - } -} - pub async fn list_advisory_paginated(user: &mut GooseUser) -> TransactionResult { let _response = user.get("/api/v2/advisory?offset=100&limit=10").await?; @@ -114,37 +144,6 @@ pub async fn search_advisory(user: &mut GooseUser) -> TransactionResult { Ok(()) } -/// List advisory with random offset and limit=1, return advisory ID -async fn list_advisory_random_single( - user: &mut GooseUser, -) -> Result> { - let total = get_cached_advisory_total(user).await?; - // Generate random offset - let offset = rand::thread_rng().gen_range(0..=total); - let url = format!("/api/v2/advisory?offset={}&limit=1", offset); - - let response = user.get(&url).await?; - let json_data = response.response?.json::().await?; - - // Extract advisory ID from the response - if let Some(items) = json_data.get("items").and_then(|i| i.as_array()) { - if let Some(first_item) = items.first() { - if let Some(id) = first_item.get("uuid").and_then(|u| u.as_str()) { - log::info!("Listing advisory with offset {}: {}", offset, id); - return Ok(id.to_string()); - } - } - } - - // Return error if no advisory found - Err(Box::new(TransactionError::Custom(format!( - "No advisory found at offset: {}", - offset - )))) -} - -// - /// Send Advisory labels request async fn send_advisory_label_request( advisory_id: String, @@ -176,7 +175,21 @@ async fn send_advisory_label_request( /// Send Advisory labels request using PUT method pub async fn put_advisory_labels(user: &mut GooseUser) -> TransactionResult { - let advisory_id = list_advisory_random_single(user).await?; + let advisory_id = { + let goose_user_data = user + .get_session_data_mut::() + .ok_or_else(|| { + Box::new(TransactionError::Custom( + "No GooseUserData found, please initialize user data first".to_string(), + )) + })?; + + goose_user_data.advisory_id.clone().ok_or_else(|| { + Box::new(TransactionError::Custom( + "No advisory_id found in GooseUserData".to_string(), + )) + })? + }; send_advisory_label_request( advisory_id, user, @@ -189,7 +202,21 @@ pub async fn put_advisory_labels(user: &mut GooseUser) -> TransactionResult { /// Send Advisory labels request using PATCH method pub async fn patch_advisory_labels(user: &mut GooseUser) -> TransactionResult { - let advisory_id = list_advisory_random_single(user).await?; + let advisory_id = { + let goose_user_data = user + .get_session_data_mut::() + .ok_or_else(|| { + Box::new(TransactionError::Custom( + "No GooseUserData found, please initialize user data first".to_string(), + )) + })?; + + goose_user_data.advisory_id.clone().ok_or_else(|| { + Box::new(TransactionError::Custom( + "No advisory_id found in GooseUserData".to_string(), + )) + })? + }; send_advisory_label_request( advisory_id, user, diff --git a/src/utils.rs b/src/utils.rs index f7f7def..fe064cf 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -23,6 +23,12 @@ impl std::fmt::Display for DisplayVec { } } +#[derive(Clone)] +pub struct GooseUserData { + pub advisory_id: Option, + pub total_advisories: Option, +} + #[cfg(test)] mod test { use super::*; From 8cd204e32aa3ce9514bbcb9c38406a6fa77bc42f Mon Sep 17 00:00:00 2001 From: "bxf12315@gmail.com" Date: Mon, 15 Dec 2025 21:36:36 +0800 Subject: [PATCH 5/6] Do not run get_advisory_total within a single transaction --- src/main.rs | 26 +++++++++++----- src/restapi.rs | 81 ++++++++++++++++++++++---------------------------- src/utils.rs | 4 ++- 3 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/main.rs b/src/main.rs index d2969e6..7bd9312 100644 --- a/src/main.rs +++ b/src/main.rs @@ -230,17 +230,27 @@ async fn main() -> Result<(), anyhow::Error> { s }) .register_scenario({ - create_scenario( + let mut s = create_scenario( "RestAdvisoryLableUser", wait_time_from, wait_time_to, custom_client, )? - .set_weight(5)? - .register_transaction(tx!(get_advisory_total).set_on_start()) - .register_transaction(tx!(list_advisory_random_single)) - .register_transaction(tx!(put_advisory_labels)) - .register_transaction(tx!(patch_advisory_labels)) + .set_weight(5)?; + // Register advisory label transactions if host is available. + // Since the scenario object doesn't provide host information, we use environment + let host = s + .host + .clone() + .or_else(|| std::env::var("HOST").ok()) + .unwrap_or_else(|| "http://localhost:8080".to_string()); + let total_advisories = restapi::get_advisory_total(host).await.ok(); + if let Some(total) = total_advisories { + tx!(s.find_random_advisory?(Some(total))); + s = s.register_transaction(tx!(put_advisory_labels)); + s = s.register_transaction(tx!(patch_advisory_labels)); + } + s }) // .register_scenario( // scenario!("GraphQLUser") @@ -252,13 +262,13 @@ async fn main() -> Result<(), anyhow::Error> { // Duration::from_secs(wait_time_to), // )? // .register_transaction(tx!(g_get_advisories)) - // .register_transaction(transactiontx!(g_get_advisory_by_id)) + // .register_transaction(tx!(g_get_advisory_by_id)) // .register_transaction(tx!(g_get_organization_by_name)) // .register_transaction(tx!(g_get_sbom_by_id)) // .register_transaction(tx!(g_get_sbom_by_labels)) // .register_transaction(tx!(g_cves_by_sbom)) // .register_transaction(tx!(g_get_vulnerability_by_id)) - // .register_(tx!(g_get_vulnerabilities)), + // .register_transaction(tx!(g_get_vulnerabilities)), // ) .execute() .await?; diff --git a/src/restapi.rs b/src/restapi.rs index 11b68ed..cace713 100644 --- a/src/restapi.rs +++ b/src/restapi.rs @@ -1,4 +1,5 @@ use crate::utils::DisplayVec; +use anyhow::Context; use goose::goose::{GooseMethod, GooseRequest, GooseUser, TransactionError, TransactionResult}; use reqwest::{Client, RequestBuilder}; @@ -11,46 +12,35 @@ use std::sync::{ }; use urlencoding::encode; -/// Get advisory total count -pub async fn get_advisory_total(user: &mut GooseUser) -> TransactionResult { - let response = user.get("/api/v2/advisory").await?; - let json_data = response.response?.json::().await?; +/// Fetch advisory total count once at application startup +pub async fn get_advisory_total(host: String) -> Result { + let url = format!("{}/api/v2/advisory", host.trim_end_matches('/')); + + log::info!("Fetching advisory total from: {}", url); + + let response = reqwest::get(&url) + .await + .context("Failed to send request to get advisory total")? + .error_for_status() + .context("Failed to get advisory total")?; + + let json_data = response.json::().await?; - // Extract total from the response if let Some(total) = json_data.get("total").and_then(|t| t.as_u64()) { - log::info!("Advisory total count: {}", total); - user.set_session_data(GooseUserData { - total_advisories: Some(total), - advisory_id: None, - }); - return Ok(()); + return Ok(total); } - Err(Box::new(TransactionError::Custom( + Err(anyhow::anyhow!( "Failed to get advisory total count".to_string(), - ))) + )) } -/// List advisory with random offset and limit=1 -pub async fn list_advisory_random_single(user: &mut GooseUser) -> TransactionResult { - // Get the total count first - let total = { - let goose_user_data = user - .get_session_data_mut::() - .ok_or_else(|| { - Box::new(TransactionError::Custom( - "No GooseUserData found, please initialize user data first".to_string(), - )) - })?; - - goose_user_data.total_advisories.ok_or_else(|| { - Box::new(TransactionError::Custom( - "No total_advisories found in GooseUserData".to_string(), - )) - })? - }; - - // Generate random offset - let offset = rand::thread_rng().gen_range(0..=total); +/// Get a random advisory ID +pub async fn find_random_advisory( + total_advisories: u64, + user: &mut GooseUser, +) -> TransactionResult { + // Generate random offset using the provided total + let offset = rand::thread_rng().gen_range(0..=total_advisories); let url = format!("/api/v2/advisory?offset={}&limit=1", offset); let response = user.get(&url).await?; @@ -62,16 +52,9 @@ pub async fn list_advisory_random_single(user: &mut GooseUser) -> TransactionRes if let Some(id) = first_item.get("uuid").and_then(|u| u.as_str()) { log::info!("Listing advisory with offset {}: {}", offset, id); - let goose_user_data = - user.get_session_data_mut::() - .ok_or_else(|| { - Box::new(TransactionError::Custom( - "No GooseUserData found, please initialize user data first" - .to_string(), - )) - })?; - goose_user_data.advisory_id = Some(id.to_string()); - + user.set_session_data(GooseUserData { + advisory_id: Some(id.to_string()), + }); return Ok(()); } } @@ -173,8 +156,8 @@ async fn send_advisory_label_request( Ok(()) } -/// Send Advisory labels request using PUT method -pub async fn put_advisory_labels(user: &mut GooseUser) -> TransactionResult { +/// Get advisory ID from Goose user data +fn get_advisory_id(user: &mut GooseUser) -> Result> { let advisory_id = { let goose_user_data = user .get_session_data_mut::() @@ -190,6 +173,12 @@ pub async fn put_advisory_labels(user: &mut GooseUser) -> TransactionResult { )) })? }; + Ok(advisory_id) +} + +/// Send Advisory labels request using PUT method +pub async fn put_advisory_labels(user: &mut GooseUser) -> TransactionResult { + let advisory_id = get_advisory_id(user)?; send_advisory_label_request( advisory_id, user, diff --git a/src/utils.rs b/src/utils.rs index fe064cf..349aa7a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -23,10 +23,12 @@ impl std::fmt::Display for DisplayVec { } } +/// This struct is used to store user-specific data that is needed for Goose transactions. +/// +/// The advisory ID is randomly selected from the available advisories. #[derive(Clone)] pub struct GooseUserData { pub advisory_id: Option, - pub total_advisories: Option, } #[cfg(test)] From d3334c6c762d520807db50fd300d3aa9e872eebc Mon Sep 17 00:00:00 2001 From: "bxf12315@gmail.com" Date: Tue, 20 Jan 2026 18:46:10 +0800 Subject: [PATCH 6/6] Fix clippy issue --- src/restapi.rs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/restapi.rs b/src/restapi.rs index cace713..c51af5b 100644 --- a/src/restapi.rs +++ b/src/restapi.rs @@ -47,17 +47,16 @@ pub async fn find_random_advisory( let json_data = response.response?.json::().await?; // Extract advisory ID from the response - if let Some(items) = json_data.get("items").and_then(|i| i.as_array()) { - if let Some(first_item) = items.first() { - if let Some(id) = first_item.get("uuid").and_then(|u| u.as_str()) { - log::info!("Listing advisory with offset {}: {}", offset, id); - - user.set_session_data(GooseUserData { - advisory_id: Some(id.to_string()), - }); - return Ok(()); - } - } + if let Some(items) = json_data.get("items").and_then(|i| i.as_array()) + && let Some(first_item) = items.first() + && let Some(id) = first_item.get("uuid").and_then(|u| u.as_str()) + { + log::info!("Listing advisory with offset {}: {}", offset, id); + + user.set_session_data(GooseUserData { + advisory_id: Some(id.to_string()), + }); + return Ok(()); } // Return error if no advisory found