Skip to content

Commit 821ed6b

Browse files
committed
TC-3071 add upload advisory and delete advisory scale test
1 parent 339d6a9 commit 821ed6b

File tree

5 files changed

+123
-6
lines changed

5 files changed

+123
-6
lines changed

scenarios/full-20250604.json5

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,5 +111,6 @@
111111
"urn:uuid:01973122-384d-7002-886f-11dddf5040ad",
112112
"urn:uuid:01973122-3a0e-73f0-9596-baceb84383aa",
113113
"urn:uuid:01973122-3bdd-78a3-898a-fb67c06387ed"
114-
]
114+
],
115+
"upload_advisory_file": "./test-data/advisories/rhba-2024_11505.json.xz"
115116
}

src/main.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ async fn main() -> Result<(), anyhow::Error> {
211211
"RestAPIUserDelete",
212212
wait_time_from,
213213
wait_time_to,
214-
custom_client,
214+
custom_client.clone(),
215215
)?
216216
.set_weight(1)?
217217
// With 100 SBOM IDs this ensure they all delete something in the sequential situation
@@ -225,6 +225,21 @@ async fn main() -> Result<(), anyhow::Error> {
225225
}
226226
s
227227
})
228+
.register_scenario({
229+
let mut s = create_scenario(
230+
"RestAPIUploadAndDeleteFiles",
231+
wait_time_from,
232+
wait_time_to,
233+
custom_client,
234+
)?
235+
.set_weight(1)?;
236+
237+
if let Some(file) = &scenario.upload_advisory_file {
238+
// Use sequential transaction execution to ensure immediate deletion after upload
239+
s = s.register_transaction(tx!(upload_and_immediately_delete(file.to_string())));
240+
}
241+
s
242+
})
228243
// .register_scenario(
229244
// scenario!("GraphQLUser")
230245
// // .set_weight(1)?

