use super::*;
fn context_with_observations(
max_capacity: usize,
observations: &[(EntryName<'static>, FieldLineValue<'static>)],
) -> HttpContext {
let context = HttpContext::default()
.with_config(crate::HttpConfig::default().with_dynamic_table_capacity(max_capacity));
let mut accum = ConnectionAccumulator::default();
for (name, value) in observations {
accum.observe(name, value);
}
context.observer.fold_connection(&accum);
context
}
fn init_table(context: &HttpContext, max_capacity: u64) -> EncoderDynamicTable {
let table = EncoderDynamicTable::new(context);
table.initialize_from_peer_settings(
H3Settings::default()
.with_qpack_max_table_capacity(max_capacity)
.with_qpack_blocked_streams(10),
);
table
}
fn count_duplicates(ops: &[EncoderInstruction]) -> usize {
ops.iter()
.filter(|op| matches!(op, EncoderInstruction::Duplicate { .. }))
.count()
}
#[test]
fn gate_closed_when_no_priming() {
let context = HttpContext::default()
.with_config(crate::HttpConfig::default().with_dynamic_table_capacity(160));
let table = init_table(&context, 160);
assert_eq!(
table.entry_count(),
0,
"no observations → no priming inserts"
);
let _ = drain_instructions(&table);
let lines = vec![(qen("x-w1"), fv("v"), false), (qen("x-w1"), fv("v"), false)];
let mut buf = Vec::new();
table.encode_field_lines(&lines, &mut buf, 1);
let ops = drain_instructions(&table);
assert_eq!(
count_duplicates(&ops),
0,
"no Duplicate expected without priming, got {ops:?}",
);
}
#[test]
fn gate_closed_when_table_has_headroom() {
let server = (
EntryName::Known(KnownHeaderName::Server),
FieldLineValue::Static(b"trillium"),
);
let context = context_with_observations(4096, &[server]);
let table = init_table(&context, 4096);
assert!(
table.entry_count() >= 1,
"expected the server primed entry to land, got {}",
table.entry_count()
);
let _ = drain_instructions(&table);
let lines = vec![(qen("x-w1"), fv("v"), false), (qen("x-w1"), fv("v"), false)];
let mut buf = Vec::new();
table.encode_field_lines(&lines, &mut buf, 1);
let ops = drain_instructions(&table);
assert_eq!(
count_duplicates(&ops),
0,
"no Duplicate expected with plenty of headroom, got {ops:?}",
);
}
#[test]
fn gate_open_hot_tail_emits_duplicate() {
let server = (
EntryName::Known(KnownHeaderName::Server),
FieldLineValue::Static(b"trillium"),
);
let ua = (
EntryName::Known(KnownHeaderName::UserAgent),
FieldLineValue::Static(b"custom"),
);
let context = context_with_observations(160, &[server, ua]);
let table = init_table(&context, 160);
assert!(
table.entry_count() >= 2,
"expected both pairs primed, got entry_count = {}",
table.entry_count(),
);
let primed_count = table.insert_count();
let _ = drain_instructions(&table);
let lines = vec![(qen("x-w1"), fv("v"), false), (qen("x-w1"), fv("v"), false)];
let mut buf = Vec::new();
table.encode_field_lines(&lines, &mut buf, 1);
let ops = drain_instructions(&table);
assert!(
count_duplicates(&ops) >= 1,
"expected a Duplicate from dup-drain, got {ops:?}",
);
assert!(
table.insert_count() >= primed_count + 2,
"expected dup + warming insert; primed_count={primed_count}, now={}",
table.insert_count(),
);
}
#[test]
fn sustained_pressure_emits_dup_and_remains_consistent() {
let server = (
EntryName::Known(KnownHeaderName::Server),
FieldLineValue::Static(b"trillium"),
);
let ua = (
EntryName::Known(KnownHeaderName::UserAgent),
FieldLineValue::Static(b"custom"),
);
let context = context_with_observations(160, &[server, ua]);
let table = init_table(&context, 160);
let _ = drain_instructions(&table);
let primed = table.insert_count();
let mut total_dups = 0;
for (stream_id, new_name) in [(1u64, "x-w1"), (2, "x-w2"), (3, "x-w3")] {
let lines = vec![
(qen(new_name), fv("v"), false),
(qen(new_name), fv("v"), false),
];
let mut buf = Vec::new();
table.encode_field_lines(&lines, &mut buf, stream_id);
let ops = drain_instructions(&table);
total_dups += count_duplicates(&ops);
}
assert!(
total_dups >= 1,
"expected at least one Duplicate under sustained pressure, got {total_dups}",
);
let grew_by = table.insert_count() - primed;
assert!(
grew_by >= 4,
"expected at least 4 inserts (3 warming + ≥1 dup), got {grew_by}",
);
}