use btctax_core::tax::form8275::Part1Item;
use btctax_core::tax::printed::Printed8275;
use btctax_core::tax::testonly::kitchen_sink_header;
use btctax_forms::testonly::*;
use btctax_forms::FormsError;
use rust_decimal_macros::dec;
use sha2::{Digest, Sha256};
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn fields_of(pdf: &[u8]) -> (lopdf::Document, Vec<Field>) {
let doc = load(pdf).unwrap();
let fields = collect_fields(&doc).unwrap();
(doc, fields)
}
fn fieldset(pdf: &[u8]) -> std::collections::HashSet<String> {
collect_fields(&load(pdf).unwrap())
.unwrap()
.into_iter()
.map(|f| f.fqn)
.collect()
}
fn tv(doc: &lopdf::Document, fields: &[Field], fqn: &str) -> Option<String> {
let f = fields.iter().find(|f| f.fqn == fqn)?;
text_value(doc, f.id)
}
fn sample_printed() -> Printed8275 {
Printed8275 {
part_i: vec![
Part1Item {
form: "8949".into(),
line: "Part I \u{2014} column (e)".into(),
description:
"basis estimated at the minimum daily closing price over the attested \
acquisition window (Cohan; the bearing-heavily minimum)"
.into(),
amount: dec!(12345),
},
Part1Item {
form: "8949".into(),
line: "Part II \u{2014} column (e)".into(),
description:
"basis estimated at the minimum daily closing price over the attested \
acquisition window (Cohan; the bearing-heavily minimum); limited so as not to \
report a loss from the estimate"
.into(),
amount: dec!(6789),
},
],
part_ii: "The taxpayer disposed of BTC acquired via an unverified peer-to-peer purchase; \
basis was estimated using the daily low close over the attested acquisition window, \
consistent with Cohan v. Commissioner."
.into(),
}
}
const ROW1_ITEM: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line1[0].p1-t4[0]";
const ROW1_DESC: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line1[0].#subform[0].p1-t5[0]";
const ROW1_FORM_SCHEDULE: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line1[0].p1-t7[0]";
const ROW1_AMOUNT: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line1[0].p1-t9[0]";
const ROW1_CITATION_A: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line1[0].p1-t3[0]";
const ROW1_LINE_NO_E: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line1[0].p1-t8[0]";
const ROW2_ITEM: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line2[0].p1-t11[0]";
const ROW2_DESC: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line2[0].#subform[0].p1-t12[0]";
const ROW2_FORM_SCHEDULE: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line2[0].p1-t14[0]";
const ROW2_AMOUNT: &str = "topmostSubform[0].Page1[0].Table_Part1[0].Line2[0].p1-t16[0]";
const PART_II_LINE1: &str = "topmostSubform[0].Page1[0].p1-t80[0]";
const PART_II_LINES_2_THROUGH_6: [&str; 5] = [
"topmostSubform[0].Page1[0].p1-t81[0]",
"topmostSubform[0].Page1[0].p1-t82[0]",
"topmostSubform[0].Page1[0].p1-t83[0]",
"topmostSubform[0].Page1[0].p1-t84[0]",
"topmostSubform[0].Page1[0].p1-t85[0]",
];
const PART_IV_LINE1: &str = "topmostSubform[0].Page2[0].p2-t1[0]";
const IDENTITY_NAME: &str = "topmostSubform[0].Page1[0].p1-t1[0]";
const IDENTITY_SSN: &str = "topmostSubform[0].Page1[0].p1-t2[0]";
#[test]
fn form_8275_fills_part_i_part_ii_and_identity() {
let printed = sample_printed();
let header = kitchen_sink_header();
let pdf = btctax_forms::fill_form_8275(&printed, &header, 2024)
.unwrap()
.expect("non-empty part_i");
let (doc, fields) = fields_of(&pdf);
assert_eq!(
tv(&doc, &fields, ROW1_ITEM).as_deref(),
Some("Part I \u{2014} column (e)")
);
assert_eq!(
tv(&doc, &fields, ROW1_DESC).as_deref(),
Some(printed.part_i[0].description.as_str())
);
assert_eq!(
tv(&doc, &fields, ROW1_FORM_SCHEDULE).as_deref(),
Some("Form 8949")
);
assert_eq!(tv(&doc, &fields, ROW1_AMOUNT).as_deref(), Some("12345"));
assert_eq!(
tv(&doc, &fields, ROW2_ITEM).as_deref(),
Some("Part II \u{2014} column (e)")
);
assert_eq!(tv(&doc, &fields, ROW2_AMOUNT).as_deref(), Some("6789"));
assert_eq!(
tv(&doc, &fields, PART_II_LINE1).as_deref(),
Some(
"The taxpayer disposed of BTC acquired via an unverified peer-to-peer purchase; basis \
was estimated using the daily low close over"
)
);
assert_eq!(
tv(&doc, &fields, PART_IV_LINE1).as_deref(),
Some(
"Part II, line 1 (continued): the attested acquisition window, consistent with Cohan v. \
Commissioner."
)
);
for fqn in PART_II_LINES_2_THROUGH_6 {
assert_eq!(
tv(&doc, &fields, fqn),
None,
"{fqn}: Part II's numbered continuation lines must never be written (finding 4)"
);
}
let part_iv_1 = tv(&doc, &fields, PART_IV_LINE1).unwrap();
let part_iv_1_text = part_iv_1
.strip_prefix("Part II, line 1 (continued): ")
.expect("the first Part IV line used must carry the cross-reference prefix");
let rejoined = format!(
"{} {}",
tv(&doc, &fields, PART_II_LINE1).unwrap(),
part_iv_1_text
);
assert_eq!(rejoined, printed.part_ii);
assert_eq!(
tv(&doc, &fields, IDENTITY_NAME).as_deref(),
Some("John Doe & Jane Doe")
);
assert_eq!(
tv(&doc, &fields, IDENTITY_SSN).as_deref(),
Some("123-45-6789")
);
assert_eq!(tv(&doc, &fields, ROW1_CITATION_A), None);
assert_eq!(tv(&doc, &fields, ROW1_LINE_NO_E), None);
}
#[test]
fn form_8275_lands_each_part_i_field_in_its_own_widget_no_swap() {
let printed = Printed8275 {
part_i: vec![
Part1Item {
form: "R1FORM".into(),
line: "R1ITEM".into(),
description: "R1DESC".into(),
amount: dec!(11111),
},
Part1Item {
form: "R2FORM".into(),
line: "R2ITEM".into(),
description: "R2DESC".into(),
amount: dec!(22222),
},
],
part_ii: "R_PARTII_NARRATIVE".into(),
};
let pdf = btctax_forms::fill_form_8275(&printed, &kitchen_sink_header(), 2024)
.unwrap()
.expect("non-empty part_i");
let (doc, fields) = fields_of(&pdf);
for (fqn, want) in [
(ROW1_ITEM, "R1ITEM"),
(ROW1_DESC, "R1DESC"),
(ROW1_FORM_SCHEDULE, "Form R1FORM"),
(ROW1_AMOUNT, "11111"),
(ROW2_ITEM, "R2ITEM"),
(ROW2_DESC, "R2DESC"),
(ROW2_FORM_SCHEDULE, "Form R2FORM"),
(ROW2_AMOUNT, "22222"),
(PART_II_LINE1, "R_PARTII_NARRATIVE"),
] {
assert_eq!(
tv(&doc, &fields, fqn).as_deref(),
Some(want),
"field {fqn} must hold its own sentinel {want:?} (a map swap would cross it)"
);
}
}
#[test]
fn form_8275_none_when_part_i_is_empty() {
let printed = Printed8275 {
part_i: vec![],
part_ii: "unused".into(),
};
assert!(
btctax_forms::fill_form_8275(&printed, &kitchen_sink_header(), 2024)
.unwrap()
.is_none(),
"an empty Part I means nothing to disclose — the form must not be written"
);
}
#[test]
fn fault_injected_8275_desc_mapped_to_maxlen3_line_no_cell_is_red() {
let mut map = Form8275Map::ty2024();
map.rows[0].desc = ROW1_LINE_NO_E.to_string();
let err = fill_8275_with_map(&sample_printed(), &kitchen_sink_header(), &map).unwrap_err();
assert!(
matches!(&err, FormsError::CellOverflow { max_len, fqn, .. }
if *max_len == 3 && fqn == ROW1_LINE_NO_E),
"expected CellOverflow at the /MaxLen 3 cell, got {err:?}"
);
}
#[test]
fn form_8275_part_ii_narrative_too_long_for_every_continuation_line_fails_closed() {
let printed = Printed8275 {
part_i: sample_printed().part_i,
part_ii: "word ".repeat(900),
};
let err = btctax_forms::fill_form_8275(&printed, &kitchen_sink_header(), 2024).unwrap_err();
assert!(
matches!(&err, FormsError::Overflow { part, rows, capacity }
if *part == "Part II" && *capacity == 28 && *rows > 28),
"expected a Part II Overflow with capacity 28 and rows > 28, got {err:?}"
);
}
#[test]
fn form_8275_is_byte_deterministic() {
let a = btctax_forms::fill_form_8275(&sample_printed(), &kitchen_sink_header(), 2024)
.unwrap()
.unwrap();
let b = btctax_forms::fill_form_8275(&sample_printed(), &kitchen_sink_header(), 2024)
.unwrap()
.unwrap();
assert_eq!(a, b, "same (data, form) must be byte-identical");
assert_eq!(
hex(&Sha256::digest(&a)),
GOLDEN_8275_SHA256,
"8275 fill changed — if intentional, update GOLDEN_8275_SHA256"
);
}
const GOLDEN_8275_SHA256: &str = "c0b6fe3c12ed1aef74a9f5ee8c4f205a3546d508962b5900722d936135dae4c7";
#[test]
fn form_8275_fills_for_every_supported_non_2024_year() {
let printed = sample_printed();
let header = kitchen_sink_header();
let pdf_2024 = btctax_forms::fill_form_8275(&printed, &header, 2024)
.unwrap()
.unwrap();
let pdf_2025 = btctax_forms::fill_form_8275(&printed, &header, 2025)
.unwrap()
.expect("2025 must also produce a filled 8275 (aliased revision)");
let pdf_2017 = btctax_forms::fill_form_8275(&printed, &header, 2017)
.unwrap()
.expect("2017 must also produce a filled 8275 (aliased revision)");
assert_eq!(
pdf_2024, pdf_2025,
"2024 and 2025 fills must be byte-identical"
);
assert_eq!(
pdf_2024, pdf_2017,
"2024 and 2017 fills must be byte-identical"
);
for pdf in [&pdf_2025, &pdf_2017] {
let (doc, fields) = fields_of(pdf);
assert_eq!(tv(&doc, &fields, ROW1_AMOUNT).as_deref(), Some("12345"));
assert_eq!(
tv(&doc, &fields, IDENTITY_NAME).as_deref(),
Some("John Doe & Jane Doe")
);
}
}
#[test]
fn form_8275_map_carries_the_32_new_continuation_fields() {
let map = Form8275Map::ty2024();
assert_eq!(
map.part_ii_continuation.len(),
5,
"Part II has 6 lines total: part_ii_narrative (1) + part_ii_continuation (5)"
);
assert_eq!(
map.part_iv_continuation.len(),
27,
"Part IV (page 2) has 27 continuation lines"
);
assert_eq!(
map.narrative_continuation_fields().len(),
33,
"1 (part_ii_narrative) + 5 (part_ii_continuation) + 27 (part_iv_continuation) = 33"
);
let got: Vec<String> = map
.narrative_continuation_fields()
.into_iter()
.map(str::to_string)
.collect();
assert_eq!(got, all_33_declared_continuation_fields());
}
#[test]
fn map_year_matches_bundled_pdf_fieldset_for_every_supported_year() {
for &year in btctax_forms::SUPPORTED_YEARS {
let map = Form8275Map::for_year(year).unwrap();
assert_eq!(map.year, year);
let set = fieldset(F8275_PDF_2024);
for name in map.field_names() {
assert!(
set.contains(name),
"year {year}: 8275 map field absent from the bundled PDF: {name}"
);
}
}
}
#[test]
fn unsupported_year_rejected_for_form_8275() {
let err =
btctax_forms::fill_form_8275(&sample_printed(), &kitchen_sink_header(), 2023).unwrap_err();
assert!(
matches!(err, FormsError::UnsupportedYear(2023)),
"got {err:?}"
);
}
const ORACLE_HELV_BOLD_ASCII: [u16; 95] = [
278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556,
556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667,
611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667,
667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556,
278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584,
];
fn oracle_helv_bold_8pt_width(s: &str) -> f32 {
let units: u32 = s
.chars()
.map(|c| {
let code = c as u32;
if c.is_ascii() && (0x20..=0x7E).contains(&code) {
ORACLE_HELV_BOLD_ASCII[(code - 0x20) as usize] as u32
} else {
1000
}
})
.sum();
units as f32 * 8.0 / 1000.0
}
const ORACLE_TEXT_INSET_PTS: f32 = 2.0;
fn assert_fits_inset_box(fqn: &str, value: &str, raw_box_width: f32) {
let usable = raw_box_width - 2.0 * ORACLE_TEXT_INSET_PTS;
let measured = oracle_helv_bold_8pt_width(value);
assert!(
measured <= usable - 0.05,
"{fqn}: its written content measures {measured:.2}pt wide at 8pt Helvetica-Bold but the \
field's INSET-adjusted usable width is only {usable:.2}pt ({raw_box_width:.2}pt raw minus \
{:.1}pt inset per side) — a PDF viewer honoring text inset (PDF 32000-1 §12.7.4.3) would \
silently clip roughly {:.2}pt of text (the field holds {} characters): {value:?}",
ORACLE_TEXT_INSET_PTS,
measured - usable,
value.chars().count(),
);
}
fn all_33_declared_continuation_fields() -> Vec<String> {
let mut v = vec![PART_II_LINE1.to_string()];
for n in 81..=85 {
v.push(format!("topmostSubform[0].Page1[0].p1-t{n}[0]"));
}
for n in 1..=27 {
v.push(format!("topmostSubform[0].Page2[0].p2-t{n}[0]"));
}
v
}
fn written_continuation_fields_in_order() -> Vec<String> {
let mut v = vec![PART_II_LINE1.to_string()];
for n in 1..=27 {
v.push(format!("topmostSubform[0].Page2[0].p2-t{n}[0]"));
}
v
}
fn long_part_ii_narrative() -> String {
"The taxpayer disposed of Bitcoin that was originally acquired over several transactions \
spanning approximately three years, during which the taxpayer used a combination of a hosted \
exchange account that has since ceased operations, a small number of in-person cash purchases \
from a now-unreachable counterparty, and at least one peer-to-peer transaction conducted \
through a messaging application whose records were not retained. The exchange that held the \
earliest lots suspended withdrawals and subsequently entered insolvency proceedings; repeated \
requests to its claims administrator for historical trade confirmations and cost-basis \
statements went unanswered, and the taxpayer has been unable to obtain contemporaneous \
documentation of the exact purchase prices paid for those lots despite good-faith efforts \
including searching personal email archives, bank and credit-card statements covering the \
relevant period, and any cached web pages of the exchange's now-defunct account dashboard. \
Because a substantial and unrecoverable portion of the original acquisition records is \
unavailable through no fault of the taxpayer, basis for the disposed lots was estimated using \
the daily low closing price over the taxpayer's best-documented estimate of the acquisition \
window, consistent with the Cohan doctrine, and the estimate was limited so as never to report \
a loss that a complete record might not support. The taxpayer maintains that this approach is \
a reasonable, conservative substitute for records that cannot be reconstructed, and discloses \
it here in the interest of full transparency with respect to the estimated basis reported on \
the attached Form 8949, so that the position is examined on its merits rather than treated as \
an undisclosed estimate."
.to_string()
}
#[test]
fn form_8275_part_ii_long_narrative_does_not_silently_clip() {
let narrative = long_part_ii_narrative();
assert!(
narrative.chars().count() > 1500,
"fixture premise: the narrative must exceed 1500 characters, got {}",
narrative.chars().count()
);
let printed = Printed8275 {
part_i: sample_printed().part_i,
part_ii: narrative.clone(),
};
let pdf = btctax_forms::fill_form_8275(&printed, &kitchen_sink_header(), 2024)
.unwrap()
.expect("non-empty part_i");
let (doc, fields) = fields_of(&pdf);
let mut reconstructed = String::new();
let mut any_nonempty = false;
let mut seen_any_part_iv = false;
for fqn in written_continuation_fields_in_order() {
let Some(mut value) = tv(&doc, &fields, &fqn) else {
continue;
};
if value.is_empty() {
continue;
}
any_nonempty = true;
let field = fields
.iter()
.find(|f| f.fqn == fqn)
.unwrap_or_else(|| panic!("{fqn} must be a real field in the bundled PDF"));
let rect = field
.rect
.unwrap_or_else(|| panic!("{fqn} must carry a /Rect"));
assert_fits_inset_box(&fqn, &value, rect[2] - rect[0]);
if fqn != PART_II_LINE1 && !seen_any_part_iv {
seen_any_part_iv = true;
value = value
.strip_prefix("Part II, line 1 (continued): ")
.unwrap_or_else(|| {
panic!("the first Part IV line used must carry the cross-reference prefix: {value:?}")
})
.to_string();
}
if !reconstructed.is_empty() {
reconstructed.push(' ');
}
reconstructed.push_str(&value);
}
assert!(
any_nonempty,
"no continuation field carried any Part II content at all"
);
assert!(
seen_any_part_iv,
"fixture premise: this narrative must be long enough to spill into Part IV"
);
let norm = |s: &str| s.split_whitespace().collect::<Vec<_>>().join(" ");
assert_eq!(
norm(&reconstructed),
norm(&narrative),
"the disclosure must carry EVERY word of the filer's Part II narrative, in order — a partial \
disclosure is not a disclosure"
);
}
#[test]
fn form_8275_never_writes_part_ii_numbered_lines_2_through_6() {
let printed = Printed8275 {
part_i: sample_printed().part_i,
part_ii: long_part_ii_narrative(),
};
let pdf = btctax_forms::fill_form_8275(&printed, &kitchen_sink_header(), 2024)
.unwrap()
.expect("non-empty part_i");
let (doc, fields) = fields_of(&pdf);
for fqn in PART_II_LINES_2_THROUGH_6 {
assert_eq!(
tv(&doc, &fields, fqn),
None,
"{fqn}: Part II's numbered continuation lines must never be written"
);
}
}
#[test]
fn form_8275_part_iv_cross_reference_appears_only_on_the_first_used_part_iv_line() {
let printed = Printed8275 {
part_i: sample_printed().part_i,
part_ii: long_part_ii_narrative(),
};
let pdf = btctax_forms::fill_form_8275(&printed, &kitchen_sink_header(), 2024)
.unwrap()
.expect("non-empty part_i");
let (doc, fields) = fields_of(&pdf);
let part_iv_fields: Vec<String> = (1..=27)
.map(|n| format!("topmostSubform[0].Page2[0].p2-t{n}[0]"))
.collect();
let mut seen_first = false;
for fqn in &part_iv_fields {
let Some(value) = tv(&doc, &fields, fqn) else {
continue;
};
if value.is_empty() {
continue;
}
if !seen_first {
assert!(
value.starts_with("Part II, line 1 (continued): "),
"{fqn}: the FIRST used Part IV line must carry the cross-reference prefix: {value:?}"
);
seen_first = true;
} else {
assert!(
!value.starts_with("Part II, line 1 (continued): "),
"{fqn}: only the FIRST Part IV line may carry the cross-reference prefix: {value:?}"
);
}
}
assert!(seen_first, "fixture premise: must spill into Part IV");
}
#[test]
fn form_8275_paragraph_breaks_are_hard_breaks_not_collapsed_to_a_space() {
let printed = Printed8275 {
part_i: sample_printed().part_i,
part_ii: "Tranche A: cash P2P purchase, no records.\n\n\
Tranche B: peer-to-peer trade, receipt lost."
.to_string(),
};
let pdf = btctax_forms::fill_form_8275(&printed, &kitchen_sink_header(), 2024)
.unwrap()
.expect("non-empty part_i");
let (doc, fields) = fields_of(&pdf);
assert_eq!(
tv(&doc, &fields, PART_II_LINE1).as_deref(),
Some("Tranche A: cash P2P purchase, no records."),
"the first paragraph must NOT be joined with the second on Part II's line 1"
);
assert_eq!(
tv(&doc, &fields, PART_IV_LINE1).as_deref(),
Some("Part II, line 1 (continued): Tranche B: peer-to-peer trade, receipt lost."),
"the second paragraph must start its OWN line (Part IV's first), not continue the first"
);
}
#[test]
fn fault_injected_8275_part_iv_reordered_fields_breaks_descent_and_is_red() {
let mut map = Form8275Map::ty2024();
map.part_iv_continuation.swap(0, 2);
let printed = Printed8275 {
part_i: sample_printed().part_i,
part_ii: long_part_ii_narrative(), };
let err = fill_8275_with_map(&printed, &kitchen_sink_header(), &map).unwrap_err();
assert!(
matches!(&err, FormsError::Geometry(msg) if msg.contains("descent")),
"expected a Geometry error naming the broken descent, got {err:?}"
);
}