146 lines
4.6 KiB
Rust
146 lines
4.6 KiB
Rust
use crank_registry::{
|
|
CreateInvocationLogRequest, InvocationHistoryLossCategory, InvocationHistoryWriteOutcome,
|
|
ListInvocationLogsQuery, UsageBucket, UsageQuery,
|
|
};
|
|
use sqlx::Row;
|
|
|
|
use super::common::{TestDatabase, test_invocation_log, test_operation, test_workspace_id};
|
|
|
|
#[tokio::test]
|
|
async fn invocation_history_write_returns_typed_loss_without_error_details() {
|
|
let database = TestDatabase::new().await;
|
|
let registry = database.registry().await;
|
|
let log = test_invocation_log(
|
|
"log_missing_owner",
|
|
&crank_core::OperationId::new("op_missing"),
|
|
None,
|
|
crank_core::InvocationStatus::Ok,
|
|
10,
|
|
"2026-03-25T12:20:00Z",
|
|
);
|
|
|
|
let outcome = registry
|
|
.create_invocation_log(CreateInvocationLogRequest { log: &log })
|
|
.await;
|
|
|
|
assert_eq!(
|
|
outcome,
|
|
InvocationHistoryWriteOutcome::Lost(crank_registry::InvocationHistoryLoss {
|
|
category: InvocationHistoryLossCategory::InvalidRecord,
|
|
})
|
|
);
|
|
database.cleanup().await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore = "production-size query-plan evidence: inserts 1,000,000 invocation_logs rows"]
|
|
async fn production_size_invocation_history_queries_stay_bounded() {
|
|
let database = TestDatabase::new().await;
|
|
let registry = database.registry().await;
|
|
let pool = database.raw_pool().await;
|
|
let operation = test_operation("op_history_scale", 1, crank_core::OperationStatus::Draft);
|
|
|
|
registry
|
|
.create_operation(&test_workspace_id(), &operation, None)
|
|
.await
|
|
.unwrap();
|
|
|
|
sqlx::query(
|
|
r#"
|
|
insert into invocation_logs (
|
|
id, workspace_id, operation_id, operation_version, source, level, status,
|
|
tool_name, message, request_id, trace_id, status_code, duration_ms,
|
|
error_kind, execution_stage, execution_error_code, retryability, outcome_certainty,
|
|
request_preview_json, response_preview_json, created_at
|
|
)
|
|
select
|
|
'log_scale_' || series::text,
|
|
'ws_default',
|
|
'op_history_scale',
|
|
1,
|
|
'admin_test_run',
|
|
'info',
|
|
case when series % 10 = 0 then 'error' else 'ok' end,
|
|
'scale_tool',
|
|
'scale invocation',
|
|
'018f0000-0000-7000-8000-' || lpad(series::text, 12, '0'),
|
|
'0af7651916cd43dd8448eb211c80319c',
|
|
case when series % 10 = 0 then 500 else 200 end,
|
|
20 + (series % 250),
|
|
null,
|
|
'runtime',
|
|
case when series % 10 = 0 then 'runtime_internal' else null end,
|
|
'never',
|
|
'certain',
|
|
'{"input":"bounded"}'::jsonb,
|
|
'{"ok":true}'::jsonb,
|
|
'2026-03-25T00:00:00Z'::timestamptz + (series || ' seconds')::interval
|
|
from generate_series(1, 1000000) as series
|
|
"#,
|
|
)
|
|
.execute(&pool)
|
|
.await
|
|
.unwrap();
|
|
|
|
let page = registry
|
|
.list_invocation_logs(ListInvocationLogsQuery {
|
|
workspace_id: &test_workspace_id(),
|
|
level: None,
|
|
status: None,
|
|
outcome_group: None,
|
|
search_text: None,
|
|
source: None,
|
|
operation_id: Some(&operation.id),
|
|
agent_id: None,
|
|
created_after: Some("2026-03-25T00:00:00Z"),
|
|
created_before: Some("2026-04-06T00:00:00Z"),
|
|
cursor_created_at: None,
|
|
cursor_id: None,
|
|
limit: 101,
|
|
})
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(page.len(), 101);
|
|
|
|
let summary = registry
|
|
.summarize_usage(UsageQuery {
|
|
workspace_id: &test_workspace_id(),
|
|
period: crank_core::UsagePeriod::Last7Days,
|
|
source: None,
|
|
created_after: "2026-03-25T00:00:00Z",
|
|
created_before: "2026-04-06T00:00:00Z",
|
|
bucket: UsageBucket::Day,
|
|
})
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(summary.rollup.calls_total, 1_000_000);
|
|
|
|
let explain = sqlx::query(
|
|
r#"
|
|
explain
|
|
select id
|
|
from invocation_logs
|
|
where workspace_id = 'ws_default'
|
|
and operation_id = 'op_history_scale'
|
|
and created_at >= '2026-03-25T00:00:00Z'::timestamptz
|
|
and created_at < '2026-04-06T00:00:00Z'::timestamptz
|
|
order by created_at desc, id desc
|
|
limit 101
|
|
"#,
|
|
)
|
|
.fetch_all(&pool)
|
|
.await
|
|
.unwrap()
|
|
.into_iter()
|
|
.map(|row| row.get::<String, _>(0))
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
assert!(
|
|
explain.contains("Index Scan") || explain.contains("Bitmap Index Scan"),
|
|
"{explain}"
|
|
);
|
|
assert!(!explain.contains("Seq Scan"), "{explain}");
|
|
|
|
database.cleanup().await;
|
|
}
|