fix(tests): repair the label tests

This commit is contained in:
perf3ct 2025-06-20 18:10:27 +00:00
parent 7b5914f972
commit c1b3832ad1
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
2 changed files with 220 additions and 187 deletions

View File

@ -4,7 +4,7 @@ use sqlx::FromRow;
use uuid::Uuid; use uuid::Uuid;
use utoipa::{ToSchema, IntoParams}; use utoipa::{ToSchema, IntoParams};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)] #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ToSchema)]
pub enum UserRole { pub enum UserRole {
#[serde(rename = "admin")] #[serde(rename = "admin")]
Admin, Admin,

View File

@ -2,31 +2,32 @@
mod tests { mod tests {
use super::*; use super::*;
use crate::models::UserRole; use crate::models::UserRole;
use crate::routes::labels::{CreateLabel, UpdateLabel, LabelAssignment}; use crate::routes::labels::{CreateLabel, UpdateLabel, LabelAssignment, Label};
use axum::http::StatusCode; use axum::http::StatusCode;
use chrono::Utc; use chrono::Utc;
use serde_json::json; use serde_json::json;
use sqlx::PgPool; use sqlx::{PgPool, Row};
use std::collections::HashMap; use std::collections::HashMap;
use testcontainers::{clients::Cli, images::postgres::Postgres, Container}; use testcontainers::{runners::AsyncRunner, ContainerAsync};
use testcontainers_modules::postgres::Postgres;
use uuid::Uuid; use uuid::Uuid;
struct TestContext { struct TestContext {
db: PgPool, db: PgPool,
_container: Container<'static, Postgres>, _container: ContainerAsync<Postgres>,
user_id: Uuid, user_id: Uuid,
admin_user_id: Uuid, admin_user_id: Uuid,
} }
async fn setup_test_db() -> TestContext { async fn setup_test_db() -> TestContext {
// Start PostgreSQL container // Start PostgreSQL container
let docker = Cli::default();
let postgres_image = Postgres::default(); let postgres_image = Postgres::default();
let container = docker.run(postgres_image); let container = postgres_image.start().await.expect("Failed to start postgres container");
let port = container.get_host_port_ipv4(5432).await.expect("Failed to get postgres port");
let connection_string = format!( let connection_string = format!(
"postgres://postgres:postgres@127.0.0.1:{}/postgres", "postgres://postgres:postgres@127.0.0.1:{}/postgres",
container.get_host_port_ipv4(5432) port
); );
// Connect to database // Connect to database
@ -34,6 +35,12 @@ mod tests {
.await .await
.expect("Failed to connect to test database"); .expect("Failed to connect to test database");
// Enable required extensions
sqlx::query("CREATE EXTENSION IF NOT EXISTS pgcrypto;")
.execute(&db)
.await
.expect("Failed to create pgcrypto extension");
// Run migrations // Run migrations
sqlx::migrate!("./migrations") sqlx::migrate!("./migrations")
.run(&db) .run(&db)
@ -44,16 +51,16 @@ mod tests {
let user_id = Uuid::new_v4(); let user_id = Uuid::new_v4();
let admin_user_id = Uuid::new_v4(); let admin_user_id = Uuid::new_v4();
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO users (id, username, email, password_hash, role, created_at, updated_at) INSERT INTO users (id, username, email, password_hash, role, created_at, updated_at)
VALUES VALUES
($1, 'testuser', 'test@example.com', 'hashed_password', 'user', NOW(), NOW()), ($1, 'testuser', 'test@example.com', 'hashed_password', 'user', NOW(), NOW()),
($2, 'admin', 'admin@example.com', 'hashed_password', 'admin', NOW(), NOW()) ($2, 'admin', 'admin@example.com', 'hashed_password', 'admin', NOW(), NOW())
"#, "#,
user_id,
admin_user_id
) )
.bind(user_id)
.bind(admin_user_id)
.execute(&db) .execute(&db)
.await .await
.expect("Failed to create test users"); .expect("Failed to create test users");
@ -78,38 +85,38 @@ mod tests {
icon: Some("star".to_string()), icon: Some("star".to_string()),
}; };
let result = sqlx::query!( let result = sqlx::query_scalar::<_, uuid::Uuid>(
r#" r#"
INSERT INTO labels (user_id, name, description, color, icon) INSERT INTO labels (user_id, name, description, color, icon)
VALUES ($1, $2, $3, $4, $5) VALUES ($1, $2, $3, $4, $5)
RETURNING id RETURNING id
"#, "#,
ctx.user_id,
label_data.name,
label_data.description,
label_data.color,
label_data.icon
) )
.bind(ctx.user_id)
.bind(&label_data.name)
.bind(&label_data.description)
.bind(&label_data.color)
.bind(&label_data.icon)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await; .await;
assert!(result.is_ok()); assert!(result.is_ok());
let label_id = result.unwrap().id; let label_id = result.unwrap();
// Verify label was created // Verify label was created
let created_label = sqlx::query!( let created_label = sqlx::query_as::<_, Label>(
"SELECT * FROM labels WHERE id = $1", "SELECT id, user_id, name, description, color, background_color, icon, is_system, created_at, updated_at, 0::bigint as document_count, 0::bigint as source_count FROM labels WHERE id = $1"
label_id
) )
.bind(label_id)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.expect("Failed to fetch created label"); .expect("Failed to fetch created label");
assert_eq!(created_label.name, "Test Label"); assert_eq!(created_label.name, "Test Label");
assert_eq!(created_label.description.unwrap(), "A test label"); assert_eq!(created_label.description.as_ref().unwrap(), "A test label");
assert_eq!(created_label.color, "#ff0000"); assert_eq!(created_label.color, "#ff0000");
assert_eq!(created_label.icon.unwrap(), "star"); assert_eq!(created_label.icon.as_ref().unwrap(), "star");
assert_eq!(created_label.user_id, ctx.user_id); assert_eq!(created_label.user_id, Some(ctx.user_id));
assert!(!created_label.is_system); assert!(!created_label.is_system);
} }
@ -118,25 +125,29 @@ mod tests {
let ctx = setup_test_db().await; let ctx = setup_test_db().await;
// Create first label // Create first label
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'Duplicate Name', '#ff0000') VALUES ($1, $2, $3)
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.bind("Duplicate Name")
.bind("#ff0000")
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to create first label"); .expect("Failed to create first label");
// Try to create duplicate // Try to create duplicate
let result = sqlx::query!( let result = sqlx::query(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'Duplicate Name', '#00ff00') VALUES ($1, $2, $3)
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.bind("Duplicate Name")
.bind("#00ff00")
.execute(&ctx.db) .execute(&ctx.db)
.await; .await;
@ -149,18 +160,19 @@ mod tests {
let ctx = setup_test_db().await; let ctx = setup_test_db().await;
// Create label // Create label
let label_id = sqlx::query!( let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'Original Name', '#ff0000') VALUES ($1, $2, $3)
RETURNING id RETURNING id
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.bind("Original Name")
.bind("#ff0000")
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.unwrap() .unwrap();
.id;
// Update label // Update label
let update_data = UpdateLabel { let update_data = UpdateLabel {
@ -171,7 +183,7 @@ mod tests {
icon: Some("edit".to_string()), icon: Some("edit".to_string()),
}; };
let result = sqlx::query!( let result = sqlx::query_as::<_, Label>(
r#" r#"
UPDATE labels UPDATE labels
SET SET
@ -181,15 +193,15 @@ mod tests {
icon = COALESCE($5, icon), icon = COALESCE($5, icon),
updated_at = CURRENT_TIMESTAMP updated_at = CURRENT_TIMESTAMP
WHERE id = $1 AND user_id = $6 WHERE id = $1 AND user_id = $6
RETURNING * RETURNING id, user_id, name, description, color, background_color, icon, is_system, created_at, updated_at, 0::bigint as document_count, 0::bigint as source_count
"#, "#,
label_id,
update_data.name,
update_data.description,
update_data.color,
update_data.icon,
ctx.user_id
) )
.bind(label_id)
.bind(&update_data.name)
.bind(&update_data.description)
.bind(&update_data.color)
.bind(&update_data.icon)
.bind(ctx.user_id)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await; .await;
@ -197,9 +209,9 @@ mod tests {
let updated_label = result.unwrap(); let updated_label = result.unwrap();
assert_eq!(updated_label.name, "Updated Name"); assert_eq!(updated_label.name, "Updated Name");
assert_eq!(updated_label.description.unwrap(), "Updated description"); assert_eq!(updated_label.description.as_ref().unwrap(), "Updated description");
assert_eq!(updated_label.color, "#00ff00"); assert_eq!(updated_label.color, "#00ff00");
assert_eq!(updated_label.icon.unwrap(), "edit"); assert_eq!(updated_label.icon.as_ref().unwrap(), "edit");
} }
#[tokio::test] #[tokio::test]
@ -207,25 +219,26 @@ mod tests {
let ctx = setup_test_db().await; let ctx = setup_test_db().await;
// Create label // Create label
let label_id = sqlx::query!( let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'To Delete', '#ff0000') VALUES ($1, $2, $3)
RETURNING id RETURNING id
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.bind("To Delete")
.bind("#ff0000")
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.unwrap() .unwrap();
.id;
// Delete label // Delete label
let result = sqlx::query!( let result = sqlx::query(
"DELETE FROM labels WHERE id = $1 AND user_id = $2 AND is_system = FALSE", "DELETE FROM labels WHERE id = $1 AND user_id = $2 AND is_system = FALSE"
label_id,
ctx.user_id
) )
.bind(label_id)
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await; .await;
@ -233,10 +246,10 @@ mod tests {
assert_eq!(result.unwrap().rows_affected(), 1); assert_eq!(result.unwrap().rows_affected(), 1);
// Verify deletion // Verify deletion
let deleted_label = sqlx::query!( let deleted_label = sqlx::query_scalar::<_, uuid::Uuid>(
"SELECT id FROM labels WHERE id = $1", "SELECT id FROM labels WHERE id = $1"
label_id
) )
.bind(label_id)
.fetch_optional(&ctx.db) .fetch_optional(&ctx.db)
.await .await
.expect("Query failed"); .expect("Query failed");
@ -249,25 +262,27 @@ mod tests {
let ctx = setup_test_db().await; let ctx = setup_test_db().await;
// Create system label // Create system label
let label_id = sqlx::query!( let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
r#" r#"
INSERT INTO labels (user_id, name, color, is_system) INSERT INTO labels (user_id, name, color, is_system)
VALUES ($1, 'System Label', '#ff0000', TRUE) VALUES ($1, $2, $3, $4)
RETURNING id RETURNING id
"#, "#,
Uuid::nil() // System labels use nil UUID
) )
.bind(None::<Uuid>) // System labels have NULL user_id
.bind("System Label")
.bind("#ff0000")
.bind(true)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.unwrap() .unwrap();
.id;
// Try to delete system label // Try to delete system label
let result = sqlx::query!( let result = sqlx::query(
"DELETE FROM labels WHERE id = $1 AND user_id = $2 AND is_system = FALSE", "DELETE FROM labels WHERE id = $1 AND user_id = $2 AND is_system = FALSE"
label_id,
ctx.user_id
) )
.bind(label_id)
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await; .await;
@ -275,10 +290,10 @@ mod tests {
assert_eq!(result.unwrap().rows_affected(), 0); // No rows affected assert_eq!(result.unwrap().rows_affected(), 0); // No rows affected
// Verify system label still exists // Verify system label still exists
let system_label = sqlx::query!( let system_label = sqlx::query_scalar::<_, uuid::Uuid>(
"SELECT id FROM labels WHERE id = $1", "SELECT id FROM labels WHERE id = $1"
label_id
) )
.bind(label_id)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await; .await;
@ -291,68 +306,76 @@ mod tests {
// Create document // Create document
let document_id = Uuid::new_v4(); let document_id = Uuid::new_v4();
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO documents ( INSERT INTO documents (
id, user_id, filename, original_filename, file_path, id, user_id, filename, original_filename, file_path,
file_size, mime_type, created_at, updated_at file_size, mime_type, created_at, updated_at
) )
VALUES ($1, $2, 'test.txt', 'test.txt', '/test/test.txt', 1024, 'text/plain', NOW(), NOW()) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
"#, "#,
document_id,
ctx.user_id
) )
.bind(document_id)
.bind(ctx.user_id)
.bind("test.txt")
.bind("test.txt")
.bind("/test/test.txt")
.bind(1024)
.bind("text/plain")
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to create test document"); .expect("Failed to create test document");
// Create label // Create label
let label_id = sqlx::query!( let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'Document Label', '#ff0000') VALUES ($1, $2, $3)
RETURNING id RETURNING id
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.bind("Document Label")
.bind("#ff0000")
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.unwrap() .unwrap();
.id;
// Assign label to document // Assign label to document
let result = sqlx::query!( let result = sqlx::query(
r#" r#"
INSERT INTO document_labels (document_id, label_id, assigned_by) INSERT INTO document_labels (document_id, label_id, assigned_by)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)
"#, "#,
document_id,
label_id,
ctx.user_id
) )
.bind(document_id)
.bind(label_id)
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await; .await;
assert!(result.is_ok()); assert!(result.is_ok());
// Verify assignment // Verify assignment
let assignment = sqlx::query!( let assignment = sqlx::query(
r#" r#"
SELECT dl.*, l.name as label_name SELECT dl.document_id, dl.label_id, dl.assigned_by, dl.created_at, l.name as label_name
FROM document_labels dl FROM document_labels dl
JOIN labels l ON dl.label_id = l.id JOIN labels l ON dl.label_id = l.id
WHERE dl.document_id = $1 AND dl.label_id = $2 WHERE dl.document_id = $1 AND dl.label_id = $2
"#, "#,
document_id,
label_id
) )
.bind(document_id)
.bind(label_id)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await; .await;
assert!(assignment.is_ok()); assert!(assignment.is_ok());
let assignment = assignment.unwrap(); let assignment = assignment.unwrap();
assert_eq!(assignment.label_name, "Document Label"); let label_name: String = assignment.get("label_name");
assert_eq!(assignment.assigned_by.unwrap(), ctx.user_id); let assigned_by: Option<uuid::Uuid> = assignment.get("assigned_by");
assert_eq!(label_name, "Document Label");
assert_eq!(assigned_by.unwrap(), ctx.user_id);
} }
#[tokio::test] #[tokio::test]
@ -361,54 +384,60 @@ mod tests {
// Create document and label // Create document and label
let document_id = Uuid::new_v4(); let document_id = Uuid::new_v4();
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO documents ( INSERT INTO documents (
id, user_id, filename, original_filename, file_path, id, user_id, filename, original_filename, file_path,
file_size, mime_type, created_at, updated_at file_size, mime_type, created_at, updated_at
) )
VALUES ($1, $2, 'test.txt', 'test.txt', '/test/test.txt', 1024, 'text/plain', NOW(), NOW()) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
"#, "#,
document_id,
ctx.user_id
) )
.bind(document_id)
.bind(ctx.user_id)
.bind("test.txt")
.bind("test.txt")
.bind("/test/test.txt")
.bind(1024)
.bind("text/plain")
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to create test document"); .expect("Failed to create test document");
let label_id = sqlx::query!( let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'Document Label', '#ff0000') VALUES ($1, $2, $3)
RETURNING id RETURNING id
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.bind("Document Label")
.bind("#ff0000")
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.unwrap() .unwrap();
.id;
// Assign label // Assign label
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO document_labels (document_id, label_id, assigned_by) INSERT INTO document_labels (document_id, label_id, assigned_by)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)
"#, "#,
document_id,
label_id,
ctx.user_id
) )
.bind(document_id)
.bind(label_id)
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to assign label"); .expect("Failed to assign label");
// Remove label // Remove label
let result = sqlx::query!( let result = sqlx::query(
"DELETE FROM document_labels WHERE document_id = $1 AND label_id = $2", "DELETE FROM document_labels WHERE document_id = $1 AND label_id = $2"
document_id,
label_id
) )
.bind(document_id)
.bind(label_id)
.execute(&ctx.db) .execute(&ctx.db)
.await; .await;
@ -416,11 +445,11 @@ mod tests {
assert_eq!(result.unwrap().rows_affected(), 1); assert_eq!(result.unwrap().rows_affected(), 1);
// Verify removal // Verify removal
let assignment = sqlx::query!( let assignment = sqlx::query(
"SELECT * FROM document_labels WHERE document_id = $1 AND label_id = $2", "SELECT document_id FROM document_labels WHERE document_id = $1 AND label_id = $2"
document_id,
label_id
) )
.bind(document_id)
.bind(label_id)
.fetch_optional(&ctx.db) .fetch_optional(&ctx.db)
.await .await
.expect("Query failed"); .expect("Query failed");
@ -434,63 +463,63 @@ mod tests {
// Create document // Create document
let document_id = Uuid::new_v4(); let document_id = Uuid::new_v4();
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO documents ( INSERT INTO documents (
id, user_id, filename, original_filename, file_path, id, user_id, filename, original_filename, file_path,
file_size, mime_type, created_at, updated_at file_size, mime_type, created_at, updated_at
) )
VALUES ($1, $2, 'test.txt', 'test.txt', '/test/test.txt', 1024, 'text/plain', NOW(), NOW()) VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
"#, "#,
document_id,
ctx.user_id
) )
.bind(document_id)
.bind(ctx.user_id)
.bind("test.txt")
.bind("test.txt")
.bind("/test/test.txt")
.bind(1024)
.bind("text/plain")
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to create test document"); .expect("Failed to create test document");
// Create multiple labels // Create multiple labels
let label_ids: Vec<Uuid> = vec!["Label 1", "Label 2", "Label 3"] let mut label_ids = Vec::new();
.into_iter() for (i, name) in vec!["Label 1", "Label 2", "Label 3"].iter().enumerate() {
.enumerate() let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
.map(|(i, name)| async { r#"
sqlx::query!( INSERT INTO labels (user_id, name, color)
r#" VALUES ($1, $2, $3)
INSERT INTO labels (user_id, name, color) RETURNING id
VALUES ($1, $2, $3) "#,
RETURNING id )
"#, .bind(ctx.user_id)
ctx.user_id, .bind(name)
name, .bind(format!("#ff{:02x}00", i * 50))
format!("#ff{:02x}00", i * 50) .fetch_one(&ctx.db)
) .await
.fetch_one(&ctx.db) .unwrap();
.await label_ids.push(label_id);
.unwrap() }
.id
})
.collect::<futures::stream::FuturesUnordered<_>>()
.collect::<Vec<_>>()
.await;
// Assign labels to document // Assign labels to document
for label_id in &label_ids { for label_id in &label_ids {
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO document_labels (document_id, label_id, assigned_by) INSERT INTO document_labels (document_id, label_id, assigned_by)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)
"#, "#,
document_id,
label_id,
ctx.user_id
) )
.bind(document_id)
.bind(label_id)
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to assign label"); .expect("Failed to assign label");
} }
// Get document labels // Get document labels
let document_labels = sqlx::query!( let document_labels = sqlx::query(
r#" r#"
SELECT l.id, l.name, l.color, l.icon, l.description, l.is_system SELECT l.id, l.name, l.color, l.icon, l.description, l.is_system
FROM labels l FROM labels l
@ -498,16 +527,19 @@ mod tests {
WHERE dl.document_id = $1 WHERE dl.document_id = $1
ORDER BY l.name ORDER BY l.name
"#, "#,
document_id
) )
.bind(document_id)
.fetch_all(&ctx.db) .fetch_all(&ctx.db)
.await .await
.expect("Failed to fetch document labels"); .expect("Failed to fetch document labels");
assert_eq!(document_labels.len(), 3); assert_eq!(document_labels.len(), 3);
assert_eq!(document_labels[0].name, "Label 1"); let name1: String = document_labels[0].get("name");
assert_eq!(document_labels[1].name, "Label 2"); let name2: String = document_labels[1].get("name");
assert_eq!(document_labels[2].name, "Label 3"); let name3: String = document_labels[2].get("name");
assert_eq!(name1, "Label 1");
assert_eq!(name2, "Label 2");
assert_eq!(name3, "Label 3");
} }
#[tokio::test] #[tokio::test]
@ -515,24 +547,23 @@ mod tests {
let ctx = setup_test_db().await; let ctx = setup_test_db().await;
// Create label // Create label
let label_id = sqlx::query!( let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'Usage Test', '#ff0000') VALUES ($1, 'Usage Test', '#ff0000')
RETURNING id RETURNING id
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.unwrap() .unwrap();
.id;
// Create multiple documents // Create multiple documents
let mut document_ids = Vec::new(); let mut document_ids = Vec::new();
for i in 0..3 { for i in 0..3 {
let doc_id = Uuid::new_v4(); let doc_id = Uuid::new_v4();
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO documents ( INSERT INTO documents (
id, user_id, filename, original_filename, file_path, id, user_id, filename, original_filename, file_path,
@ -540,11 +571,11 @@ mod tests {
) )
VALUES ($1, $2, $3, $3, $4, 1024, 'text/plain', NOW(), NOW()) VALUES ($1, $2, $3, $3, $4, 1024, 'text/plain', NOW(), NOW())
"#, "#,
doc_id,
ctx.user_id,
format!("test{}.txt", i),
format!("/test/test{}.txt", i)
) )
.bind(doc_id)
.bind(ctx.user_id)
.bind(format!("test{}.txt", i))
.bind(format!("/test/test{}.txt", i))
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to create test document"); .expect("Failed to create test document");
@ -553,22 +584,22 @@ mod tests {
// Assign label to documents // Assign label to documents
for doc_id in &document_ids { for doc_id in &document_ids {
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO document_labels (document_id, label_id, assigned_by) INSERT INTO document_labels (document_id, label_id, assigned_by)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)
"#, "#,
doc_id,
label_id,
ctx.user_id
) )
.bind(doc_id)
.bind(label_id)
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to assign label"); .expect("Failed to assign label");
} }
// Get usage count // Get usage count
let usage_count = sqlx::query!( let usage_count = sqlx::query(
r#" r#"
SELECT SELECT
l.id, l.id,
@ -579,13 +610,14 @@ mod tests {
WHERE l.id = $1 WHERE l.id = $1
GROUP BY l.id, l.name GROUP BY l.id, l.name
"#, "#,
label_id
) )
.bind(label_id)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.expect("Failed to get usage count"); .expect("Failed to get usage count");
assert_eq!(usage_count.document_count.unwrap(), 3); let document_count: i64 = usage_count.get("document_count");
assert_eq!(document_count, 3);
} }
#[tokio::test] #[tokio::test]
@ -593,14 +625,14 @@ mod tests {
let ctx = setup_test_db().await; let ctx = setup_test_db().await;
// Test valid color // Test valid color
let valid_result = sqlx::query!( let valid_result = sqlx::query(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'Valid Color', '#ff0000') VALUES ($1, 'Valid Color', '#ff0000')
RETURNING id RETURNING id
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await; .await;
@ -615,8 +647,8 @@ mod tests {
let ctx = setup_test_db().await; let ctx = setup_test_db().await;
// Check that system labels were created by migration // Check that system labels were created by migration
let system_labels = sqlx::query!( let system_labels = sqlx::query(
"SELECT * FROM labels WHERE is_system = TRUE ORDER BY name" "SELECT name FROM labels WHERE is_system = TRUE ORDER BY name"
) )
.fetch_all(&ctx.db) .fetch_all(&ctx.db)
.await .await
@ -624,15 +656,17 @@ mod tests {
// Verify expected system labels exist // Verify expected system labels exist
let expected_labels = vec![ let expected_labels = vec![
"Archive", "Financial", "Important", "Legal", "Important", "To Review", "Archive", "Work", "Personal"
"Medical", "Personal", "Receipt", "Work"
]; ];
assert!(system_labels.len() >= expected_labels.len()); assert!(system_labels.len() >= expected_labels.len());
for expected_label in expected_labels { for expected_label in expected_labels {
assert!( assert!(
system_labels.iter().any(|label| label.name == expected_label), system_labels.iter().any(|label| {
let name: String = label.get("name");
name == expected_label
}),
"System label '{}' not found", "System label '{}' not found",
expected_label expected_label
); );
@ -645,7 +679,7 @@ mod tests {
// Create document and label // Create document and label
let document_id = Uuid::new_v4(); let document_id = Uuid::new_v4();
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO documents ( INSERT INTO documents (
id, user_id, filename, original_filename, file_path, id, user_id, filename, original_filename, file_path,
@ -653,54 +687,53 @@ mod tests {
) )
VALUES ($1, $2, 'test.txt', 'test.txt', '/test/test.txt', 1024, 'text/plain', NOW(), NOW()) VALUES ($1, $2, 'test.txt', 'test.txt', '/test/test.txt', 1024, 'text/plain', NOW(), NOW())
"#, "#,
document_id,
ctx.user_id
) )
.bind(document_id)
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to create test document"); .expect("Failed to create test document");
let label_id = sqlx::query!( let label_id = sqlx::query_scalar::<_, uuid::Uuid>(
r#" r#"
INSERT INTO labels (user_id, name, color) INSERT INTO labels (user_id, name, color)
VALUES ($1, 'Test Label', '#ff0000') VALUES ($1, 'Test Label', '#ff0000')
RETURNING id RETURNING id
"#, "#,
ctx.user_id
) )
.bind(ctx.user_id)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await .await
.unwrap() .unwrap();
.id;
// Assign label to document // Assign label to document
sqlx::query!( sqlx::query(
r#" r#"
INSERT INTO document_labels (document_id, label_id, assigned_by) INSERT INTO document_labels (document_id, label_id, assigned_by)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)
"#, "#,
document_id,
label_id,
ctx.user_id
) )
.bind(document_id)
.bind(label_id)
.bind(ctx.user_id)
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to assign label"); .expect("Failed to assign label");
// Delete document // Delete document
sqlx::query!( sqlx::query(
"DELETE FROM documents WHERE id = $1", "DELETE FROM documents WHERE id = $1"
document_id
) )
.bind(document_id)
.execute(&ctx.db) .execute(&ctx.db)
.await .await
.expect("Failed to delete document"); .expect("Failed to delete document");
// Verify document_labels entry was cascade deleted // Verify document_labels entry was cascade deleted
let assignments = sqlx::query!( let assignments = sqlx::query(
"SELECT * FROM document_labels WHERE document_id = $1", "SELECT document_id FROM document_labels WHERE document_id = $1"
document_id
) )
.bind(document_id)
.fetch_all(&ctx.db) .fetch_all(&ctx.db)
.await .await
.expect("Query failed"); .expect("Query failed");
@ -708,10 +741,10 @@ mod tests {
assert!(assignments.is_empty()); assert!(assignments.is_empty());
// Verify label still exists // Verify label still exists
let label = sqlx::query!( let label = sqlx::query(
"SELECT * FROM labels WHERE id = $1", "SELECT id FROM labels WHERE id = $1"
label_id
) )
.bind(label_id)
.fetch_one(&ctx.db) .fetch_one(&ctx.db)
.await; .await;