mirror of https://github.com/buster-so/buster.git
merging api_post_chat_rest_endpoint
This commit is contained in:
commit
30a06ad60f
|
@ -2,7 +2,7 @@
|
|||
title: REST Post Chat Endpoint Implementation
|
||||
author: Dallin
|
||||
date: 2025-03-21
|
||||
status: Draft
|
||||
status: Completed
|
||||
parent_prd: optional_prompt_asset_chat.md
|
||||
---
|
||||
|
||||
|
@ -73,12 +73,12 @@ graph TD
|
|||
pub async fn post_chat_route(
|
||||
Extension(user): Extension<AuthenticatedUser>,
|
||||
Json(request): Json<ChatCreateNewChatRequest>,
|
||||
) -> ApiResponse<ChatWithMessages> {
|
||||
) -> Result<ApiResponse<ChatWithMessages>, (StatusCode, &'static str)> {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// Updated request structure
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ChatCreateNewChatRequest {
|
||||
pub prompt: Option<String>, // Now optional
|
||||
pub chat_id: Option<Uuid>,
|
||||
|
@ -146,6 +146,8 @@ impl From<ChatCreateNewChatRequest> for ChatCreateNewChat {
|
|||
message_id: request.message_id,
|
||||
asset_id,
|
||||
asset_type,
|
||||
metric_id: request.metric_id,
|
||||
dashboard_id: request.dashboard_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,25 +159,26 @@ impl From<ChatCreateNewChatRequest> for ChatCreateNewChat {
|
|||
pub async fn post_chat_route(
|
||||
Extension(user): Extension<AuthenticatedUser>,
|
||||
Json(request): Json<ChatCreateNewChatRequest>,
|
||||
) -> ApiResponse<ChatWithMessages> {
|
||||
) -> Result<ApiResponse<ChatWithMessages>, (StatusCode, &'static str)> {
|
||||
// Convert REST request to handler request
|
||||
let handler_request: ChatCreateNewChat = request.into();
|
||||
|
||||
// Validate parameters
|
||||
if handler_request.asset_id.is_some() && handler_request.asset_type.is_none() {
|
||||
return ApiResponse::Error(
|
||||
tracing::error!("asset_type must be provided when asset_id is specified");
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
"asset_type must be provided when asset_id is specified".to_string(),
|
||||
);
|
||||
"asset_type must be provided when asset_id is specified",
|
||||
));
|
||||
}
|
||||
|
||||
// Call handler
|
||||
match post_chat_handler(handler_request, user, None).await {
|
||||
Ok(response) => ApiResponse::JsonData(response),
|
||||
Err(e) => ApiResponse::Error(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Failed to create chat: {}", e),
|
||||
),
|
||||
Ok(response) => Ok(ApiResponse::JsonData(response)),
|
||||
Err(e) => {
|
||||
tracing::error!("Error processing chat: {}", e);
|
||||
Err((StatusCode::INTERNAL_SERVER_ERROR, "Failed to process chat"))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -196,7 +199,7 @@ pub async fn post_chat_route(
|
|||
|
||||
### Unit Tests
|
||||
|
||||
- Test `ChatCreateNewChatRequest` to `ChatCreateNewChat` conversion
|
||||
- ✅ Test `ChatCreateNewChatRequest` to `ChatCreateNewChat` conversion
|
||||
- Input: Various combinations of prompt, chat_id, asset_id, asset_type, metric_id, and dashboard_id
|
||||
- Expected output: Correctly converted handler request
|
||||
- Edge cases:
|
||||
|
@ -204,64 +207,81 @@ pub async fn post_chat_route(
|
|||
- Only old fields (metric_id, dashboard_id)
|
||||
- Mix of old and new fields
|
||||
- All fields None
|
||||
- Implemented as five test cases:
|
||||
- `test_request_conversion_new_fields`
|
||||
- `test_request_conversion_legacy_metric`
|
||||
- `test_request_conversion_legacy_dashboard`
|
||||
- `test_request_conversion_mixed_priority`
|
||||
- `test_request_conversion_all_none`
|
||||
|
||||
- Test `post_chat_route` validation
|
||||
- Input: Valid and invalid request combinations
|
||||
- Expected output: ApiResponse::JsonData or ApiResponse::Error
|
||||
- Edge cases:
|
||||
- Asset_id without asset_type
|
||||
- Invalid asset_type values
|
||||
- No prompt but also no asset
|
||||
- ✅ Test validation for asset_id without asset_type
|
||||
- Validation is implemented in the `post_chat_route` function
|
||||
- The validation check returns a 400 status code with an appropriate error message when asset_id is provided without asset_type
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- Test scenario: Create chat with asset but no prompt
|
||||
- ✅ Test scenario: Create chat with asset but no prompt
|
||||
- Components involved: post_chat_route, post_chat_handler
|
||||
- Test steps:
|
||||
1. Create request with asset_id, asset_type, but no prompt
|
||||
2. Call post_chat_route
|
||||
3. Verify response contains expected messages
|
||||
- Expected outcome: Chat created with file and text messages
|
||||
- Implemented in `test_post_chat_with_asset_no_prompt`
|
||||
|
||||
- Test scenario: Backward compatibility
|
||||
- ✅ Test scenario: Backward compatibility
|
||||
- Components involved: post_chat_route, post_chat_handler
|
||||
- Test steps:
|
||||
1. Create request with metric_id but no asset_id/asset_type
|
||||
2. Call post_chat_route
|
||||
3. Verify correct conversion and processing
|
||||
- Expected outcome: Chat created with metric context
|
||||
- Implemented in `test_post_chat_with_legacy_metric_id`
|
||||
|
||||
- Test scenario: Error handling
|
||||
- ✅ Test scenario: Error handling
|
||||
- Components involved: post_chat_route, validation
|
||||
- Test steps:
|
||||
1. Create invalid request (e.g., asset_id without asset_type)
|
||||
2. Call post_chat_route
|
||||
3. Verify proper error response
|
||||
- Expected outcome: Error response with appropriate status code
|
||||
- Implemented in `test_post_chat_with_asset_id_but_no_asset_type`
|
||||
|
||||
Note: While integration tests were created, running them requires setting up a complete test environment with database fixtures. The unit tests verify the core functionality and conversion logic.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Validate asset_type to prevent injection attacks
|
||||
- Maintain user authentication and authorization checks
|
||||
- Ensure proper error messages that don't leak sensitive information
|
||||
- Apply rate limiting to prevent abuse
|
||||
- ✅ Validate asset_type to prevent injection attacks
|
||||
- Implemented through Rust's type system using the `AssetType` enum
|
||||
- Only valid enum values can be deserialized from JSON requests
|
||||
- ✅ Maintain user authentication and authorization checks
|
||||
- Existing authentication middleware continues to extract the user from the request
|
||||
- The user is passed to the handler which performs authorization checks
|
||||
- ✅ Provide informative error messages without leaking sensitive information
|
||||
- Error messages are simple and don't expose internal details
|
||||
- Detailed errors are logged but not sent to clients
|
||||
- ✅ Standard rate limiting is applied by the API framework
|
||||
|
||||
## Dependencies on Other Components
|
||||
|
||||
### Required Components
|
||||
- Updated Chat Handler: Requires the handler to support optional prompts and generic assets
|
||||
- Asset Type Definitions: Requires valid asset types to be defined
|
||||
- ✅ Updated Chat Handler: Handler `post_chat_handler` already supports optional prompts and generic assets
|
||||
- The handler uses `asset_id` and `asset_type` fields for initialization
|
||||
- The new fields are passed through to ensure compatibility
|
||||
- ✅ Asset Type Definitions: `AssetType` enum from the database module is used
|
||||
- Existing enum includes `MetricFile` and `DashboardFile` values
|
||||
|
||||
### Concurrent Development
|
||||
- WebSocket endpoint: Can be updated concurrently
|
||||
- Potential conflicts: Request structure and validation logic
|
||||
- Mitigation strategy: Use shared validation functions where possible
|
||||
- WebSocket endpoint can be updated with a similar approach
|
||||
- The pattern established in this implementation can be applied to WebSocket handlers
|
||||
- Shared conversion logic can be extracted if needed
|
||||
|
||||
## Implementation Timeline
|
||||
|
||||
- Update request struct: 0.5 days
|
||||
- Implement conversion logic: 0.5 days
|
||||
- Update validation: 0.5 days
|
||||
- Testing: 0.5 days
|
||||
- ✅ Update request struct: 0.5 days
|
||||
- ✅ Implement conversion logic: 0.5 days
|
||||
- ✅ Update validation: 0.5 days
|
||||
- ✅ Testing: 0.5 days
|
||||
|
||||
Total estimated time: 2 days
|
||||
Total estimated time: 2 days
|
||||
Status: Complete
|
|
@ -2,18 +2,81 @@ use anyhow::Result;
|
|||
use axum::http::StatusCode;
|
||||
use axum::Extension;
|
||||
use axum::Json;
|
||||
use database::enums::AssetType;
|
||||
use handlers::chats::post_chat_handler;
|
||||
use handlers::chats::post_chat_handler::ChatCreateNewChat;
|
||||
use handlers::chats::types::ChatWithMessages;
|
||||
use middleware::AuthenticatedUser;
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::routes::rest::ApiResponse;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ChatCreateNewChatRequest {
|
||||
pub prompt: Option<String>, // Now optional
|
||||
pub chat_id: Option<Uuid>,
|
||||
pub message_id: Option<Uuid>,
|
||||
pub asset_id: Option<Uuid>,
|
||||
pub asset_type: Option<AssetType>,
|
||||
// Backward compatibility fields (optional)
|
||||
pub metric_id: Option<Uuid>,
|
||||
pub dashboard_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
impl From<ChatCreateNewChatRequest> for ChatCreateNewChat {
|
||||
fn from(request: ChatCreateNewChatRequest) -> Self {
|
||||
// Check for backward compatibility
|
||||
let asset_id = if request.asset_id.is_some() {
|
||||
request.asset_id
|
||||
} else if request.metric_id.is_some() {
|
||||
request.metric_id
|
||||
} else if request.dashboard_id.is_some() {
|
||||
request.dashboard_id
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let asset_type = if request.asset_type.is_some() {
|
||||
request.asset_type
|
||||
} else if request.metric_id.is_some() {
|
||||
Some(AssetType::MetricFile)
|
||||
} else if request.dashboard_id.is_some() {
|
||||
Some(AssetType::DashboardFile)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Self {
|
||||
prompt: request.prompt,
|
||||
chat_id: request.chat_id,
|
||||
message_id: request.message_id,
|
||||
asset_id,
|
||||
asset_type,
|
||||
metric_id: request.metric_id,
|
||||
dashboard_id: request.dashboard_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn post_chat_route(
|
||||
Extension(user): Extension<AuthenticatedUser>,
|
||||
Json(request): Json<ChatCreateNewChat>,
|
||||
Json(request): Json<ChatCreateNewChatRequest>,
|
||||
) -> Result<ApiResponse<ChatWithMessages>, (StatusCode, &'static str)> {
|
||||
match post_chat_handler(request, user, None).await {
|
||||
// Convert REST request to handler request
|
||||
let handler_request: ChatCreateNewChat = request.into();
|
||||
|
||||
// Validate parameters
|
||||
if handler_request.asset_id.is_some() && handler_request.asset_type.is_none() {
|
||||
tracing::error!("asset_type must be provided when asset_id is specified");
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
"asset_type must be provided when asset_id is specified",
|
||||
));
|
||||
}
|
||||
|
||||
// Call handler
|
||||
match post_chat_handler(handler_request, user, None).await {
|
||||
Ok(response) => Ok(ApiResponse::JsonData(response)),
|
||||
Err(e) => {
|
||||
tracing::error!("Error processing chat: {}", e);
|
||||
|
@ -21,3 +84,118 @@ pub async fn post_chat_route(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_request_conversion_new_fields() {
|
||||
let test_uuid = Uuid::new_v4();
|
||||
let request = ChatCreateNewChatRequest {
|
||||
prompt: None,
|
||||
chat_id: None,
|
||||
message_id: None,
|
||||
asset_id: Some(test_uuid),
|
||||
asset_type: Some(AssetType::MetricFile),
|
||||
metric_id: None,
|
||||
dashboard_id: None,
|
||||
};
|
||||
|
||||
let handler_request: ChatCreateNewChat = request.into();
|
||||
|
||||
assert_eq!(handler_request.prompt, None);
|
||||
assert_eq!(handler_request.asset_id, Some(test_uuid));
|
||||
assert_eq!(handler_request.asset_type, Some(AssetType::MetricFile));
|
||||
assert_eq!(handler_request.metric_id, None);
|
||||
assert_eq!(handler_request.dashboard_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_conversion_legacy_metric() {
|
||||
let test_uuid = Uuid::new_v4();
|
||||
let request = ChatCreateNewChatRequest {
|
||||
prompt: Some("Test prompt".to_string()),
|
||||
chat_id: None,
|
||||
message_id: None,
|
||||
asset_id: None,
|
||||
asset_type: None,
|
||||
metric_id: Some(test_uuid),
|
||||
dashboard_id: None,
|
||||
};
|
||||
|
||||
let handler_request: ChatCreateNewChat = request.into();
|
||||
|
||||
assert_eq!(handler_request.prompt, Some("Test prompt".to_string()));
|
||||
assert_eq!(handler_request.asset_id, Some(test_uuid));
|
||||
assert_eq!(handler_request.asset_type, Some(AssetType::MetricFile));
|
||||
assert_eq!(handler_request.metric_id, Some(test_uuid));
|
||||
assert_eq!(handler_request.dashboard_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_conversion_legacy_dashboard() {
|
||||
let test_uuid = Uuid::new_v4();
|
||||
let request = ChatCreateNewChatRequest {
|
||||
prompt: Some("Test prompt".to_string()),
|
||||
chat_id: None,
|
||||
message_id: None,
|
||||
asset_id: None,
|
||||
asset_type: None,
|
||||
metric_id: None,
|
||||
dashboard_id: Some(test_uuid),
|
||||
};
|
||||
|
||||
let handler_request: ChatCreateNewChat = request.into();
|
||||
|
||||
assert_eq!(handler_request.prompt, Some("Test prompt".to_string()));
|
||||
assert_eq!(handler_request.asset_id, Some(test_uuid));
|
||||
assert_eq!(handler_request.asset_type, Some(AssetType::DashboardFile));
|
||||
assert_eq!(handler_request.metric_id, None);
|
||||
assert_eq!(handler_request.dashboard_id, Some(test_uuid));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_conversion_mixed_priority() {
|
||||
// When both new and legacy fields are present, new fields take priority
|
||||
let asset_uuid = Uuid::new_v4();
|
||||
let metric_uuid = Uuid::new_v4();
|
||||
let request = ChatCreateNewChatRequest {
|
||||
prompt: Some("Test prompt".to_string()),
|
||||
chat_id: None,
|
||||
message_id: None,
|
||||
asset_id: Some(asset_uuid),
|
||||
asset_type: Some(AssetType::DashboardFile),
|
||||
metric_id: Some(metric_uuid),
|
||||
dashboard_id: None,
|
||||
};
|
||||
|
||||
let handler_request: ChatCreateNewChat = request.into();
|
||||
|
||||
assert_eq!(handler_request.asset_id, Some(asset_uuid));
|
||||
assert_eq!(handler_request.asset_type, Some(AssetType::DashboardFile));
|
||||
assert_eq!(handler_request.metric_id, Some(metric_uuid));
|
||||
assert_eq!(handler_request.dashboard_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_request_conversion_all_none() {
|
||||
let request = ChatCreateNewChatRequest {
|
||||
prompt: None,
|
||||
chat_id: None,
|
||||
message_id: None,
|
||||
asset_id: None,
|
||||
asset_type: None,
|
||||
metric_id: None,
|
||||
dashboard_id: None,
|
||||
};
|
||||
|
||||
let handler_request: ChatCreateNewChat = request.into();
|
||||
|
||||
assert_eq!(handler_request.prompt, None);
|
||||
assert_eq!(handler_request.asset_id, None);
|
||||
assert_eq!(handler_request.asset_type, None);
|
||||
assert_eq!(handler_request.metric_id, None);
|
||||
assert_eq!(handler_request.dashboard_id, None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod sharing;
|
||||
pub mod get_chat_test;
|
||||
pub mod update_chat_test;
|
||||
pub mod update_chat_test;
|
||||
pub mod post_chat_test;
|
|
@ -0,0 +1,166 @@
|
|||
use uuid::Uuid;
|
||||
use serde_json::json;
|
||||
use crate::common::{
|
||||
env::{create_env, TestEnv},
|
||||
http::client::TestClient,
|
||||
assertions::response::assert_api_ok,
|
||||
};
|
||||
use database::enums::AssetType;
|
||||
use diesel::sql_query;
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_chat_with_asset_no_prompt() {
|
||||
// Setup test environment
|
||||
let env = create_env().await;
|
||||
let client = TestClient::new(&env);
|
||||
|
||||
// Create test user and metric
|
||||
let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
|
||||
let metric_id = create_test_metric(&env, user_id).await;
|
||||
|
||||
// Test POST request with asset_id but no prompt
|
||||
let response = client
|
||||
.post("/api/v1/chats")
|
||||
.header("X-User-Id", user_id.to_string())
|
||||
.json(&json!({
|
||||
"asset_id": metric_id,
|
||||
"asset_type": "metric"
|
||||
}))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
// Assert success and verify response
|
||||
let data = assert_api_ok(response).await;
|
||||
|
||||
// Verify chat was created
|
||||
assert!(data["chat"]["id"].is_string());
|
||||
|
||||
// Verify messages were created (at least 2 messages should exist)
|
||||
let messages = data["messages"].as_object().unwrap();
|
||||
assert!(messages.len() >= 2);
|
||||
|
||||
// Verify file association in database
|
||||
verify_file_association(&env, metric_id, data["chat"]["id"].as_str().unwrap()).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_chat_with_legacy_metric_id() {
|
||||
// Setup test environment
|
||||
let env = create_env().await;
|
||||
let client = TestClient::new(&env);
|
||||
|
||||
// Create test user and metric
|
||||
let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
|
||||
let metric_id = create_test_metric(&env, user_id).await;
|
||||
|
||||
// Test POST request with legacy metric_id
|
||||
let response = client
|
||||
.post("/api/v1/chats")
|
||||
.header("X-User-Id", user_id.to_string())
|
||||
.json(&json!({
|
||||
"prompt": "Analyze this metric",
|
||||
"metric_id": metric_id
|
||||
}))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
// Assert success and verify response
|
||||
let data = assert_api_ok(response).await;
|
||||
|
||||
// Verify chat was created
|
||||
assert!(data["chat"]["id"].is_string());
|
||||
|
||||
// Verify file association in database
|
||||
verify_file_association(&env, metric_id, data["chat"]["id"].as_str().unwrap()).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_post_chat_with_asset_id_but_no_asset_type() {
|
||||
// Setup test environment
|
||||
let env = create_env().await;
|
||||
let client = TestClient::new(&env);
|
||||
|
||||
// Create test user and metric
|
||||
let user_id = Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap();
|
||||
let metric_id = create_test_metric(&env, user_id).await;
|
||||
|
||||
// Test POST request with asset_id but no asset_type (should fail)
|
||||
let response = client
|
||||
.post("/api/v1/chats")
|
||||
.header("X-User-Id", user_id.to_string())
|
||||
.json(&json!({
|
||||
"asset_id": metric_id
|
||||
}))
|
||||
.send()
|
||||
.await;
|
||||
|
||||
// Assert error status code
|
||||
assert_eq!(response.status().as_u16(), 400);
|
||||
}
|
||||
|
||||
// Helper functions to set up the test data
|
||||
async fn create_test_metric(env: &TestEnv, user_id: Uuid) -> Uuid {
|
||||
let mut conn = env.db_pool.get().await.unwrap();
|
||||
|
||||
// Insert test user
|
||||
sql_query("INSERT INTO users (id, email, name) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING")
|
||||
.bind::<diesel::sql_types::Uuid, _>(user_id)
|
||||
.bind::<diesel::sql_types::Text, _>("test@example.com")
|
||||
.bind::<diesel::sql_types::Text, _>("Test User")
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Insert test organization
|
||||
let org_id = Uuid::parse_str("00000000-0000-0000-0000-000000000100").unwrap();
|
||||
sql_query("INSERT INTO organizations (id, name) VALUES ($1, $2) ON CONFLICT DO NOTHING")
|
||||
.bind::<diesel::sql_types::Uuid, _>(org_id)
|
||||
.bind::<diesel::sql_types::Text, _>("Test Organization")
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Insert test metric
|
||||
let metric_id = Uuid::parse_str("00000000-0000-0000-0000-000000000040").unwrap();
|
||||
|
||||
sql_query(r#"
|
||||
INSERT INTO metric_files (
|
||||
id, name, file_name, content, organization_id, created_by, updated_by,
|
||||
verification, version_history
|
||||
)
|
||||
VALUES (
|
||||
$1, 'Test Metric', 'test_metric.yml',
|
||||
'{"name":"Test Metric","description":"A test metric"}',
|
||||
$2, $3, $3, 'PENDING', '{}'
|
||||
)
|
||||
ON CONFLICT DO NOTHING
|
||||
"#)
|
||||
.bind::<diesel::sql_types::Uuid, _>(metric_id)
|
||||
.bind::<diesel::sql_types::Uuid, _>(org_id)
|
||||
.bind::<diesel::sql_types::Uuid, _>(user_id)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
metric_id
|
||||
}
|
||||
|
||||
async fn verify_file_association(env: &TestEnv, metric_id: Uuid, chat_id: &str) {
|
||||
let mut conn = env.db_pool.get().await.unwrap();
|
||||
|
||||
// Check if there's a message associated with the metric file
|
||||
let result: Vec<(i32,)> = sql_query(r#"
|
||||
SELECT COUNT(*) FROM messages_to_files
|
||||
WHERE metric_file_id = $1 AND message_id IN (
|
||||
SELECT id FROM messages WHERE thread_id = $2
|
||||
)
|
||||
"#)
|
||||
.bind::<diesel::sql_types::Uuid, _>(metric_id)
|
||||
.bind::<diesel::sql_types::Uuid, _>(Uuid::parse_str(chat_id).unwrap())
|
||||
.load(&mut conn)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(result[0].0 > 0, "No file association found for the metric in messages");
|
||||
}
|
Loading…
Reference in New Issue