fix(bruno): convert drifted body keys to body.data — CLI scenario runs now carry bodies
Follow-up to the sweep route audit, correcting its diagnosis: the CLI
DOES read .yml bodies — via OpenCollection's `body.data` string form
(the NewDevDBSetup script always worked because it used it). The
failing scenario POSTs used `body.json` (structured) / `body.jsonBody`
(string) — keys the CLI silently ignores, sending Content-Length: 0
with form-urlencoded content-type, so every server bind failed.
- 81 bodies converted to data-form (json maps serialized; jsonBody
strings kept verbatim, {{vars}} intact).
- 6 pre-existing TRUNCATED payloads repaired: the original generator
cut bodies at the first {{var}} (every SHA-256-identified scenario
plus Sync Progress/Sync Bookmarks shipped mid-JSON and 400'd from
day one). Rewritten whole, faithful to each file's documented
purpose; book_sha256/book_uuid added to the environment.
- environments: opds_base_url correction from the sweep retained.
Verified live against the rebuilt dev container: the full koreader
scenario tree runs green through `bru run` — 14/14 (202s across
immediate/checkpoint/SHA-256 progress, 200s bookmarks, highlights,
notes; SHA-256 book identification exercised against a real
file_sha256).
This commit is contained in:
1 parent
63a20dcc95
commit
09b6693505
83 files changed
+386
-2769
No files matched your search
@@ -8,238 +8,11 @@ http:
|
||||
auth: inherit
|
||||
body:
|
||||
type: json
|
||||
jsonBody: "{\n \"name\": \"Complete Workflow Test\",\n \"resource_type\"\
|
||||
: \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\"\
|
||||
,\n \"sort\": \"title ASC\"\n }\n }"
|
||||
|
||||
docs: |-
|
||||
## Complete CRUD Workflow
|
||||
|
||||
End-to-end test demonstrating the complete lifecycle of a saved filter: Create → Read → Update → Delete.
|
||||
|
||||
**Workflow Steps:**
|
||||
|
||||
**Step 1: CREATE a new filter**
|
||||
```bash
|
||||
CREATE_RESPONSE=$(curl -s -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Complete Workflow Test",
|
||||
"resource_type": "media-items",
|
||||
"filters": {
|
||||
"genre_filter": "Science Fiction",
|
||||
"sort": "title ASC"
|
||||
}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters")
|
||||
|
||||
echo "Create Response:"
|
||||
echo $CREATE_RESPONSE | jq '.'
|
||||
|
||||
# Extract filter ID for subsequent requests
|
||||
FILTER_ID=$(echo $CREATE_RESPONSE | jq -r '.id')
|
||||
echo "Created filter ID: $FILTER_ID"
|
||||
|
||||
# Expected Status: 201 Created
|
||||
# Expected Response:
|
||||
# {
|
||||
# "id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
# "name": "Complete Workflow Test",
|
||||
# "resource_type": "media-items",
|
||||
# "filters": {
|
||||
# "genre_filter": "Science Fiction",
|
||||
# "sort": "title ASC"
|
||||
# },
|
||||
# "created_at": "2024-03-20T12:00:00Z",
|
||||
# "updated_at": "2024-03-20T12:00:00Z"
|
||||
# }
|
||||
```
|
||||
|
||||
**Step 2: READ the created filter (list all)**
|
||||
```bash
|
||||
# List all filters for media-items
|
||||
LIST_RESPONSE=$(curl -s \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
"{{base_url}}/api/saved-filters?resource_type=media-items")
|
||||
|
||||
echo "List Response:"
|
||||
echo $LIST_RESPONSE | jq '.'
|
||||
|
||||
# Verify our filter is in the list
|
||||
FILTER_EXISTS=$(echo $LIST_RESPONSE | jq --arg id "$FILTER_ID" \
|
||||
'.[] | select(.id == $id) | .id')
|
||||
|
||||
if [ "$FILTER_EXISTS" == "$FILTER_ID" ]; then
|
||||
echo "✓ Filter found in list"
|
||||
else
|
||||
echo "✗ Filter NOT found in list"
|
||||
fi
|
||||
|
||||
# Expected Status: 200 OK
|
||||
# Expected Response: Array containing our newly created filter
|
||||
```
|
||||
|
||||
**Step 3: UPDATE the filter**
|
||||
```bash
|
||||
# Update filter name and criteria
|
||||
UPDATE_RESPONSE=$(curl -s -X PUT \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Complete Workflow Test - Updated",
|
||||
"resource_type": "media-items",
|
||||
"filters": {
|
||||
"genre_filter": "Science Fiction Fantasy",
|
||||
"sort": "author ASC",
|
||||
"year_min": "2000"
|
||||
}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters/$FILTER_ID")
|
||||
|
||||
echo "Update Response:"
|
||||
echo $UPDATE_RESPONSE | jq '.'
|
||||
|
||||
# Verify updated_at timestamp changed
|
||||
UPDATED_AT=$(echo $UPDATE_RESPONSE | jq -r '.updated_at')
|
||||
echo "Filter updated at: $UPDATED_AT"
|
||||
|
||||
# Expected Status: 200 OK
|
||||
# Expected Response:
|
||||
# {
|
||||
# "id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
# "name": "Complete Workflow Test - Updated",
|
||||
# "resource_type": "media-items",
|
||||
# "filters": {
|
||||
# "genre_filter": "Science Fiction Fantasy",
|
||||
# "sort": "author ASC",
|
||||
# "year_min": "2000"
|
||||
# },
|
||||
# "created_at": "2024-03-20T12:00:00Z",
|
||||
# "updated_at": "2024-03-20T12:05:00Z" # NEW TIMESTAMP
|
||||
# }
|
||||
```
|
||||
|
||||
**Step 4: DELETE the filter**
|
||||
```bash
|
||||
# Delete the filter
|
||||
DELETE_RESPONSE=$(curl -s -X DELETE \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
"{{base_url}}/api/saved-filters/$FILTER_ID")
|
||||
|
||||
echo "Delete Response:"
|
||||
echo $DELETE_RESPONSE
|
||||
|
||||
# Expected Status: 204 No Content
|
||||
# Expected Response: (empty body)
|
||||
if [ -z "$DELETE_RESPONSE" ]; then
|
||||
echo "✓ Filter deleted successfully (empty response)"
|
||||
fi
|
||||
```
|
||||
|
||||
**Step 5: VERIFY deletion**
|
||||
```bash
|
||||
# Try to get the deleted filter
|
||||
GET_RESPONSE=$(curl -s \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
"{{base_url}}/api/saved-filters?resource_type=media-items")
|
||||
|
||||
echo "List After Delete:"
|
||||
echo $GET_RESPONSE | jq '.'
|
||||
|
||||
# Verify filter is NOT in the list
|
||||
FILTER_EXISTS=$(echo $GET_RESPONSE | jq --arg id "$FILTER_ID" \
|
||||
'.[] | select(.id == $id) | .id')
|
||||
|
||||
if [ -z "$FILTER_EXISTS" ]; then
|
||||
echo "✓ Filter successfully deleted (not found in list)"
|
||||
else
|
||||
echo "✗ Filter still exists (deletion failed)"
|
||||
fi
|
||||
|
||||
# Expected: Filter no longer appears in list
|
||||
```
|
||||
|
||||
**Shell Script Complete Example:**
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
BASE_URL="{{base_url}}"
|
||||
TOKEN="YOUR_TOKEN"
|
||||
|
||||
echo "=== Complete CRUD Workflow Test ==="
|
||||
echo ""
|
||||
|
||||
# 1. CREATE
|
||||
echo "Step 1: Creating filter..."
|
||||
FILTER_ID=$(curl -s -X POST \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Workflow Test",
|
||||
"resource_type": "media-items",
|
||||
"filters": {"genre_filter": "Science Fiction"}
|
||||
}' \
|
||||
"$BASE_URL/api/saved-filters" | jq -r '.id')
|
||||
|
||||
echo "✓ Created filter: $FILTER_ID"
|
||||
echo ""
|
||||
|
||||
# 2. READ
|
||||
echo "Step 2: Reading filters..."
|
||||
curl -s -H "Authorization: Bearer $TOKEN" \
|
||||
"$BASE_URL/api/saved-filters?resource_type=media-items" | \
|
||||
jq --arg id "$FILTER_ID" '.[] | select(.id == $id)'
|
||||
|
||||
echo "✓ Filter retrieved"
|
||||
echo ""
|
||||
|
||||
# 3. UPDATE
|
||||
echo "Step 3: Updating filter..."
|
||||
curl -s -X PUT \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Workflow Test - Updated",
|
||||
"resource_type": "media-items",
|
||||
"filters": {"genre_filter": "Fantasy"}
|
||||
}' \
|
||||
"$BASE_URL/api/saved-filters/$FILTER_ID" | jq '.'
|
||||
|
||||
echo "✓ Filter updated"
|
||||
echo ""
|
||||
|
||||
# 4. DELETE
|
||||
echo "Step 4: Deleting filter..."
|
||||
curl -s -X DELETE \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
"$BASE_URL/api/saved-filters/$FILTER_ID" -w "\nStatus: %{http_code}\n"
|
||||
|
||||
echo "✓ Filter deleted"
|
||||
echo ""
|
||||
|
||||
# 5. VERIFY
|
||||
echo "Step 5: Verifying deletion..."
|
||||
COUNT=$(curl -s -H "Authorization: Bearer $TOKEN" \
|
||||
"$BASE_URL/api/saved-filters?resource_type=media-items" | \
|
||||
jq --arg id "$FILTER_ID" '[.[] | select(.id == $id)] | length')
|
||||
|
||||
if [ "$COUNT" -eq 0 ]; then
|
||||
echo "✓ Verification successful - filter deleted"
|
||||
else
|
||||
echo "✗ Verification failed - filter still exists"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== All CRUD operations completed successfully ==="
|
||||
```
|
||||
|
||||
**Success Criteria:**
|
||||
- ✅ Create returns 201 with generated ID
|
||||
- ✅ Read returns filter in list
|
||||
- ✅ Update modifies filter and updates timestamp
|
||||
- ✅ Delete returns 204 No Content
|
||||
- ✅ Filter no longer appears in list after deletion
|
||||
data: "{\n \"name\": \"Complete Workflow Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"sort\": \"title ASC\"\n }\n }"
|
||||
docs: "## Complete CRUD Workflow\n\nEnd-to-end test demonstrating the complete lifecycle of a saved filter: Create → Read → Update → Delete.\n\n**Workflow Steps:**\n\n**Step 1: CREATE a new filter**\n```bash\nCREATE_RESPONSE=$(curl -s -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Complete Workflow Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"sort\": \"title ASC\"\n }\n }' \\\n \"{{base_url}}/api/saved-filters\")\n\necho \"Create Response:\"\necho $CREATE_RESPONSE | jq '.'\n\n# Extract filter ID for subsequent requests\nFILTER_ID=$(echo $CREATE_RESPONSE | jq -r '.id')\necho \"Created filter ID: $FILTER_ID\"\n\n# Expected Status: 201 Created\n# Expected Response:\n# {\n# \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n# \"name\": \"Complete Workflow Test\",\n# \"resource_type\": \"media-items\",\n# \"filters\": {\n#\
|
||||
\ \"genre_filter\": \"Science Fiction\",\n# \"sort\": \"title ASC\"\n# },\n# \"created_at\": \"2024-03-20T12:00:00Z\",\n# \"updated_at\": \"2024-03-20T12:00:00Z\"\n# }\n```\n\n**Step 2: READ the created filter (list all)**\n```bash\n# List all filters for media-items\nLIST_RESPONSE=$(curl -s \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n \"{{base_url}}/api/saved-filters?resource_type=media-items\")\n\necho \"List Response:\"\necho $LIST_RESPONSE | jq '.'\n\n# Verify our filter is in the list\nFILTER_EXISTS=$(echo $LIST_RESPONSE | jq --arg id \"$FILTER_ID\" \\\n '.[] | select(.id == $id) | .id')\n\nif [ \"$FILTER_EXISTS\" == \"$FILTER_ID\" ]; then\n echo \"✓ Filter found in list\"\nelse\n echo \"✗ Filter NOT found in list\"\nfi\n\n# Expected Status: 200 OK\n# Expected Response: Array containing our newly created filter\n```\n\n**Step 3: UPDATE the filter**\n```bash\n# Update filter name and criteria\nUPDATE_RESPONSE=$(curl -s -X PUT \\\n -H \"Authorization: Bearer\
|
||||
\ YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Complete Workflow Test - Updated\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction Fantasy\",\n \"sort\": \"author ASC\",\n \"year_min\": \"2000\"\n }\n }' \\\n \"{{base_url}}/api/saved-filters/$FILTER_ID\")\n\necho \"Update Response:\"\necho $UPDATE_RESPONSE | jq '.'\n\n# Verify updated_at timestamp changed\nUPDATED_AT=$(echo $UPDATE_RESPONSE | jq -r '.updated_at')\necho \"Filter updated at: $UPDATED_AT\"\n\n# Expected Status: 200 OK\n# Expected Response:\n# {\n# \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n# \"name\": \"Complete Workflow Test - Updated\",\n# \"resource_type\": \"media-items\",\n# \"filters\": {\n# \"genre_filter\": \"Science Fiction Fantasy\",\n# \"sort\": \"author ASC\",\n# \"year_min\": \"2000\"\n# },\n# \"created_at\": \"2024-03-20T12:00:00Z\",\n# \"updated_at\": \"2024-03-20T12:05:00Z\"\
|
||||
\ # NEW TIMESTAMP\n# }\n```\n\n**Step 4: DELETE the filter**\n```bash\n# Delete the filter\nDELETE_RESPONSE=$(curl -s -X DELETE \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n \"{{base_url}}/api/saved-filters/$FILTER_ID\")\n\necho \"Delete Response:\"\necho $DELETE_RESPONSE\n\n# Expected Status: 204 No Content\n# Expected Response: (empty body)\nif [ -z \"$DELETE_RESPONSE\" ]; then\n echo \"✓ Filter deleted successfully (empty response)\"\nfi\n```\n\n**Step 5: VERIFY deletion**\n```bash\n# Try to get the deleted filter\nGET_RESPONSE=$(curl -s \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n \"{{base_url}}/api/saved-filters?resource_type=media-items\")\n\necho \"List After Delete:\"\necho $GET_RESPONSE | jq '.'\n\n# Verify filter is NOT in the list\nFILTER_EXISTS=$(echo $GET_RESPONSE | jq --arg id \"$FILTER_ID\" \\\n '.[] | select(.id == $id) | .id')\n\nif [ -z \"$FILTER_EXISTS\" ]; then\n echo \"✓ Filter successfully deleted (not found in list)\"\nelse\n echo \"✗ Filter still\
|
||||
\ exists (deletion failed)\"\nfi\n\n# Expected: Filter no longer appears in list\n```\n\n**Shell Script Complete Example:**\n```bash\n#!/bin/bash\n\nset -e # Exit on error\n\nBASE_URL=\"{{base_url}}\"\nTOKEN=\"YOUR_TOKEN\"\n\necho \"=== Complete CRUD Workflow Test ===\"\necho \"\"\n\n# 1. CREATE\necho \"Step 1: Creating filter...\"\nFILTER_ID=$(curl -s -X POST \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Workflow Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\"genre_filter\": \"Science Fiction\"}\n }' \\\n \"$BASE_URL/api/saved-filters\" | jq -r '.id')\n\necho \"✓ Created filter: $FILTER_ID\"\necho \"\"\n\n# 2. READ\necho \"Step 2: Reading filters...\"\ncurl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"$BASE_URL/api/saved-filters?resource_type=media-items\" | \\\n jq --arg id \"$FILTER_ID\" '.[] | select(.id == $id)'\n\necho \"✓ Filter retrieved\"\necho \"\"\n\n# 3. UPDATE\necho \"Step 3:\
|
||||
\ Updating filter...\"\ncurl -s -X PUT \\\n -H \"Authorization: Bearer $TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Workflow Test - Updated\",\n \"resource_type\": \"media-items\",\n \"filters\": {\"genre_filter\": \"Fantasy\"}\n }' \\\n \"$BASE_URL/api/saved-filters/$FILTER_ID\" | jq '.'\n\necho \"✓ Filter updated\"\necho \"\"\n\n# 4. DELETE\necho \"Step 4: Deleting filter...\"\ncurl -s -X DELETE \\\n -H \"Authorization: Bearer $TOKEN\" \\\n \"$BASE_URL/api/saved-filters/$FILTER_ID\" -w \"\\nStatus: %{http_code}\\n\"\n\necho \"✓ Filter deleted\"\necho \"\"\n\n# 5. VERIFY\necho \"Step 5: Verifying deletion...\"\nCOUNT=$(curl -s -H \"Authorization: Bearer $TOKEN\" \\\n \"$BASE_URL/api/saved-filters?resource_type=media-items\" | \\\n jq --arg id \"$FILTER_ID\" '[.[] | select(.id == $id)] | length')\n\nif [ \"$COUNT\" -eq 0 ]; then\n echo \"✓ Verification successful - filter deleted\"\nelse\n echo \"✗ Verification failed - filter still exists\"\
|
||||
\n exit 1\nfi\n\necho \"\"\necho \"=== All CRUD operations completed successfully ===\"\n```\n\n**Success Criteria:**\n- ✅ Create returns 201 with generated ID\n- ✅ Read returns filter in list\n- ✅ Update modifies filter and updates timestamp\n- ✅ Delete returns 204 No Content\n- ✅ Filter no longer appears in list after deletion"
|
||||
@@ -8,69 +8,6 @@ http:
|
||||
auth: inherit
|
||||
body:
|
||||
type: json
|
||||
jsonBody: "{\n \"name\": \"Duplicate Test\",\n \"resource_type\":\
|
||||
\ \"media-items\",\n \"filters\": {\n \"search\": \"test\",\n \"genre_filter\"\
|
||||
: \"Science Fiction\"\n }\n }"
|
||||
|
||||
docs: |-
|
||||
## Duplicate Name Validation
|
||||
|
||||
Tests that the API correctly prevents creating duplicate filter names for the same user + resource type combination.
|
||||
|
||||
**Scenario:**
|
||||
1. Create first filter with name "Duplicate Test"
|
||||
2. Attempt to create second filter with same name
|
||||
3. Expect 409 Conflict error
|
||||
|
||||
**Expected Behavior:**
|
||||
- First request: 201 Created
|
||||
- Second request: 409 Conflict with error message
|
||||
|
||||
**Error Response:**
|
||||
```json
|
||||
{
|
||||
"error": "filter with name 'Duplicate Test' already exists for this resource type"
|
||||
}
|
||||
```
|
||||
|
||||
**Test Steps:**
|
||||
```bash
|
||||
# First request - should succeed
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Duplicate Test",
|
||||
"resource_type": "media-items",
|
||||
"filters": {"search": "test"}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Response: 201 Created
|
||||
|
||||
# Second request - should fail with 409
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Duplicate Test",
|
||||
"resource_type": "media-items",
|
||||
"filters": {"search": "test"}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Response: 409 Conflict
|
||||
# {
|
||||
# "error": "filter with name 'Duplicate Test' already exists for this resource type"
|
||||
# }
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
- Duplicate check is per user + resource type
|
||||
- Same filter name can be used for different resource types
|
||||
- Different users can have filters with the same name
|
||||
- Validation happens in service layer before database insertion
|
||||
|
||||
**Related Tests:**
|
||||
- Update filter with name that conflicts with another filter (should also return 409)
|
||||
- Create filter with different name for different resource type (should succeed)
|
||||
data: "{\n \"name\": \"Duplicate Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"search\": \"test\",\n \"genre_filter\": \"Science Fiction\"\n }\n }"
|
||||
docs: "## Duplicate Name Validation\n\nTests that the API correctly prevents creating duplicate filter names for the same user + resource type combination.\n\n**Scenario:**\n1. Create first filter with name \"Duplicate Test\"\n2. Attempt to create second filter with same name\n3. Expect 409 Conflict error\n\n**Expected Behavior:**\n- First request: 201 Created\n- Second request: 409 Conflict with error message\n\n**Error Response:**\n```json\n{\n \"error\": \"filter with name 'Duplicate Test' already exists for this resource type\"\n}\n```\n\n**Test Steps:**\n```bash\n# First request - should succeed\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Duplicate Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\"search\": \"test\"}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Response: 201 Created\n\n# Second request - should fail with 409\ncurl -X POST \\\n -H \"Authorization: Bearer\
|
||||
\ YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Duplicate Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\"search\": \"test\"}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Response: 409 Conflict\n# {\n# \"error\": \"filter with name 'Duplicate Test' already exists for this resource type\"\n# }\n```\n\n**Important Notes:**\n- Duplicate check is per user + resource type\n- Same filter name can be used for different resource types\n- Different users can have filters with the same name\n- Validation happens in service layer before database insertion\n\n**Related Tests:**\n- Update filter with name that conflicts with another filter (should also return 409)\n- Create filter with different name for different resource type (should succeed)"
|
||||
@@ -8,254 +8,10 @@ http:
|
||||
auth: inherit
|
||||
body:
|
||||
type: json
|
||||
jsonBody: "{\n \"name\": \"\",\n \"resource_type\": \"media-items\"\
|
||||
,\n \"filters\": {}\n }"
|
||||
|
||||
docs: |-
|
||||
## Filter Validation - Edge Cases
|
||||
|
||||
Tests various validation edge cases including empty names, invalid resource types, malformed JSON, etc.
|
||||
|
||||
**Test Cases:**
|
||||
|
||||
**Test 1: Empty Filter Name**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "",
|
||||
"resource_type": "media-items",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 400 Bad Request
|
||||
# {
|
||||
# "error": "name and resource_type are required"
|
||||
# }
|
||||
```
|
||||
|
||||
**Test 2: Missing Name Field**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"resource_type": "media-items",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 400 Bad Request
|
||||
# Missing required field validation
|
||||
```
|
||||
|
||||
**Test 3: Missing Resource Type**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Test Filter",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 400 Bad Request
|
||||
# {
|
||||
# "error": "name and resource_type are required"
|
||||
# }
|
||||
```
|
||||
|
||||
**Test 4: Invalid Resource Type**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Test Filter",
|
||||
"resource_type": "invalid-type",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 400 Bad Request or success
|
||||
# Note: Current validation allows any string (extensible design)
|
||||
# Future: May validate against allowed resource types
|
||||
```
|
||||
|
||||
**Test 5: Empty Filters Object**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Empty Filter",
|
||||
"resource_type": "media-items",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 201 Created
|
||||
# Empty filters object is valid (matches no criteria)
|
||||
```
|
||||
|
||||
**Test 6: Very Long Filter Name**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "This is a very long filter name that exceeds one hundred characters and should be rejected by the validation logic in the handler",
|
||||
"resource_type": "media-items",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 400 Bad Request or 201 Created
|
||||
# Note: Plan specifies max 100 chars, but validation may not be enforced
|
||||
```
|
||||
|
||||
**Test 7: Special Characters in Name**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Filter with <script>alert(\"xss\")</script>",
|
||||
"resource_type": "media-items",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 201 Created (input sanitized)
|
||||
# Note: Names are stored as-is, no HTML escaping needed
|
||||
```
|
||||
|
||||
**Test 8: Unicode Characters in Name**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "🚀 Sci-Fi Books 📚",
|
||||
"resource_type": "media-items",
|
||||
"filters": {
|
||||
"genre_filter": "Science Fiction",
|
||||
"search": "科幻"
|
||||
}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 201 Created
|
||||
# Unicode (emoji, CJK characters) should be supported
|
||||
```
|
||||
|
||||
**Test 9: Malformed JSON**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Test",
|
||||
"resource_type": "media-items",
|
||||
"filters": {
|
||||
"missing closing brace": "broken"
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 400 Bad Request
|
||||
# {
|
||||
# "error": "invalid request body"
|
||||
# }
|
||||
```
|
||||
|
||||
**Test 10: Invalid Filter ID Format**
|
||||
```bash
|
||||
curl -X PUT \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Updated",
|
||||
"resource_type": "media-items",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters/not-a-uuid"
|
||||
|
||||
# Expected: 400 Bad Request
|
||||
# {
|
||||
# "error": "invalid filter ID"
|
||||
# }
|
||||
```
|
||||
|
||||
**Test 11: Missing Filters Field**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Test Filter",
|
||||
"resource_type": "media-items"
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 400 Bad Request
|
||||
# Missing required field
|
||||
```
|
||||
|
||||
**Test 12: Whitespace-Only Name**
|
||||
```bash
|
||||
curl -X POST \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": " ",
|
||||
"resource_type": "media-items",
|
||||
"filters": {}
|
||||
}' \
|
||||
"{{base_url}}/api/saved-filters"
|
||||
|
||||
# Expected: 400 Bad Request
|
||||
# Name validation should reject whitespace-only names
|
||||
```
|
||||
|
||||
**Validation Rules Summary:**
|
||||
|
||||
**Required Fields:**
|
||||
- `name`: Non-empty string (max 100 chars per plan)
|
||||
- `resource_type`: Non-empty string
|
||||
- `filters`: Object (can be empty {})
|
||||
|
||||
**Optional Fields:**
|
||||
- None (all 3 fields are required)
|
||||
|
||||
**Field Validation:**
|
||||
- Name uniqueness checked per user + resource type
|
||||
- Filter ID must be valid UUID for PUT/DELETE
|
||||
- Filters must be valid JSON object
|
||||
|
||||
**Common Validation Errors:**
|
||||
```json
|
||||
{
|
||||
"error": "name and resource_type are required"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "invalid request body"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "invalid filter ID"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "filter with name 'X' already exists for this resource type"
|
||||
}
|
||||
```
|
||||
data: "{\n \"name\": \"\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }"
|
||||
docs: "## Filter Validation - Edge Cases\n\nTests various validation edge cases including empty names, invalid resource types, malformed JSON, etc.\n\n**Test Cases:**\n\n**Test 1: Empty Filter Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# {\n# \"error\": \"name and resource_type are required\"\n# }\n```\n\n**Test 2: Missing Name Field**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# Missing required field validation\n```\n\n**Test 3: Missing Resource Type**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\"\
|
||||
\ \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Test Filter\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# {\n# \"error\": \"name and resource_type are required\"\n# }\n```\n\n**Test 4: Invalid Resource Type**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Test Filter\",\n \"resource_type\": \"invalid-type\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request or success\n# Note: Current validation allows any string (extensible design)\n# Future: May validate against allowed resource types\n```\n\n**Test 5: Empty Filters Object**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Empty Filter\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \
|
||||
\ \"{{base_url}}/api/saved-filters\"\n\n# Expected: 201 Created\n# Empty filters object is valid (matches no criteria)\n```\n\n**Test 6: Very Long Filter Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"This is a very long filter name that exceeds one hundred characters and should be rejected by the validation logic in the handler\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request or 201 Created\n# Note: Plan specifies max 100 chars, but validation may not be enforced\n```\n\n**Test 7: Special Characters in Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Filter with <script>alert(\\\"xss\\\")</script>\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters\"\
|
||||
\n\n# Expected: 201 Created (input sanitized)\n# Note: Names are stored as-is, no HTML escaping needed\n```\n\n**Test 8: Unicode Characters in Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"\U0001F680 Sci-Fi Books \U0001F4DA\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n \"genre_filter\": \"Science Fiction\",\n \"search\": \"科幻\"\n }\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 201 Created\n# Unicode (emoji, CJK characters) should be supported\n```\n\n**Test 9: Malformed JSON**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Test\",\n \"resource_type\": \"media-items\",\n \"filters\": {\n\"missing closing brace\": \"broken\"\n}' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# {\n# \"error\": \"invalid request body\"\
|
||||
\n# }\n```\n\n**Test 10: Invalid Filter ID Format**\n```bash\ncurl -X PUT \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Updated\",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n }' \\\n \"{{base_url}}/api/saved-filters/not-a-uuid\"\n\n# Expected: 400 Bad Request\n# {\n# \"error\": \"invalid filter ID\"\n# }\n```\n\n**Test 11: Missing Filters Field**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \"Test Filter\",\n \"resource_type\": \"media-items\"\n }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# Missing required field\n```\n\n**Test 12: Whitespace-Only Name**\n```bash\ncurl -X POST \\\n -H \"Authorization: Bearer YOUR_TOKEN\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\n \"name\": \" \",\n \"resource_type\": \"media-items\",\n \"filters\": {}\n\
|
||||
\ }' \\\n \"{{base_url}}/api/saved-filters\"\n\n# Expected: 400 Bad Request\n# Name validation should reject whitespace-only names\n```\n\n**Validation Rules Summary:**\n\n**Required Fields:**\n- `name`: Non-empty string (max 100 chars per plan)\n- `resource_type`: Non-empty string\n- `filters`: Object (can be empty {})\n\n**Optional Fields:**\n- None (all 3 fields are required)\n\n**Field Validation:**\n- Name uniqueness checked per user + resource type\n- Filter ID must be valid UUID for PUT/DELETE\n- Filters must be valid JSON object\n\n**Common Validation Errors:**\n```json\n{\n \"error\": \"name and resource_type are required\"\n}\n```\n\n```json\n{\n \"error\": \"invalid request body\"\n}\n```\n\n```json\n{\n \"error\": \"invalid filter ID\"\n}\n```\n\n```json\n{\n \"error\": \"filter with name 'X' already exists for this resource type\"\n}\n```"
|
||||
Reference in new issue
Block a user