From d4f72bf7cb90cfd0052deaa015259a44234a701e Mon Sep 17 00:00:00 2001 From: Cherrypick14 Date: Fri, 1 Aug 2025 21:47:59 +0300 Subject: [PATCH 1/8] feat(http): Add report module and integrate with API router --- src/http/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/http/mod.rs b/src/http/mod.rs index b577f8b7..d54fcb6b 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -16,6 +16,7 @@ mod escrow; mod health_check; pub mod newsletter; mod project; +mod report; mod support_ticket; mod transaction; mod types; @@ -55,6 +56,7 @@ pub fn api_router(app_state: AppState) -> Router { .merge(escrow::router()) .merge(newsletter::router()) .merge(validator::router()) + .merge(report::router()) .layer(trace_layer) .layer(request_id_layer) .layer(propagate_request_id_layer) From 54bbe2219ad6abf41dbaa03d208867041daea29d Mon Sep 17 00:00:00 2001 From: Cherrypick14 Date: Fri, 1 Aug 2025 21:48:53 +0300 Subject: [PATCH 2/8] feat(http): Add RejectReportRequest and RejectReportResponse structures with validation functions. --- src/http/report/domain.rs | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/http/report/domain.rs diff --git a/src/http/report/domain.rs b/src/http/report/domain.rs new file mode 100644 index 00000000..b3d681dd --- /dev/null +++ b/src/http/report/domain.rs @@ -0,0 +1,70 @@ +use garde::Validate; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize, Deserialize, Validate)] +pub struct RejectReportRequest { + #[garde(skip)] + pub report_id: Uuid, + #[garde(custom(validate_rejection_reason))] + pub reason: String, + #[garde(ascii, length(max = 1000))] + pub validator_notes: Option, + #[garde(custom(validate_starknet_address))] + pub validated_by: String, +} + +#[derive(Debug, Serialize)] +pub struct RejectReportResponse { + pub message: String, + pub report_id: Uuid, + pub status: String, + pub reason: String, + pub validator_notes: Option, + pub validated_by: String, + pub rejected_at: chrono::DateTime, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct Report { + pub id: Uuid, + pub title: String, + pub project_id: Uuid, + pub body: String, + pub reported_by: String, + pub validated_by: Option, + pub status: String, // Cast from enum to string in query + pub severity: Option, // Cast from enum to string in query + pub allocated_reward: Option, // Use BigDecimal for numeric fields + pub reason: Option, // Cast from enum to string in query + pub validator_notes: Option, + pub researcher_response: Option, + pub created_at: chrono::DateTime, + pub updated_at: Option>, +} + +pub fn validate_rejection_reason(reason: &str, _context: &()) -> garde::Result { + let valid_reasons = [ + "duplicate_report", + "incomplete_information", + "already_known", + "out_of_scope" + ]; + + if valid_reasons.contains(&reason) { + Ok(()) + } else { + Err(garde::Error::new("Invalid rejection reason")) + } +} + +pub fn validate_starknet_address(address: &str, _context: &()) -> garde::Result { + if address.starts_with("0x") + && address.len() == 66 + && address.chars().skip(2).all(|c| c.is_ascii_hexdigit()) + { + Ok(()) + } else { + Err(garde::Error::new("Invalid Starknet address")) + } +} \ No newline at end of file From 3bdcca741264320cec89d6f547f08951020c19e6 Mon Sep 17 00:00:00 2001 From: Cherrypick14 Date: Fri, 1 Aug 2025 21:49:12 +0300 Subject: [PATCH 3/8] feat(http): Introduce report module with reject report endpoint --- src/http/report/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/http/report/mod.rs diff --git a/src/http/report/mod.rs b/src/http/report/mod.rs new file mode 100644 index 00000000..ea9abe49 --- /dev/null +++ b/src/http/report/mod.rs @@ -0,0 +1,14 @@ +mod domain; +mod reject_report; + +use axum::{Router, routing::post}; +pub use domain::*; + +use crate::AppState; + +pub(crate) fn router() -> Router { + Router::new().route( + "/report/reject", + post(reject_report::reject_report), + ) +} \ No newline at end of file From ed164706405bfef751d0373dbdef4a8433069e1a Mon Sep 17 00:00:00 2001 From: Cherrypick14 Date: Fri, 1 Aug 2025 21:49:27 +0300 Subject: [PATCH 4/8] feat(http): Implement reject report functionality with validation and database updates --- src/http/report/reject_report.rs | 153 +++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/http/report/reject_report.rs diff --git a/src/http/report/reject_report.rs b/src/http/report/reject_report.rs new file mode 100644 index 00000000..35b48401 --- /dev/null +++ b/src/http/report/reject_report.rs @@ -0,0 +1,153 @@ +use crate::{ + AppState, Error, Result, + http::report::{RejectReportRequest, RejectReportResponse, Report}, +}; +use axum::{Json, extract::State, http::StatusCode}; +use garde::Validate; +use sqlx::PgPool; +use uuid::Uuid; + +#[tracing::instrument(name = "Reject Report", skip(state, request))] +pub async fn reject_report( + State(state): State, + Json(request): Json, +) -> Result<(StatusCode, Json)> { + request.validate()?; + + tracing::info!( + report_id = %request.report_id, + validated_by = %request.validated_by, + "Attempting to reject report" + ); + + // First, verify the report exists and is in a valid state for rejection + let report = get_report_by_id(&state.db.pool, &request.report_id).await?; + + if report.is_none() { + tracing::warn!( + report_id = %request.report_id, + "Report not found" + ); + return Err(Error::NotFound); + } + + let report = report.unwrap(); + + // Check if the report is already rejected or in a final state + if report.status == "rejected" || report.status == "closed" { + tracing::warn!( + report_id = %request.report_id, + status = %report.status, + "Cannot reject report that is already in final state" + ); + return Err(Error::Conflict); + } + + // Verify the validator is authorized to reject this report + if let Some(assigned_validator) = &report.validated_by { + if assigned_validator != &request.validated_by { + tracing::warn!( + report_id = %request.report_id, + assigned_validator = %assigned_validator, + request_validator = %request.validated_by, + "Validator not authorized to reject this report" + ); + return Err(Error::Forbidden); + } + } + // If no validator is assigned, any validator can reject (based on business logic) + + // Perform the rejection + let rejection_time = chrono::Utc::now(); + reject_report_in_db( + &state.db.pool, + &request.report_id, + &request.reason, + &request.validator_notes, + &request.validated_by, + &rejection_time, + ) + .await?; + + tracing::info!( + report_id = %request.report_id, + validated_by = %request.validated_by, + "Successfully rejected report" + ); + + Ok(( + StatusCode::OK, + Json(RejectReportResponse { + message: "Report successfully rejected".to_string(), + report_id: request.report_id, + status: "rejected".to_string(), + reason: request.reason, + validator_notes: request.validator_notes, + validated_by: request.validated_by, + rejected_at: rejection_time, + }), + )) +} + +async fn get_report_by_id( + pool: &PgPool, + report_id: &Uuid, +) -> Result> { + let report = sqlx::query_as::<_, Report>( + r#" + SELECT + id, title, project_id, body, reported_by, validated_by, + status::text as status, + severity::text as severity, + allocated_reward, + reason::text as reason, + validator_notes, + researcher_response, + created_at, + updated_at + FROM research_report + WHERE id = $1 + "#, + ) + .bind(report_id) + .fetch_optional(pool) + .await?; + + Ok(report) +} + +async fn reject_report_in_db( + pool: &PgPool, + report_id: &Uuid, + reason: &str, + validator_notes: &Option, + validated_by: &str, + rejection_time: &chrono::DateTime, +) -> Result<()> { + let result = sqlx::query( + r#" + UPDATE research_report + SET + status = 'rejected'::report_status_type, + reason = $2::rejection_reason, + validator_notes = $3, + validated_by = $4, + updated_at = $5 + WHERE id = $1 + "#, + ) + .bind(report_id) + .bind(reason) + .bind(validator_notes) + .bind(validated_by) + .bind(rejection_time) + .execute(pool) + .await?; + + // Check if any rows were affected (report exists and was updated) + if result.rows_affected() == 0 { + return Err(Error::NotFound); + } + + Ok(()) +} \ No newline at end of file From a5e643dae132300dec241e636c8d4079b9138a32 Mon Sep 17 00:00:00 2001 From: Cherrypick14 Date: Fri, 1 Aug 2025 21:49:39 +0300 Subject: [PATCH 5/8] feat(api): Add report module to main test suite --- tests/api/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/api/main.rs b/tests/api/main.rs index 86da4f78..75777c7a 100644 --- a/tests/api/main.rs +++ b/tests/api/main.rs @@ -5,6 +5,7 @@ mod health_check; mod helpers; mod newsletter; mod projects; +mod report; mod support_tickets; mod transaction; mod validator; From f12698cc698fd0bf6e20f6c81b5e1e1ca50b602b Mon Sep 17 00:00:00 2001 From: Cherrypick14 Date: Fri, 1 Aug 2025 21:50:04 +0300 Subject: [PATCH 6/8] test(api): Add comprehensive tests for report rejection functionality --- tests/api/report.rs | 456 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 tests/api/report.rs diff --git a/tests/api/report.rs b/tests/api/report.rs new file mode 100644 index 00000000..b7f3c20f --- /dev/null +++ b/tests/api/report.rs @@ -0,0 +1,456 @@ +use crate::helpers::{TestApp, generate_address}; +use axum::{body::Body, extract::Request, http::StatusCode}; +use serde_json::json; +use uuid::Uuid; + +#[tokio::test] +async fn test_reject_report_success() { + let app = TestApp::new().await; + let db = &app.db; + + // Create a test project first (using correct schema fields) + let project_id = Uuid::now_v7(); + let project_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO projects ( + id, name, description, contract_address, owner_address, contact_info, created_at + ) VALUES ( + $1, 'Test Project', 'A test project for reports', $2, $2, 'test@example.com', now() + ) + "#, + ) + .bind(project_id) + .bind(&project_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert test project"); + + // Create a test report + let report_id = Uuid::now_v7(); + let researcher_wallet = generate_address(); + let validator_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO research_report ( + id, title, project_id, body, reported_by, status, created_at + ) VALUES ( + $1, 'Test Report', $2, 'This is a test report body with sufficient content to meet the minimum requirement of 50 characters.', $3, 'submitted', now() + ) + "#, + ) + .bind(report_id) + .bind(project_id) + .bind(&researcher_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert test report"); + + // Prepare rejection request + let payload = json!({ + "report_id": report_id, + "reason": "incomplete_information", + "validator_notes": "The report lacks sufficient technical details to assess the vulnerability.", + "validated_by": validator_wallet + }); + + let req = Request::builder() + .method("POST") + .uri("/report/reject") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(); + + let res = app.request(req).await; + let status = res.status(); + + assert_eq!(status, StatusCode::OK); + + // Verify the report has been rejected + let report_status = sqlx::query_scalar::<_, String>( + "SELECT status::text FROM research_report WHERE id = $1", + ) + .bind(report_id) + .fetch_one(&db.pool) + .await + .expect("Failed to check report status"); + + assert_eq!(report_status, "rejected"); + + // Verify the rejection details + let report = sqlx::query!( + r#" + SELECT reason::text as reason, validator_notes, validated_by + FROM research_report + WHERE id = $1 + "#, + report_id + ) + .fetch_one(&db.pool) + .await + .expect("Failed to fetch rejected report"); + + assert_eq!(report.reason, Some("incomplete_information".to_string())); + assert_eq!(report.validator_notes, Some("The report lacks sufficient technical details to assess the vulnerability.".to_string())); + assert_eq!(report.validated_by, Some(validator_wallet)); +} + +#[tokio::test] +async fn test_reject_report_not_found() { + let app = TestApp::new().await; + let non_existent_report_id = Uuid::now_v7(); + let validator_wallet = generate_address(); + + let payload = json!({ + "report_id": non_existent_report_id, + "reason": "duplicate_report", + "validator_notes": "This report duplicates an existing finding.", + "validated_by": validator_wallet + }); + + let req = Request::builder() + .method("POST") + .uri("/report/reject") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(); + + let res = app.request(req).await; + let status = res.status(); + + assert_eq!(status, StatusCode::NOT_FOUND); +} + +#[tokio::test] +async fn test_reject_report_already_rejected() { + let app = TestApp::new().await; + let db = &app.db; + + // Create a test project (using correct schema fields) + let project_id = Uuid::now_v7(); + let project_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO projects ( + id, name, description, contract_address, owner_address, contact_info, created_at + ) VALUES ( + $1, 'Test Project', 'A test project for reports', $2, $2, 'test@example.com', now() + ) + "#, + ) + .bind(project_id) + .bind(&project_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert test project"); + + // Create an already rejected report + let report_id = Uuid::now_v7(); + let researcher_wallet = generate_address(); + let validator_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO research_report ( + id, title, project_id, body, reported_by, status, reason, + validator_notes, validated_by, created_at + ) VALUES ( + $1, 'Already Rejected Report', $2, 'This report was already rejected. It contains sufficient content to meet the minimum requirement of 50 characters for the body field.', $3, 'rejected', 'duplicate_report', 'Already rejected', $4, now() + ) + "#, + ) + .bind(report_id) + .bind(project_id) + .bind(&researcher_wallet) + .bind(&validator_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert already rejected report"); + + // Try to reject it again + let payload = json!({ + "report_id": report_id, + "reason": "out_of_scope", + "validator_notes": "Trying to reject again.", + "validated_by": validator_wallet + }); + + let req = Request::builder() + .method("POST") + .uri("/report/reject") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(); + + let res = app.request(req).await; + let status = res.status(); + + assert_eq!(status, StatusCode::CONFLICT); +} + +#[tokio::test] +async fn test_reject_report_unauthorized_validator() { + let app = TestApp::new().await; + let db = &app.db; + + // Create a test project (using correct schema fields) + let project_id = Uuid::now_v7(); + let project_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO projects ( + id, name, description, contract_address, owner_address, contact_info, created_at + ) VALUES ( + $1, 'Test Project', 'A test project for reports', $2, $2, 'test@example.com', now() + ) + "#, + ) + .bind(project_id) + .bind(&project_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert test project"); + + // Create a report assigned to a specific validator + let report_id = Uuid::now_v7(); + let researcher_wallet = generate_address(); + let assigned_validator = generate_address(); + let unauthorized_validator = generate_address(); + + sqlx::query( + r#" + INSERT INTO research_report ( + id, title, project_id, body, reported_by, status, validated_by, created_at + ) VALUES ( + $1, 'Assigned Report', $2, 'This report is assigned to a specific validator. It contains sufficient content to meet the minimum requirement of 50 characters for the body field.', $3, 'assigned', $4, now() + ) + "#, + ) + .bind(report_id) + .bind(project_id) + .bind(&researcher_wallet) + .bind(&assigned_validator) + .execute(&db.pool) + .await + .expect("Failed to insert assigned report"); + + // Try to reject with unauthorized validator + let payload = json!({ + "report_id": report_id, + "reason": "already_known", + "validator_notes": "Unauthorized rejection attempt.", + "validated_by": unauthorized_validator + }); + + let req = Request::builder() + .method("POST") + .uri("/report/reject") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(); + + let res = app.request(req).await; + let status = res.status(); + + assert_eq!(status, StatusCode::FORBIDDEN); +} + +#[tokio::test] +async fn test_reject_report_invalid_reason() { + let app = TestApp::new().await; + let db = &app.db; + + // Create a test project (using correct schema fields) + let project_id = Uuid::now_v7(); + let project_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO projects ( + id, name, description, contract_address, owner_address, contact_info, created_at + ) VALUES ( + $1, 'Test Project', 'A test project for reports', $2, $2, 'test@example.com', now() + ) + "#, + ) + .bind(project_id) + .bind(&project_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert test project"); + + // Create a test report + let report_id = Uuid::now_v7(); + let researcher_wallet = generate_address(); + let validator_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO research_report ( + id, title, project_id, body, reported_by, status, created_at + ) VALUES ( + $1, 'Test Report', $2, 'This is a test report body with sufficient content.', $3, 'submitted', now() + ) + "#, + ) + .bind(report_id) + .bind(project_id) + .bind(&researcher_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert test report"); + + // Try to reject with invalid reason + let payload = json!({ + "report_id": report_id, + "reason": "invalid_reason", + "validator_notes": "Invalid reason test.", + "validated_by": validator_wallet + }); + + let req = Request::builder() + .method("POST") + .uri("/report/reject") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(); + + let res = app.request(req).await; + let status = res.status(); + + assert_eq!(status, StatusCode::BAD_REQUEST); +} + +#[tokio::test] +async fn test_reject_report_invalid_validator_address() { + let app = TestApp::new().await; + let report_id = Uuid::now_v7(); + + let payload = json!({ + "report_id": report_id, + "reason": "duplicate_report", + "validator_notes": "Test with invalid address.", + "validated_by": "invalid_address" + }); + + let req = Request::builder() + .method("POST") + .uri("/report/reject") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(); + + let res = app.request(req).await; + let status = res.status(); + + assert_eq!(status, StatusCode::BAD_REQUEST); +} + +#[tokio::test] +async fn test_reject_report_missing_fields() { + let app = TestApp::new().await; + + let payload = json!({ + // Missing required fields + "validator_notes": "Test missing fields." + }); + + let req = Request::builder() + .method("POST") + .uri("/report/reject") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(); + + let res = app.request(req).await; + let status = res.status(); + + assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY); +} + +#[tokio::test] +async fn test_reject_report_without_notes() { + let app = TestApp::new().await; + let db = &app.db; + + // Create a test project (using correct schema fields) + let project_id = Uuid::now_v7(); + let project_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO projects ( + id, name, description, contract_address, owner_address, contact_info, created_at + ) VALUES ( + $1, 'Test Project', 'A test project for reports', $2, $2, 'test@example.com', now() + ) + "#, + ) + .bind(project_id) + .bind(&project_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert test project"); + + // Create a test report + let report_id = Uuid::now_v7(); + let researcher_wallet = generate_address(); + let validator_wallet = generate_address(); + + sqlx::query( + r#" + INSERT INTO research_report ( + id, title, project_id, body, reported_by, status, created_at + ) VALUES ( + $1, 'Test Report', $2, 'This is a test report body with sufficient content.', $3, 'submitted', now() + ) + "#, + ) + .bind(report_id) + .bind(project_id) + .bind(&researcher_wallet) + .execute(&db.pool) + .await + .expect("Failed to insert test report"); + + // Reject without validator notes + let payload = json!({ + "report_id": report_id, + "reason": "out_of_scope", + "validated_by": validator_wallet + }); + + let req = Request::builder() + .method("POST") + .uri("/report/reject") + .header("content-type", "application/json") + .body(Body::from(payload.to_string())) + .unwrap(); + + let res = app.request(req).await; + let status = res.status(); + + assert_eq!(status, StatusCode::OK); + + // Verify the report was rejected without notes + let report = sqlx::query!( + r#" + SELECT status::text as status, reason::text as reason, validator_notes, validated_by + FROM research_report + WHERE id = $1 + "#, + report_id + ) + .fetch_one(&db.pool) + .await + .expect("Failed to fetch rejected report"); + + assert_eq!(report.status, Some("rejected".to_string())); + assert_eq!(report.reason, Some("out_of_scope".to_string())); + assert_eq!(report.validator_notes, None); + assert_eq!(report.validated_by, Some(validator_wallet)); +} \ No newline at end of file From f2dd36d9723d546b12f89f9447520092917f9ca9 Mon Sep 17 00:00:00 2001 From: Cherrypick14 Date: Sun, 17 Aug 2025 22:17:28 +0300 Subject: [PATCH 7/8] Fix clippy warnings and code formatting - Add allow(dead_code) attribute to Report struct fields that are part of database schema - Run cargo fmt to ensure consistent code formatting - All tests still passing (77/77) --- src/http/report/domain.rs | 13 +++++++------ src/http/report/mod.rs | 7 ++----- src/http/report/reject_report.rs | 7 ++----- tests/api/report.rs | 33 ++++++++++++++++++-------------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/http/report/domain.rs b/src/http/report/domain.rs index b3d681dd..7db4226d 100644 --- a/src/http/report/domain.rs +++ b/src/http/report/domain.rs @@ -26,6 +26,7 @@ pub struct RejectReportResponse { } #[derive(Debug, sqlx::FromRow)] +#[allow(dead_code)] // Fields are part of database schema and will be used in future functionality pub struct Report { pub id: Uuid, pub title: String, @@ -33,10 +34,10 @@ pub struct Report { pub body: String, pub reported_by: String, pub validated_by: Option, - pub status: String, // Cast from enum to string in query + pub status: String, // Cast from enum to string in query pub severity: Option, // Cast from enum to string in query pub allocated_reward: Option, // Use BigDecimal for numeric fields - pub reason: Option, // Cast from enum to string in query + pub reason: Option, // Cast from enum to string in query pub validator_notes: Option, pub researcher_response: Option, pub created_at: chrono::DateTime, @@ -46,11 +47,11 @@ pub struct Report { pub fn validate_rejection_reason(reason: &str, _context: &()) -> garde::Result { let valid_reasons = [ "duplicate_report", - "incomplete_information", + "incomplete_information", "already_known", - "out_of_scope" + "out_of_scope", ]; - + if valid_reasons.contains(&reason) { Ok(()) } else { @@ -67,4 +68,4 @@ pub fn validate_starknet_address(address: &str, _context: &()) -> garde::Result } else { Err(garde::Error::new("Invalid Starknet address")) } -} \ No newline at end of file +} diff --git a/src/http/report/mod.rs b/src/http/report/mod.rs index ea9abe49..321728c3 100644 --- a/src/http/report/mod.rs +++ b/src/http/report/mod.rs @@ -7,8 +7,5 @@ pub use domain::*; use crate::AppState; pub(crate) fn router() -> Router { - Router::new().route( - "/report/reject", - post(reject_report::reject_report), - ) -} \ No newline at end of file + Router::new().route("/report/reject", post(reject_report::reject_report)) +} diff --git a/src/http/report/reject_report.rs b/src/http/report/reject_report.rs index 35b48401..2019a7d9 100644 --- a/src/http/report/reject_report.rs +++ b/src/http/report/reject_report.rs @@ -89,10 +89,7 @@ pub async fn reject_report( )) } -async fn get_report_by_id( - pool: &PgPool, - report_id: &Uuid, -) -> Result> { +async fn get_report_by_id(pool: &PgPool, report_id: &Uuid) -> Result> { let report = sqlx::query_as::<_, Report>( r#" SELECT @@ -150,4 +147,4 @@ async fn reject_report_in_db( } Ok(()) -} \ No newline at end of file +} diff --git a/tests/api/report.rs b/tests/api/report.rs index b7f3c20f..487847c7 100644 --- a/tests/api/report.rs +++ b/tests/api/report.rs @@ -11,7 +11,7 @@ async fn test_reject_report_success() { // Create a test project first (using correct schema fields) let project_id = Uuid::now_v7(); let project_wallet = generate_address(); - + sqlx::query( r#" INSERT INTO projects ( @@ -69,13 +69,12 @@ async fn test_reject_report_success() { assert_eq!(status, StatusCode::OK); // Verify the report has been rejected - let report_status = sqlx::query_scalar::<_, String>( - "SELECT status::text FROM research_report WHERE id = $1", - ) - .bind(report_id) - .fetch_one(&db.pool) - .await - .expect("Failed to check report status"); + let report_status = + sqlx::query_scalar::<_, String>("SELECT status::text FROM research_report WHERE id = $1") + .bind(report_id) + .fetch_one(&db.pool) + .await + .expect("Failed to check report status"); assert_eq!(report_status, "rejected"); @@ -93,7 +92,13 @@ async fn test_reject_report_success() { .expect("Failed to fetch rejected report"); assert_eq!(report.reason, Some("incomplete_information".to_string())); - assert_eq!(report.validator_notes, Some("The report lacks sufficient technical details to assess the vulnerability.".to_string())); + assert_eq!( + report.validator_notes, + Some( + "The report lacks sufficient technical details to assess the vulnerability." + .to_string() + ) + ); assert_eq!(report.validated_by, Some(validator_wallet)); } @@ -131,7 +136,7 @@ async fn test_reject_report_already_rejected() { // Create a test project (using correct schema fields) let project_id = Uuid::now_v7(); let project_wallet = generate_address(); - + sqlx::query( r#" INSERT INTO projects ( @@ -199,7 +204,7 @@ async fn test_reject_report_unauthorized_validator() { // Create a test project (using correct schema fields) let project_id = Uuid::now_v7(); let project_wallet = generate_address(); - + sqlx::query( r#" INSERT INTO projects ( @@ -267,7 +272,7 @@ async fn test_reject_report_invalid_reason() { // Create a test project (using correct schema fields) let project_id = Uuid::now_v7(); let project_wallet = generate_address(); - + sqlx::query( r#" INSERT INTO projects ( @@ -380,7 +385,7 @@ async fn test_reject_report_without_notes() { // Create a test project (using correct schema fields) let project_id = Uuid::now_v7(); let project_wallet = generate_address(); - + sqlx::query( r#" INSERT INTO projects ( @@ -453,4 +458,4 @@ async fn test_reject_report_without_notes() { assert_eq!(report.reason, Some("out_of_scope".to_string())); assert_eq!(report.validator_notes, None); assert_eq!(report.validated_by, Some(validator_wallet)); -} \ No newline at end of file +} From 52b41428fcc83c827ae0ebc78ef1adb5e6f76531 Mon Sep 17 00:00:00 2001 From: Cherrypick14 Date: Mon, 18 Aug 2025 12:31:02 +0300 Subject: [PATCH 8/8] fix: Update SQLx query cache to include test queries - Regenerate .sqlx/ metadata with test queries included - This resolves all SQLx offline compilation errors - IDE red lines should now be completely resolved --- ...ecf6cbe356950d8ee7d042b6f32a3f8d19ed3.json | 15 ++++ ...1d656796e4d72978756c3794a80a193daaf42.json | 14 ++++ ...bd6bccd4edb58f94e2869830a62b0a17ef95d.json | 15 ++++ ...84739bdb30b175048dd6791bc0c225abbf809.json | 15 ++++ ...57c635403eaf7209be35f62f04ef486f1a26a.json | 69 +++++++++++++++++++ ...8dbff54eb70f7eae9fa898b28b99253956610.json | 33 +++++++++ ...b821a6f15140381e35f94734f28d89dd94329.json | 19 +++++ ...dbc45a014a58e205e7e63b424bb4da4adb470.json | 14 ++++ ...ebf928f26b7a6763c7a09d379f3f125bec29f.json | 22 ++++++ ...29280502796be032f3b795931d4499c7b0423.json | 41 +++++++++++ ...f4f509ef347b5de3fd1540594439de2a22807.json | 40 +++++++++++ ...5b4e60efb7f99fe9471fe650ca06b0d03a618.json | 34 +++++++++ ...1dbcc34b4a8f88c11c5342b96ac24bf36dbd5.json | 22 ++++++ ...944a55d7ab955f3e280d2be09a5261da91d7d.json | 15 ++++ 14 files changed, 368 insertions(+) create mode 100644 .sqlx/query-065775eed418be50a65681072d9ecf6cbe356950d8ee7d042b6f32a3f8d19ed3.json create mode 100644 .sqlx/query-11aa9c9cfe5c77fcb19baf0c6d41d656796e4d72978756c3794a80a193daaf42.json create mode 100644 .sqlx/query-1f870e16873ababc69e3b2e5e4bbd6bccd4edb58f94e2869830a62b0a17ef95d.json create mode 100644 .sqlx/query-48f35d90b9ecd0e1de90d52902684739bdb30b175048dd6791bc0c225abbf809.json create mode 100644 .sqlx/query-4d9c26ba7a741292b958a39414857c635403eaf7209be35f62f04ef486f1a26a.json create mode 100644 .sqlx/query-5f2ff0456eb826c144c6ac177f58dbff54eb70f7eae9fa898b28b99253956610.json create mode 100644 .sqlx/query-623317aa926d53805d2fa57a917b821a6f15140381e35f94734f28d89dd94329.json create mode 100644 .sqlx/query-b6c9a65110b90a4b0caf2a29e4adbc45a014a58e205e7e63b424bb4da4adb470.json create mode 100644 .sqlx/query-cfbcf2a97493a43be69a43da254ebf928f26b7a6763c7a09d379f3f125bec29f.json create mode 100644 .sqlx/query-d2a025435b3487d2891896c80d129280502796be032f3b795931d4499c7b0423.json create mode 100644 .sqlx/query-d928006be2e4b33668b606cfb4cf4f509ef347b5de3fd1540594439de2a22807.json create mode 100644 .sqlx/query-dd220f05c4526998c187238cd905b4e60efb7f99fe9471fe650ca06b0d03a618.json create mode 100644 .sqlx/query-e5beae84945488d05739bbde3b81dbcc34b4a8f88c11c5342b96ac24bf36dbd5.json create mode 100644 .sqlx/query-f90de51df9f3e40be8dc965bc68944a55d7ab955f3e280d2be09a5261da91d7d.json diff --git a/.sqlx/query-065775eed418be50a65681072d9ecf6cbe356950d8ee7d042b6f32a3f8d19ed3.json b/.sqlx/query-065775eed418be50a65681072d9ecf6cbe356950d8ee7d042b6f32a3f8d19ed3.json new file mode 100644 index 00000000..5ac588c8 --- /dev/null +++ b/.sqlx/query-065775eed418be50a65681072d9ecf6cbe356950d8ee7d042b6f32a3f8d19ed3.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO escrow_users (wallet_address, balance) VALUES ($1, $2)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Numeric" + ] + }, + "nullable": [] + }, + "hash": "065775eed418be50a65681072d9ecf6cbe356950d8ee7d042b6f32a3f8d19ed3" +} diff --git a/.sqlx/query-11aa9c9cfe5c77fcb19baf0c6d41d656796e4d72978756c3794a80a193daaf42.json b/.sqlx/query-11aa9c9cfe5c77fcb19baf0c6d41d656796e4d72978756c3794a80a193daaf42.json new file mode 100644 index 00000000..0e60e861 --- /dev/null +++ b/.sqlx/query-11aa9c9cfe5c77fcb19baf0c6d41d656796e4d72978756c3794a80a193daaf42.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO escrow_users (wallet_address, balance) VALUES ($1, 0)", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "11aa9c9cfe5c77fcb19baf0c6d41d656796e4d72978756c3794a80a193daaf42" +} diff --git a/.sqlx/query-1f870e16873ababc69e3b2e5e4bbd6bccd4edb58f94e2869830a62b0a17ef95d.json b/.sqlx/query-1f870e16873ababc69e3b2e5e4bbd6bccd4edb58f94e2869830a62b0a17ef95d.json new file mode 100644 index 00000000..e6acf3cb --- /dev/null +++ b/.sqlx/query-1f870e16873ababc69e3b2e5e4bbd6bccd4edb58f94e2869830a62b0a17ef95d.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE projects\n SET bounty_amount = bounty_amount - $1\n WHERE id = $2\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Numeric", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "1f870e16873ababc69e3b2e5e4bbd6bccd4edb58f94e2869830a62b0a17ef95d" +} diff --git a/.sqlx/query-48f35d90b9ecd0e1de90d52902684739bdb30b175048dd6791bc0c225abbf809.json b/.sqlx/query-48f35d90b9ecd0e1de90d52902684739bdb30b175048dd6791bc0c225abbf809.json new file mode 100644 index 00000000..02bacc2e --- /dev/null +++ b/.sqlx/query-48f35d90b9ecd0e1de90d52902684739bdb30b175048dd6791bc0c225abbf809.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO escrow_users (wallet_address, balance)\n VALUES ($1, $2)\n ON CONFLICT (wallet_address) DO UPDATE\n SET balance = EXCLUDED.balance\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Numeric" + ] + }, + "nullable": [] + }, + "hash": "48f35d90b9ecd0e1de90d52902684739bdb30b175048dd6791bc0c225abbf809" +} diff --git a/.sqlx/query-4d9c26ba7a741292b958a39414857c635403eaf7209be35f62f04ef486f1a26a.json b/.sqlx/query-4d9c26ba7a741292b958a39414857c635403eaf7209be35f62f04ef486f1a26a.json new file mode 100644 index 00000000..5d406952 --- /dev/null +++ b/.sqlx/query-4d9c26ba7a741292b958a39414857c635403eaf7209be35f62f04ef486f1a26a.json @@ -0,0 +1,69 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT id, email, name, status as \"status!: SubscriberStatus\", subscribed_at, created_at, updated_at FROM newsletter_subscribers", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + }, + { + "ordinal": 1, + "name": "email", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "name", + "type_info": "Varchar" + }, + { + "ordinal": 3, + "name": "status!: SubscriberStatus", + "type_info": { + "Custom": { + "name": "subscriber_status", + "kind": { + "Enum": [ + "pending", + "active", + "unsubscribed", + "bounced", + "spam_complaint" + ] + } + } + } + }, + { + "ordinal": 4, + "name": "subscribed_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 5, + "name": "created_at", + "type_info": "Timestamptz" + }, + { + "ordinal": 6, + "name": "updated_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false, + false, + false, + false, + true, + false, + true + ] + }, + "hash": "4d9c26ba7a741292b958a39414857c635403eaf7209be35f62f04ef486f1a26a" +} diff --git a/.sqlx/query-5f2ff0456eb826c144c6ac177f58dbff54eb70f7eae9fa898b28b99253956610.json b/.sqlx/query-5f2ff0456eb826c144c6ac177f58dbff54eb70f7eae9fa898b28b99253956610.json new file mode 100644 index 00000000..428ab6d0 --- /dev/null +++ b/.sqlx/query-5f2ff0456eb826c144c6ac177f58dbff54eb70f7eae9fa898b28b99253956610.json @@ -0,0 +1,33 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO projects (\n owner_address, contract_address, name, description, contact_info,\n supporting_document_path, project_logo_path, repository_url,\n bounty_amount, bounty_currency, bounty_expiry_date, closed_at\n ) VALUES (\n $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12\n ) RETURNING id\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Uuid" + } + ], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Text", + "Varchar", + "Text", + "Text", + "Text", + "Numeric", + "Varchar", + "Timestamptz", + "Timestamptz" + ] + }, + "nullable": [ + false + ] + }, + "hash": "5f2ff0456eb826c144c6ac177f58dbff54eb70f7eae9fa898b28b99253956610" +} diff --git a/.sqlx/query-623317aa926d53805d2fa57a917b821a6f15140381e35f94734f28d89dd94329.json b/.sqlx/query-623317aa926d53805d2fa57a917b821a6f15140381e35f94734f28d89dd94329.json new file mode 100644 index 00000000..a40543aa --- /dev/null +++ b/.sqlx/query-623317aa926d53805d2fa57a917b821a6f15140381e35f94734f28d89dd94329.json @@ -0,0 +1,19 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO escrow_transactions (\n wallet_address, project_id, type, amount, currency, status, notes, transaction_hash\n ) VALUES ($1, $2, 'bounty_disbursement', $3, $4, 'completed', $5, $6)\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Uuid", + "Numeric", + "Varchar", + "Text", + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "623317aa926d53805d2fa57a917b821a6f15140381e35f94734f28d89dd94329" +} diff --git a/.sqlx/query-b6c9a65110b90a4b0caf2a29e4adbc45a014a58e205e7e63b424bb4da4adb470.json b/.sqlx/query-b6c9a65110b90a4b0caf2a29e4adbc45a014a58e205e7e63b424bb4da4adb470.json new file mode 100644 index 00000000..af241db6 --- /dev/null +++ b/.sqlx/query-b6c9a65110b90a4b0caf2a29e4adbc45a014a58e205e7e63b424bb4da4adb470.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO escrow_users (wallet_address) VALUES ($1) ON CONFLICT (wallet_address) DO NOTHING", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar" + ] + }, + "nullable": [] + }, + "hash": "b6c9a65110b90a4b0caf2a29e4adbc45a014a58e205e7e63b424bb4da4adb470" +} diff --git a/.sqlx/query-cfbcf2a97493a43be69a43da254ebf928f26b7a6763c7a09d379f3f125bec29f.json b/.sqlx/query-cfbcf2a97493a43be69a43da254ebf928f26b7a6763c7a09d379f3f125bec29f.json new file mode 100644 index 00000000..23edd7fe --- /dev/null +++ b/.sqlx/query-cfbcf2a97493a43be69a43da254ebf928f26b7a6763c7a09d379f3f125bec29f.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT closed_at FROM projects WHERE id = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "closed_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + true + ] + }, + "hash": "cfbcf2a97493a43be69a43da254ebf928f26b7a6763c7a09d379f3f125bec29f" +} diff --git a/.sqlx/query-d2a025435b3487d2891896c80d129280502796be032f3b795931d4499c7b0423.json b/.sqlx/query-d2a025435b3487d2891896c80d129280502796be032f3b795931d4499c7b0423.json new file mode 100644 index 00000000..e7d83179 --- /dev/null +++ b/.sqlx/query-d2a025435b3487d2891896c80d129280502796be032f3b795931d4499c7b0423.json @@ -0,0 +1,41 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT amount, type as \"type!: String\"\n FROM escrow_transactions\n WHERE wallet_address = $1 AND transaction_hash = $2\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "amount", + "type_info": "Numeric" + }, + { + "ordinal": 1, + "name": "type!: String", + "type_info": { + "Custom": { + "name": "transaction_type", + "kind": { + "Enum": [ + "deposit", + "bounty_allocation", + "bounty_disbursement", + "withdrawal" + ] + } + } + } + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false + ] + }, + "hash": "d2a025435b3487d2891896c80d129280502796be032f3b795931d4499c7b0423" +} diff --git a/.sqlx/query-d928006be2e4b33668b606cfb4cf4f509ef347b5de3fd1540594439de2a22807.json b/.sqlx/query-d928006be2e4b33668b606cfb4cf4f509ef347b5de3fd1540594439de2a22807.json new file mode 100644 index 00000000..ddc3e7bc --- /dev/null +++ b/.sqlx/query-d928006be2e4b33668b606cfb4cf4f509ef347b5de3fd1540594439de2a22807.json @@ -0,0 +1,40 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT status::text as status, reason::text as reason, validator_notes, validated_by \n FROM research_report \n WHERE id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "status", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "reason", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "validator_notes", + "type_info": "Text" + }, + { + "ordinal": 3, + "name": "validated_by", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + null, + null, + true, + true + ] + }, + "hash": "d928006be2e4b33668b606cfb4cf4f509ef347b5de3fd1540594439de2a22807" +} diff --git a/.sqlx/query-dd220f05c4526998c187238cd905b4e60efb7f99fe9471fe650ca06b0d03a618.json b/.sqlx/query-dd220f05c4526998c187238cd905b4e60efb7f99fe9471fe650ca06b0d03a618.json new file mode 100644 index 00000000..b44d5059 --- /dev/null +++ b/.sqlx/query-dd220f05c4526998c187238cd905b4e60efb7f99fe9471fe650ca06b0d03a618.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT reason::text as reason, validator_notes, validated_by \n FROM research_report \n WHERE id = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "reason", + "type_info": "Text" + }, + { + "ordinal": 1, + "name": "validator_notes", + "type_info": "Text" + }, + { + "ordinal": 2, + "name": "validated_by", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Uuid" + ] + }, + "nullable": [ + null, + true, + true + ] + }, + "hash": "dd220f05c4526998c187238cd905b4e60efb7f99fe9471fe650ca06b0d03a618" +} diff --git a/.sqlx/query-e5beae84945488d05739bbde3b81dbcc34b4a8f88c11c5342b96ac24bf36dbd5.json b/.sqlx/query-e5beae84945488d05739bbde3b81dbcc34b4a8f88c11c5342b96ac24bf36dbd5.json new file mode 100644 index 00000000..1cdbf166 --- /dev/null +++ b/.sqlx/query-e5beae84945488d05739bbde3b81dbcc34b4a8f88c11c5342b96ac24bf36dbd5.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT balance FROM escrow_users WHERE wallet_address = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "balance", + "type_info": "Numeric" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "e5beae84945488d05739bbde3b81dbcc34b4a8f88c11c5342b96ac24bf36dbd5" +} diff --git a/.sqlx/query-f90de51df9f3e40be8dc965bc68944a55d7ab955f3e280d2be09a5261da91d7d.json b/.sqlx/query-f90de51df9f3e40be8dc965bc68944a55d7ab955f3e280d2be09a5261da91d7d.json new file mode 100644 index 00000000..b570b5e7 --- /dev/null +++ b/.sqlx/query-f90de51df9f3e40be8dc965bc68944a55d7ab955f3e280d2be09a5261da91d7d.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE escrow_users\n SET balance = balance + $1\n WHERE wallet_address = $2\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Numeric", + "Text" + ] + }, + "nullable": [] + }, + "hash": "f90de51df9f3e40be8dc965bc68944a55d7ab955f3e280d2be09a5261da91d7d" +}