From b6a76f5b2a4d728f413f2399b54829c50f9f2a22 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Fri, 9 Jan 2026 14:35:26 +0000 Subject: [PATCH] Add unittests for MVP milestone tracking and update roadmap logic --- ROADMAP.md | 2 +- src/autometabuilder/roadmap_utils.py | 21 ++++-- tests/test_roadmap.py | 106 +++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 tests/test_roadmap.py diff --git a/ROADMAP.md b/ROADMAP.md index 141604d..72c7944 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -16,7 +16,7 @@ - [x] **Automated Testing**: Integration with test runners to verify changes before PR. - [x] **Linting Integration**: Automatically run and fix linting issues. - [x] **Multi-Model Support**: Easily switch between different LLM providers. -- [x] **CI/CD Integration**: Github Actions to run AutoMetabuilder on schedule or trigger. +- [ ] **CI/CD Integration**: Github Actions to run AutoMetabuilder on schedule or trigger. ## Phase 4: Optimization & Scalability - [x] **Dockerization**: Provide a Dockerfile and docker-compose for easy environment setup. Added `run_docker_task` tool. diff --git a/src/autometabuilder/roadmap_utils.py b/src/autometabuilder/roadmap_utils.py index 328afae..dbdd565 100644 --- a/src/autometabuilder/roadmap_utils.py +++ b/src/autometabuilder/roadmap_utils.py @@ -19,16 +19,21 @@ def is_mvp_reached() -> bool: with open("ROADMAP.md", "r", encoding="utf-8") as f: content = f.read() - # Find the MVP section - mvp_match = re.search(r"## .*?\(MVP\)(.*?)##", content, re.DOTALL | re.IGNORECASE) - if not mvp_match: - # Try finding it if it's the last section - mvp_match = re.search(r"## .*?\(MVP\)(.*)", content, re.DOTALL | re.IGNORECASE) - - if not mvp_match: + # Find the header line containing (MVP) + header_match = re.search(r"^## .*?\(MVP\).*?$", content, re.MULTILINE | re.IGNORECASE) + if not header_match: return False - mvp_section = mvp_match.group(1) + # Get the position of the header + start_pos = header_match.end() + + # Find the next header starting from start_pos + next_header_match = re.search(r"^## ", content[start_pos:], re.MULTILINE) + if next_header_match: + mvp_section = content[start_pos : start_pos + next_header_match.start()] + else: + mvp_section = content[start_pos:] + # Check if there are any unchecked items [ ] if "[ ]" in mvp_section: return False diff --git a/tests/test_roadmap.py b/tests/test_roadmap.py new file mode 100644 index 0000000..a269b74 --- /dev/null +++ b/tests/test_roadmap.py @@ -0,0 +1,106 @@ +import os +import unittest +from src.autometabuilder.roadmap_utils import is_mvp_reached, update_roadmap + +class TestRoadmap(unittest.TestCase): + def setUp(self): + # Backup original ROADMAP.md if it exists + self.original_content = None + if os.path.exists("ROADMAP.md"): + with open("ROADMAP.md", "r", encoding="utf-8") as f: + self.original_content = f.read() + + def tearDown(self): + # Restore original ROADMAP.md + if self.original_content is not None: + with open("ROADMAP.md", "w", encoding="utf-8") as f: + f.write(self.original_content) + elif os.path.exists("ROADMAP.md"): + os.remove("ROADMAP.md") + + def test_is_mvp_reached_true(self): + content = """ +# Roadmap +## Phase 3: Advanced Automation (MVP) +- [x] Item 1 +- [x] Item 2 +""" + update_roadmap(content) + self.assertTrue(is_mvp_reached()) + + def test_is_mvp_reached_false_unchecked(self): + content = """ +# Roadmap +## Phase 3: Advanced Automation (MVP) +- [x] Item 1 +- [ ] Item 2 +""" + update_roadmap(content) + self.assertFalse(is_mvp_reached()) + + def test_is_mvp_reached_false_no_items(self): + content = """ +# Roadmap +## Phase 3: Advanced Automation (MVP) +No items here +""" + update_roadmap(content) + self.assertFalse(is_mvp_reached()) + + def test_is_mvp_reached_case_insensitive(self): + content = """ +# Roadmap +## Phase 3: (mvp) +- [x] Done +""" + update_roadmap(content) + self.assertTrue(is_mvp_reached()) + + def test_is_mvp_reached_with_other_sections(self): + content = """ +# Roadmap +## Phase 1 +- [x] Done + +## Phase 3 (MVP) +- [ ] Not done + +## Phase 4 +- [x] Done +""" + update_roadmap(content) + self.assertFalse(is_mvp_reached()) + + def test_is_mvp_reached_no_section(self): + content = """ +# Roadmap +## Phase 1 +- [x] Done +""" + update_roadmap(content) + self.assertFalse(is_mvp_reached()) + + def test_is_mvp_reached_multiple_mvp_markers(self): + # Should probably pick the first one or behave consistently + content = """ +# Roadmap +## Phase 3 (MVP) +- [x] Done + +## Phase 5 (MVP) +- [ ] Not done +""" + update_roadmap(content) + # Current logic picks the first match + self.assertTrue(is_mvp_reached()) + + def test_is_mvp_reached_not_in_header(self): + content = """ +# Roadmap +## Phase 1 +This is not (MVP) but it mentions it. +- [x] Done +""" + update_roadmap(content) + # Should be False because (MVP) is not in a header + self.assertFalse(is_mvp_reached())