use agileplus_domain::builder::*;
use agileplus_domain::intent_graph::*;
use agileplus_trace_validator::*;
use proptest::prelude::*;
use proptest::strategy::BoxedStrategy;
#[derive(Debug, Clone, Copy)]
pub(crate) struct ArbNodeType(pub NodeType);
#[derive(Debug, Clone, Copy)]
pub(crate) struct ArbDagStage(pub DagStage);
#[derive(Debug, Clone, Copy)]
pub(crate) struct ArbStatus(pub Status);
#[derive(Debug, Clone, Copy)]
pub(crate) struct ArbRelationshipType(pub RelationshipType);
impl Arbitrary for ArbNodeType {
type Strategy = BoxedStrategy<Self>;
type Parameters = ();
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![
Just(ArbNodeType(NodeType::Intent)),
Just(ArbNodeType(NodeType::Plan)),
Just(ArbNodeType(NodeType::Feature)),
Just(ArbNodeType(NodeType::Story)),
Just(ArbNodeType(NodeType::Task)),
Just(ArbNodeType(NodeType::Spec)),
Just(ArbNodeType(NodeType::Commit)),
Just(ArbNodeType(NodeType::Test)),
Just(ArbNodeType(NodeType::PR)),
Just(ArbNodeType(NodeType::Bug)),
Just(ArbNodeType(NodeType::Artifact)),
]
.boxed()
}
}
impl Arbitrary for ArbDagStage {
type Strategy = BoxedStrategy<Self>;
type Parameters = ();
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![
Just(ArbDagStage(DagStage::Intent)),
Just(ArbDagStage(DagStage::Plan)),
Just(ArbDagStage(DagStage::Feature)),
Just(ArbDagStage(DagStage::Story)),
Just(ArbDagStage(DagStage::Task)),
Just(ArbDagStage(DagStage::Spec)),
Just(ArbDagStage(DagStage::Commit)),
Just(ArbDagStage(DagStage::Test)),
Just(ArbDagStage(DagStage::Pr)),
Just(ArbDagStage(DagStage::Bug)),
Just(ArbDagStage(DagStage::Artifact)),
]
.boxed()
}
}
impl Arbitrary for ArbStatus {
type Strategy = BoxedStrategy<Self>;
type Parameters = ();
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![
Just(ArbStatus(Status::Draft)),
Just(ArbStatus(Status::Active)),
Just(ArbStatus(Status::Completed)),
Just(ArbStatus(Status::Deprecated)),
Just(ArbStatus(Status::Rejected)),
Just(ArbStatus(Status::Open)),
Just(ArbStatus(Status::InProgress)),
Just(ArbStatus(Status::Blocked)),
Just(ArbStatus(Status::Deferred)),
Just(ArbStatus(Status::Cancelled)),
]
.boxed()
}
}
impl Arbitrary for ArbRelationshipType {
type Strategy = BoxedStrategy<Self>;
type Parameters = ();
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
prop_oneof![
Just(ArbRelationshipType(RelationshipType::Implements)),
Just(ArbRelationshipType(RelationshipType::Tests)),
Just(ArbRelationshipType(RelationshipType::Covers)),
Just(ArbRelationshipType(RelationshipType::TracesTo)),
Just(ArbRelationshipType(RelationshipType::DerivesFrom)),
Just(ArbRelationshipType(RelationshipType::Resolves)),
Just(ArbRelationshipType(RelationshipType::Blocks)),
Just(ArbRelationshipType(RelationshipType::DependsOn)),
]
.boxed()
}
}
fn canonical_graph() -> IntentGraph {
let intent = NodeBuilder::new()
.id("Intent#canonical")
.node_type(NodeType::Intent)
.dag_stage(DagStage::Intent)
.title("Canonical intent")
.status(Status::Active)
.with_meta("validator-proptest")
.build()
.expect("intent node");
let feature = NodeBuilder::new()
.id("Feature#canonical")
.node_type(NodeType::Feature)
.dag_stage(DagStage::Feature)
.title("Canonical feature")
.status(Status::Active)
.with_meta("validator-proptest")
.build()
.expect("feature node");
let test = NodeBuilder::new()
.id("Test#canonical")
.node_type(NodeType::Test)
.dag_stage(DagStage::Test)
.title("Canonical test")
.status(Status::Active)
.with_meta("validator-proptest")
.build()
.expect("test node");
let edge_if = EdgeBuilder::new()
.auto_id()
.source("Intent#canonical")
.target("Feature#canonical")
.relationship_type(RelationshipType::Implements)
.with_meta("validator-proptest")
.build()
.expect("intent->feature edge");
let edge_ft = EdgeBuilder::new()
.auto_id()
.source("Feature#canonical")
.target("Test#canonical")
.relationship_type(RelationshipType::Tests)
.with_meta("validator-proptest")
.build()
.expect("feature->test edge");
let mut builder = IntentGraphBuilder::new().with_default_metadata();
for n in vec![intent, feature, test] {
builder = builder.node(n);
}
for e in vec![edge_if, edge_ft] {
builder = builder.edge(e);
}
builder.build().expect("canonical graph builds")
}
#[test]
fn canonical_graph_validates() {
let g = canonical_graph();
let report = validate_intent_graph(&g);
assert!(
report.is_ok(),
"canonical graph failed validation: {:?}",
report.err()
);
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 64,
.. ProptestConfig::default()
})]
#[test]
fn validator_never_panics_on_random_graph(graph in arb_intent_graph()) {
let _ = validate_intent_graph(&graph);
let _ = validate_node_completeness(&graph);
let _ = validate_metadata(&graph.metadata);
let _ = validate_dag_acyclicity(&graph);
let _ = validate_edge_constraints(&graph);
}
#[test]
fn validate_is_idempotent(graph in arb_intent_graph()) {
let r1 = validate_intent_graph(&graph);
let r2 = validate_intent_graph(&graph);
prop_assert_eq!(r1.is_ok(), r2.is_ok());
}
#[test]
fn first_error_matches_full(graph in arb_intent_graph()) {
let full = validate_intent_graph(&graph);
let first = first_error(&graph);
prop_assert_eq!(full.is_ok(), first.is_none());
}
#[test]
fn is_valid_agrees_with_full(graph in arb_intent_graph()) {
let full = validate_intent_graph(&graph);
prop_assert_eq!(full.is_ok(), is_valid(&graph));
}
#[test]
fn adding_node_grows_count(st in any::<ArbStatus>()) {
let mut g = canonical_graph();
let before = g.nodes.len();
let extra = NodeBuilder::new()
.id("Test#added")
.node_type(NodeType::Test)
.dag_stage(DagStage::Test)
.title("Added test")
.status(st.0)
.with_meta("validator-proptest-add")
.build()
.expect("added node");
g.nodes.push(extra);
prop_assert_eq!(g.nodes.len(), before + 1);
}
}
fn arb_intent_graph() -> BoxedStrategy<IntentGraph> {
(
proptest::collection::vec(arb_node(), 0..8),
proptest::collection::vec(arb_edge(), 0..8),
)
.prop_map(|(nodes, edges)| {
let mut b = IntentGraphBuilder::new().with_default_metadata();
for n in nodes {
b = b.node(n);
}
for e in edges {
b = b.edge(e);
}
b.build().unwrap_or_else(|_| canonical_graph())
})
.boxed()
}
fn arb_node() -> BoxedStrategy<Node> {
(
"[A-Z][a-z]+#test-[0-9]+",
any::<ArbNodeType>(),
any::<ArbDagStage>(),
any::<ArbStatus>(),
)
.prop_map(|(id, nt, ds, st)| {
NodeBuilder::new()
.id(id)
.node_type(nt.0)
.dag_stage(ds.0)
.title("PropTest node")
.status(st.0)
.with_meta("validator-proptest")
.build()
.expect("arb node")
})
.boxed()
}
fn arb_edge() -> BoxedStrategy<Edge> {
(
"[A-Z][a-z]+#test-[0-9]+",
"[A-Z][a-z]+#test-[0-9]+",
any::<ArbRelationshipType>(),
)
.prop_map(|(src, tgt, rt)| {
EdgeBuilder::new()
.auto_id()
.source(&src)
.target(&tgt)
.relationship_type(rt.0)
.with_meta("validator-proptest")
.build()
.expect("arb edge")
})
.boxed()
}