test(api): add full API test suite with get_keycloak_client async fix
All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
All checks were successful
ci/woodpecker/pr/pipeline Pipeline was successful
Add 45 API tests covering all 12 post endpoints (CRUD, publish/unpublish) with RBAC policy coverage across guest, user, admin roles. Fix get_keycloak_client() in deps.py to be async - Dishka's async container requires await on get(), without it a coroutine object was returned instead of the actual client.
This commit is contained in:
@@ -250,17 +250,199 @@ Covers both API use cases and web UI end-to-end flows.
|
||||
- Post no longer appears on home page
|
||||
- **Last Verified:** 2026-05-07
|
||||
|
||||
## API Test Cases
|
||||
|
||||
### TC-API-001: Create Post — Success
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestCreatePost::test_create_post_success`
|
||||
- **Steps:**
|
||||
1. POST `/api/v1/posts` with valid payload and user auth
|
||||
- **Expected:** 201 with correct title, content, tags, author_id, generated UUID/slug, published=False
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-002: Create Post — Validation Error
|
||||
- **Type:** Negative
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestCreatePost::test_create_post_invalid_payload`
|
||||
- **Steps:**
|
||||
1. POST `/api/v1/posts` with too-short title
|
||||
- **Expected:** 422 validation error
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-004: List Posts — Default
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestListPosts::test_list_posts_default`
|
||||
- **Steps:**
|
||||
1. GET `/api/v1/posts` without auth
|
||||
- **Expected:** 200 with `items` and `total` fields
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-005: List Posts — Include Unpublished as Admin
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestListPosts::test_list_posts_include_unpublished_as_admin`
|
||||
- **Steps:**
|
||||
1. GET `/api/v1/posts?include_unpublished=true` with admin auth
|
||||
- **Expected:** 200 including unpublished posts
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-006: List Posts — Include Unpublished as User (Forbidden)
|
||||
- **Type:** Policy
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestListPosts::test_list_posts_include_unpublished_as_user_returns_403`
|
||||
- **Steps:**
|
||||
1. GET `/api/v1/posts?include_unpublished=true` with user auth
|
||||
- **Expected:** 403 ForbiddenException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-007: List Posts — Include Unpublished as Guest (Forbidden)
|
||||
- **Type:** Policy
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestListPosts::test_list_posts_include_unpublished_as_guest_returns_403`
|
||||
- **Steps:**
|
||||
1. GET `/api/v1/posts?include_unpublished=true` with guest auth
|
||||
- **Expected:** 403 ForbiddenException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-008: List Published Posts
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestListPublishedPosts::test_list_published_posts_success`
|
||||
- **Expected:** 200 with published posts only, public endpoint
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-009: Search Posts
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestSearchPosts::test_search_posts_success`
|
||||
- **Expected:** 200 with matching posts, public endpoint
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-010: Get Posts by Tag
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestGetPostsByTag::test_get_posts_by_tag_success`
|
||||
- **Expected:** 200 with tagged posts, public endpoint
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-011: Get Posts by Author
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestGetPostsByAuthor::test_get_posts_by_author_success`
|
||||
- **Expected:** 200 with author's posts, public endpoint
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-012: Get Post by ID — Success
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestGetPost::test_get_post_by_id_success`
|
||||
- **Expected:** 200 with post data, public endpoint
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-013: Get Post by ID — Not Found
|
||||
- **Type:** Negative
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestGetPost::test_get_post_by_id_not_found`
|
||||
- **Expected:** 404 NotFoundException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-014: Get Post by Slug — Success
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestGetPostBySlug::test_get_post_by_slug_success`
|
||||
- **Expected:** 200 with post data, public endpoint
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-015: Get Post by Slug — Not Found
|
||||
- **Type:** Negative
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestGetPostBySlug::test_get_post_by_slug_not_found`
|
||||
- **Expected:** 404 NotFoundException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-016: Update Post — Own Post
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestUpdatePost::test_update_own_post_success`
|
||||
- **Expected:** 200 with updated fields
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-017: Update Post — Other User's Post (Forbidden)
|
||||
- **Type:** Policy
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestUpdatePost::test_update_other_user_post_returns_403`
|
||||
- **Expected:** 403 ForbiddenException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-018: Update Post — No Auth
|
||||
- **Type:** Negative
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestUpdatePost::test_update_post_no_auth`
|
||||
- **Expected:** 401 UnauthorizedException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-019: Delete Post — Own Post
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestDeletePost::test_delete_own_post_success`
|
||||
- **Expected:** 204, post no longer accessible
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-020: Delete Post — Other User's Post (Forbidden)
|
||||
- **Type:** Policy
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestDeletePost::test_delete_other_user_post_returns_403`
|
||||
- **Expected:** 403 ForbiddenException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-021: Delete Post — No Auth
|
||||
- **Type:** Negative
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestDeletePost::test_delete_post_no_auth`
|
||||
- **Expected:** 401 UnauthorizedException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-022: Publish Post — Own Post
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestPublishPost::test_publish_own_post_success`
|
||||
- **Expected:** 200 with published=True
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-023: Publish Post — Other User's Post (Forbidden)
|
||||
- **Type:** Policy
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestPublishPost::test_publish_other_user_post_returns_403`
|
||||
- **Expected:** 403 ForbiddenException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-024: Unpublish Post — Own Post
|
||||
- **Type:** Positive
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestUnpublishPost::test_unpublish_own_post_success`
|
||||
- **Expected:** 200 with published=False
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
### TC-API-025: Unpublish Post — Other User's Post (Forbidden)
|
||||
- **Type:** Policy
|
||||
- **Layer:** API
|
||||
- **File:** `api/test_posts.py::TestUnpublishPost::test_unpublish_other_user_post_returns_403`
|
||||
- **Expected:** 403 ForbiddenException
|
||||
- **Last Verified:** 2026-05-10
|
||||
|
||||
## Coverage Summary
|
||||
|
||||
| Aspect | Coverage | Notes |
|
||||
|--------|----------|-------|
|
||||
| Create post | Unit + E2E | Both happy path and duplicate slug covered |
|
||||
| Read post (by id/slug) | Unit | E2E implicitly via detail page |
|
||||
| Update post | Unit | No dedicated E2E |
|
||||
| Delete post | Unit + E2E | Own-post and admin-delete covered |
|
||||
| Publish / Unpublish | Unit + E2E | Draft-to-publish flow covered via edit |
|
||||
| List posts (all filters) | Unit | Pagination arguments passed but not edge-case tested |
|
||||
| Search posts | Unit | No E2E search flow |
|
||||
| Create post | Unit + API + E2E | TC-API-001, TC-API-101, TC-API-102 |
|
||||
| Read post (by id/slug) | Unit + API | TC-API-012, TC-API-013, TC-API-014, TC-API-015 |
|
||||
| Update post | Unit + API | TC-API-016, TC-API-017, TC-API-018 |
|
||||
| Delete post | Unit + API + E2E | TC-API-019, TC-API-020, TC-API-021 |
|
||||
| Publish / Unpublish | Unit + API | TC-API-022, TC-API-023, TC-API-024, TC-API-025 |
|
||||
| List posts (all filters) | Unit + API | TC-API-004, TC-API-005, TC-API-006, TC-API-007, TC-API-008 |
|
||||
| Search posts | Unit + API | TC-API-009 |
|
||||
|
||||
## Gaps (Not Yet Covered)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user