Fundamental_Analysis/services/data-persistence-service/tests/api_tests.rs
Lv, Qi 0c975bb8f1 Refactor: Remove legacy analysis results and implement workflow history
- **Common Contracts**: Updated DTOs and models to support workflow history; removed legacy analysis result DTOs.
- **Data Persistence Service**:
    - Removed `analysis_results` table logic and API endpoints.
    - Implemented `workflow_history` API and DB access (`history.rs`).
    - Fixed compilation errors and updated tests.
    - Exposed Postgres port in `docker-compose.yml` for easier debugging/offline checks.
- **API Gateway**:
    - Implemented `history` endpoints (get history list, get by ID).
    - Removed legacy `analysis-results` endpoints.
    - Fixed routing and handler logic in `api.rs`.
- **Report Generator Service**:
    - Removed dependency on legacy `analysis-results` persistence calls.
    - Fixed compilation errors.
- **Workflow Orchestrator**: Fixed warnings and minor logic issues.
- **Providers**: Updated provider services (alphavantage, tushare, finnhub, yfinance, mock) to align with contract changes.
- **Frontend**:
    - Updated `ReportPage` and stores to use new workflow history.
    - Added `RecentReportsDropdown` component.
    - Cleaned up `RealtimeLogs` component.
- **Documentation**: Moved completed design tasks to `completed/` and added refactoring context docs.

Confirmed all services pass `cargo check`.
2025-11-29 14:46:44 +08:00

171 lines
6.0 KiB
Rust

#![allow(unused_imports)]
use axum::{
body::Body,
http::{self, Request, StatusCode},
response::Response,
};
use data_persistence_service::{
self as app,
dtos::{
CompanyProfileDto, DailyMarketDataBatchDto, DailyMarketDataDto,
TimeSeriesFinancialBatchDto, TimeSeriesFinancialDto,
},
AppState,
};
use http_body_util::BodyExt;
use sqlx::PgPool;
use tower::util::ServiceExt; // for `oneshot`
use uuid::Uuid;
// Note: We need to make `build_rest_router` and `AppState` public in lib.rs and main.rs respectively.
// This test structure assumes that has been done.
#[sqlx::test]
async fn test_api_upsert_and_get_company(pool: PgPool) {
let state = AppState::new(pool);
let openapi = app::build_openapi_spec();
let app = app::build_rest_router_with_state(openapi, state).unwrap();
// 1. Act: Upsert a new company
let new_company = CompanyProfileDto {
symbol: "API.TEST".to_string(),
name: "API Test Corp".to_string(),
industry: Some("API Testing".to_string()),
list_date: Some(chrono::NaiveDate::from_ymd_opt(2025, 1, 1).unwrap()),
additional_info: None,
updated_at: None,
};
let request = Request::builder()
.method("PUT")
.uri("/api/v1/companies")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_string(&new_company).unwrap()))
.unwrap();
let response = ServiceExt::oneshot(app.clone().into_service(), request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
// 2. Act: Get the company
let request_get = Request::builder()
.method("GET")
.uri("/api/v1/companies/API.TEST")
.body(Body::empty())
.unwrap();
let response_get = ServiceExt::oneshot(app.clone().into_service(), request_get).await.unwrap();
assert_eq!(response_get.status(), StatusCode::OK);
// 3. Assert: Check the response body
let body = response_get.into_body().collect().await.unwrap().to_bytes();
let fetched_company: CompanyProfileDto = serde_json::from_slice(&body).unwrap();
assert_eq!(fetched_company.symbol, new_company.symbol);
assert_eq!(fetched_company.name, new_company.name);
}
#[sqlx::test]
async fn test_api_batch_insert_and_get_financials(pool: PgPool) {
let state = AppState::new(pool);
let openapi = app::build_openapi_spec();
let app = app::build_rest_router_with_state(openapi, state).unwrap();
// 1. Act: Batch insert financials
let financials = TimeSeriesFinancialBatchDto {
records: vec![
TimeSeriesFinancialDto {
symbol: "API.FIN".to_string(),
metric_name: "revenue".to_string(),
period_date: chrono::NaiveDate::from_ymd_opt(2023, 12, 31).unwrap(),
value: 2000.0,
source: Some("api_test".to_string()),
},
],
};
let request = Request::builder()
.method("POST")
.uri("/api/v1/market-data/financials/batch")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_string(&financials).unwrap()))
.unwrap();
let response = ServiceExt::oneshot(app.clone().into_service(), request).await.unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
// 2. Act: Get the financials
let request_get = Request::builder()
.method("GET")
.uri("/api/v1/market-data/financials/API.FIN?metrics=revenue")
.body(Body::empty())
.unwrap();
let response_get = ServiceExt::oneshot(app.clone().into_service(), request_get).await.unwrap();
assert_eq!(response_get.status(), StatusCode::OK);
// 3. Assert: Check the response body
let body = response_get.into_body().collect().await.unwrap().to_bytes();
let fetched_financials: Vec<TimeSeriesFinancialDto> = serde_json::from_slice(&body).unwrap();
assert_eq!(fetched_financials.len(), 1);
assert_eq!(fetched_financials[0].symbol, "API.FIN");
assert_eq!(fetched_financials[0].metric_name, "revenue");
assert_eq!(fetched_financials[0].value, 2000.0);
}
#[sqlx::test]
async fn test_api_batch_insert_and_get_daily(pool: PgPool) {
let state = AppState::new(pool);
let openapi = app::build_openapi_spec();
let app = app::build_rest_router_with_state(openapi, state).unwrap();
// 1. Act: Batch insert daily data
let daily_data = DailyMarketDataBatchDto {
records: vec![
DailyMarketDataDto {
symbol: "API.DAILY".to_string(),
trade_date: chrono::NaiveDate::from_ymd_opt(2024, 1, 5).unwrap(),
close_price: Some(250.5),
// ... other fields are None
open_price: None,
high_price: None,
low_price: None,
volume: None,
pe: None,
pb: None,
total_mv: None,
},
],
};
let request = Request::builder()
.method("POST")
.uri("/api/v1/market-data/daily/batch")
.header("content-type", "application/json")
.body(Body::from(serde_json::to_string(&daily_data).unwrap()))
.unwrap();
let response = ServiceExt::oneshot(app.clone().into_service(), request).await.unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
// 2. Act: Get the daily data
let request_get = Request::builder()
.method("GET")
.uri("/api/v1/market-data/daily/API.DAILY")
.body(Body::empty())
.unwrap();
let response_get = ServiceExt::oneshot(app.clone().into_service(), request_get).await.unwrap();
assert_eq!(response_get.status(), StatusCode::OK);
// 3. Assert: Check the response body
let body = response_get.into_body().collect().await.unwrap().to_bytes();
let fetched_data: Vec<DailyMarketDataDto> = serde_json::from_slice(&body).unwrap();
assert_eq!(fetched_data.len(), 1);
assert_eq!(fetched_data[0].symbol, "API.DAILY");
assert_eq!(fetched_data[0].close_price, Some(250.5));
}