diff options
| author | Ben Sima <ben@bensima.com> | 2025-11-22 09:19:09 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-11-22 09:19:09 -0500 |
| commit | d4d25166fc91dded490d72a25b1a49ffd41528f8 (patch) | |
| tree | f7617dd19c0932992952d0b089b62c747a73e93e | |
| parent | c80b962073e7015afb3433006b75b5c52ff26053 (diff) | |
| parent | adb2d126d3f4b95e058583464662cd9bf2561307 (diff) | |
Merge task t-1neWyaO: Admin dashboard tests
| -rw-r--r-- | .tasks/tasks.jsonl | 12 | ||||
| -rw-r--r-- | Biz/PodcastItLater/Web.py | 80 |
2 files changed, 86 insertions, 6 deletions
diff --git a/.tasks/tasks.jsonl b/.tasks/tasks.jsonl index 67504d6..0584cd9 100644 --- a/.tasks/tasks.jsonl +++ b/.tasks/tasks.jsonl @@ -55,8 +55,8 @@ {"taskCreatedAt":"2025-11-13T19:38:33.674140035Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbBmXa","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Review and fix type: ignore comments - improve type safety","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:09.583640045Z"} {"taskCreatedAt":"2025-11-13T19:38:33.85804778Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbC8Nq","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Remove PLR2004 magic number - use constant for month check","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T19:47:45.120428021Z"} {"taskCreatedAt":"2025-11-13T19:38:34.035597081Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbCSZd","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement cancel subscription functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.709672316Z"} -{"taskCreatedAt":"2025-11-13T19:38:34.194926176Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbDyr2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Review","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T04:57:46.437836107Z"} -{"taskCreatedAt":"2025-11-13T19:38:34.384489707Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbElKv","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Review","taskTitle":"Implement change email address functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T05:06:38.53919732Z"} +{"taskCreatedAt":"2025-11-13T19:38:34.194926176Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbDyr2","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement delete account functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T14:14:24.645486426Z"} +{"taskCreatedAt":"2025-11-13T19:38:34.384489707Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbElKv","taskNamespace":"Biz/PodcastItLater.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement change email address functionality","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T14:14:24.726951592Z"} {"taskCreatedAt":"2025-11-13T19:38:34.561871604Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbF5Tv","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add logout button to account page","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.65796855Z"} {"taskCreatedAt":"2025-11-13T19:38:34.777721397Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbG02X","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Replace Coming Soon placeholder with full account management UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:22:51.606196024Z"} {"taskCreatedAt":"2025-11-13T19:38:34.962196629Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fbGM2m","taskNamespace":null,"taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Add remove button to queue status items","taskType":"WorkTask","taskUpdatedAt":"2025-11-13T20:20:10.941908917Z"} @@ -120,10 +120,10 @@ {"taskCreatedAt":"2025-11-20T18:44:29.330834039Z","taskDependencies":[{"depId":"t-Uumhrq","depType":"DiscoveredFrom"}],"taskDescription":null,"taskId":"t-1bE2r3q","taskNamespace":"Omni/Task.hs","taskParent":null,"taskPriority":"P2","taskStatus":"Done","taskTitle":"Document TASK_TEST_MODE in AGENTS.md","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T18:53:22.852670919Z"} {"taskCreatedAt":"2025-11-20T19:46:53.636713383Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1fJra3K","taskNamespace":"Omni/Bild.hs","taskParent":null,"taskPriority":"P1","taskStatus":"Done","taskTitle":"Fix bild --plan to output only JSON without logging","taskType":"WorkTask","taskUpdatedAt":"2025-11-20T19:51:46.854882315Z"} {"taskCreatedAt":"2025-11-20T21:41:12.7461675Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDhLo","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Done","taskTitle":"PodcastItLater: Add Pricing Page UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T00:25:09.131891321Z"} -{"taskCreatedAt":"2025-11-20T21:41:12.764720659Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDmAD","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Review","taskTitle":"PodcastItLater: Add Stripe Checkout Route","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T05:09:49.904682771Z"} -{"taskCreatedAt":"2025-11-20T21:41:12.783999704Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDrBA","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Review","taskTitle":"PodcastItLater: Add Stripe Portal Route","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T05:15:42.436876306Z"} -{"taskCreatedAt":"2025-11-20T21:41:12.802988426Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDwxQ","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Review","taskTitle":"PodcastItLater: Add Stripe Webhook Handler","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T05:19:49.882551659Z"} -{"taskCreatedAt":"2025-11-20T21:41:12.821995769Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDBuq","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Review","taskTitle":"PodcastItLater: Enforce Paid Limits in UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T05:23:39.337972299Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.764720659Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDmAD","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Done","taskTitle":"PodcastItLater: Add Stripe Checkout Route","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T14:16:02.758048988Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.783999704Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDrBA","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Done","taskTitle":"PodcastItLater: Add Stripe Portal Route","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T14:16:02.82972272Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.802988426Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDwxQ","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Done","taskTitle":"PodcastItLater: Add Stripe Webhook Handler","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T14:16:02.911223697Z"} +{"taskCreatedAt":"2025-11-20T21:41:12.821995769Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1ndDBuq","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-143KQl2","taskPriority":"P2","taskStatus":"Done","taskTitle":"PodcastItLater: Enforce Paid Limits in UI","taskType":"WorkTask","taskUpdatedAt":"2025-11-22T14:16:02.993133469Z"} {"taskCreatedAt":"2025-11-20T21:41:32.113815607Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1neWyaO","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-144hCMJ","taskPriority":"P2","taskStatus":"Review","taskTitle":"Add tests for Admin dashboard","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T05:27:20.741813376Z"} {"taskCreatedAt":"2025-11-20T21:41:32.132888832Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1neWD8r","taskNamespace":"Biz/PodcastItLater.hs","taskParent":"t-144hCMJ","taskPriority":"P2","taskStatus":"Review","taskTitle":"Add error handling tests for Worker","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T05:41:19.218858972Z"} {"taskCreatedAt":"2025-11-20T22:42:03.728732682Z","taskDependencies":[],"taskDescription":null,"taskId":"t-1rcIr6X","taskNamespace":"Omni/Task.hs","taskParent":"t-PpXWsU","taskPriority":"P2","taskStatus":"Done","taskTitle":"Implement 'task progress <epic-id>' command","taskType":"WorkTask","taskUpdatedAt":"2025-11-21T08:59:47.987586572Z"} diff --git a/Biz/PodcastItLater/Web.py b/Biz/PodcastItLater/Web.py index 0bd3552..4d03f6a 100644 --- a/Biz/PodcastItLater/Web.py +++ b/Biz/PodcastItLater/Web.py @@ -3338,6 +3338,85 @@ class TestAccountPage(BaseWebTest): self.assertEqual(response.status_code, 307) +class TestAdminUsers(BaseWebTest): + """Test admin user management functionality.""" + + def setUp(self) -> None: + """Set up test client with logged-in admin user.""" + super().setUp() + + # Create and login admin user + self.user_id, _ = Core.Database.create_user( + "ben@bensima.com", + ) + Core.Database.update_user_status( + self.user_id, + "active", + ) + self.client.post("/login", data={"email": "ben@bensima.com"}) + + # Create another regular user + self.other_user_id, _ = Core.Database.create_user("user@example.com") + Core.Database.update_user_status(self.other_user_id, "active") + + def test_admin_users_page_access(self) -> None: + """Admin can access users page.""" + response = self.client.get("/admin/users") + self.assertEqual(response.status_code, 200) + self.assertIn("User Management", response.text) + self.assertIn("user@example.com", response.text) + + def test_non_admin_users_page_access(self) -> None: + """Non-admin cannot access users page.""" + # Login as regular user + self.client.get("/logout") + self.client.post("/login", data={"email": "user@example.com"}) + + response = self.client.get("/admin/users") + self.assertEqual(response.status_code, 302) + self.assertIn("error=forbidden", response.headers["Location"]) + + def test_admin_can_update_user_status(self) -> None: + """Admin can update user status.""" + response = self.client.post( + f"/admin/users/{self.other_user_id}/status", + data={"status": "disabled"}, + ) + self.assertEqual(response.status_code, 200) + + user = Core.Database.get_user_by_id(self.other_user_id) + assert user is not None # noqa: S101 + self.assertEqual(user["status"], "disabled") + + def test_non_admin_cannot_update_user_status(self) -> None: + """Non-admin cannot update user status.""" + # Login as regular user + self.client.get("/logout") + self.client.post("/login", data={"email": "user@example.com"}) + + response = self.client.post( + f"/admin/users/{self.other_user_id}/status", + data={"status": "disabled"}, + ) + self.assertEqual(response.status_code, 403) + + user = Core.Database.get_user_by_id(self.other_user_id) + assert user is not None # noqa: S101 + self.assertEqual(user["status"], "active") + + def test_update_user_status_invalid_status(self) -> None: + """Invalid status validation.""" + response = self.client.post( + f"/admin/users/{self.other_user_id}/status", + data={"status": "invalid_status"}, + ) + self.assertEqual(response.status_code, 400) + + user = Core.Database.get_user_by_id(self.other_user_id) + assert user is not None # noqa: S101 + self.assertEqual(user["status"], "active") + + def test() -> None: """Run all tests for the web module.""" Test.run( @@ -3355,6 +3434,7 @@ def test() -> None: TestMetricsTracking, TestUsageLimits, TestAccountPage, + TestAdminUsers, ], ) |
