duplciate is unnested

This commit is contained in:
dal 2025-04-02 09:25:38 -06:00
parent c85d51b7d2
commit fd61fc3910
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
2 changed files with 169 additions and 48 deletions

View File

@ -4,7 +4,7 @@ use axum::Extension;
use handlers::chats::duplicate_chat_handler; use handlers::chats::duplicate_chat_handler;
use handlers::chats::types::ChatWithMessages; use handlers::chats::types::ChatWithMessages;
use middleware::AuthenticatedUser; use middleware::AuthenticatedUser;
use serde::{Deserialize, Serialize}; use serde::Deserialize;
use uuid::Uuid; use uuid::Uuid;
use crate::routes::rest::ApiResponse; use crate::routes::rest::ApiResponse;
@ -18,36 +18,35 @@ pub struct DuplicateChatRequest {
pub message_id: Option<Uuid>, pub message_id: Option<Uuid>,
} }
/// Response containing the duplicated chat with messages
#[derive(Serialize, Deserialize)]
pub struct DuplicateChatResponse {
/// The duplicated chat with all its messages
pub chat: ChatWithMessages,
}
/// Handler for POST /chats/duplicate endpoint /// Handler for POST /chats/duplicate endpoint
/// ///
/// Duplicates an existing chat, including all messages and file references. /// Duplicates an existing chat, including all messages and file references.
/// If message_id is provided, only messages from that point onward are duplicated. /// If message_id is provided, only messages from that point onward are duplicated.
/// Each file reference is duplicated with is_duplicate=true to track duplicated content. /// Each file reference is duplicated with is_duplicate=true to track duplicated content.
pub async fn duplicate_chat_route( pub async fn duplicate_chat_route(
Extension(user): Extension<AuthenticatedUser>, Extension(user): Extension<AuthenticatedUser>,
Json(request): Json<DuplicateChatRequest>, Json(request): Json<DuplicateChatRequest>,
) -> Result<ApiResponse<DuplicateChatResponse>, (StatusCode, String)> { ) -> Result<ApiResponse<ChatWithMessages>, (StatusCode, String)> {
// Call the handler function with the request parameters // Call the handler function with the request parameters
match duplicate_chat_handler(&request.id, request.message_id.as_ref(), &user).await { match duplicate_chat_handler(&request.id, request.message_id.as_ref(), &user).await {
Ok(chat) => Ok(ApiResponse::JsonData(DuplicateChatResponse { chat })), Ok(chat) => Ok(ApiResponse::JsonData(chat)),
Err(e) => { Err(e) => {
tracing::error!("Error duplicating chat: {}", e); tracing::error!("Error duplicating chat: {}", e);
// Return different error codes based on the type of error // Return different error codes based on the type of error
let error_msg = e.to_string(); let error_msg = e.to_string();
if error_msg.contains("not found") { if error_msg.contains("not found") {
Err((StatusCode::NOT_FOUND, "Chat not found".to_string())) Err((StatusCode::NOT_FOUND, "Chat not found".to_string()))
} else if error_msg.contains("permission") { } else if error_msg.contains("permission") {
Err((StatusCode::FORBIDDEN, "You don't have permission to view this chat".to_string())) Err((
StatusCode::FORBIDDEN,
"You don't have permission to view this chat".to_string(),
))
} else { } else {
Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to duplicate chat".to_string())) Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to duplicate chat".to_string(),
))
} }
} }
} }
@ -61,21 +60,21 @@ mod tests {
use axum::routing::post; use axum::routing::post;
use axum::Router; use axum::Router;
use chrono::Utc; use chrono::Utc;
use middleware::{AuthenticatedUser, types::OrganizationMembership};
use database::{ use database::{
enums::{AssetPermissionRole, AssetType, IdentityType, UserOrganizationRole}, enums::{AssetPermissionRole, AssetType, IdentityType, UserOrganizationRole},
models::{AssetPermission, Chat, Message, MessageToFile, User}, models::{AssetPermission, Chat, Message, MessageToFile, User},
pool::get_pg_pool, pool::get_pg_pool,
schema::{asset_permissions, chats, messages, messages_to_files, users}, schema::{asset_permissions, chats, messages, messages_to_files, users},
}; };
use diesel::prelude::*;
use diesel::insert_into; use diesel::insert_into;
use diesel::prelude::*;
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use middleware::{types::OrganizationMembership, AuthenticatedUser};
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::collections::HashMap; use std::collections::HashMap;
use tower::ServiceExt; use tower::ServiceExt;
use uuid::Uuid; use uuid::Uuid;
async fn setup_test_user() -> (AuthenticatedUser, User) { async fn setup_test_user() -> (AuthenticatedUser, User) {
let user_id = Uuid::new_v4(); let user_id = Uuid::new_v4();
let user = User { let user = User {
@ -88,21 +87,20 @@ mod tests {
attributes: json!({}), attributes: json!({}),
avatar_url: None, avatar_url: None,
}; };
let mut conn = get_pg_pool().get().await.unwrap(); let mut conn = get_pg_pool().get().await.unwrap();
insert_into(users::table) insert_into(users::table)
.values(&user) .values(&user)
.execute(&mut conn) .execute(&mut conn)
.await.unwrap(); .await
.unwrap();
let org_id = Uuid::new_v4(); let org_id = Uuid::new_v4();
let organizations = vec![ let organizations = vec![OrganizationMembership {
OrganizationMembership { id: org_id,
id: org_id, role: UserOrganizationRole::Owner,
role: UserOrganizationRole::Owner, }];
}
];
let auth_user = AuthenticatedUser { let auth_user = AuthenticatedUser {
id: user_id, id: user_id,
email: "test@example.com".to_string(), email: "test@example.com".to_string(),
@ -115,14 +113,14 @@ mod tests {
attributes: json!({}), attributes: json!({}),
avatar_url: None, avatar_url: None,
}; };
(auth_user, user) (auth_user, user)
} }
async fn setup_test_chat(user: &User) -> (Uuid, Vec<Uuid>) { async fn setup_test_chat(user: &User) -> (Uuid, Vec<Uuid>) {
let chat_id = Uuid::new_v4(); let chat_id = Uuid::new_v4();
let now = Utc::now(); let now = Utc::now();
// Create chat record // Create chat record
let chat = Chat { let chat = Chat {
id: chat_id, id: chat_id,
@ -139,13 +137,14 @@ mod tests {
most_recent_file_id: None, most_recent_file_id: None,
most_recent_file_type: None, most_recent_file_type: None,
}; };
let mut conn = get_pg_pool().get().await.unwrap(); let mut conn = get_pg_pool().get().await.unwrap();
insert_into(chats::table) insert_into(chats::table)
.values(&chat) .values(&chat)
.execute(&mut conn) .execute(&mut conn)
.await.unwrap(); .await
.unwrap();
// Create permission for user // Create permission for user
let permission = AssetPermission { let permission = AssetPermission {
identity_id: user.id, identity_id: user.id,
@ -159,15 +158,16 @@ mod tests {
created_by: user.id, created_by: user.id,
updated_by: user.id, updated_by: user.id,
}; };
insert_into(asset_permissions::table) insert_into(asset_permissions::table)
.values(&permission) .values(&permission)
.execute(&mut conn) .execute(&mut conn)
.await.unwrap(); .await
.unwrap();
// Create test messages // Create test messages
let message_ids = vec![Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()]; let message_ids = vec![Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()];
for (i, message_id) in message_ids.iter().enumerate() { for (i, message_id) in message_ids.iter().enumerate() {
let message = Message { let message = Message {
id: *message_id, id: *message_id,
@ -184,20 +184,21 @@ mod tests {
created_by: user.id, created_by: user.id,
feedback: None, feedback: None,
}; };
insert_into(messages::table) insert_into(messages::table)
.values(&message) .values(&message)
.execute(&mut conn) .execute(&mut conn)
.await.unwrap(); .await
.unwrap();
} }
(chat_id, message_ids) (chat_id, message_ids)
} }
async fn setup_test_file_reference(message_id: &Uuid, user_id: &Uuid) -> Uuid { async fn setup_test_file_reference(message_id: &Uuid, user_id: &Uuid) -> Uuid {
let file_id = Uuid::new_v4(); let file_id = Uuid::new_v4();
let now = Utc::now(); let now = Utc::now();
let file_ref = MessageToFile { let file_ref = MessageToFile {
id: Uuid::new_v4(), id: Uuid::new_v4(),
message_id: *message_id, message_id: *message_id,
@ -207,23 +208,24 @@ mod tests {
deleted_at: None, deleted_at: None,
is_duplicate: false, is_duplicate: false,
}; };
let mut conn = get_pg_pool().get().await.unwrap(); let mut conn = get_pg_pool().get().await.unwrap();
insert_into(messages_to_files::table) insert_into(messages_to_files::table)
.values(&file_ref) .values(&file_ref)
.execute(&mut conn) .execute(&mut conn)
.await.unwrap(); .await
.unwrap();
file_id file_id
} }
fn create_test_app(auth_user: AuthenticatedUser) -> Router { fn create_test_app(auth_user: AuthenticatedUser) -> Router {
Router::new() Router::new()
.route("/chats/duplicate", post(duplicate_chat_route)) .route("/chats/duplicate", post(duplicate_chat_route))
.layer(Extension(auth_user)) .layer(Extension(auth_user))
} }
// Instead of unit tests that depend on a real database connection, // Instead of unit tests that depend on a real database connection,
// we should run the integration tests from the tests directory. // we should run the integration tests from the tests directory.
// These unit tests might become outdated if the model structures change. // These unit tests might become outdated if the model structures change.
} }

119
api/tests/example_test.rs Normal file
View File

@ -0,0 +1,119 @@
use anyhow::Result;
use uuid::Uuid;
// Import the TestInstance type
use crate::TestInstance;
/// Example test that requires database access - environment automatically initialized
#[tokio::test]
async fn test_simple_database_connection() -> Result<()> {
// The environment is already initialized by the test framework
// Just create a test instance to get a unique test ID and access to pools
let test = TestInstance::new().await?;
// Now we can use the test database
let pool = test.get_diesel_pool();
// Verify connection works by getting a connection from the pool
let conn = pool.get().await?;
// We've successfully connected!
Ok(())
}
/// Example test for working with test data
#[tokio::test]
async fn test_with_isolation() -> Result<()> {
// Create a test instance with a unique ID
let test = TestInstance::new().await?;
// Use the test_id to tag test data
let test_id = &test.test_id;
// Create a connection
let mut conn = test.get_diesel_pool().get().await?;
// Example raw SQL for test data creation (using the test_id)
diesel::sql_query("INSERT INTO example_table (id, name, test_id) VALUES ($1, $2, $3)")
.bind::<diesel::sql_types::Uuid, _>(Uuid::new_v4())
.bind::<diesel::sql_types::Text, _>("Test item")
.bind::<diesel::sql_types::Text, _>(test_id)
.execute(&mut conn)
.await?;
// Run your test logic...
// Clean up after the test
diesel::sql_query("DELETE FROM example_table WHERE test_id = $1")
.bind::<diesel::sql_types::Text, _>(test_id)
.execute(&mut conn)
.await?;
Ok(())
}
/// Example test for third-party API integration
#[tokio::test]
async fn test_third_party_api() -> Result<()> {
// Skip test if third-party testing is disabled
if std::env::var("ENABLE_THIRD_PARTY_TESTS").is_err() {
println!("Skipping third-party API test");
return Ok(());
}
// Get API credentials from the test environment
let api_key = std::env::var("THIRD_PARTY_API_KEY")
.expect("THIRD_PARTY_API_KEY must be set for this test");
let api_url = std::env::var("THIRD_PARTY_API_URL")
.expect("THIRD_PARTY_API_URL must be set for this test");
// Create test instance for database access if needed
let test = TestInstance::new().await?;
// Create API client with test credentials
// let client = ThirdPartyClient::new(&api_key, &api_url);
// Run API tests...
Ok(())
}
/// Example test that specifically uses Redis
#[tokio::test]
async fn test_redis_connection() -> Result<()> {
// Create test instance
let test = TestInstance::new().await?;
// Get Redis pool
let redis_pool = test.get_redis_pool();
// Get Redis connection
let mut conn = redis_pool.get().await?;
// Example Redis operations
// redis::cmd("SET").arg("test_key").arg("test_value").execute(&mut *conn);
// let value: String = redis::cmd("GET").arg("test_key").query_async(&mut *conn).await?;
// assert_eq!(value, "test_value");
Ok(())
}
/// Example of a test that runs with parallel services
#[tokio::test]
async fn test_multiple_services() -> Result<()> {
// Create test instance
let test = TestInstance::new().await?;
// Access both SQL and Redis databases
let pg_pool = test.get_diesel_pool();
let redis_pool = test.get_redis_pool();
// Example of parallel operations
let pg_conn = pg_pool.get().await?;
let redis_conn = redis_pool.get().await?;
// Use both connections...
Ok(())
}