test_project_scaffold.py 5.51 KB
"""U-002 tests: c_project_create's directory-scaffolding + DB-persistence logic.

Run: python3 -m unittest src.test_project_scaffold -v
"""

import os
import shutil
import stat
import subprocess
import tempfile
import unittest

from src import project_scaffold, schema


def _init_git_repo(path):
    subprocess.run(["git", "init"], cwd=path, check=True, capture_output=True)


class ProjectScaffoldTestBase(unittest.TestCase):
    def setUp(self):
        self.conn = schema.connect(":memory:")
        schema.create_schema(self.conn)
        self.tmpdir = tempfile.mkdtemp(prefix="graphlemon-u002-")

    def tearDown(self):
        self.conn.close()
        shutil.rmtree(self.tmpdir, ignore_errors=True)


class HappyPathTest(ProjectScaffoldTestBase):
    def test_existing_git_repo_gets_graphlemon_subdir_and_init_commit(self):
        _init_git_repo(self.tmpdir)

        result = project_scaffold.create_project(self.conn, self.tmpdir)

        self.assertTrue(result["has_git_repo"])
        self.assertTrue(result["graphlemon_subdir_initialized"])
        self.assertTrue(os.path.isdir(os.path.join(self.tmpdir, ".graphlemon")))

        log = subprocess.run(
            ["git", "log", "--oneline"], cwd=self.tmpdir, check=True, capture_output=True, text=True
        )
        self.assertIn("graphlemon: initialize .graphlemon/ workspace", log.stdout)

        row = self.conn.execute(
            "SELECT project_id, target_folder_path, has_git_repo, graphlemon_subdir_initialized, status "
            "FROM project WHERE project_id = ?",
            (result["project_id"],),
        ).fetchone()
        self.assertIsNotNone(row)
        self.assertEqual(row[1], self.tmpdir)
        self.assertEqual(row[2], 1)
        self.assertEqual(row[3], 1)
        self.assertEqual(row[4], "active")

    def test_project_name_defaults_to_folder_name(self):
        _init_git_repo(self.tmpdir)
        result = project_scaffold.create_project(self.conn, self.tmpdir)
        row = self.conn.execute(
            "SELECT name FROM project WHERE project_id = ?", (result["project_id"],)
        ).fetchone()
        self.assertEqual(row[0], os.path.basename(self.tmpdir))

    def test_explicit_name_used_when_given(self):
        _init_git_repo(self.tmpdir)
        result = project_scaffold.create_project(self.conn, self.tmpdir, name="Snake Game")
        row = self.conn.execute(
            "SELECT name FROM project WHERE project_id = ?", (result["project_id"],)
        ).fetchone()
        self.assertEqual(row[0], "Snake Game")


class NonGitRepoConfirmationFlowTest(ProjectScaffoldTestBase):
    """02-V §2.1 / 03-C §3.0: never silently `git init`."""

    def test_declines_without_confirmation_and_leaves_folder_untouched(self):
        with self.assertRaises(project_scaffold.GitInitDeclinedError):
            project_scaffold.create_project(self.conn, self.tmpdir, init_git_if_missing=False)

        self.assertFalse(os.path.isdir(os.path.join(self.tmpdir, ".git")))
        self.assertFalse(os.path.isdir(os.path.join(self.tmpdir, ".graphlemon")))
        count = self.conn.execute("SELECT COUNT(*) FROM project").fetchone()[0]
        self.assertEqual(count, 0)

    def test_initializes_git_when_explicitly_confirmed(self):
        result = project_scaffold.create_project(self.conn, self.tmpdir, init_git_if_missing=True)

        self.assertTrue(result["has_git_repo"])
        self.assertTrue(os.path.isdir(os.path.join(self.tmpdir, ".git")))
        self.assertTrue(os.path.isdir(os.path.join(self.tmpdir, ".graphlemon")))


class IdempotentCreationTest(ProjectScaffoldTestBase):
    def test_second_call_returns_existing_record_without_new_commit(self):
        _init_git_repo(self.tmpdir)
        first = project_scaffold.create_project(self.conn, self.tmpdir)
        second = project_scaffold.create_project(self.conn, self.tmpdir)

        self.assertEqual(first["project_id"], second["project_id"])
        count = self.conn.execute("SELECT COUNT(*) FROM project").fetchone()[0]
        self.assertEqual(count, 1)

        log = subprocess.run(
            ["git", "log", "--oneline"], cwd=self.tmpdir, check=True, capture_output=True, text=True
        )
        self.assertEqual(len(log.stdout.strip().splitlines()), 1)

    def test_idempotent_hit_skips_path_validation(self):
        # REQ-11 failure case: repeat calls on an already-registered path must
        # return the existing record even if the path itself is no longer
        # valid, not re-validate and error.
        _init_git_repo(self.tmpdir)
        first = project_scaffold.create_project(self.conn, self.tmpdir)
        shutil.rmtree(self.tmpdir)

        second = project_scaffold.create_project(self.conn, self.tmpdir)
        self.assertEqual(first["project_id"], second["project_id"])


class PathValidationErrorsTest(ProjectScaffoldTestBase):
    def test_nonexistent_path_raises_path_not_found(self):
        missing = os.path.join(self.tmpdir, "does-not-exist")
        with self.assertRaises(project_scaffold.PathNotFoundError):
            project_scaffold.create_project(self.conn, missing)

    def test_unwritable_path_raises_path_not_writable(self):
        os.chmod(self.tmpdir, stat.S_IRUSR | stat.S_IXUSR)
        try:
            if os.access(self.tmpdir, os.W_OK):
                self.skipTest("running as a user that bypasses permission bits (e.g. root)")
            with self.assertRaises(project_scaffold.PathNotWritableError):
                project_scaffold.create_project(self.conn, self.tmpdir)
        finally:
            os.chmod(self.tmpdir, stat.S_IRWXU)


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