Create a plan for implementing projects and also create the backend API. (#19)
Creates projects to organize tasks under. (Note the migration also contains the tables for creating folders for projects as well because adding foreign keys is a PITA in sqlite apparently). Reviewed-on: #19 Co-authored-by: Drew Galbraith <drew@tiramisu.one> Co-committed-by: Drew Galbraith <drew@tiramisu.one>
This commit is contained in:
parent
69f4a6f1ca
commit
4552c347c6
17 changed files with 957 additions and 12 deletions
387
backend/tests/api/projects.hurl
Normal file
387
backend/tests/api/projects.hurl
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
# Project API Tests
|
||||
|
||||
# Test: Create a new project with all fields (POST /api/projects)
|
||||
POST {{host}}/api/projects
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Test Project Alpha",
|
||||
"color": "#1976d2"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
project_id: jsonpath "$.id"
|
||||
[Asserts]
|
||||
jsonpath "$.title" == "Test Project Alpha"
|
||||
jsonpath "$.color" == "#1976d2"
|
||||
jsonpath "$.status" == "active"
|
||||
jsonpath "$.folder_id" == null
|
||||
jsonpath "$.sort_order" == 0
|
||||
jsonpath "$.id" exists
|
||||
jsonpath "$.created_at" exists
|
||||
jsonpath "$.updated_at" exists
|
||||
|
||||
# Test: Create another new project
|
||||
POST {{host}}/api/projects
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Test Project Beta",
|
||||
"color": "#ffffff"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
project_id_beta: jsonpath "$.id"
|
||||
[Asserts]
|
||||
jsonpath "$.title" == "Test Project Beta"
|
||||
jsonpath "$.color" == "#ffffff"
|
||||
jsonpath "$.status" == "active"
|
||||
jsonpath "$.folder_id" == null
|
||||
jsonpath "$.sort_order" == 1000
|
||||
jsonpath "$.id" exists
|
||||
jsonpath "$.created_at" exists
|
||||
jsonpath "$.updated_at" exists
|
||||
|
||||
|
||||
POST {{host}}/api/projects
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Missing Color"
|
||||
}
|
||||
|
||||
HTTP 422
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
|
||||
# Test: Create project with invalid data (missing title)
|
||||
POST {{host}}/api/projects
|
||||
Content-Type: application/json
|
||||
{
|
||||
"color": "#ffffff"
|
||||
}
|
||||
|
||||
HTTP 422
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Create project with invalid data (invalid color)
|
||||
POST {{host}}/api/projects
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Invalid Color",
|
||||
"color": "#ffffffasdf"
|
||||
}
|
||||
|
||||
HTTP 422
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
|
||||
# Test: Get a specific project by ID (GET /api/projects/{id})
|
||||
GET {{host}}/api/projects/{{project_id}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.id" == "{{project_id}}"
|
||||
jsonpath "$.title" == "Test Project Alpha"
|
||||
jsonpath "$.color" == "#1976d2"
|
||||
jsonpath "$.status" == "active"
|
||||
jsonpath "$.folder_id" == null
|
||||
jsonpath "$.created_at" exists
|
||||
jsonpath "$.updated_at" exists
|
||||
|
||||
# Test: Get a minimal project by ID
|
||||
GET {{host}}/api/projects/{{project_id_beta}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.id" == "{{project_id_beta}}"
|
||||
jsonpath "$.title" == "Test Project Beta"
|
||||
jsonpath "$.color" == "#ffffff"
|
||||
jsonpath "$.status" == "active"
|
||||
|
||||
# Test: Get non-existent project
|
||||
GET {{host}}/api/projects/00000000-0000-0000-0000-000000000000
|
||||
|
||||
HTTP 404
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Get project with invalid UUID format
|
||||
GET {{host}}/api/projects/invalid-uuid
|
||||
|
||||
HTTP 400
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: List all projects (GET /api/projects)
|
||||
GET {{host}}/api/projects
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
initial_count: jsonpath "$" count
|
||||
[Asserts]
|
||||
jsonpath "$" isCollection
|
||||
jsonpath "$[*].id" contains "{{project_id}}"
|
||||
jsonpath "$[*].id" contains "{{project_id_beta}}"
|
||||
jsonpath "$[*].title" contains "Test Project Alpha"
|
||||
jsonpath "$[*].title" contains "Test Project Beta"
|
||||
|
||||
# Test: Verify all projects have required fields
|
||||
GET {{host}}/api/projects
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$[?(@.id=='{{project_id}}')].title" exists
|
||||
jsonpath "$[?(@.id=='{{project_id}}')].status" exists
|
||||
jsonpath "$[?(@.id=='{{project_id}}')].created_at" exists
|
||||
jsonpath "$[?(@.id=='{{project_id}}')].updated_at" exists
|
||||
jsonpath "$[?(@.id=='{{project_id}}')].sort_order" exists
|
||||
|
||||
# Test: Update project title only (PUT /api/projects/{id})
|
||||
PUT {{host}}/api/projects/{{project_id}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Updated Project Alpha"
|
||||
}
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
first_updated_at: jsonpath "$.updated_at"
|
||||
[Asserts]
|
||||
jsonpath "$.id" == "{{project_id}}"
|
||||
jsonpath "$.title" == "Updated Project Alpha"
|
||||
jsonpath "$.color" == "#1976d2"
|
||||
jsonpath "$.status" == "active"
|
||||
|
||||
|
||||
# Test: Update status to done
|
||||
PUT {{host}}/api/projects/{{project_id}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"status": "done"
|
||||
}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.id" == "{{project_id}}"
|
||||
jsonpath "$.status" == "done"
|
||||
|
||||
# Test: Update status to backlog
|
||||
PUT {{host}}/api/projects/{{project_id}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"status": "backlog"
|
||||
}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.id" == "{{project_id}}"
|
||||
jsonpath "$.status" == "backlog"
|
||||
|
||||
# Test: Update color
|
||||
PUT {{host}}/api/projects/{{project_id}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"color": "#388e3c"
|
||||
}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.id" == "{{project_id}}"
|
||||
jsonpath "$.color" == "#388e3c"
|
||||
|
||||
|
||||
# Test: Update multiple fields together
|
||||
PUT {{host}}/api/projects/{{project_id}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Completely Updated Project",
|
||||
"color": "#d32f2f",
|
||||
"status": "active"
|
||||
}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.id" == "{{project_id}}"
|
||||
jsonpath "$.title" == "Completely Updated Project"
|
||||
jsonpath "$.color" == "#d32f2f"
|
||||
jsonpath "$.status" == "active"
|
||||
|
||||
|
||||
# Test: Update non-existent project
|
||||
PUT {{host}}/api/projects/00000000-0000-0000-0000-000000000000
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "This should fail"
|
||||
}
|
||||
|
||||
HTTP 404
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Update with invalid UUID format
|
||||
PUT {{host}}/api/projects/invalid-uuid-format
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "This should also fail"
|
||||
}
|
||||
|
||||
HTTP 400
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Update with empty title
|
||||
PUT {{host}}/api/projects/{{project_id_beta}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": ""
|
||||
}
|
||||
|
||||
HTTP 422
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Update with invalid status
|
||||
PUT {{host}}/api/projects/{{project_id_beta}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"status": "invalid_status"
|
||||
}
|
||||
|
||||
HTTP 422
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Update with invalid color format
|
||||
PUT {{host}}/api/projects/{{project_id_beta}}
|
||||
Content-Type: application/json
|
||||
{
|
||||
"color": "invalid-color"
|
||||
}
|
||||
|
||||
HTTP 422
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Setup: Create a project to delete
|
||||
POST {{host}}/api/projects
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Project to Delete",
|
||||
"color": "#222222"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
delete_project_id: jsonpath "$.id"
|
||||
|
||||
# Test: Delete project successfully (DELETE /api/projects/{id})
|
||||
DELETE {{host}}/api/projects/{{delete_project_id}}
|
||||
|
||||
HTTP 204
|
||||
|
||||
# Test: Verify project is deleted (should return 404)
|
||||
GET {{host}}/api/projects/{{delete_project_id}}
|
||||
|
||||
HTTP 404
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Setup: Create another project for additional delete tests
|
||||
POST {{host}}/api/projects
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Another Project to Delete",
|
||||
"color": "#333333"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
another_delete_project_id: jsonpath "$.id"
|
||||
|
||||
# Test: Verify project exists before deletion
|
||||
GET {{host}}/api/projects/{{another_delete_project_id}}
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$.title" == "Another Project to Delete"
|
||||
|
||||
# Test: Delete the project
|
||||
DELETE {{host}}/api/projects/{{another_delete_project_id}}
|
||||
|
||||
HTTP 204
|
||||
|
||||
# Test: Confirm project no longer exists
|
||||
GET {{host}}/api/projects/{{another_delete_project_id}}
|
||||
|
||||
HTTP 404
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Delete non-existent project
|
||||
DELETE {{host}}/api/projects/00000000-0000-0000-0000-000000000000
|
||||
|
||||
HTTP 404
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Delete with invalid UUID format
|
||||
DELETE {{host}}/api/projects/invalid-uuid-format
|
||||
|
||||
HTTP 400
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: Multiple deletions of same project (idempotency test)
|
||||
POST {{host}}/api/projects
|
||||
Content-Type: application/json
|
||||
{
|
||||
"title": "Project for Idempotency Test",
|
||||
"color": "#234567"
|
||||
}
|
||||
|
||||
HTTP 201
|
||||
[Captures]
|
||||
idempotent_project_id: jsonpath "$.id"
|
||||
|
||||
# First deletion should succeed
|
||||
DELETE {{host}}/api/projects/{{idempotent_project_id}}
|
||||
|
||||
HTTP 204
|
||||
|
||||
# Second deletion should return 404 (project already gone)
|
||||
DELETE {{host}}/api/projects/{{idempotent_project_id}}
|
||||
|
||||
HTTP 404
|
||||
[Asserts]
|
||||
jsonpath "$.error" exists
|
||||
|
||||
# Test: List shows remaining projects after deletions
|
||||
GET {{host}}/api/projects
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$[*].id" contains "{{project_id}}"
|
||||
jsonpath "$[*].id" contains "{{project_id_beta}}"
|
||||
jsonpath "$[*].id" not contains "{{delete_project_id}}"
|
||||
jsonpath "$[*].id" not contains "{{another_delete_project_id}}"
|
||||
jsonpath "$[*].id" not contains "{{idempotent_project_id}}"
|
||||
|
||||
# Cleanup: Delete remaining test projects
|
||||
DELETE {{host}}/api/projects/{{project_id}}
|
||||
|
||||
HTTP 204
|
||||
|
||||
DELETE {{host}}/api/projects/{{project_id_beta}}
|
||||
|
||||
HTTP 204
|
||||
|
||||
# Test: Verify all test projects are cleaned up
|
||||
GET {{host}}/api/projects
|
||||
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
jsonpath "$[*].id" not contains "{{project_id}}"
|
||||
jsonpath "$[*].id" not contains "{{project_id_beta}}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue