mirror of
https://github.com/khodges42/nightShift.git
synced 2026-06-14 10:08:37 +00:00
Includes starter project generation, validation for configs/tasks/commands, artifact snapshot writing, structured stage results, command output capture, devlogs for phases 1-6, and unit coverage for the implemented MVP layers.
85 lines
3.3 KiB
Python
85 lines
3.3 KiB
Python
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
|
|
from nightshift.config import load_config, validate_config
|
|
from nightshift.errors import ConfigError
|
|
from nightshift.init import init_project
|
|
|
|
|
|
class ConfigTests(unittest.TestCase):
|
|
def test_valid_config_loads(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
init_project(root)
|
|
|
|
config = validate_config(root / "nightshift.yaml")
|
|
|
|
self.assertEqual(config.project.name, "example-project")
|
|
self.assertIn("planner", config.agents)
|
|
self.assertEqual(config.pipeline.max_task_retries, 3)
|
|
self.assertEqual(config.pipeline.stages[0].id, "plan")
|
|
|
|
def test_missing_required_section_fails_clearly(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
config_path = root / "nightshift.yaml"
|
|
config_path.write_text("project:\n name: broken\n", encoding="utf-8")
|
|
|
|
with self.assertRaisesRegex(ConfigError, "missing required section 'safety'"):
|
|
load_config(config_path)
|
|
|
|
def test_pipeline_stage_cannot_reference_missing_agent(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
init_project(root)
|
|
config_path = root / "nightshift.yaml"
|
|
config_text = config_path.read_text(encoding="utf-8").replace(
|
|
"agent: planner", "agent: critic", 1
|
|
)
|
|
config_path.write_text(config_text, encoding="utf-8")
|
|
|
|
with self.assertRaisesRegex(ConfigError, "references unknown agent 'critic'"):
|
|
load_config(config_path)
|
|
|
|
def test_on_fail_must_reference_existing_stage(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
init_project(root)
|
|
config_path = root / "nightshift.yaml"
|
|
config_text = config_path.read_text(encoding="utf-8").replace(
|
|
"on_fail: plan", "on_fail: missing_stage", 1
|
|
)
|
|
config_path.write_text(config_text, encoding="utf-8")
|
|
|
|
with self.assertRaisesRegex(ConfigError, "on_fail references unknown stage"):
|
|
load_config(config_path)
|
|
|
|
def test_validate_requires_prompt_files(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
init_project(root)
|
|
(root / "agents" / "planner.md").unlink()
|
|
|
|
with self.assertRaisesRegex(ConfigError, "system prompt does not exist"):
|
|
validate_config(root / "nightshift.yaml")
|
|
|
|
def test_validate_rejects_unallowlisted_stage_command(self) -> None:
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
init_project(root)
|
|
config_path = root / "nightshift.yaml"
|
|
config_text = config_path.read_text(encoding="utf-8").replace(
|
|
"- python -m unittest",
|
|
"- python -m pytest",
|
|
1,
|
|
)
|
|
config_path.write_text(config_text, encoding="utf-8")
|
|
|
|
with self.assertRaisesRegex(ConfigError, "not allowlisted"):
|
|
validate_config(config_path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|