# Feature: trading-feedback-engine, Property 1: Chunking round-trip and size constraint """Property-based tests for report data chunking. Feature: trading-feedback-engine Tests the chunking round-trip and size constraint property from the design specification: for any input string, splitting it into chunks with a maximum size limit produces chunks where (a) every chunk is ≤ the size limit in characters (for chunks that don't contain a single oversized line), (b) no chunk is empty (except when the input itself is empty, which produces exactly one empty chunk), and (c) concatenating all chunks in order reconstructs the original input string. """ from __future__ import annotations from hypothesis import given, settings from hypothesis import strategies as st from services.reporting.summarizer import chunk_data # --------------------------------------------------------------------------- # Property 1: Chunking Round-Trip and Size Constraint # Validates: Requirements 2.2 # --------------------------------------------------------------------------- @given( text=st.text(), max_chars=st.integers(min_value=1, max_value=10000), ) @settings(max_examples=100) def test_chunk_data_round_trip(text: str, max_chars: int) -> None: """**Validates: Requirements 2.2** For any input string and any max_chars ≥ 1, concatenating all chunks produced by chunk_data SHALL reconstruct the original input string exactly (round-trip property). """ chunks = chunk_data(text, max_chars) reconstructed = "".join(chunks) assert reconstructed == text, ( f"Round-trip failed: concatenation of {len(chunks)} chunks does not " f"equal original input.\n" f" original length: {len(text)}\n" f" reconstructed length: {len(reconstructed)}\n" f" max_chars: {max_chars}" ) @given( text=st.text(), max_chars=st.integers(min_value=1, max_value=10000), ) @settings(max_examples=100) def test_chunk_data_no_empty_chunks(text: str, max_chars: int) -> None: """**Validates: Requirements 2.2** For any input string and any max_chars ≥ 1, chunk_data SHALL produce no empty chunks — except when the input itself is empty, in which case it SHALL produce exactly one empty chunk. """ chunks = chunk_data(text, max_chars) if text == "": assert chunks == [""], ( f"Empty input should produce exactly [''], got {chunks!r}" ) else: for i, chunk in enumerate(chunks): assert chunk != "", ( f"Chunk {i} is empty for non-empty input.\n" f" input length: {len(text)}\n" f" max_chars: {max_chars}\n" f" total chunks: {len(chunks)}" ) @given( text=st.text(), max_chars=st.integers(min_value=1, max_value=10000), ) @settings(max_examples=100) def test_chunk_data_size_constraint(text: str, max_chars: int) -> None: """**Validates: Requirements 2.2** For any input string and any max_chars ≥ 1, every chunk produced by chunk_data SHALL be ≤ max_chars in length — UNLESS the chunk contains a single line that by itself exceeds max_chars (since chunk_data never breaks mid-line, such a line is emitted as its own chunk). A chunk is considered "oversized due to a single long line" when it consists of exactly one segment (a line with its trailing newline, or the final line without one) whose length exceeds max_chars. """ chunks = chunk_data(text, max_chars) for i, chunk in enumerate(chunks): if len(chunk) > max_chars: # This chunk exceeds the limit. It must be because it contains # a single line that is itself longer than max_chars. # A single-segment chunk has at most one newline (at the end). lines_in_chunk = chunk.split("\n") # If the chunk ends with \n, split produces a trailing empty string non_empty_lines = [ln for ln in lines_in_chunk if ln] assert len(non_empty_lines) <= 1, ( f"Chunk {i} exceeds max_chars={max_chars} " f"(len={len(chunk)}) but contains multiple non-empty lines, " f"which should not happen.\n" f" lines: {non_empty_lines!r}" )