summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.tasks/tasks.jsonl12
-rw-r--r--Biz/PodcastItLater/Web.py80
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,
],
)