use crate::audit::{Actor, ActorKind, AuditEvent, Operation, Outcome, ResourceRef, Source};
use crate::render::{Cell, Query, Row, RowId, RowPatch, StatusLevel};
use crate::surface::{Column, ColumnKind, Surface};
#[test]
fn query_and_page_round_trip() {
let query = Query {
start: 400,
end: 460,
sort: Some(crate::render::SortSpec {
column: "restarts".into(),
descending: true,
}),
filter: Some(crate::render::Filter {
expression: "metadata.labels.app = \"foo\"".into(),
}),
};
let json = serde_json::to_string(&query).expect("query must serialize");
let back: Query = serde_json::from_str(&json).expect("query must deserialize");
assert_eq!(query, back);
}
#[test]
fn row_patch_is_keyed_by_identity() {
let id = RowId("pod-123".into());
let row = Row {
id: id.clone(),
cells: vec![Cell::Status {
level: StatusLevel::Warning,
label_key: "status.running".into(),
}],
};
let patch = RowPatch::Upsert {
id: id.clone(),
row: row.clone(),
};
match &patch {
RowPatch::Upsert { id: patch_id, .. } => assert_eq!(patch_id, &id),
RowPatch::Remove { .. } => panic!("expected upsert"),
}
}
#[test]
fn audit_event_round_trips_with_agent_identity() {
let event = AuditEvent {
timestamp: 1_700_000_000_000,
actor: Actor {
kind: ActorKind::Agent,
name: "agent-scaling-7".into(),
},
context: "prod-eu".into(),
operation: Operation::Scale,
target: ResourceRef {
group: "apps".into(),
kind: "Deployment".into(),
namespace: "payments".into(),
name: "checkout".into(),
},
outcome: Outcome::Applied,
source: Source::Mcp,
session_id: "sess-42".into(),
reason: None,
on_behalf_of: Some("erling".into()),
};
let json = serde_json::to_string(&event).expect("audit must serialize");
let back: AuditEvent = serde_json::from_str(&json).expect("audit must deserialize");
assert_eq!(event, back);
assert_eq!(event.source, Source::Mcp);
}
#[test]
#[ignore = "full cross-frontend contract suite arrives with the first frontend"]
fn surface_kind_is_derived_from_surface() {
let table = Surface::Table {
columns: vec![Column {
id: "name".into(),
header_key: "col.name".into(),
kind: ColumnKind::Text,
sortable: true,
field: Some("metadata.name".into()),
}],
};
assert_eq!(table.kind(), crate::surface::SurfaceKind::Table);
}
#[test]
fn support_matrix_is_exhaustive_and_correct() {
use crate::surface::{Projection, SupportLevel, SurfaceKind, support_level};
assert_eq!(
support_level(Projection::Tui, SurfaceKind::Table),
SupportLevel::Full
);
assert_eq!(
support_level(Projection::Tui, SurfaceKind::Graph),
SupportLevel::Alternate
);
assert_eq!(
support_level(Projection::Gui, SurfaceKind::Graph),
SupportLevel::Full
);
assert_eq!(
support_level(Projection::Tui, SurfaceKind::Editor),
SupportLevel::Alternate
);
assert_eq!(
support_level(Projection::Browser, SurfaceKind::Editor),
SupportLevel::Full
);
}
#[test]
fn same_query_yields_same_page_across_projections() {
use crate::mem_plane::{MemPlane, Schema};
use crate::render::{Cell, DataPlane, Filter, Query, Row, RowId, SortSpec};
fn text(v: &str) -> Cell {
Cell::Text { value: v.into() }
}
fn row(id: &str, name: &str, count: i64) -> Row {
Row {
id: RowId(id.into()),
cells: vec![text(name), Cell::Number { value: count }],
}
}
let plane = MemPlane::new(Schema {
column_ids: vec!["name".into(), "count".into()],
});
plane.upsert(row("a", "web", 3));
plane.upsert(row("b", "api", 5));
plane.upsert(row("c", "worker", 1));
let query = Query {
start: 0,
end: 10,
sort: Some(SortSpec {
column: "count".into(),
descending: true,
}),
filter: Some(Filter {
expression: "e".into(), }),
};
let page = {
let fut = plane.query(&query);
futures_util::pin_mut!(fut);
let waker = futures_util::task::noop_waker();
let mut cx = std::task::Context::from_waker(&waker);
match fut.as_mut().poll(&mut cx) {
std::task::Poll::Ready(p) => p.expect("query"),
std::task::Poll::Pending => panic!("mem plane query must not pend"),
}
};
assert_eq!(page.total, 2);
let names: Vec<&str> = page
.rows
.iter()
.map(|r| match &r.cells[0] {
Cell::Text { value } => value.as_str(),
_ => "",
})
.collect();
assert_eq!(names, vec!["web", "worker"]);
let again = {
let fut = plane.query(&query);
futures_util::pin_mut!(fut);
let waker = futures_util::task::noop_waker();
let mut cx = std::task::Context::from_waker(&waker);
match fut.as_mut().poll(&mut cx) {
std::task::Poll::Ready(p) => p.expect("query"),
std::task::Poll::Pending => panic!("mem plane query must not pend"),
}
};
assert_eq!(again, page);
}