Files
taskpile/backend/tests/integration_test.rs
Alvis f1d51b8cc8 Add side panels, task selection, graph animation, and project docs
- Foldable left panel (user profile) and right panel (task details)
- Clicking a task in the list or graph node selects it and shows details
- Both views (task list + graph) always mounted via absolute inset-0 for
  correct canvas dimensions; tabs toggle visibility with opacity
- Graph node selection animation: other nodes repel outward (charge -600),
  then selected node smoothly slides to center (500ms cubic ease-out),
  then charge restores to -120 and graph stabilizes
- Graph re-fits on tab switch and panel resize via ResizeObserver
- Fix UUID string IDs throughout (backend returns UUIDs, not integers)
- Add TaskDetailPanel, UserPanel components
- Add CLAUDE.md project documentation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 11:23:06 +00:00

154 lines
4.7 KiB
Rust

use axum::{
routing::{delete, get, patch, post},
Router,
};
use axum_test::TestServer;
use serde_json::{json, Value};
use sqlx::SqlitePool;
async fn setup_server() -> TestServer {
let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY NOT NULL,
title TEXT NOT NULL,
description TEXT,
completed BOOLEAN NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL
)
"#,
)
.execute(&pool)
.await
.unwrap();
let app = Router::new()
.route("/api/tasks", get(taskpile_backend::routes::tasks::list_tasks))
.route("/api/tasks", post(taskpile_backend::routes::tasks::create_task))
.route("/api/tasks/:id", patch(taskpile_backend::routes::tasks::update_task))
.route("/api/tasks/:id", delete(taskpile_backend::routes::tasks::delete_task))
.route("/api/graph", get(taskpile_backend::routes::graph::get_graph))
.with_state(pool);
TestServer::new(app).unwrap()
}
#[tokio::test]
async fn test_list_tasks_empty() {
let server = setup_server().await;
let resp = server.get("/api/tasks").await;
resp.assert_status_ok();
let body: Value = resp.json();
assert_eq!(body, json!([]));
}
#[tokio::test]
async fn test_create_task() {
let server = setup_server().await;
let resp = server
.post("/api/tasks")
.json(&json!({"title": "Buy milk"}))
.await;
resp.assert_status(axum::http::StatusCode::CREATED);
let body: Value = resp.json();
assert_eq!(body["title"], "Buy milk");
assert_eq!(body["completed"], false);
assert!(body["id"].is_string());
}
#[tokio::test]
async fn test_crud_flow() {
let server = setup_server().await;
// Create
let resp = server
.post("/api/tasks")
.json(&json!({"title": "Write tests", "description": "Important"}))
.await;
resp.assert_status(axum::http::StatusCode::CREATED);
let created: Value = resp.json();
let id = created["id"].as_str().unwrap().to_string();
// List
let resp = server.get("/api/tasks").await;
resp.assert_status_ok();
let tasks: Value = resp.json();
assert_eq!(tasks.as_array().unwrap().len(), 1);
// Update
let resp = server
.patch(&format!("/api/tasks/{id}"))
.json(&json!({"completed": true}))
.await;
resp.assert_status_ok();
let updated: Value = resp.json();
assert_eq!(updated["completed"], true);
assert_eq!(updated["title"], "Write tests");
// Graph
let resp = server.get("/api/graph").await;
resp.assert_status_ok();
let graph: Value = resp.json();
assert_eq!(graph["nodes"].as_array().unwrap().len(), 1);
assert!(graph["edges"].is_array());
// Delete
let resp = server.delete(&format!("/api/tasks/{id}")).await;
resp.assert_status(axum::http::StatusCode::NO_CONTENT);
// List again - empty
let resp = server.get("/api/tasks").await;
resp.assert_status_ok();
let tasks: Value = resp.json();
assert_eq!(tasks.as_array().unwrap().len(), 0);
}
#[tokio::test]
async fn test_delete_nonexistent_returns_404() {
let server = setup_server().await;
let resp = server.delete("/api/tasks/does-not-exist").await;
resp.assert_status(axum::http::StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn test_graph_endpoint_structure() {
let server = setup_server().await;
let resp = server.get("/api/graph").await;
resp.assert_status_ok();
let graph: Value = resp.json();
assert!(graph["nodes"].is_array());
assert!(graph["edges"].is_array());
}
#[tokio::test]
async fn test_create_task_with_description() {
let server = setup_server().await;
let resp = server
.post("/api/tasks")
.json(&json!({"title": "Task with desc", "description": "Some details"}))
.await;
resp.assert_status(axum::http::StatusCode::CREATED);
let body: Value = resp.json();
assert_eq!(body["description"], "Some details");
}
#[tokio::test]
async fn test_graph_with_multiple_tasks_has_edges() {
let server = setup_server().await;
// Create enough tasks to statistically guarantee edges (~30% of pairs)
for i in 0..10 {
server
.post("/api/tasks")
.json(&json!({"title": format!("Task {i}")}))
.await;
}
let resp = server.get("/api/graph").await;
resp.assert_status_ok();
let graph: Value = resp.json();
assert_eq!(graph["nodes"].as_array().unwrap().len(), 10);
// 45 possible pairs at 30% => highly likely to have at least 1
let edge_count = graph["edges"].as_array().unwrap().len();
assert!(edge_count <= 45);
}