Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/agent_discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ const RESOURCE_SPECS: &[ResourceSpec] = &[
},
ResourceSpec {
name: "metrics",
commands: &["context", "list", "get", "create", "update", "delete"],
commands: &["context", "list", "get", "create", "update", "delete", "test"],
description: "Metrics score conversations and simulations with built-in or configured evaluation logic.",
id_name: "metric_id",
id_format: "22-character Coval ID",
Expand Down
9 changes: 9 additions & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,15 @@ impl MetricsClient<'_> {
let url = self.0.url(&format!("/v1/metrics/{id}"));
self.0.delete(url).await
}

pub async fn test(
&self,
id: &str,
req: models::TestMetricRequest,
) -> Result<models::TestMetricResponse, ApiError> {
let url = self.0.url(&format!("/v1/metrics/{id}/test"));
self.0.post(url, &req).await
}
}

impl MutationsClient<'_> {
Expand Down
12 changes: 12 additions & 0 deletions src/client/models/metric.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,18 @@ pub struct UpdateMetricResponse {
pub metric: Metric,
}

#[derive(Debug, Serialize)]
pub struct TestMetricRequest {
pub simulation_output_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub dev_id: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct TestMetricResponse {
pub metric_output_ulid: String,
}

impl Tabular for Metric {
fn headers() -> Vec<&'static str> {
vec!["ID", "NAME", "TYPE", "CREATED"]
Expand Down
35 changes: 34 additions & 1 deletion src/commands/metrics.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use anyhow::Result;
use clap::{Args, Subcommand};

use crate::client::models::{CreateMetricRequest, ListParams, MetricType, UpdateMetricRequest};
use crate::client::models::{
CreateMetricRequest, ListParams, MetricType, TestMetricRequest, UpdateMetricRequest,
};
use crate::client::CovalClient;
use crate::input_json::{self, InputJsonArg};
use crate::next_actions;
Expand All @@ -17,6 +19,7 @@ pub enum MetricCommands {
Create(Box<CreateArgs>),
Update(Box<UpdateArgs>),
Delete(DeleteArgs),
Test(TestArgs),
}

impl MetricCommands {
Expand All @@ -28,6 +31,7 @@ impl MetricCommands {
Self::Create(_) => "create",
Self::Update(_) => "update",
Self::Delete(_) => "delete",
Self::Test(_) => "test",
}
}
}
Expand Down Expand Up @@ -202,6 +206,18 @@ pub struct DeleteArgs {
metric_id: String,
}

#[derive(Args)]
pub struct TestArgs {
/// The metric ID to test
metric_id: String,
/// The simulation output ID to run the metric against
#[arg(long)]
simulation_output_id: String,
/// Optional developer identifier for debugging
#[arg(long)]
dev_id: Option<String>,
}

pub async fn execute(cmd: MetricCommands, client: &CovalClient, ctx: &OutputContext) -> Result<()> {
let operation = cmd.operation();
match cmd {
Expand Down Expand Up @@ -344,6 +360,23 @@ pub async fn execute(cmd: MetricCommands, client: &CovalClient, ctx: &OutputCont
next_actions::delete_result("metrics"),
);
}
MetricCommands::Test(args) => {
let req = TestMetricRequest {
simulation_output_id: args.simulation_output_id,
dev_id: args.dev_id,
};
let response = client.metrics().test(&args.metric_id, req).await?;
emit_one_with_actions(
ctx,
"metrics",
operation,
&response,
vec![
next_actions::get("metrics", &args.metric_id).primary(),
next_actions::context("metrics"),
],
);
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}
}
Ok(())
}
Expand Down
65 changes: 65 additions & 0 deletions tests/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3045,6 +3045,71 @@ fn test_metrics_create_composite_metadata_requires_criteria() {
.stderr(predicate::str::contains("--criteria is required"));
}

#[tokio::test]
async fn test_metrics_test_subcommand() {
let mock_server = MockServer::start().await;

Mock::given(method("POST"))
.and(path("/v1/metrics/met_abc/test"))
.and(header("X-API-Key", "test_key"))
.and(body_partial_json(json!({
"simulation_output_id": "simout_def456"
})))
.respond_with(ResponseTemplate::new(202).set_body_json(json!({
"metric_output_ulid": "01HXKZ4M5N6P7Q8R9STVWXYZAB"
})))
.mount(&mock_server)
.await;

coval()
.arg("--api-key")
.arg("test_key")
.arg("--api-url")
.arg(mock_server.uri())
.arg("metrics")
.arg("test")
.arg("met_abc")
.arg("--simulation-output-id")
.arg("simout_def456")
.assert()
.success()
.stdout(predicate::str::contains("01HXKZ4M5N6P7Q8R9STVWXYZAB"));
}

#[tokio::test]
async fn test_metrics_test_subcommand_with_dev_id() {
let mock_server = MockServer::start().await;

Mock::given(method("POST"))
.and(path("/v1/metrics/met_abc/test"))
.and(header("X-API-Key", "test_key"))
.and(body_partial_json(json!({
"simulation_output_id": "simout_def456",
"dev_id": "debug-trace-001"
})))
.respond_with(ResponseTemplate::new(202).set_body_json(json!({
"metric_output_ulid": "01HXKZ4M5N6P7Q8R9STVWXYZAB"
})))
.mount(&mock_server)
.await;

coval()
.arg("--api-key")
.arg("test_key")
.arg("--api-url")
.arg(mock_server.uri())
.arg("metrics")
.arg("test")
.arg("met_abc")
.arg("--simulation-output-id")
.arg("simout_def456")
.arg("--dev-id")
.arg("debug-trace-001")
.assert()
.success()
.stdout(predicate::str::contains("01HXKZ4M5N6P7Q8R9STVWXYZAB"));
}

#[tokio::test]
async fn test_test_cases_create_with_expected_behaviors() {
let mock_server = MockServer::start().await;
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Expand Down
Loading