| 1 |
"""Verify dlm_factory produces structurally-valid .dlm text. |
| 2 |
|
| 3 |
Until Sprint 03's parser lands, we assert shape (frontmatter delimiters, |
| 4 |
section fences, Q/A markers). The parser tests in Sprint 03 will |
| 5 |
round-trip these blobs as the real acceptance check. |
| 6 |
""" |
| 7 |
|
| 8 |
from __future__ import annotations |
| 9 |
|
| 10 |
import re |
| 11 |
|
| 12 |
from tests.fixtures.dlm_factory import instruction, make_dlm, preference, prose |
| 13 |
|
| 14 |
|
| 15 |
class TestMakeDlm: |
| 16 |
def test_default_has_frontmatter_and_body(self) -> None: |
| 17 |
text = make_dlm() |
| 18 |
assert text.startswith("---\n") |
| 19 |
fm_end = text.index("\n---\n", 4) |
| 20 |
body = text[fm_end + len("\n---\n") :] |
| 21 |
assert body.strip(), "body must not be empty" |
| 22 |
|
| 23 |
def test_explicit_dlm_id_is_preserved(self) -> None: |
| 24 |
text = make_dlm(dlm_id="01HZ000000000000000000000X") |
| 25 |
assert "dlm_id: 01HZ000000000000000000000X" in text |
| 26 |
|
| 27 |
def test_base_model_reflects_arg(self) -> None: |
| 28 |
text = make_dlm(base_model="hf:org/custom-model") |
| 29 |
assert ( |
| 30 |
"base_model: hf:org/custom-model" in text or 'base_model: "hf:org/custom-model"' in text |
| 31 |
) |
| 32 |
|
| 33 |
def test_instruction_section_emits_q_a_pairs(self) -> None: |
| 34 |
text = make_dlm( |
| 35 |
sections=[instruction(("Q1?", "A1."), ("Q2?", "A2."))], |
| 36 |
) |
| 37 |
assert "::instruction::" in text |
| 38 |
# Count Q/A headers — must match pair count. |
| 39 |
assert text.count("### Q") == 2 |
| 40 |
assert text.count("### A") == 2 |
| 41 |
assert "Q1?" in text |
| 42 |
assert "A1." in text |
| 43 |
assert "Q2?" in text |
| 44 |
assert "A2." in text |
| 45 |
|
| 46 |
def test_preference_section_emits_triples(self) -> None: |
| 47 |
text = make_dlm( |
| 48 |
sections=[preference(("prompt", "good", "bad"))], |
| 49 |
) |
| 50 |
assert "::preference::" in text |
| 51 |
assert "### Prompt" in text |
| 52 |
assert "### Chosen" in text |
| 53 |
assert "### Rejected" in text |
| 54 |
for piece in ("prompt", "good", "bad"): |
| 55 |
assert piece in text |
| 56 |
|
| 57 |
def test_prose_section_is_verbatim(self) -> None: |
| 58 |
body = "# heading\n\nparagraph with **markdown**.\n" |
| 59 |
text = make_dlm(sections=[prose(body)]) |
| 60 |
assert body in text |
| 61 |
|
| 62 |
def test_mixed_sections_in_order(self) -> None: |
| 63 |
text = make_dlm( |
| 64 |
sections=[ |
| 65 |
prose("intro.\n"), |
| 66 |
instruction(("q", "a")), |
| 67 |
preference(("p", "c", "r")), |
| 68 |
], |
| 69 |
) |
| 70 |
# Assert positional order via index. |
| 71 |
i_intro = text.index("intro.") |
| 72 |
i_instr = text.index("::instruction::") |
| 73 |
i_pref = text.index("::preference::") |
| 74 |
assert i_intro < i_instr < i_pref |
| 75 |
|
| 76 |
def test_training_override_appears_in_frontmatter(self) -> None: |
| 77 |
text = make_dlm(training_overrides={"lora_r": 16, "learning_rate": 1e-3}) |
| 78 |
assert re.search(r"^\s*lora_r:\s*16$", text, re.MULTILINE) |
| 79 |
assert re.search(r"^\s*learning_rate:\s*0\.001$", text, re.MULTILINE) |
| 80 |
|
| 81 |
def test_system_prompt_emitted_as_block_scalar(self) -> None: |
| 82 |
text = make_dlm(system_prompt="line one\nline two") |
| 83 |
assert "system_prompt: |\n line one\n line two" in text |
| 84 |
|
| 85 |
def test_generated_output_ends_with_newline(self) -> None: |
| 86 |
text = make_dlm() |
| 87 |
assert text.endswith("\n") |
| 88 |
|
| 89 |
def test_no_explicit_id_generates_ulid(self) -> None: |
| 90 |
text_a = make_dlm() |
| 91 |
text_b = make_dlm() |
| 92 |
# Two calls should produce different ULIDs. |
| 93 |
id_a = re.search(r"^dlm_id:\s*(\S+)$", text_a, re.MULTILINE) |
| 94 |
id_b = re.search(r"^dlm_id:\s*(\S+)$", text_b, re.MULTILINE) |
| 95 |
assert id_a is not None |
| 96 |
assert id_b is not None |
| 97 |
assert id_a.group(1) != id_b.group(1) |
| 98 |
assert len(id_a.group(1)) == 26 # ULID canonical length |