boxology-cli 0.1.1

Command-line interface
Documentation
use std::{
    future::Future,
    pin::pin,
    task::{Context, Poll, Waker},
};

use boxology_cli::{ClassifierComposition, classify};
use boxology_contract::{CallContext, Caller, CancelToken, TraceContext};
use classifier_contract::{
    ClassifierHandle, ClassifyFailure, ClassifyFailureStage, ClassifyFinding, ClassifyOutcome,
    ClassifyReport, ClassifyRequest, CompatibilityClass, test_support::ClassifierFake,
};

const HELLO: &[u8] = include_bytes!("../../fixtures/hello/generated/schema.json");
const PING: &[u8] = include_bytes!("../../fixtures/ping/generated/schema.json");

fn context() -> CallContext {
    CallContext::new(
        Caller::Anonymous,
        None,
        CancelToken::new(),
        TraceContext::empty(),
        None,
    )
}

fn ready<F: Future>(future: F) -> F::Output {
    let mut future = pin!(future);
    match future
        .as_mut()
        .poll(&mut Context::from_waker(Waker::noop()))
    {
        Poll::Ready(output) => output,
        Poll::Pending => panic!("pure classifier future unexpectedly pending"),
    }
}

#[test]
fn real_composition_preserves_reports_and_failures() {
    let classifier = ClassifierComposition::start().expect("classifier composition starts");
    let report = classifier
        .classify(None, HELLO)
        .expect("introduction classifies");
    assert_eq!(report.verdict, CompatibilityClass::Additive);
    assert_eq!(report.findings.len(), 1);
    assert_eq!(report.findings[0].code, "BXC0026");
    assert_eq!(
        report.rendered_text,
        boxology_classifier::render_text(&classify(None, HELLO).unwrap())
    );

    for (base, submitted) in [
        (Some(b"{".as_slice()), HELLO),
        (None, b"{".as_slice()),
        (Some(HELLO), PING),
    ] {
        assert_eq!(
            classifier.classify(base, submitted).unwrap_err(),
            classify(base, submitted).unwrap_err().to_string()
        );
    }
}

#[test]
fn generated_fake_carries_success_and_every_failure_stage() {
    let expected = ClassifyOutcome {
        report: Some(ClassifyReport {
            verdict: CompatibilityClass::CompatibleWithConditions,
            findings: vec![ClassifyFinding {
                code: "BXC0068".into(),
                path: "box/type/Mode/variant/Future".into(),
                kind: "output enum variant added".into(),
                class: CompatibilityClass::CompatibleWithConditions,
                base_excerpt: None,
                submitted_excerpt: Some("Future".into()),
                condition: Some("unknown-variant tolerance".into()),
            }],
            rendered_text: "classification compatible_with_conditions\n".into(),
        }),
        failure: None,
    };
    let returned = expected.clone();
    let fake = ClassifierFake::new().with_classify(move |_context, request| {
        assert_eq!(request.base, Some(vec![1, 2]));
        assert_eq!(request.submitted, vec![3, 4]);
        let returned = returned.clone();
        async move { Ok(returned) }
    });
    let handle: ClassifierHandle = fake.handle();
    let actual = ready(handle.classify(
        context(),
        ClassifyRequest {
            base: Some(vec![1, 2]),
            submitted: vec![3, 4],
        },
    ))
    .expect("programmed fake returns its typed outcome");
    assert_eq!(actual, expected);

    for stage in [
        ClassifyFailureStage::Base,
        ClassifyFailureStage::Submitted,
        ClassifyFailureStage::Pairing,
    ] {
        let expected = ClassifyOutcome {
            report: None,
            failure: Some(ClassifyFailure {
                stage,
                diagnostics: "coded diagnostics".into(),
            }),
        };
        let returned = expected.clone();
        let fake = ClassifierFake::new().with_classify(move |_context, _request| {
            let returned = returned.clone();
            async move { Ok(returned) }
        });
        let actual = ready(fake.handle().classify(
            context(),
            ClassifyRequest {
                base: None,
                submitted: Vec::new(),
            },
        ))
        .expect("programmed fake returns its typed failure");
        assert_eq!(actual, expected);
    }
}

#[test]
fn generated_fake_carries_constructible_invalid_outcomes() {
    for expected in [
        ClassifyOutcome {
            report: None,
            failure: None,
        },
        ClassifyOutcome {
            report: Some(ClassifyReport {
                verdict: CompatibilityClass::Unchanged,
                findings: Vec::new(),
                rendered_text: "classification unchanged\n".into(),
            }),
            failure: Some(ClassifyFailure {
                stage: ClassifyFailureStage::Base,
                diagnostics: "diagnostic".into(),
            }),
        },
    ] {
        let returned = expected.clone();
        let fake = ClassifierFake::new().with_classify(move |_context, _request| {
            let returned = returned.clone();
            async move { Ok(returned) }
        });
        let actual = ready(fake.handle().classify(
            context(),
            ClassifyRequest {
                base: None,
                submitted: Vec::new(),
            },
        ))
        .expect("generated fake transports the invalid combination for consumer testing");
        assert_eq!(actual, expected);
    }
}