nooise 1.9.2

Ambient music generator for the terminal
import subprocess
import tempfile
import unittest
from pathlib import Path
from typing import Sequence

from scripts.add_morph import AddMorphError, add_morph, append_code


SOURCE = """\
const AUTO_STATES: &[&str] = &[
    "n1_first",
    // add-morph inserts new entries immediately above this line.
];
"""


class FakeRunner:
    def __init__(self, fail_on: tuple[str, ...] | None = None) -> None:
        self.commands: list[tuple[str, ...]] = []
        self.fail_on = fail_on

    def __call__(
        self, command: Sequence[str], root: Path, check: bool
    ) -> subprocess.CompletedProcess[str]:
        del root
        recorded = tuple(command)
        self.commands.append(recorded)
        if self.fail_on is not None and recorded[: len(self.fail_on)] == self.fail_on:
            if check:
                raise subprocess.CalledProcessError(1, command)
            return subprocess.CompletedProcess(command, 1)
        return subprocess.CompletedProcess(command, 0)


class AddMorphTests(unittest.TestCase):
    def test_append_uses_the_scoped_marker(self) -> None:
        source = SOURCE + "\nconst OTHER: &[&str] = &[];\n"

        updated = append_code(source, "n1_second")

        self.assertIn('"n1_first",\n    "n1_second",', updated)
        self.assertTrue(updated.endswith("\nconst OTHER: &[&str] = &[];\n"))

    def test_duplicate_is_rejected(self) -> None:
        with self.assertRaisesRegex(AddMorphError, "already in AUTO_STATES"):
            append_code(SOURCE, "n1_first")

    def test_missing_or_repeated_marker_is_rejected(self) -> None:
        with self.assertRaisesRegex(AddMorphError, "insertion marker"):
            source = SOURCE.replace(
                "    // add-morph inserts new entries immediately above this line.\n",
                "",
            )
            append_code(source, "n1_second")
        with self.assertRaisesRegex(AddMorphError, "insertion marker"):
            append_code(SOURCE + SOURCE, "n1_second")

    def test_failed_validation_restores_the_source(self) -> None:
        runner = FakeRunner(("cargo", "test"))
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            auto_rs = root / "auto.rs"
            auto_rs.write_text(SOURCE)

            with self.assertRaises(subprocess.CalledProcessError):
                add_morph("n1_second", root=root, auto_rs=auto_rs, run=runner)

            self.assertEqual(auto_rs.read_text(), SOURCE)

    def test_commit_is_limited_to_auto_rs(self) -> None:
        runner = FakeRunner()
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            auto_rs = root / "auto.rs"
            auto_rs.write_text(SOURCE)

            add_morph("n1_second", root=root, auto_rs=auto_rs, run=runner)

        self.assertEqual(
            runner.commands[-1],
            (
                "git",
                "commit",
                "--only",
                "-m",
                "feat: add auto-morph state",
                "--",
                "src/fluid/auto.rs",
            ),
        )

    def test_dirty_target_is_rejected_before_writing(self) -> None:
        runner = FakeRunner(("git", "diff", "--quiet"))
        with tempfile.TemporaryDirectory() as directory:
            root = Path(directory)
            auto_rs = root / "auto.rs"
            auto_rs.write_text(SOURCE)

            with self.assertRaisesRegex(AddMorphError, "uncommitted changes"):
                add_morph("n1_second", root=root, auto_rs=auto_rs, run=runner)

            self.assertEqual(auto_rs.read_text(), SOURCE)


if __name__ == "__main__":
    unittest.main()