src/restapi.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use goose::goose::{GooseUser, TransactionResult};
1+
use goose::goose::{GooseUser, TransactionError, TransactionResult};
22
use serde_json::json;
33
use std::sync::{
44
Arc,
@@ -8,6 +8,54 @@ use urlencoding::encode;
88

99
use crate::utils::DisplayVec;
1010

11+
/// Upload file and return advisory ID
12+
pub async fn upload_advisory_and_get_id(
13+
advisory_file: String,
14+
user: &mut GooseUser,
15+
) -> Result<String, Box<TransactionError>> {
16+
let file_bytes = tokio::fs::read(&advisory_file).await.map_err(|e| {
17+
Box::new(TransactionError::Custom(format!(
18+
"Failed to read file {}: {}",
19+
advisory_file, e
20+
)))
21+
})?;
22+
23+
let response = user.post("/api/v2/advisory", file_bytes).await?;
24+
let v = response.response?.json::<serde_json::Value>().await?;
25+
26+
let advisory_id = v["id"]
27+
.as_str()
28+
.ok_or_else(|| {
29+
Box::new(TransactionError::Custom(
30+
"Missing advisory ID in response".to_string(),
31+
))
32+
})?
33+
.to_string();
34+
35+
Ok(advisory_id)
36+
}
37+
38+
/// Delete advisory by ID
39+
pub async fn delete_advisory_by_id(advisory_id: String, user: &mut GooseUser) -> TransactionResult {
40+
let uri = format!("/api/v2/advisory/{}", advisory_id);
41+
user.delete(&uri).await?;
42+
Ok(())
43+
}
44+
45+
/// Sequential execution: upload and then immediately delete
46+
pub async fn upload_and_immediately_delete(
47+
advisory_file: String,
48+
user: &mut GooseUser,
49+
) -> TransactionResult {
50+
// 1. Upload file and get ID
51+
let advisory_id = upload_advisory_and_get_id(advisory_file, user).await?;
52+
53+
// 2. Immediately delete (no waiting required)
54+
delete_advisory_by_id(advisory_id, user).await?;
55+
56+
Ok(())
57+
}
58+
1159
pub async fn list_advisory(user: &mut GooseUser) -> TransactionResult {
1260
let _response = user.get("/api/v2/advisory").await?;
1361

src/scenario/mod.rs

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
mod purl;
22

33
use crate::{scenario::purl::CanonicalPurl, utils::DisplayVec};
4-
use anyhow::{Context, anyhow};
4+
use anyhow::{Context, anyhow, bail};
55
use serde_json::Value;
66
use sqlx::{Executor, Row, postgres::PgRow};
7-
use std::io::BufReader;
8-
7+
use std::{io::BufReader, path::PathBuf};
98
/// implement to that we can explicitly state what we want
109
mod required {
1110
use serde::{
@@ -96,6 +95,9 @@ pub(crate) struct Scenario {
9695

9796
#[serde(default, skip_serializing_if = "Option::is_none")]
9897
pub delete_sbom_pool: Option<Vec<String>>,
98+
99+
#[serde(with = "required")]
100+
pub upload_advisory_file: Option<String>,
99101
}
100102

101103
impl Scenario {
@@ -136,6 +138,9 @@ impl Scenario {
136138
.collect(),
137139
);
138140

141+
// Get advisory file paths from the advisories subdirectory within UPLOAD_FILE_PATH
142+
let advisory_files = loader.load_advisory_files()?;
143+
139144
Ok(Self {
140145
get_sbom: large_sbom_digest.clone(),
141146
get_sbom_advisories: large_sbom_digest.clone(),
@@ -150,6 +155,9 @@ impl Scenario {
150155
get_purl_details,
151156
get_recommendations: recommendations_purl,
152157
delete_sbom_pool,
158+
upload_advisory_file: advisory_files
159+
.first()
160+
.map(|p| p.to_string_lossy().to_string()),
153161
})
154162
}
155163
}
@@ -356,6 +364,51 @@ LIMIT 100
356364
.map(|row| row.get::<String, _>("id"))
357365
.collect())
358366
}
367+
368+
/// Load advisory files from UPLOAD_FILE_PATH directory, or fall back to CARGO_MANIFEST_DIR
369+
fn load_advisory_files(&self) -> anyhow::Result<Vec<PathBuf>> {
370+
// Try UPLOAD_FILE_PATH first, then fall back to CARGO_MANIFEST_DIR
371+
let base_path = std::env::var("UPLOAD_FILE_PATH")
372+
.or_else(|_| Ok::<String, std::env::VarError>(env!("CARGO_MANIFEST_DIR").to_string()))
373+
.ok()
374+
.ok_or_else(|| {
375+
anyhow::anyhow!(
376+
"Neither UPLOAD_FILE_PATH nor CARGO_MANIFEST_DIR environment variables are set"
377+
)
378+
})?;
379+
380+
let advisories_dir: PathBuf = [&base_path, "advisories"].iter().collect();
381+
382+
let entries = std::fs::read_dir(&advisories_dir).with_context(|| {
383+
format!(
384+
"failed to read advisories directory: {}",
385+
advisories_dir.display()
386+
)
387+
})?;
388+
389+
let files: Vec<PathBuf> = entries
390+
.filter_map(|entry| {
391+
let path = entry.ok()?.path();
392+
if path.extension()? == "xz" {
393+
Some(path)
394+
} else {
395+
None
396+
}
397+
})
398+
.collect();
399+
400+
if files.is_empty() {
401+
bail!("No advisory files found in {}", advisories_dir.display());
402+
} else {
403+
log::info!(
404+
"Found {} advisory files in {}",
405+
files.len(),
406+
advisories_dir.display()
407+
);
408+
}
409+
410+
Ok(files)
411+
}
359412
}
360413

361414
#[cfg(test)]
6.82 KB
Binary file not shown.

0 commit comments

Comments
 (0)