Merge branch 'evals' of https://github.com/buster-so/buster into evals

This commit is contained in:
Nate Kelley 2025-04-02 10:42:57 -06:00
commit b77848f150
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 207 additions and 52 deletions

View File

@ -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(())
}

View File

@ -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,13 +18,6 @@ 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.
@ -33,10 +26,10 @@ pub struct DuplicateChatResponse {
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);
@ -45,9 +38,15 @@ pub async fn duplicate_chat_route(
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,16 +60,16 @@ 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;
@ -93,15 +92,14 @@ mod tests {
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,
@ -144,7 +142,8 @@ mod tests {
insert_into(chats::table)
.values(&chat)
.execute(&mut conn)
.await.unwrap();
.await
.unwrap();
// Create permission for user
let permission = AssetPermission {
@ -163,7 +162,8 @@ mod tests {
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()];
@ -188,7 +188,8 @@ mod tests {
insert_into(messages::table)
.values(&message)
.execute(&mut conn)
.await.unwrap();
.await
.unwrap();
}
(chat_id, message_ids)
@ -212,7 +213,8 @@ mod tests {
insert_into(messages_to_files::table)
.values(&file_ref)
.execute(&mut conn)
.await.unwrap();
.await
.unwrap();
file_id
}

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(())
}