mirror of https://github.com/buster-so/buster.git
Merge branch 'evals' of https://github.com/buster-so/buster into evals
This commit is contained in:
commit
b77848f150
|
@ -1,20 +1,28 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use chrono::Utc;
|
||||
use database::{models::Message, pool::get_pg_pool, schema::messages};
|
||||
use database::{
|
||||
models::Message,
|
||||
pool::get_pg_pool,
|
||||
schema::messages,
|
||||
chats::fetch_chat_with_permission,
|
||||
enums::AssetPermissionRole,
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use tracing::info;
|
||||
use middleware::AuthenticatedUser;
|
||||
use sharing::check_permission_access;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Deletes a message and marks all subsequent messages in the same chat as deleted
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `user` - The authenticated user requesting the deletion
|
||||
/// * `message_id` - The ID of the message to delete
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<()>` - Success or error
|
||||
pub async fn delete_message_handler(_user: AuthenticatedUser, message_id: Uuid) -> Result<()> {
|
||||
pub async fn delete_message_handler(user: AuthenticatedUser, message_id: Uuid) -> Result<()> {
|
||||
let pool = get_pg_pool();
|
||||
let mut conn = pool.get().await?;
|
||||
|
||||
|
@ -29,6 +37,34 @@ pub async fn delete_message_handler(_user: AuthenticatedUser, message_id: Uuid)
|
|||
Err(e) => return Err(anyhow!("Database error: {}", e)),
|
||||
};
|
||||
|
||||
// Check if the user has permission to delete messages in this chat
|
||||
let chat_with_permission = fetch_chat_with_permission(&message.chat_id, &user.id).await?;
|
||||
|
||||
// If chat not found, return error
|
||||
let chat_with_permission = match chat_with_permission {
|
||||
Some(cwp) => cwp,
|
||||
None => return Err(anyhow!("Chat not found")),
|
||||
};
|
||||
|
||||
// Check if user has appropriate permissions (CanEdit, FullAccess, or Owner)
|
||||
let has_permission = check_permission_access(
|
||||
chat_with_permission.permission,
|
||||
&[
|
||||
AssetPermissionRole::CanEdit,
|
||||
AssetPermissionRole::FullAccess,
|
||||
AssetPermissionRole::Owner,
|
||||
],
|
||||
chat_with_permission.chat.organization_id,
|
||||
&user.organizations,
|
||||
);
|
||||
|
||||
// If user is the creator, they automatically have access
|
||||
let is_creator = chat_with_permission.chat.created_by == user.id;
|
||||
|
||||
if !has_permission && !is_creator {
|
||||
return Err(anyhow!("You don't have permission to delete messages in this chat"));
|
||||
}
|
||||
|
||||
// Mark the target message and all subsequent messages in the same chat as deleted
|
||||
let updated_count = diesel::update(messages::table)
|
||||
.filter(
|
||||
|
@ -47,7 +83,5 @@ pub async fn delete_message_handler(_user: AuthenticatedUser, message_id: Uuid)
|
|||
"Deleted message and subsequent messages in chat"
|
||||
);
|
||||
|
||||
// TODO: Add access controls to verify the user has permission to delete this message
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use axum::Extension;
|
|||
use handlers::chats::duplicate_chat_handler;
|
||||
use handlers::chats::types::ChatWithMessages;
|
||||
use middleware::AuthenticatedUser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::routes::rest::ApiResponse;
|
||||
|
@ -18,36 +18,35 @@ pub struct DuplicateChatRequest {
|
|||
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
|
||||
///
|
||||
///
|
||||
/// Duplicates an existing chat, including all messages and file references.
|
||||
/// 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.
|
||||
pub async fn duplicate_chat_route(
|
||||
Extension(user): Extension<AuthenticatedUser>,
|
||||
Json(request): Json<DuplicateChatRequest>,
|
||||
) -> Result<ApiResponse<DuplicateChatResponse>, (StatusCode, String)> {
|
||||
) -> Result<ApiResponse<ChatWithMessages>, (StatusCode, String)> {
|
||||
// Call the handler function with the request parameters
|
||||
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) => {
|
||||
tracing::error!("Error duplicating chat: {}", e);
|
||||
|
||||
|
||||
// Return different error codes based on the type of error
|
||||
let error_msg = e.to_string();
|
||||
if error_msg.contains("not found") {
|
||||
Err((StatusCode::NOT_FOUND, "Chat not found".to_string()))
|
||||
} 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 {
|
||||
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::Router;
|
||||
use chrono::Utc;
|
||||
use middleware::{AuthenticatedUser, types::OrganizationMembership};
|
||||
use database::{
|
||||
enums::{AssetPermissionRole, AssetType, IdentityType, UserOrganizationRole},
|
||||
models::{AssetPermission, Chat, Message, MessageToFile, User},
|
||||
pool::get_pg_pool,
|
||||
schema::{asset_permissions, chats, messages, messages_to_files, users},
|
||||
};
|
||||
use diesel::prelude::*;
|
||||
use diesel::insert_into;
|
||||
use diesel::prelude::*;
|
||||
use diesel_async::RunQueryDsl;
|
||||
use middleware::{types::OrganizationMembership, AuthenticatedUser};
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use tower::ServiceExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
async fn setup_test_user() -> (AuthenticatedUser, User) {
|
||||
let user_id = Uuid::new_v4();
|
||||
let user = User {
|
||||
|
@ -88,21 +87,20 @@ mod tests {
|
|||
attributes: json!({}),
|
||||
avatar_url: None,
|
||||
};
|
||||
|
||||
|
||||
let mut conn = get_pg_pool().get().await.unwrap();
|
||||
insert_into(users::table)
|
||||
.values(&user)
|
||||
.execute(&mut conn)
|
||||
.await.unwrap();
|
||||
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let org_id = Uuid::new_v4();
|
||||
let organizations = vec![
|
||||
OrganizationMembership {
|
||||
id: org_id,
|
||||
role: UserOrganizationRole::Owner,
|
||||
}
|
||||
];
|
||||
|
||||
let organizations = vec![OrganizationMembership {
|
||||
id: org_id,
|
||||
role: UserOrganizationRole::Owner,
|
||||
}];
|
||||
|
||||
let auth_user = AuthenticatedUser {
|
||||
id: user_id,
|
||||
email: "test@example.com".to_string(),
|
||||
|
@ -115,14 +113,14 @@ mod tests {
|
|||
attributes: json!({}),
|
||||
avatar_url: None,
|
||||
};
|
||||
|
||||
|
||||
(auth_user, user)
|
||||
}
|
||||
|
||||
|
||||
async fn setup_test_chat(user: &User) -> (Uuid, Vec<Uuid>) {
|
||||
let chat_id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
|
||||
|
||||
// Create chat record
|
||||
let chat = Chat {
|
||||
id: chat_id,
|
||||
|
@ -139,13 +137,14 @@ mod tests {
|
|||
most_recent_file_id: None,
|
||||
most_recent_file_type: None,
|
||||
};
|
||||
|
||||
|
||||
let mut conn = get_pg_pool().get().await.unwrap();
|
||||
insert_into(chats::table)
|
||||
.values(&chat)
|
||||
.execute(&mut conn)
|
||||
.await.unwrap();
|
||||
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create permission for user
|
||||
let permission = AssetPermission {
|
||||
identity_id: user.id,
|
||||
|
@ -159,15 +158,16 @@ mod tests {
|
|||
created_by: user.id,
|
||||
updated_by: user.id,
|
||||
};
|
||||
|
||||
|
||||
insert_into(asset_permissions::table)
|
||||
.values(&permission)
|
||||
.execute(&mut conn)
|
||||
.await.unwrap();
|
||||
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Create test messages
|
||||
let message_ids = vec![Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()];
|
||||
|
||||
|
||||
for (i, message_id) in message_ids.iter().enumerate() {
|
||||
let message = Message {
|
||||
id: *message_id,
|
||||
|
@ -184,20 +184,21 @@ mod tests {
|
|||
created_by: user.id,
|
||||
feedback: None,
|
||||
};
|
||||
|
||||
|
||||
insert_into(messages::table)
|
||||
.values(&message)
|
||||
.execute(&mut conn)
|
||||
.await.unwrap();
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
|
||||
(chat_id, message_ids)
|
||||
}
|
||||
|
||||
|
||||
async fn setup_test_file_reference(message_id: &Uuid, user_id: &Uuid) -> Uuid {
|
||||
let file_id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
|
||||
|
||||
let file_ref = MessageToFile {
|
||||
id: Uuid::new_v4(),
|
||||
message_id: *message_id,
|
||||
|
@ -207,23 +208,24 @@ mod tests {
|
|||
deleted_at: None,
|
||||
is_duplicate: false,
|
||||
};
|
||||
|
||||
|
||||
let mut conn = get_pg_pool().get().await.unwrap();
|
||||
insert_into(messages_to_files::table)
|
||||
.values(&file_ref)
|
||||
.execute(&mut conn)
|
||||
.await.unwrap();
|
||||
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
file_id
|
||||
}
|
||||
|
||||
|
||||
fn create_test_app(auth_user: AuthenticatedUser) -> Router {
|
||||
Router::new()
|
||||
.route("/chats/duplicate", post(duplicate_chat_route))
|
||||
.layer(Extension(auth_user))
|
||||
}
|
||||
|
||||
|
||||
// Instead of unit tests that depend on a real database connection,
|
||||
// we should run the integration tests from the tests directory.
|
||||
// These unit tests might become outdated if the model structures change.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
}
|
Loading…
Reference in New Issue