Support --before/--after positioning on task create
Allows inserting tasks at specific positions instead of always appending.
Extracted resolve_position helper shared by both create and move_task.
Introduced CreateTask struct to avoid clippy too-many-arguments warning.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/src/bin/ranger/commands/task.rs b/src/bin/ranger/commands/task.rs
index 762acc0..d7fa8a6 100644
--- a/src/bin/ranger/commands/task.rs
+++ b/src/bin/ranger/commands/task.rs
@@ -26,6 +26,12 @@ pub enum TaskCommands {
/// Tags to add (comma-separated)
#[arg(long)]
tag: Option<String>,
+ /// Place before this task key
+ #[arg(long)]
+ before: Option<String>,
+ /// Place after this task key
+ #[arg(long)]
+ after: Option<String>,
},
/// List tasks
List {
@@ -99,6 +105,8 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> anyhow
state,
parent,
tag,
+ before,
+ after,
} => {
let bl = ops::backlog::get_by_key_prefix(pool, &backlog).await?;
let parent_id = if let Some(parent_key) = &parent {
@@ -107,6 +115,17 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> anyhow
None
};
+ let before_id = if let Some(k) = &before {
+ Some(ops::task::get_by_key_prefix(pool, k).await?.id)
+ } else {
+ None
+ };
+ let after_id = if let Some(k) = &after {
+ Some(ops::task::get_by_key_prefix(pool, k).await?.id)
+ } else {
+ None
+ };
+
let state = state
.map(|s| s.parse::<State>())
.transpose()
@@ -114,11 +133,15 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> anyhow
let task = ops::task::create(
pool,
- &title,
- bl.id,
- state,
- parent_id,
- description.as_deref(),
+ ops::task::CreateTask {
+ title: &title,
+ backlog_id: bl.id,
+ state,
+ parent_id,
+ description: description.as_deref(),
+ before_task_id: before_id,
+ after_task_id: after_id,
+ },
)
.await?;
diff --git a/src/ops/blocker.rs b/src/ops/blocker.rs
index 093031f..83876cf 100644
--- a/src/ops/blocker.rs
+++ b/src/ops/blocker.rs
@@ -58,12 +58,34 @@ mod tests {
async fn add_and_list_blockers() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t1 = task::create(&pool, "Blocked", bl.id, None, None, None)
- .await
- .unwrap();
- let t2 = task::create(&pool, "Blocker", bl.id, None, None, None)
- .await
- .unwrap();
+ let t1 = task::create(
+ &pool,
+ task::CreateTask {
+ title: "Blocked",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = task::create(
+ &pool,
+ task::CreateTask {
+ title: "Blocker",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
add(&pool, t1.id, t2.id).await.unwrap();
@@ -76,12 +98,34 @@ mod tests {
async fn remove_blocker() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t1 = task::create(&pool, "Blocked", bl.id, None, None, None)
- .await
- .unwrap();
- let t2 = task::create(&pool, "Blocker", bl.id, None, None, None)
- .await
- .unwrap();
+ let t1 = task::create(
+ &pool,
+ task::CreateTask {
+ title: "Blocked",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = task::create(
+ &pool,
+ task::CreateTask {
+ title: "Blocker",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
add(&pool, t1.id, t2.id).await.unwrap();
remove(&pool, t1.id, t2.id).await.unwrap();
diff --git a/src/ops/comment.rs b/src/ops/comment.rs
index 4463062..8196b74 100644
--- a/src/ops/comment.rs
+++ b/src/ops/comment.rs
@@ -41,9 +41,20 @@ mod tests {
async fn add_and_list_comments() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t = task::create(&pool, "Task", bl.id, None, None, None)
- .await
- .unwrap();
+ let t = task::create(
+ &pool,
+ task::CreateTask {
+ title: "Task",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
add(&pool, t.id, "First comment").await.unwrap();
add(&pool, t.id, "Second comment").await.unwrap();
diff --git a/src/ops/tag.rs b/src/ops/tag.rs
index fc0acd6..178228c 100644
--- a/src/ops/tag.rs
+++ b/src/ops/tag.rs
@@ -82,9 +82,20 @@ mod tests {
async fn add_tag_to_task_and_list() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t = task::create(&pool, "Task", bl.id, None, None, None)
- .await
- .unwrap();
+ let t = task::create(
+ &pool,
+ task::CreateTask {
+ title: "Task",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
let tag = get_or_create(&pool, "important").await.unwrap();
add_to_task(&pool, t.id, tag.id).await.unwrap();
@@ -98,9 +109,20 @@ mod tests {
async fn add_tag_to_task_is_idempotent() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t = task::create(&pool, "Task", bl.id, None, None, None)
- .await
- .unwrap();
+ let t = task::create(
+ &pool,
+ task::CreateTask {
+ title: "Task",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
let tag = get_or_create(&pool, "dup").await.unwrap();
add_to_task(&pool, t.id, tag.id).await.unwrap();
diff --git a/src/ops/task.rs b/src/ops/task.rs
index cd7c882..3a58069 100644
--- a/src/ops/task.rs
+++ b/src/ops/task.rs
@@ -4,16 +4,19 @@ use crate::models::{State, Task};
use crate::position;
use sqlx::SqlitePool;
-pub async fn create(
- pool: &SqlitePool,
- title: &str,
- backlog_id: i64,
- state: Option<State>,
- parent_id: Option<i64>,
- description: Option<&str>,
-) -> Result<Task, RangerError> {
+pub struct CreateTask<'a> {
+ pub title: &'a str,
+ pub backlog_id: i64,
+ pub state: Option<State>,
+ pub parent_id: Option<i64>,
+ pub description: Option<&'a str>,
+ pub before_task_id: Option<i64>,
+ pub after_task_id: Option<i64>,
+}
+
+pub async fn create(pool: &SqlitePool, params: CreateTask<'_>) -> Result<Task, RangerError> {
let key = key::generate_key();
- let state = state.unwrap_or(State::Icebox);
+ let state = params.state.unwrap_or(State::Icebox);
let task = sqlx::query_as::<_, Task>(
"INSERT INTO tasks (key, parent_id, title, description, state) \
@@ -21,29 +24,24 @@ pub async fn create(
RETURNING id, key, parent_id, title, description, state, created_at, updated_at",
)
.bind(&key)
- .bind(parent_id)
- .bind(title)
- .bind(description)
+ .bind(params.parent_id)
+ .bind(params.title)
+ .bind(params.description)
.bind(state.as_str())
.fetch_one(pool)
.await?;
- // Get the last position in this backlog+state to append
- let last_pos: Option<String> = sqlx::query_scalar(
- "SELECT bt.position FROM backlog_tasks bt \
- JOIN tasks t ON t.id = bt.task_id \
- WHERE bt.backlog_id = ? AND t.state = ? \
- ORDER BY bt.position DESC LIMIT 1",
+ let new_pos = resolve_position(
+ pool,
+ params.backlog_id,
+ task.id,
+ params.before_task_id,
+ params.after_task_id,
)
- .bind(backlog_id)
- .bind(state.as_str())
- .fetch_optional(pool)
.await?;
- let new_pos = position::midpoint(last_pos.as_deref(), None);
-
sqlx::query("INSERT INTO backlog_tasks (backlog_id, task_id, position) VALUES (?, ?, ?)")
- .bind(backlog_id)
+ .bind(params.backlog_id)
.bind(task.id)
.bind(&new_pos)
.execute(pool)
@@ -153,13 +151,33 @@ pub async fn edit(
Ok(task)
}
-pub async fn move_task(
+/// Compute a position string for a task within a backlog.
+///
+/// When both bounds are None, appends after the last task in the backlog.
+/// When `before_task_id` or `after_task_id` is given, positions relative
+/// to those tasks. `task_id` is excluded from adjacent-position lookups
+/// (relevant for moves where the task already has a position).
+async fn resolve_position(
pool: &SqlitePool,
- task_id: i64,
backlog_id: i64,
+ task_id: i64,
before_task_id: Option<i64>,
after_task_id: Option<i64>,
-) -> Result<(), RangerError> {
+) -> Result<String, RangerError> {
+ if before_task_id.is_none() && after_task_id.is_none() {
+ // Append to end of backlog
+ let last_pos: Option<String> = sqlx::query_scalar(
+ "SELECT bt.position FROM backlog_tasks bt \
+ WHERE bt.backlog_id = ? \
+ ORDER BY bt.position DESC LIMIT 1",
+ )
+ .bind(backlog_id)
+ .fetch_optional(pool)
+ .await?;
+
+ return Ok(position::midpoint(last_pos.as_deref(), None));
+ }
+
// "before" task = the task we want to appear after us (upper bound)
// "after" task = the task we want to appear before us (lower bound)
let upper_bound: Option<String> = if let Some(id) = before_task_id {
@@ -220,7 +238,18 @@ pub async fn move_task(
_ => (lower_bound.clone(), upper_bound.clone()),
};
- let new_pos = position::midpoint(lower.as_deref(), upper.as_deref());
+ Ok(position::midpoint(lower.as_deref(), upper.as_deref()))
+}
+
+pub async fn move_task(
+ pool: &SqlitePool,
+ task_id: i64,
+ backlog_id: i64,
+ before_task_id: Option<i64>,
+ after_task_id: Option<i64>,
+) -> Result<(), RangerError> {
+ let new_pos =
+ resolve_position(pool, backlog_id, task_id, before_task_id, after_task_id).await?;
sqlx::query("UPDATE backlog_tasks SET position = ? WHERE backlog_id = ? AND task_id = ?")
.bind(&new_pos)
@@ -305,9 +334,20 @@ mod tests {
async fn create_task_in_backlog() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let task = create(&pool, "My Task", bl.id, None, None, None)
- .await
- .unwrap();
+ let task = create(
+ &pool,
+ CreateTask {
+ title: "My Task",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
assert_eq!(task.title, "My Task");
assert_eq!(task.state, State::Icebox);
@@ -329,15 +369,48 @@ mod tests {
async fn list_tasks_ordered_by_position() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t1 = create(&pool, "First", bl.id, None, None, None)
- .await
- .unwrap();
- let t2 = create(&pool, "Second", bl.id, None, None, None)
- .await
- .unwrap();
- let t3 = create(&pool, "Third", bl.id, None, None, None)
- .await
- .unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "Third",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
let tasks = list(&pool, bl.id, None).await.unwrap();
assert_eq!(tasks.len(), 3);
@@ -350,12 +423,34 @@ mod tests {
async fn list_tasks_with_state_filter() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- create(&pool, "Icebox task", bl.id, None, None, None)
- .await
- .unwrap();
- create(&pool, "Queued task", bl.id, Some(State::Queued), None, None)
- .await
- .unwrap();
+ create(
+ &pool,
+ CreateTask {
+ title: "Icebox task",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ create(
+ &pool,
+ CreateTask {
+ title: "Queued task",
+ backlog_id: bl.id,
+ state: Some(State::Queued),
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
let icebox = list(&pool, bl.id, Some(State::Icebox)).await.unwrap();
assert_eq!(icebox.len(), 1);
@@ -370,9 +465,20 @@ mod tests {
async fn get_task_by_key_prefix() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let task = create(&pool, "Find me", bl.id, None, None, None)
- .await
- .unwrap();
+ let task = create(
+ &pool,
+ CreateTask {
+ title: "Find me",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
let found = get_by_key_prefix(&pool, &task.key[..3]).await.unwrap();
assert_eq!(found.id, task.id);
@@ -382,9 +488,20 @@ mod tests {
async fn edit_task_fields() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let task = create(&pool, "Original", bl.id, None, None, None)
- .await
- .unwrap();
+ let task = create(
+ &pool,
+ CreateTask {
+ title: "Original",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
let updated = edit(
&pool,
@@ -406,9 +523,20 @@ mod tests {
let pool = test_pool().await;
let bl1 = backlog::create(&pool, "First").await.unwrap();
let bl2 = backlog::create(&pool, "Second").await.unwrap();
- let task = create(&pool, "Shared", bl1.id, None, None, None)
- .await
- .unwrap();
+ let task = create(
+ &pool,
+ CreateTask {
+ title: "Shared",
+ backlog_id: bl1.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
add_to_backlog(&pool, task.id, bl2.id).await.unwrap();
@@ -423,9 +551,20 @@ mod tests {
async fn remove_task_from_backlog() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let task = create(&pool, "Remove me", bl.id, None, None, None)
- .await
- .unwrap();
+ let task = create(
+ &pool,
+ CreateTask {
+ title: "Remove me",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
remove_from_backlog(&pool, task.id, bl.id).await.unwrap();
@@ -437,15 +576,48 @@ mod tests {
async fn move_task_before() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t1 = create(&pool, "First", bl.id, None, None, None)
- .await
- .unwrap();
- let t2 = create(&pool, "Second", bl.id, None, None, None)
- .await
- .unwrap();
- let t3 = create(&pool, "Third", bl.id, None, None, None)
- .await
- .unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "Third",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
// Move t3 before t1 — should produce order: t3, t1, t2
move_task(&pool, t3.id, bl.id, Some(t1.id), None)
@@ -462,15 +634,48 @@ mod tests {
async fn move_task_after() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t1 = create(&pool, "First", bl.id, None, None, None)
- .await
- .unwrap();
- let t2 = create(&pool, "Second", bl.id, None, None, None)
- .await
- .unwrap();
- let t3 = create(&pool, "Third", bl.id, None, None, None)
- .await
- .unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "Third",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
// Move t1 after t3 — should produce order: t2, t3, t1
move_task(&pool, t1.id, bl.id, None, Some(t3.id))
@@ -487,15 +692,48 @@ mod tests {
async fn move_task_after_into_middle() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t1 = create(&pool, "First", bl.id, None, None, None)
- .await
- .unwrap();
- let t2 = create(&pool, "Second", bl.id, None, None, None)
- .await
- .unwrap();
- let t3 = create(&pool, "Third", bl.id, None, None, None)
- .await
- .unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "Third",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
// Move t3 after t1 (but before t2) — should produce order: t1, t3, t2
move_task(&pool, t3.id, bl.id, None, Some(t1.id))
@@ -512,15 +750,48 @@ mod tests {
async fn move_task_before_from_middle() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t1 = create(&pool, "First", bl.id, None, None, None)
- .await
- .unwrap();
- let t2 = create(&pool, "Second", bl.id, None, None, None)
- .await
- .unwrap();
- let t3 = create(&pool, "Third", bl.id, None, None, None)
- .await
- .unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "Third",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
// Move t1 before t3 (but after t2) — should produce order: t2, t1, t3
move_task(&pool, t1.id, bl.id, Some(t3.id), None)
@@ -537,15 +808,48 @@ mod tests {
async fn move_task_between() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let t1 = create(&pool, "First", bl.id, None, None, None)
- .await
- .unwrap();
- let t2 = create(&pool, "Second", bl.id, None, None, None)
- .await
- .unwrap();
- let t3 = create(&pool, "Third", bl.id, None, None, None)
- .await
- .unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "Third",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
// Move t3 after t1 and before t2 — should produce order: t1, t3, t2
move_task(&pool, t3.id, bl.id, Some(t2.id), Some(t1.id))
@@ -562,9 +866,20 @@ mod tests {
async fn delete_task() {
let pool = test_pool().await;
let bl = backlog::create(&pool, "Test").await.unwrap();
- let task = create(&pool, "Delete me", bl.id, None, None, None)
- .await
- .unwrap();
+ let task = create(
+ &pool,
+ CreateTask {
+ title: "Delete me",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
delete(&pool, task.id).await.unwrap();
@@ -575,4 +890,169 @@ mod tests {
let tasks = list(&pool, bl.id, None).await.unwrap();
assert_eq!(tasks.len(), 0);
}
+
+ #[tokio::test]
+ async fn create_task_before() {
+ let pool = test_pool().await;
+ let bl = backlog::create(&pool, "Test").await.unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+
+ // Create a task before t1 — should produce order: t3, t1, t2
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "Before first",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: Some(t1.id),
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+
+ let tasks = list(&pool, bl.id, None).await.unwrap();
+ assert_eq!(tasks[0].id, t3.id, "t3 should be first");
+ assert_eq!(tasks[1].id, t1.id, "t1 should be second");
+ assert_eq!(tasks[2].id, t2.id, "t2 should be third");
+ }
+
+ #[tokio::test]
+ async fn create_task_after() {
+ let pool = test_pool().await;
+ let bl = backlog::create(&pool, "Test").await.unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+
+ // Create a task after t1 — should produce order: t1, t3, t2
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "After first",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: Some(t1.id),
+ },
+ )
+ .await
+ .unwrap();
+
+ let tasks = list(&pool, bl.id, None).await.unwrap();
+ assert_eq!(tasks[0].id, t1.id, "t1 should be first");
+ assert_eq!(tasks[1].id, t3.id, "t3 should be second");
+ assert_eq!(tasks[2].id, t2.id, "t2 should be third");
+ }
+
+ #[tokio::test]
+ async fn create_task_between() {
+ let pool = test_pool().await;
+ let bl = backlog::create(&pool, "Test").await.unwrap();
+ let t1 = create(
+ &pool,
+ CreateTask {
+ title: "First",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+ let t2 = create(
+ &pool,
+ CreateTask {
+ title: "Second",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: None,
+ after_task_id: None,
+ },
+ )
+ .await
+ .unwrap();
+
+ // Create a task after t1 and before t2 — should produce order: t1, t3, t2
+ let t3 = create(
+ &pool,
+ CreateTask {
+ title: "Between",
+ backlog_id: bl.id,
+ state: None,
+ parent_id: None,
+ description: None,
+ before_task_id: Some(t2.id),
+ after_task_id: Some(t1.id),
+ },
+ )
+ .await
+ .unwrap();
+
+ let tasks = list(&pool, bl.id, None).await.unwrap();
+ assert_eq!(tasks[0].id, t1.id, "t1 should be first");
+ assert_eq!(tasks[1].id, t3.id, "t3 should be second");
+ assert_eq!(tasks[2].id, t2.id, "t2 should be third");
+ }
}