use chrono::Utc;
use std::path::Path;
use tempfile::{tempdir, NamedTempFile};
use things3_core::{
models::{TaskStatus, TaskType},
ThingsDatabase,
};
use uuid::Uuid;
#[allow(clippy::too_many_lines)]
async fn create_test_schema(db: &ThingsDatabase) -> Result<(), Box<dyn std::error::Error>> {
let pool = db.pool();
sqlx::query(
r"
-- TMTask table (main tasks table) - matches real Things 3 schema
CREATE TABLE IF NOT EXISTS TMTask (
uuid TEXT PRIMARY KEY,
title TEXT NOT NULL,
type INTEGER NOT NULL DEFAULT 0,
status INTEGER NOT NULL DEFAULT 0,
notes TEXT,
startDate INTEGER,
deadline INTEGER,
stopDate REAL,
creationDate REAL NOT NULL,
userModificationDate REAL NOT NULL,
project TEXT,
area TEXT,
heading TEXT,
trashed INTEGER NOT NULL DEFAULT 0,
tags TEXT DEFAULT '[]',
cachedTags BLOB,
todayIndex INTEGER
)
",
)
.execute(pool)
.await?;
sqlx::query(
r"
-- TMArea table (areas table) - matches real Things 3 schema
CREATE TABLE IF NOT EXISTS TMArea (
uuid TEXT PRIMARY KEY,
title TEXT NOT NULL,
visible INTEGER NOT NULL DEFAULT 1,
'index' INTEGER NOT NULL DEFAULT 0
)
",
)
.execute(pool)
.await?;
sqlx::query(
r"
CREATE TABLE IF NOT EXISTS TMTag (
uuid TEXT PRIMARY KEY,
title TEXT,
shortcut TEXT,
usedDate REAL,
parent TEXT,
'index' INTEGER,
experimental BLOB
)
",
)
.execute(pool)
.await?;
sqlx::query(
r"
CREATE TABLE IF NOT EXISTS TMTaskTag (
tasks TEXT,
tags TEXT,
PRIMARY KEY (tasks, tags)
)
",
)
.execute(pool)
.await?;
let timestamp_i64 = Utc::now().timestamp();
let now_timestamp = if timestamp_i64 <= i64::from(i32::MAX) {
f64::from(i32::try_from(timestamp_i64).unwrap_or(0))
} else {
1_700_000_000.0 };
let area_uuid = Uuid::new_v4().to_string();
let project_uuid = Uuid::new_v4().to_string();
let inbox_task_uuid = Uuid::new_v4().to_string();
let project_task_uuid = Uuid::new_v4().to_string();
sqlx::query("INSERT INTO TMArea (uuid, title, visible, 'index') VALUES (?, ?, ?, ?)")
.bind(&area_uuid)
.bind("Work")
.bind(1) .bind(0) .execute(pool)
.await?;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, area, creationDate, userModificationDate, trashed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(&project_uuid)
.bind("Website Redesign")
.bind(1) .bind(0) .bind(&area_uuid)
.bind(now_timestamp)
.bind(now_timestamp)
.bind(0) .execute(pool).await?;
let base_2001 = chrono::DateTime::parse_from_rfc3339("2001-01-01T00:00:00Z")
.unwrap()
.timestamp();
let today_things3 = Utc::now().timestamp() - base_2001;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, project, area, creationDate, userModificationDate, startDate, trashed, todayIndex) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(&inbox_task_uuid)
.bind("Inbox Task")
.bind(0) .bind(0) .bind::<Option<String>>(None) .bind(&area_uuid) .bind(now_timestamp)
.bind(now_timestamp)
.bind(today_things3)
.bind(0) .bind(1) .execute(pool).await?;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, project, area, creationDate, userModificationDate, startDate, trashed, todayIndex) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(&project_task_uuid)
.bind("Research competitors")
.bind(0) .bind(0) .bind(&project_uuid)
.bind(&area_uuid) .bind(now_timestamp)
.bind(now_timestamp)
.bind(today_things3)
.bind(0) .bind(2) .execute(pool).await?;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, project, creationDate, userModificationDate, trashed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(Uuid::new_v4().to_string())
.bind("Learn the basics")
.bind(2) .bind(0) .bind(&project_uuid)
.bind(now_timestamp)
.bind(now_timestamp)
.bind(0) .execute(pool).await?;
Ok(())
}
#[tokio::test]
async fn test_database_new() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
assert!(db.is_connected().await);
}
#[tokio::test]
async fn test_database_default_path() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
assert!(db.is_connected().await);
}
#[tokio::test]
async fn test_get_inbox() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let inbox = db.get_inbox(None).await.unwrap();
assert_eq!(inbox.len(), 1);
let first_task = &inbox[0];
assert_eq!(first_task.title, "Inbox Task");
assert_eq!(first_task.status, TaskStatus::Incomplete);
assert_eq!(first_task.task_type, TaskType::Todo);
}
#[tokio::test]
async fn test_get_today() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let today = db.get_today(None).await.unwrap();
assert!(!today.is_empty());
}
#[tokio::test]
async fn test_get_projects() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let projects = db.get_projects(None).await.unwrap();
assert!(!projects.is_empty());
let first_project = &projects[0];
assert_eq!(first_project.title, "Website Redesign");
assert_eq!(first_project.status, TaskStatus::Incomplete);
}
#[tokio::test]
async fn test_get_areas() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let areas = db.get_areas().await.unwrap();
assert!(!areas.is_empty());
let first_area = &areas[0];
assert_eq!(first_area.title, "Work");
}
#[tokio::test]
async fn test_search_tasks() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let results = db.search_tasks("competitors").await.unwrap();
assert!(!results.is_empty());
let found_task = results.iter().find(|t| t.title.contains("competitors"));
assert!(found_task.is_some());
}
#[tokio::test]
async fn test_search_tasks_empty_query() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let results = db.search_tasks("").await.unwrap();
assert!(!results.is_empty());
}
#[tokio::test]
async fn test_search_tasks_no_results() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let results = db.search_tasks("nonexistent").await.unwrap();
assert!(results.is_empty());
}
#[tokio::test]
async fn test_database_error_handling() {
let invalid_path = Path::new("/invalid/path/that/does/not/exist/database.sqlite");
let result = ThingsDatabase::new(Path::new(invalid_path));
assert!(result.await.is_err());
}
#[tokio::test]
async fn test_database_connection_persistence() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let db2 = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db2).await.unwrap();
let _inbox1 = db.get_inbox(None).await.unwrap();
let _inbox2 = db2.get_inbox(None).await.unwrap();
}
#[tokio::test]
async fn test_database_with_mock_data_consistency() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let inbox = db.get_inbox(None).await.unwrap();
let projects = db.get_projects(None).await.unwrap();
let areas = db.get_areas().await.unwrap();
assert_eq!(inbox.len(), 1); assert_eq!(projects.len(), 1); assert_eq!(areas.len(), 1);
let all_tasks = db.search_tasks("").await.unwrap();
assert_eq!(all_tasks.len(), 3);
assert_eq!(
all_tasks.iter().filter(|t| t.area_uuid.is_some()).count(),
2
);
assert_eq!(
all_tasks
.iter()
.filter(|t| t.task_type == TaskType::Todo)
.count(),
2
);
assert_eq!(
all_tasks
.iter()
.filter(|t| t.task_type == TaskType::Heading)
.count(),
1
);
}
#[tokio::test]
async fn test_database_query_consistency() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let inbox = db.get_inbox(None).await.unwrap();
let all_tasks = db.search_tasks("").await.unwrap();
assert!(all_tasks.len() >= inbox.len());
for inbox_task in &inbox {
let found = all_tasks.iter().any(|t| t.uuid == inbox_task.uuid);
assert!(
found,
"Inbox task {} not found in all tasks",
inbox_task.uuid
);
}
}
#[tokio::test]
async fn test_database_date_filtering() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let today = db.get_today(None).await.unwrap();
let today_date = Utc::now().date_naive();
for task in &today {
let is_today = (task.start_date == Some(today_date)) || (task.deadline == Some(today_date));
assert!(is_today, "Task {} is not for today", task.title);
}
}
#[tokio::test]
async fn test_database_error_recovery() {
let temp_dir = tempdir().unwrap();
let _db_path = temp_dir.path().join("test.db");
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let inbox = db.get_inbox(None).await.unwrap();
assert!(!inbox.is_empty());
let projects = db.get_projects(None).await.unwrap();
assert!(!projects.is_empty());
}
#[tokio::test]
async fn test_database_concurrent_access() {
let temp_dir = tempdir().unwrap();
let _db_path = temp_dir.path().join("test.db");
let db1 = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let db2 = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db1).await.unwrap();
create_test_schema(&db2).await.unwrap();
let inbox1 = db1.get_inbox(None).await.unwrap();
let inbox2 = db2.get_inbox(None).await.unwrap();
assert_eq!(inbox1.len(), inbox2.len());
assert_eq!(inbox1.len(), 1);
assert_eq!(inbox1[0].title, inbox2[0].title);
assert_eq!(inbox1[0].title, "Inbox Task");
}
#[tokio::test]
async fn test_database_helper_functions_indirectly() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let tasks = db.get_inbox(Some(1)).await.unwrap();
if !tasks.is_empty() {
let task = &tasks[0];
assert!(matches!(
task.task_type,
things3_core::models::TaskType::Todo
| things3_core::models::TaskType::Project
| things3_core::models::TaskType::Heading
| things3_core::models::TaskType::Area
));
}
let tasks = db.get_inbox(None).await.unwrap();
for task in &tasks {
assert!(matches!(
task.status,
things3_core::models::TaskStatus::Incomplete
| things3_core::models::TaskStatus::Completed
| things3_core::models::TaskStatus::Canceled
| things3_core::models::TaskStatus::Trashed
));
}
for task in &tasks {
assert!(task.created <= chrono::Utc::now());
assert!(task.modified <= chrono::Utc::now());
}
for task in &tasks {
if let Some(start_date) = task.start_date {
let now = chrono::Utc::now().date_naive();
let year_ago = now - chrono::Duration::days(365);
let year_from_now = now + chrono::Duration::days(365);
assert!(start_date >= year_ago);
assert!(start_date <= year_from_now);
}
}
for task in &tasks {
assert!(!task.uuid.as_str().is_empty());
}
}
#[tokio::test]
async fn test_database_error_handling_comprehensive() {
let invalid_path = "/invalid/path/that/does/not/exist/database.sqlite";
let result = ThingsDatabase::new(Path::new(invalid_path));
assert!(result.await.is_err());
let temp_file = NamedTempFile::new().unwrap();
let db_path = temp_file.path();
std::fs::write(db_path, "not a database").unwrap();
let result = ThingsDatabase::new(db_path).await;
let _ = result;
}
#[tokio::test]
async fn test_database_edge_cases() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let empty_results = db.search_tasks("").await.unwrap();
assert_eq!(empty_results.len(), 3);
let long_query = "a".repeat(1000);
let long_results = db.search_tasks(&long_query).await.unwrap();
assert!(long_results.is_empty() || !long_results.is_empty());
let tasks = db.get_inbox(Some(0)).await.unwrap();
assert_eq!(tasks.len(), 0);
let tasks = db.get_inbox(Some(1)).await.unwrap();
assert!(tasks.len() <= 1);
let today_tasks = db.get_today(Some(0)).await.unwrap();
assert_eq!(today_tasks.len(), 0);
}
#[tokio::test]
async fn test_database_performance_with_large_limits() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let tasks = db.get_inbox(Some(10000)).await.unwrap();
assert!(tasks.len() <= 10000);
let tasks = db.get_today(Some(10000)).await.unwrap();
assert!(tasks.len() <= 10000);
let tasks = db.search_tasks("test").await.unwrap();
assert!(tasks.len() <= 10000);
}
async fn create_minimal_task_schema(pool: &sqlx::SqlitePool) {
sqlx::query(
r"
CREATE TABLE IF NOT EXISTS TMTask (
uuid TEXT PRIMARY KEY,
title TEXT NOT NULL,
type INTEGER NOT NULL DEFAULT 0,
status INTEGER NOT NULL DEFAULT 0,
notes TEXT,
startDate INTEGER,
deadline INTEGER,
stopDate REAL,
creationDate REAL NOT NULL,
userModificationDate REAL NOT NULL,
project TEXT,
area TEXT,
heading TEXT,
trashed INTEGER NOT NULL DEFAULT 0,
cachedTags BLOB,
todayIndex INTEGER
)
",
)
.execute(pool)
.await
.unwrap();
sqlx::query(
r"
CREATE TABLE IF NOT EXISTS TMTag (
uuid TEXT PRIMARY KEY,
title TEXT,
shortcut TEXT,
usedDate REAL,
parent TEXT,
'index' INTEGER,
experimental BLOB
)
",
)
.execute(pool)
.await
.unwrap();
sqlx::query(
r"
CREATE TABLE IF NOT EXISTS TMTaskTag (
tasks TEXT,
tags TEXT,
PRIMARY KEY (tasks, tags)
)
",
)
.execute(pool)
.await
.unwrap();
}
#[tokio::test]
async fn test_get_today_with_null_today_index() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, trashed, todayIndex)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind("task-null-today")
.bind("Task with NULL todayIndex")
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind(0)
.bind(Option::<i64>::None) .execute(pool)
.await
.unwrap();
let today_tasks = db.get_today(None).await.unwrap();
assert_eq!(
today_tasks.len(),
0,
"Tasks with NULL todayIndex should not appear in Today"
);
}
#[tokio::test]
async fn test_get_today_with_zero_today_index() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, trashed, todayIndex)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind("task-zero-today")
.bind("Task with zero todayIndex")
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind(0)
.bind(0) .execute(pool)
.await
.unwrap();
let today_tasks = db.get_today(None).await.unwrap();
assert_eq!(
today_tasks.len(),
0,
"Tasks with todayIndex = 0 should not appear in Today"
);
}
#[tokio::test]
async fn test_get_today_with_positive_today_index() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, trashed, todayIndex)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind("task-positive-today")
.bind("Task in Today")
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind(0)
.bind(1) .execute(pool)
.await
.unwrap();
let today_tasks = db.get_today(None).await.unwrap();
assert_eq!(
today_tasks.len(),
1,
"Tasks with positive todayIndex should appear in Today"
);
assert_eq!(today_tasks[0].title, "Task in Today");
}
#[tokio::test]
async fn test_get_today_excludes_trashed() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, trashed, todayIndex)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind("task-trashed")
.bind("Trashed Task")
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind(1) .bind(1) .execute(pool)
.await
.unwrap();
let today_tasks = db.get_today(None).await.unwrap();
assert_eq!(
today_tasks.len(),
0,
"Trashed tasks should not appear in Today"
);
}
#[tokio::test]
async fn test_get_today_with_limit() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0;
for i in 1..=5 {
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, trashed, todayIndex)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(format!("task-{}", i))
.bind(format!("Task {}", i))
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind(0)
.bind(i as i64)
.execute(pool)
.await
.unwrap();
}
let today_tasks = db.get_today(Some(3)).await.unwrap();
assert_eq!(today_tasks.len(), 3, "Should respect limit parameter");
}
#[tokio::test]
async fn test_get_inbox_empty_database() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let inbox = db.get_inbox(None).await.unwrap();
assert_eq!(inbox.len(), 0, "Empty database should return empty inbox");
}
#[tokio::test]
async fn test_get_inbox_excludes_tasks_with_project() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, project, trashed)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind("task-with-project")
.bind("Task in Project")
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind("project-uuid")
.bind(0)
.execute(pool)
.await
.unwrap();
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, project, trashed)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind("task-inbox")
.bind("Inbox Task")
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind(Option::<String>::None)
.bind(0)
.execute(pool)
.await
.unwrap();
let inbox = db.get_inbox(None).await.unwrap();
assert_eq!(
inbox.len(),
1,
"Only tasks without project should be in inbox"
);
assert_eq!(inbox[0].title, "Inbox Task");
}
#[tokio::test]
async fn test_get_inbox_with_limit_zero() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, project, trashed)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind("task-1")
.bind("Task 1")
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind(Option::<String>::None)
.bind(0)
.execute(pool)
.await
.unwrap();
let inbox = db.get_inbox(Some(0)).await.unwrap();
assert_eq!(inbox.len(), 0, "Limit of 0 should return no tasks");
}
#[tokio::test]
async fn test_get_inbox_large_result_set() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0;
for i in 0..100 {
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, project, trashed)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind(format!("task-{}", i))
.bind(format!("Task {}", i))
.bind(0)
.bind(0)
.bind(now)
.bind(now)
.bind(Option::<String>::None)
.bind(0)
.execute(pool)
.await
.unwrap();
}
let inbox = db.get_inbox(None).await.unwrap();
assert_eq!(inbox.len(), 100, "Should handle large result sets");
let limited_inbox = db.get_inbox(Some(10)).await.unwrap();
assert_eq!(
limited_inbox.len(),
10,
"Should respect limit with large datasets"
);
}
#[tokio::test]
async fn test_search_tasks_includes_headings() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
create_test_schema(&db).await.unwrap();
let results = db.search_tasks("Learn the basics").await.unwrap();
assert!(
!results.is_empty(),
"search_tasks should include heading-type tasks"
);
let heading = results
.iter()
.find(|t| t.title == "Learn the basics")
.expect("heading task should be returned by search_tasks");
assert_eq!(heading.task_type, TaskType::Heading);
}
#[tokio::test]
async fn test_get_inbox_includes_headings_without_project() {
let db = ThingsDatabase::from_connection_string("sqlite::memory:")
.await
.unwrap();
let pool = db.pool();
create_minimal_task_schema(pool).await;
let now = 1_700_000_000.0_f64;
sqlx::query(
"INSERT INTO TMTask (uuid, title, type, status, creationDate, userModificationDate, project, trashed) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.bind("heading-inbox")
.bind("Phase 1")
.bind(2) .bind(0) .bind(now)
.bind(now)
.bind(Option::<String>::None)
.bind(0)
.execute(pool)
.await
.unwrap();
let inbox = db.get_inbox(None).await.unwrap();
let heading = inbox.iter().find(|t| t.title == "Phase 1");
assert!(
heading.is_some(),
"get_inbox should include heading-type tasks without a project"
);
assert_eq!(heading.unwrap().task_type, TaskType::Heading);
}