Update SQLX stuff
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT \n t.id as \"id!: Uuid\", \n t.project_id as \"project_id!: Uuid\", \n t.title, \n t.description, \n t.status as \"status!: TaskStatus\", \n t.created_at as \"created_at!: DateTime<Utc>\", \n t.updated_at as \"updated_at!: DateTime<Utc>\",\n CASE WHEN in_progress_attempts.task_id IS NOT NULL THEN true ELSE false END as \"has_in_progress_attempt!\",\n CASE WHEN merged_attempts.task_id IS NOT NULL THEN true ELSE false END as \"has_merged_attempt!\"\n FROM tasks t\n LEFT JOIN (\n SELECT DISTINCT ta.task_id \n FROM task_attempts ta\n INNER JOIN (\n SELECT task_attempt_id, MAX(created_at) as latest_created_at\n FROM task_attempt_activities\n GROUP BY task_attempt_id\n ) latest_activity ON ta.id = latest_activity.task_attempt_id\n INNER JOIN task_attempt_activities taa ON ta.id = taa.task_attempt_id \n AND taa.created_at = latest_activity.latest_created_at\n WHERE taa.status IN ('setuprunning', 'executorrunning')\n ) in_progress_attempts ON t.id = in_progress_attempts.task_id\n LEFT JOIN (\n SELECT DISTINCT ta.task_id \n FROM task_attempts ta\n WHERE ta.merge_commit IS NOT NULL\n ) merged_attempts ON t.id = merged_attempts.task_id\n WHERE t.project_id = $1 \n ORDER BY t.created_at DESC",
|
"query": "SELECT \n t.id as \"id!: Uuid\", \n t.project_id as \"project_id!: Uuid\", \n t.title, \n t.description, \n t.status as \"status!: TaskStatus\", \n t.created_at as \"created_at!: DateTime<Utc>\", \n t.updated_at as \"updated_at!: DateTime<Utc>\",\n CASE WHEN in_progress_attempts.task_id IS NOT NULL THEN true ELSE false END as \"has_in_progress_attempt!: i64\",\n CASE WHEN merged_attempts.task_id IS NOT NULL THEN true ELSE false END as \"has_merged_attempt!\"\n FROM tasks t\n LEFT JOIN (\n SELECT DISTINCT ta.task_id \n FROM task_attempts ta\n INNER JOIN (\n SELECT task_attempt_id, MAX(created_at) as latest_created_at\n FROM task_attempt_activities\n GROUP BY task_attempt_id\n ) latest_activity ON ta.id = latest_activity.task_attempt_id\n INNER JOIN task_attempt_activities taa ON ta.id = taa.task_attempt_id \n AND taa.created_at = latest_activity.latest_created_at\n WHERE taa.status IN ('setuprunning', 'executorrunning')\n ) in_progress_attempts ON t.id = in_progress_attempts.task_id\n LEFT JOIN (\n SELECT DISTINCT ta.task_id \n FROM task_attempts ta\n WHERE ta.merge_commit IS NOT NULL\n ) merged_attempts ON t.id = merged_attempts.task_id\n WHERE t.project_id = $1 \n ORDER BY t.created_at DESC",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -39,14 +39,14 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "has_in_progress_attempt!",
|
"name": "has_in_progress_attempt!: i64",
|
||||||
"ordinal": 7,
|
"ordinal": 7,
|
||||||
"type_info": "Int"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "has_merged_attempt!",
|
"name": "has_merged_attempt!",
|
||||||
"ordinal": 8,
|
"ordinal": 8,
|
||||||
"type_info": "Int"
|
"type_info": "Integer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -64,5 +64,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "ffd6ac9bbe361cdb172198be10ecfbcc10970bba5d9f41fd5018c858fd784cb2"
|
"hash": "35c2c111465a13b4a9d45ef505512796b6e6970ffd35ee4095d9c5caf28502e4"
|
||||||
}
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "SELECT COUNT(*) as count FROM projects WHERE id = $1",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "count",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "b4efb71365b75edd3dfc843f774f7cdb06dc756328ed5929d6b13ef3a956be0e"
|
|
||||||
}
|
|
||||||
20
backend/.sqlx/query-d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2.json
generated
Normal file
20
backend/.sqlx/query-d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2.json
generated
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n SELECT COUNT(*) as \"count!: i64\"\n FROM projects\n WHERE id = $1\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "count!: i64",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2"
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ serde_json = { workspace = true }
|
|||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "sqlite", "chrono", "uuid"] }
|
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "sqlite", "chrono", "uuid"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
uuid = { version = "1.0", features = ["v4", "serde"] }
|
uuid = { version = "1.0", features = ["v4", "serde"] }
|
||||||
bcrypt = "0.15"
|
bcrypt = "0.15"
|
||||||
|
|||||||
@@ -12,9 +12,17 @@ use crate::models::{
|
|||||||
task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity},
|
task_attempt_activity::{CreateTaskAttemptActivity, TaskAttemptActivity},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// #[derive(Debug)]
|
||||||
|
// pub enum ExecutionType {
|
||||||
|
// SetupScript,
|
||||||
|
// CodingAgent,
|
||||||
|
// DevServer,
|
||||||
|
// }
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RunningExecution {
|
pub struct RunningExecution {
|
||||||
pub task_attempt_id: Uuid,
|
pub task_attempt_id: Uuid,
|
||||||
|
// pub execution_type: ExecutionType,
|
||||||
pub child: tokio::process::Child,
|
pub child: tokio::process::Child,
|
||||||
pub started_at: DateTime<Utc>,
|
pub started_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,9 +143,17 @@ impl Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn exists(pool: &SqlitePool, id: Uuid) -> Result<bool, sqlx::Error> {
|
pub async fn exists(pool: &SqlitePool, id: Uuid) -> Result<bool, sqlx::Error> {
|
||||||
let result = sqlx::query!("SELECT COUNT(*) as count FROM projects WHERE id = $1", id)
|
let result = sqlx::query!(
|
||||||
.fetch_one(pool)
|
r#"
|
||||||
.await?;
|
SELECT COUNT(*) as "count!: i64"
|
||||||
|
FROM projects
|
||||||
|
WHERE id = $1
|
||||||
|
"#,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(result.count > 0)
|
Ok(result.count > 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ impl Task {
|
|||||||
t.status as "status!: TaskStatus",
|
t.status as "status!: TaskStatus",
|
||||||
t.created_at as "created_at!: DateTime<Utc>",
|
t.created_at as "created_at!: DateTime<Utc>",
|
||||||
t.updated_at as "updated_at!: DateTime<Utc>",
|
t.updated_at as "updated_at!: DateTime<Utc>",
|
||||||
CASE WHEN in_progress_attempts.task_id IS NOT NULL THEN true ELSE false END as "has_in_progress_attempt!",
|
CASE WHEN in_progress_attempts.task_id IS NOT NULL THEN true ELSE false END as "has_in_progress_attempt!: i64",
|
||||||
CASE WHEN merged_attempts.task_id IS NOT NULL THEN true ELSE false END as "has_merged_attempt!"
|
CASE WHEN merged_attempts.task_id IS NOT NULL THEN true ELSE false END as "has_merged_attempt!"
|
||||||
FROM tasks t
|
FROM tasks t
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
|
|||||||
@@ -135,8 +135,15 @@ pub async fn create_task_and_start(
|
|||||||
|
|
||||||
// Create task attempt
|
// Create task attempt
|
||||||
let attempt_id = Uuid::new_v4();
|
let attempt_id = Uuid::new_v4();
|
||||||
let worktree_path = format!("/tmp/task-{}-attempt-{}", task_id, std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis());
|
let worktree_path = format!(
|
||||||
|
"/tmp/task-{}-attempt-{}",
|
||||||
|
task_id,
|
||||||
|
std::time::SystemTime::now()
|
||||||
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_millis()
|
||||||
|
);
|
||||||
|
|
||||||
let attempt_payload = CreateTaskAttempt {
|
let attempt_payload = CreateTaskAttempt {
|
||||||
task_id,
|
task_id,
|
||||||
worktree_path,
|
worktree_path,
|
||||||
@@ -756,399 +763,3 @@ pub fn tasks_router() -> Router {
|
|||||||
post(delete_task_attempt_file),
|
post(delete_task_attempt_file),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::auth::hash_password;
|
|
||||||
use crate::models::{
|
|
||||||
project::Project,
|
|
||||||
task::{CreateTask, TaskStatus, UpdateTask},
|
|
||||||
user::User,
|
|
||||||
};
|
|
||||||
use axum::extract::Extension;
|
|
||||||
use chrono::Utc;
|
|
||||||
use sqlx::SqlitePool;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
async fn create_test_user(
|
|
||||||
pool: &SqlitePool,
|
|
||||||
email: &str,
|
|
||||||
password: &str,
|
|
||||||
is_admin: bool,
|
|
||||||
) -> User {
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
let now = Utc::now();
|
|
||||||
let password_hash = hash_password(password).unwrap();
|
|
||||||
|
|
||||||
sqlx::query_as!(
|
|
||||||
User,
|
|
||||||
"INSERT INTO users (id, email, password_hash, is_admin, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, email, password_hash, is_admin, created_at, updated_at",
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
password_hash,
|
|
||||||
is_admin,
|
|
||||||
now,
|
|
||||||
now
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_test_project(pool: &SqlitePool, name: &str, owner_id: Uuid) -> Project {
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
let now = Utc::now();
|
|
||||||
let git_repo_path = format!("/tmp/test-repo-{}", id);
|
|
||||||
|
|
||||||
sqlx::query_as!(
|
|
||||||
Project,
|
|
||||||
"INSERT INTO projects (id, name, git_repo_path, owner_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, name, git_repo_path, owner_id, created_at, updated_at",
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
git_repo_path,
|
|
||||||
owner_id,
|
|
||||||
now,
|
|
||||||
now
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_test_task(
|
|
||||||
pool: &SqlitePool,
|
|
||||||
project_id: Uuid,
|
|
||||||
title: &str,
|
|
||||||
description: Option<String>,
|
|
||||||
status: TaskStatus,
|
|
||||||
) -> Task {
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
let now = Utc::now();
|
|
||||||
|
|
||||||
sqlx::query_as!(
|
|
||||||
Task,
|
|
||||||
r#"INSERT INTO tasks (id, project_id, title, description, status, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at"#,
|
|
||||||
id,
|
|
||||||
project_id,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
status as TaskStatus,
|
|
||||||
now,
|
|
||||||
now
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_project_tasks_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
|
|
||||||
// Create multiple tasks
|
|
||||||
create_test_task(
|
|
||||||
&pool,
|
|
||||||
project.id,
|
|
||||||
"Task 1",
|
|
||||||
Some("Description 1".to_string()),
|
|
||||||
TaskStatus::Todo,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
create_test_task(&pool, project.id, "Task 2", None, TaskStatus::InProgress).await;
|
|
||||||
create_test_task(
|
|
||||||
&pool,
|
|
||||||
project.id,
|
|
||||||
"Task 3",
|
|
||||||
Some("Description 3".to_string()),
|
|
||||||
TaskStatus::Done,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let result = get_project_tasks(Path(project.id), Extension(pool)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
assert_eq!(response.data.unwrap().len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_project_tasks_empty_project(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Empty Project", user.id).await;
|
|
||||||
|
|
||||||
let result = get_project_tasks(Path(project.id), Extension(pool)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
assert_eq!(response.data.unwrap().len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_task_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
let task = create_test_task(
|
|
||||||
&pool,
|
|
||||||
project.id,
|
|
||||||
"Test Task",
|
|
||||||
Some("Test Description".to_string()),
|
|
||||||
TaskStatus::Todo,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let result = get_task(Path((project.id, task.id)), Extension(pool)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let returned_task = response.data.unwrap();
|
|
||||||
assert_eq!(returned_task.id, task.id);
|
|
||||||
assert_eq!(returned_task.title, task.title);
|
|
||||||
assert_eq!(returned_task.description, task.description);
|
|
||||||
assert_eq!(returned_task.status, task.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_task_not_found(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
let nonexistent_task_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
let result = get_task(Path((project.id, nonexistent_task_id)), Extension(pool)).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_task_wrong_project(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project1 = create_test_project(&pool, "Project 1", user.id).await;
|
|
||||||
let project2 = create_test_project(&pool, "Project 2", user.id).await;
|
|
||||||
let task = create_test_task(&pool, project1.id, "Test Task", None, TaskStatus::Todo).await;
|
|
||||||
|
|
||||||
// Try to get task from wrong project
|
|
||||||
let result = get_task(Path((project2.id, task.id)), Extension(pool)).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_create_task_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
|
|
||||||
let create_request = CreateTask {
|
|
||||||
project_id: project.id, // This will be overridden by the path parameter
|
|
||||||
title: "New Task".to_string(),
|
|
||||||
description: Some("Task description".to_string()),
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = create_task(Path(project.id), Extension(pool), Json(create_request)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let created_task = response.data.unwrap();
|
|
||||||
assert_eq!(created_task.title, "New Task");
|
|
||||||
assert_eq!(
|
|
||||||
created_task.description,
|
|
||||||
Some("Task description".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(created_task.status, TaskStatus::Todo);
|
|
||||||
assert_eq!(created_task.project_id, project.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_create_task_project_not_found(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let nonexistent_project_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
let create_request = CreateTask {
|
|
||||||
project_id: nonexistent_project_id,
|
|
||||||
title: "New Task".to_string(),
|
|
||||||
description: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = create_task(
|
|
||||||
Path(nonexistent_project_id),
|
|
||||||
Extension(pool),
|
|
||||||
Json(create_request),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_update_task_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
let task = create_test_task(
|
|
||||||
&pool,
|
|
||||||
project.id,
|
|
||||||
"Original Title",
|
|
||||||
Some("Original Description".to_string()),
|
|
||||||
TaskStatus::Todo,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let update_request = UpdateTask {
|
|
||||||
title: Some("Updated Title".to_string()),
|
|
||||||
description: Some("Updated Description".to_string()),
|
|
||||||
status: Some(TaskStatus::InProgress),
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = update_task(
|
|
||||||
Path((project.id, task.id)),
|
|
||||||
Extension(pool),
|
|
||||||
Json(update_request),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let updated_task = response.data.unwrap();
|
|
||||||
assert_eq!(updated_task.title, "Updated Title");
|
|
||||||
assert_eq!(
|
|
||||||
updated_task.description,
|
|
||||||
Some("Updated Description".to_string())
|
|
||||||
);
|
|
||||||
assert_eq!(updated_task.status, TaskStatus::InProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_update_task_partial(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
let task = create_test_task(
|
|
||||||
&pool,
|
|
||||||
project.id,
|
|
||||||
"Original Title",
|
|
||||||
Some("Original Description".to_string()),
|
|
||||||
TaskStatus::Todo,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Only update status
|
|
||||||
let update_request = UpdateTask {
|
|
||||||
title: None,
|
|
||||||
description: None,
|
|
||||||
status: Some(TaskStatus::Done),
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = update_task(
|
|
||||||
Path((project.id, task.id)),
|
|
||||||
Extension(pool),
|
|
||||||
Json(update_request),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let updated_task = response.data.unwrap();
|
|
||||||
assert_eq!(updated_task.title, "Original Title"); // Should remain unchanged
|
|
||||||
assert_eq!(
|
|
||||||
updated_task.description,
|
|
||||||
Some("Original Description".to_string())
|
|
||||||
); // Should remain unchanged
|
|
||||||
assert_eq!(updated_task.status, TaskStatus::Done); // Should be updated
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_update_task_not_found(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
let nonexistent_task_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
let update_request = UpdateTask {
|
|
||||||
title: Some("Updated Title".to_string()),
|
|
||||||
description: None,
|
|
||||||
status: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = update_task(
|
|
||||||
Path((project.id, nonexistent_task_id)),
|
|
||||||
Extension(pool),
|
|
||||||
Json(update_request),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_update_task_wrong_project(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project1 = create_test_project(&pool, "Project 1", user.id).await;
|
|
||||||
let project2 = create_test_project(&pool, "Project 2", user.id).await;
|
|
||||||
let task = create_test_task(&pool, project1.id, "Test Task", None, TaskStatus::Todo).await;
|
|
||||||
|
|
||||||
let update_request = UpdateTask {
|
|
||||||
title: Some("Updated Title".to_string()),
|
|
||||||
description: None,
|
|
||||||
status: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try to update task in wrong project
|
|
||||||
let result = update_task(
|
|
||||||
Path((project2.id, task.id)),
|
|
||||||
Extension(pool),
|
|
||||||
Json(update_request),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_delete_task_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
let task =
|
|
||||||
create_test_task(&pool, project.id, "Task to Delete", None, TaskStatus::Todo).await;
|
|
||||||
|
|
||||||
let result = delete_task(Path((project.id, task.id)), Extension(pool)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert_eq!(response.message.unwrap(), "Task deleted successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_delete_task_not_found(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", user.id).await;
|
|
||||||
let nonexistent_task_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
let result = delete_task(Path((project.id, nonexistent_task_id)), Extension(pool)).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_delete_task_wrong_project(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project1 = create_test_project(&pool, "Project 1", user.id).await;
|
|
||||||
let project2 = create_test_project(&pool, "Project 2", user.id).await;
|
|
||||||
let task =
|
|
||||||
create_test_task(&pool, project1.id, "Task to Delete", None, TaskStatus::Todo).await;
|
|
||||||
|
|
||||||
// Try to delete task from wrong project
|
|
||||||
let result = delete_task(Path((project2.id, task.id)), Extension(pool)).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user