410 lines
12 KiB
Rust
410 lines
12 KiB
Rust
//! Pack Test Repository
|
|
//!
|
|
//! Database operations for pack test execution tracking.
|
|
|
|
use crate::error::Result;
|
|
use crate::models::{Id, PackLatestTest, PackTestExecution, PackTestResult, PackTestStats};
|
|
use sqlx::{PgPool, Row};
|
|
|
|
/// Repository for pack test operations
|
|
pub struct PackTestRepository {
|
|
pool: PgPool,
|
|
}
|
|
|
|
impl PackTestRepository {
|
|
/// Create a new pack test repository
|
|
pub fn new(pool: PgPool) -> Self {
|
|
Self { pool }
|
|
}
|
|
|
|
/// Create a new pack test execution record
|
|
pub async fn create(
|
|
&self,
|
|
pack_id: Id,
|
|
pack_version: &str,
|
|
trigger_reason: &str,
|
|
result: &PackTestResult,
|
|
) -> Result<PackTestExecution> {
|
|
let result_json = serde_json::to_value(result)?;
|
|
|
|
let record = sqlx::query_as::<_, PackTestExecution>(
|
|
r#"
|
|
INSERT INTO pack_test_execution (
|
|
pack_id, pack_version, execution_time, trigger_reason,
|
|
total_tests, passed, failed, skipped, pass_rate, duration_ms, result
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
RETURNING *
|
|
"#,
|
|
)
|
|
.bind(pack_id)
|
|
.bind(pack_version)
|
|
.bind(result.execution_time)
|
|
.bind(trigger_reason)
|
|
.bind(result.total_tests)
|
|
.bind(result.passed)
|
|
.bind(result.failed)
|
|
.bind(result.skipped)
|
|
.bind(result.pass_rate)
|
|
.bind(result.duration_ms)
|
|
.bind(result_json)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
Ok(record)
|
|
}
|
|
|
|
/// Find pack test execution by ID
|
|
pub async fn find_by_id(&self, id: Id) -> Result<Option<PackTestExecution>> {
|
|
let record = sqlx::query_as::<_, PackTestExecution>(
|
|
r#"
|
|
SELECT * FROM pack_test_execution
|
|
WHERE id = $1
|
|
"#,
|
|
)
|
|
.bind(id)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
Ok(record)
|
|
}
|
|
|
|
/// List all test executions for a pack
|
|
pub async fn list_by_pack(
|
|
&self,
|
|
pack_id: Id,
|
|
limit: i64,
|
|
offset: i64,
|
|
) -> Result<Vec<PackTestExecution>> {
|
|
let records = sqlx::query_as::<_, PackTestExecution>(
|
|
r#"
|
|
SELECT * FROM pack_test_execution
|
|
WHERE pack_id = $1
|
|
ORDER BY execution_time DESC
|
|
LIMIT $2 OFFSET $3
|
|
"#,
|
|
)
|
|
.bind(pack_id)
|
|
.bind(limit)
|
|
.bind(offset)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
Ok(records)
|
|
}
|
|
|
|
/// Get latest test execution for a pack
|
|
pub async fn get_latest_by_pack(&self, pack_id: Id) -> Result<Option<PackTestExecution>> {
|
|
let record = sqlx::query_as::<_, PackTestExecution>(
|
|
r#"
|
|
SELECT * FROM pack_test_execution
|
|
WHERE pack_id = $1
|
|
ORDER BY execution_time DESC
|
|
LIMIT 1
|
|
"#,
|
|
)
|
|
.bind(pack_id)
|
|
.fetch_optional(&self.pool)
|
|
.await?;
|
|
|
|
Ok(record)
|
|
}
|
|
|
|
/// Get latest test for all packs
|
|
pub async fn get_all_latest(&self) -> Result<Vec<PackLatestTest>> {
|
|
let records = sqlx::query_as::<_, PackLatestTest>(
|
|
r#"
|
|
SELECT * FROM pack_latest_test
|
|
ORDER BY test_time DESC
|
|
"#,
|
|
)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
Ok(records)
|
|
}
|
|
|
|
/// Get test statistics for a pack
|
|
pub async fn get_stats(&self, pack_id: Id) -> Result<PackTestStats> {
|
|
let row = sqlx::query(
|
|
r#"
|
|
SELECT * FROM get_pack_test_stats($1)
|
|
"#,
|
|
)
|
|
.bind(pack_id)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
Ok(PackTestStats {
|
|
total_executions: row.get("total_executions"),
|
|
successful_executions: row.get("successful_executions"),
|
|
failed_executions: row.get("failed_executions"),
|
|
avg_pass_rate: row.get("avg_pass_rate"),
|
|
avg_duration_ms: row.get("avg_duration_ms"),
|
|
last_test_time: row.get("last_test_time"),
|
|
last_test_passed: row.get("last_test_passed"),
|
|
})
|
|
}
|
|
|
|
/// Check if pack has recent passing tests
|
|
pub async fn has_passing_tests(&self, pack_id: Id, hours_ago: i32) -> Result<bool> {
|
|
let row = sqlx::query(
|
|
r#"
|
|
SELECT pack_has_passing_tests($1, $2) as has_passing
|
|
"#,
|
|
)
|
|
.bind(pack_id)
|
|
.bind(hours_ago)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
Ok(row.get("has_passing"))
|
|
}
|
|
|
|
/// Count test executions by pack
|
|
pub async fn count_by_pack(&self, pack_id: Id) -> Result<i64> {
|
|
let row = sqlx::query(
|
|
r#"
|
|
SELECT COUNT(*) as count FROM pack_test_execution
|
|
WHERE pack_id = $1
|
|
"#,
|
|
)
|
|
.bind(pack_id)
|
|
.fetch_one(&self.pool)
|
|
.await?;
|
|
|
|
Ok(row.get("count"))
|
|
}
|
|
|
|
/// List test executions by trigger reason
|
|
pub async fn list_by_trigger_reason(
|
|
&self,
|
|
trigger_reason: &str,
|
|
limit: i64,
|
|
offset: i64,
|
|
) -> Result<Vec<PackTestExecution>> {
|
|
let records = sqlx::query_as::<_, PackTestExecution>(
|
|
r#"
|
|
SELECT * FROM pack_test_execution
|
|
WHERE trigger_reason = $1
|
|
ORDER BY execution_time DESC
|
|
LIMIT $2 OFFSET $3
|
|
"#,
|
|
)
|
|
.bind(trigger_reason)
|
|
.bind(limit)
|
|
.bind(offset)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
Ok(records)
|
|
}
|
|
|
|
/// Get failed test executions for a pack
|
|
pub async fn get_failed_by_pack(
|
|
&self,
|
|
pack_id: Id,
|
|
limit: i64,
|
|
) -> Result<Vec<PackTestExecution>> {
|
|
let records = sqlx::query_as::<_, PackTestExecution>(
|
|
r#"
|
|
SELECT * FROM pack_test_execution
|
|
WHERE pack_id = $1 AND failed > 0
|
|
ORDER BY execution_time DESC
|
|
LIMIT $2
|
|
"#,
|
|
)
|
|
.bind(pack_id)
|
|
.bind(limit)
|
|
.fetch_all(&self.pool)
|
|
.await?;
|
|
|
|
Ok(records)
|
|
}
|
|
|
|
/// Delete old test executions (cleanup)
|
|
pub async fn delete_old_executions(&self, days_old: i32) -> Result<u64> {
|
|
let result = sqlx::query(
|
|
r#"
|
|
DELETE FROM pack_test_execution
|
|
WHERE execution_time < NOW() - ($1 || ' days')::INTERVAL
|
|
"#,
|
|
)
|
|
.bind(days_old)
|
|
.execute(&self.pool)
|
|
.await?;
|
|
|
|
Ok(result.rows_affected())
|
|
}
|
|
}
|
|
|
|
// TODO: Update these tests to use the new repository API (static methods)
|
|
// These tests are currently disabled due to repository refactoring
|
|
#[cfg(test)]
|
|
#[allow(dead_code)]
|
|
mod tests {
|
|
// Disabled - needs update for new repository API
|
|
/*
|
|
async fn setup() -> (PgPool, PackRepository, PackTestRepository) {
|
|
let config = DatabaseConfig::from_env();
|
|
let db = Database::new(&config)
|
|
.await
|
|
.expect("Failed to create database");
|
|
let pool = db.pool().clone();
|
|
let pack_repo = PackRepository::new(pool.clone());
|
|
let test_repo = PackTestRepository::new(pool.clone());
|
|
(pool, pack_repo, test_repo)
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires database
|
|
async fn test_create_test_execution() {
|
|
let (_pool, pack_repo, test_repo) = setup().await;
|
|
|
|
// Create a test pack
|
|
let pack = pack_repo
|
|
.create("test_pack", "Test Pack", "Test pack for testing", "1.0.0")
|
|
.await
|
|
.expect("Failed to create pack");
|
|
|
|
// Create test result
|
|
let test_result = PackTestResult {
|
|
pack_ref: "test_pack".to_string(),
|
|
pack_version: "1.0.0".to_string(),
|
|
execution_time: Utc::now(),
|
|
status: TestStatus::Passed,
|
|
total_tests: 10,
|
|
passed: 8,
|
|
failed: 2,
|
|
skipped: 0,
|
|
pass_rate: 0.8,
|
|
duration_ms: 5000,
|
|
test_suites: vec![TestSuiteResult {
|
|
name: "Test Suite 1".to_string(),
|
|
runner_type: "shell".to_string(),
|
|
total: 10,
|
|
passed: 8,
|
|
failed: 2,
|
|
skipped: 0,
|
|
duration_ms: 5000,
|
|
test_cases: vec![
|
|
TestCaseResult {
|
|
name: "test_1".to_string(),
|
|
status: TestStatus::Passed,
|
|
duration_ms: 500,
|
|
error_message: None,
|
|
stdout: Some("Success".to_string()),
|
|
stderr: None,
|
|
},
|
|
TestCaseResult {
|
|
name: "test_2".to_string(),
|
|
status: TestStatus::Failed,
|
|
duration_ms: 300,
|
|
error_message: Some("Test failed".to_string()),
|
|
stdout: None,
|
|
stderr: Some("Error output".to_string()),
|
|
},
|
|
],
|
|
}],
|
|
};
|
|
|
|
// Create test execution
|
|
let execution = test_repo
|
|
.create(pack.id, "1.0.0", "manual", &test_result)
|
|
.await
|
|
.expect("Failed to create test execution");
|
|
|
|
assert_eq!(execution.pack_id, pack.id);
|
|
assert_eq!(execution.total_tests, 10);
|
|
assert_eq!(execution.passed, 8);
|
|
assert_eq!(execution.failed, 2);
|
|
assert_eq!(execution.pass_rate, 0.8);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires database
|
|
async fn test_get_latest_by_pack() {
|
|
let (_pool, pack_repo, test_repo) = setup().await;
|
|
|
|
// Create a test pack
|
|
let pack = pack_repo
|
|
.create("test_pack_2", "Test Pack 2", "Test pack 2", "1.0.0")
|
|
.await
|
|
.expect("Failed to create pack");
|
|
|
|
// Create multiple test executions
|
|
for i in 1..=3 {
|
|
let test_result = PackTestResult {
|
|
pack_ref: "test_pack_2".to_string(),
|
|
pack_version: "1.0.0".to_string(),
|
|
execution_time: Utc::now(),
|
|
total_tests: i,
|
|
passed: i,
|
|
failed: 0,
|
|
skipped: 0,
|
|
pass_rate: 1.0,
|
|
duration_ms: 1000,
|
|
test_suites: vec![],
|
|
};
|
|
|
|
test_repo
|
|
.create(pack.id, "1.0.0", "manual", &test_result)
|
|
.await
|
|
.expect("Failed to create test execution");
|
|
}
|
|
|
|
// Get latest
|
|
let latest = test_repo
|
|
.get_latest_by_pack(pack.id)
|
|
.await
|
|
.expect("Failed to get latest")
|
|
.expect("No latest found");
|
|
|
|
assert_eq!(latest.total_tests, 3);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires database
|
|
async fn test_get_stats() {
|
|
let (_pool, pack_repo, test_repo) = setup().await;
|
|
|
|
// Create a test pack
|
|
let pack = pack_repo
|
|
.create("test_pack_3", "Test Pack 3", "Test pack 3", "1.0.0")
|
|
.await
|
|
.expect("Failed to create pack");
|
|
|
|
// Create test executions
|
|
for _ in 1..=5 {
|
|
let test_result = PackTestResult {
|
|
pack_ref: "test_pack_3".to_string(),
|
|
pack_version: "1.0.0".to_string(),
|
|
execution_time: Utc::now(),
|
|
total_tests: 10,
|
|
passed: 10,
|
|
failed: 0,
|
|
skipped: 0,
|
|
pass_rate: 1.0,
|
|
duration_ms: 2000,
|
|
test_suites: vec![],
|
|
};
|
|
|
|
test_repo
|
|
.create(pack.id, "1.0.0", "manual", &test_result)
|
|
.await
|
|
.expect("Failed to create test execution");
|
|
}
|
|
|
|
// Get stats
|
|
let stats = test_repo
|
|
.get_stats(pack.id)
|
|
.await
|
|
.expect("Failed to get stats");
|
|
|
|
assert_eq!(stats.total_executions, 5);
|
|
assert_eq!(stats.successful_executions, 5);
|
|
assert_eq!(stats.failed_executions, 0);
|
|
}
|
|
*/
|
|
}
|