From 9c91978c850cb4454b3124bf9d8d9995e83b2e2e Mon Sep 17 00:00:00 2001 From: Nick Moreton Date: Thu, 25 Dec 2025 22:15:17 +0000 Subject: [PATCH 1/5] Add comprehensive tests for home, search, and style_guide apps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add tests for home app frontend and admin pages (6 tests) - Frontend homepage test - Admin edit, delete, copy, move, and history pages - Add tests for search app with various query scenarios (4 tests) - Search page, with query, no results, empty query - Add test for style_guide app frontend (1 test) - All tests verify 200 OK responses 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- app/home/tests.py | 42 +++++++++++++++++++++++++++++++++++----- app/search/tests.py | 15 ++++++++++++-- app/style_guide/tests.py | 5 ++++- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/app/home/tests.py b/app/home/tests.py index af1c59d..35f1899 100644 --- a/app/home/tests.py +++ b/app/home/tests.py @@ -5,19 +5,51 @@ class HomeTestCase(TestCase): + """Tests for the home app frontend and admin.""" + def setUp(self): + """Create a test user for admin access.""" + # Create test user User.objects.create_user( username="testuser", password="12345", is_staff=True, is_superuser=True - ).save() + ) + + # Get the existing home page + self.home_page = HomePage.objects.first() - def test_home_view(self): + def test_home_frontend_returns_200(self): + """Test that the home page frontend returns 200 OK.""" response = self.client.get("/") self.assertEqual(response.status_code, 200) self.assertContains(response, "Welcome to your new Wagtail site!") self.assertTemplateUsed(response, "home/home_page.html") - def test_home_admin(self): + def test_home_admin_edit_returns_200(self): + """Test that the home page admin edit page returns 200 OK.""" + self.client.login(username="testuser", password="12345") + response = self.client.get(f"/admin/pages/{self.home_page.pk}/edit/") + self.assertEqual(response.status_code, 200) + + def test_home_admin_delete_returns_200(self): + """Test that the home page admin delete page returns 200 OK.""" + self.client.login(username="testuser", password="12345") + response = self.client.get(f"/admin/pages/{self.home_page.pk}/delete/") + self.assertEqual(response.status_code, 200) + + def test_home_admin_copy_returns_200(self): + """Test that the home page admin copy page returns 200 OK.""" + self.client.login(username="testuser", password="12345") + response = self.client.get(f"/admin/pages/{self.home_page.pk}/copy/") + self.assertEqual(response.status_code, 200) + + def test_home_admin_move_returns_200(self): + """Test that the home page admin move page returns 200 OK.""" + self.client.login(username="testuser", password="12345") + response = self.client.get(f"/admin/pages/{self.home_page.pk}/move/") + self.assertEqual(response.status_code, 200) + + def test_home_admin_history_returns_200(self): + """Test that the home page admin history page returns 200 OK.""" self.client.login(username="testuser", password="12345") - home_page = HomePage.objects.first() - response = self.client.get(f"/admin/pages/{home_page.pk}/") + response = self.client.get(f"/admin/pages/{self.home_page.pk}/history/") self.assertEqual(response.status_code, 200) diff --git a/app/search/tests.py b/app/search/tests.py index 2825d25..55e6786 100644 --- a/app/search/tests.py +++ b/app/search/tests.py @@ -2,19 +2,30 @@ class SearchTestCase(TestCase): - def test_search_view(self): + """Tests for the search app frontend.""" + + def test_search_frontend_returns_200(self): + """Test that the search page returns 200 OK.""" response = self.client.get("/search/") self.assertEqual(response.status_code, 200) self.assertContains(response, "Search") self.assertTemplateUsed(response, "search/search.html") - def test_seach_no_results(self): + def test_search_with_query_returns_200(self): + """Test that search with a query returns 200 OK.""" + response = self.client.get("/search/?query=test") + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "search/search.html") + + def test_search_no_results(self): + """Test that search with no results still returns 200 OK.""" response = self.client.get("/search/?query=empty") self.assertEqual(response.status_code, 200) self.assertContains(response, "No results found") self.assertTemplateUsed(response, "search/search.html") def test_search_empty_query(self): + """Test that search with empty query returns 200 OK.""" response = self.client.get("/search/?query=") self.assertEqual(response.status_code, 200) self.assertNotContains(response, "No results found") diff --git a/app/style_guide/tests.py b/app/style_guide/tests.py index 6bfd345..b574f73 100644 --- a/app/style_guide/tests.py +++ b/app/style_guide/tests.py @@ -2,7 +2,10 @@ class StyleGuideTestCase(TestCase): - def test_style_guide(self): + """Tests for the style_guide app frontend.""" + + def test_style_guide_frontend_returns_200(self): + """Test that the style guide page returns 200 OK.""" response = self.client.get("/style-guide/") self.assertEqual(response.status_code, 200) self.assertContains(response, "Style Guide") From a67e2fc2e6dc5f417f0e291a76b56d991bde97b2 Mon Sep 17 00:00:00 2001 From: Nick Moreton Date: Thu, 25 Dec 2025 22:27:41 +0000 Subject: [PATCH 2/5] Add GitHub Actions CI workflow for pre-commit and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Run pre-commit hooks on all files - Build frontend assets with npm - Run Django tests with uv - Trigger on push to main and pull requests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/ci.yml | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c1bd098 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + pre-commit: + name: Pre-commit checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install dependencies + run: uv sync + + - name: Run pre-commit + run: | + uv run pre-commit install + uv run pre-commit run --all-files + + test: + name: Run tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install Python dependencies + run: uv sync + + - name: Install Node dependencies + run: npm install + + - name: Build frontend assets + run: npm run build + + - name: Collect static files + run: uv run python manage.py collectstatic --noinput + env: + DJANGO_SETTINGS_MODULE: app.settings.dev + + - name: Run Django tests + run: uv run python manage.py test + env: + DJANGO_SETTINGS_MODULE: app.settings.dev From d4d5157eeeec05dbbf2b15bf695e1a8ed1f753d3 Mon Sep 17 00:00:00 2001 From: Nick Moreton Date: Thu, 25 Dec 2025 22:32:20 +0000 Subject: [PATCH 3/5] Trigger CI workflow From cc819d241822647e46ce6e2ac6ccade5952ac360 Mon Sep 17 00:00:00 2001 From: Nick Moreton Date: Thu, 25 Dec 2025 22:34:30 +0000 Subject: [PATCH 4/5] Fix end-of-file for GitHub workflow files --- .github/workflows/claude-code-review.yml | 1 - .github/workflows/claude.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index a4c8f6a..8164f7d 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -54,4 +54,3 @@ jobs: # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://code.claude.com/docs/en/cli-reference for available options claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"' - diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 79fe056..d199848 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -47,4 +47,3 @@ jobs: # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://code.claude.com/docs/en/cli-reference for available options # claude_args: '--allowed-tools Bash(gh pr:*)' - From 56575cf8230a82a9f9defb6bddda11637352b2b8 Mon Sep 17 00:00:00 2001 From: Nick Moreton Date: Thu, 25 Dec 2025 22:43:46 +0000 Subject: [PATCH 5/5] Address code review suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI Improvements: - Add caching for Python (uv) and Node dependencies - Move DJANGO_SETTINGS_MODULE to job-level env - Use built-in npm caching in setup-node action Test Improvements: - Use stronger test password (TestPass123!) - Add helper method _login_as_admin() to reduce duplication - Use setUpTestData() for class-level test data - Add content assertions beyond just status codes - Improve test docstrings for clarity 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .github/workflows/ci.yml | 23 +++++++++++++++++++---- app/home/tests.py | 31 +++++++++++++++++++++---------- app/search/tests.py | 13 ++++++++----- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1bd098..4c13890 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,14 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v4 + - name: Cache Python dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/uv + key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }} + restore-keys: | + ${{ runner.os }}-uv- + - name: Install dependencies run: uv sync @@ -32,6 +40,8 @@ jobs: test: name: Run tests runs-on: ubuntu-latest + env: + DJANGO_SETTINGS_MODULE: app.settings.dev steps: - uses: actions/checkout@v4 @@ -44,10 +54,19 @@ jobs: uses: actions/setup-node@v4 with: node-version: '20' + cache: 'npm' - name: Install uv uses: astral-sh/setup-uv@v4 + - name: Cache Python dependencies + uses: actions/cache@v4 + with: + path: ~/.cache/uv + key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }} + restore-keys: | + ${{ runner.os }}-uv- + - name: Install Python dependencies run: uv sync @@ -59,10 +78,6 @@ jobs: - name: Collect static files run: uv run python manage.py collectstatic --noinput - env: - DJANGO_SETTINGS_MODULE: app.settings.dev - name: Run Django tests run: uv run python manage.py test - env: - DJANGO_SETTINGS_MODULE: app.settings.dev diff --git a/app/home/tests.py b/app/home/tests.py index 35f1899..5b9f502 100644 --- a/app/home/tests.py +++ b/app/home/tests.py @@ -7,15 +7,24 @@ class HomeTestCase(TestCase): """Tests for the home app frontend and admin.""" + @classmethod + def setUpTestData(cls): + """Set up test data for the entire test case.""" + cls.home_page = HomePage.objects.first() + cls.test_password = "TestPass123!" + def setUp(self): """Create a test user for admin access.""" - # Create test user - User.objects.create_user( - username="testuser", password="12345", is_staff=True, is_superuser=True + self.user = User.objects.create_user( + username="testuser", + password=self.test_password, + is_staff=True, + is_superuser=True, ) - # Get the existing home page - self.home_page = HomePage.objects.first() + def _login_as_admin(self): + """Helper method to log in as admin user.""" + self.client.login(username="testuser", password=self.test_password) def test_home_frontend_returns_200(self): """Test that the home page frontend returns 200 OK.""" @@ -26,30 +35,32 @@ def test_home_frontend_returns_200(self): def test_home_admin_edit_returns_200(self): """Test that the home page admin edit page returns 200 OK.""" - self.client.login(username="testuser", password="12345") + self._login_as_admin() response = self.client.get(f"/admin/pages/{self.home_page.pk}/edit/") self.assertEqual(response.status_code, 200) + self.assertContains(response, self.home_page.title) def test_home_admin_delete_returns_200(self): """Test that the home page admin delete page returns 200 OK.""" - self.client.login(username="testuser", password="12345") + self._login_as_admin() response = self.client.get(f"/admin/pages/{self.home_page.pk}/delete/") self.assertEqual(response.status_code, 200) + self.assertContains(response, "Are you sure") def test_home_admin_copy_returns_200(self): """Test that the home page admin copy page returns 200 OK.""" - self.client.login(username="testuser", password="12345") + self._login_as_admin() response = self.client.get(f"/admin/pages/{self.home_page.pk}/copy/") self.assertEqual(response.status_code, 200) def test_home_admin_move_returns_200(self): """Test that the home page admin move page returns 200 OK.""" - self.client.login(username="testuser", password="12345") + self._login_as_admin() response = self.client.get(f"/admin/pages/{self.home_page.pk}/move/") self.assertEqual(response.status_code, 200) def test_home_admin_history_returns_200(self): """Test that the home page admin history page returns 200 OK.""" - self.client.login(username="testuser", password="12345") + self._login_as_admin() response = self.client.get(f"/admin/pages/{self.home_page.pk}/history/") self.assertEqual(response.status_code, 200) diff --git a/app/search/tests.py b/app/search/tests.py index 55e6786..99f9727 100644 --- a/app/search/tests.py +++ b/app/search/tests.py @@ -5,28 +5,31 @@ class SearchTestCase(TestCase): """Tests for the search app frontend.""" def test_search_frontend_returns_200(self): - """Test that the search page returns 200 OK.""" + """Test that the search page returns 200 OK and contains search form.""" response = self.client.get("/search/") self.assertEqual(response.status_code, 200) self.assertContains(response, "Search") + self.assertContains(response, 'name="query"') self.assertTemplateUsed(response, "search/search.html") def test_search_with_query_returns_200(self): - """Test that search with a query returns 200 OK.""" + """Test that search with a query returns 200 OK and shows query.""" response = self.client.get("/search/?query=test") self.assertEqual(response.status_code, 200) + self.assertContains(response, "test") self.assertTemplateUsed(response, "search/search.html") def test_search_no_results(self): - """Test that search with no results still returns 200 OK.""" - response = self.client.get("/search/?query=empty") + """Test that search with no results shows appropriate message.""" + response = self.client.get("/search/?query=nonexistentquery12345") self.assertEqual(response.status_code, 200) self.assertContains(response, "No results found") self.assertTemplateUsed(response, "search/search.html") def test_search_empty_query(self): - """Test that search with empty query returns 200 OK.""" + """Test that search with empty query shows form but no results.""" response = self.client.get("/search/?query=") self.assertEqual(response.status_code, 200) self.assertNotContains(response, "No results found") + self.assertContains(response, 'name="query"') self.assertTemplateUsed(response, "search/search.html")