use std::path::{Path, PathBuf};
use wiremock::MockServer;
use crate::analysis::payload::{PAYLOAD_MAX_BYTES, render};
use crate::analysis::result::FailureReason;
use crate::diff::hunks::{Hunk, HunkLine};
use crate::languages;
use crate::test_support::request_count;
use super::support::analyzer_for;
fn added_lines_hunk(file_path: &str, lines: usize) -> Vec<Hunk> {
let body: Vec<HunkLine> = (0..lines)
.map(|i| HunkLine::Added(format!("value_{i} = \"{}\"", "x".repeat(64))))
.collect();
vec![Hunk {
file_path: PathBuf::from(file_path),
old_start: 0,
old_count: 0,
new_start: 1,
new_count: lines as u32,
lines: body,
}]
}
#[tokio::test]
async fn an_oversize_diff_payload_fails_the_file_without_calling_the_endpoint() {
let server = MockServer::start().await;
let (analyzer, _dir) = analyzer_for(&server);
let hunks = added_lines_hunk("src/huge.py", ((PAYLOAD_MAX_BYTES / 80) as usize) + 500);
let result = analyzer.analyze_file(&hunks).await;
assert!(
result.findings.is_empty(),
"an unanalyzed file produces no findings, got {:?}",
result.findings
);
let reason = result
.failed_files
.get(&PathBuf::from("src/huge.py"))
.expect("an oversize payload must fail the file, not skip it");
match reason {
FailureReason::PayloadTooLarge { bytes, limit } => {
assert_eq!(*limit, PAYLOAD_MAX_BYTES);
assert!(
*bytes > PAYLOAD_MAX_BYTES,
"reported size {bytes} must exceed the limit {limit}"
);
}
other => panic!("expected PayloadTooLarge, got {other:?}"),
}
assert_eq!(
request_count(&server).await,
0,
"an oversize payload must never be sent"
);
}
fn hunks_rendering_to_exactly(file_path: &str, target: u64) -> Vec<Hunk> {
let build = |width: usize| {
vec![Hunk {
file_path: PathBuf::from(file_path),
old_start: 0,
old_count: 0,
new_start: 1,
new_count: 1,
lines: vec![HunkLine::Added("b".repeat(width))],
}]
};
let language = languages::detect(Path::new(file_path)).expect("a registered language");
let overhead = render(language, &build(0))
.expect("a non-empty hunk renders")
.text
.len() as u64;
let padding = target
.checked_sub(overhead)
.expect("target must leave room for the payload's fixed overhead");
let hunks = build(padding as usize);
let actual = render(language, &hunks)
.expect("a non-empty hunk renders")
.text
.len() as u64;
assert_eq!(
actual, target,
"the fixture must sit exactly on the boundary, or it cannot \
distinguish `>` from `>=`"
);
hunks
}
#[tokio::test]
async fn a_payload_exactly_at_the_ceiling_is_sent_and_one_byte_over_is_not() {
let server = MockServer::start().await;
let (analyzer, _dir) = analyzer_for(&server);
let exact = hunks_rendering_to_exactly("src/exact.py", PAYLOAD_MAX_BYTES);
let result = analyzer.analyze_file(&exact).await;
assert!(
!matches!(
result.failed_files.get(&PathBuf::from("src/exact.py")),
Some(FailureReason::PayloadTooLarge { .. })
),
"a payload of exactly the limit is within it"
);
assert_eq!(
request_count(&server).await,
1,
"the boundary payload must reach the endpoint"
);
let over = hunks_rendering_to_exactly("src/over.py", PAYLOAD_MAX_BYTES + 1);
let result = analyzer.analyze_file(&over).await;
match result.failed_files.get(&PathBuf::from("src/over.py")) {
Some(FailureReason::PayloadTooLarge { bytes, limit }) => {
assert_eq!(*bytes, PAYLOAD_MAX_BYTES + 1);
assert_eq!(*limit, PAYLOAD_MAX_BYTES);
}
other => panic!("one byte over the limit must fail; got {other:?}"),
}
assert_eq!(
request_count(&server).await,
1,
"still one: the over-limit payload must not have been sent"
);
}
#[tokio::test]
async fn a_payload_under_the_ceiling_is_still_sent() {
let server = MockServer::start().await;
let (analyzer, _dir) = analyzer_for(&server);
let hunks = added_lines_hunk("src/small.py", 10);
let result = analyzer.analyze_file(&hunks).await;
assert!(
!matches!(
result.failed_files.get(&PathBuf::from("src/small.py")),
Some(FailureReason::PayloadTooLarge { .. })
),
"a small payload must not be rejected for size"
);
assert_eq!(
request_count(&server).await,
1,
"a payload under the ceiling must reach the endpoint"
);
}