kcode-k1-web-code-session 0.1.0

Own pure session-bound Web-code handles and document preparation
Documentation
use kcode_k1_transaction_id::TxId;
use kcode_k1_web_code_session::{DocumentPart, SessionError, SessionScope};
use kcode_k1_web_package::{AuthorityId, SourceFile, SourcePackage, WebFamily, WebId};
use semver::Version;
use std::sync::Arc;

const USER: [u8; 12] = [0x21; 12];
const OTHER_USER: [u8; 12] = [0x34; 12];
const DOCUMENTATION: &str = concat!(
    "<!-- k1-web/v1\n",
    "{\"dependencies\":[]}\n",
    "-->\n",
    "\u{feff}Original documentation\r\n",
    "with exact bytes.\n",
);
const REPLACEMENT_DOCUMENTATION: &str = concat!(
    "<!-- k1-web/v1\n",
    "{\"dependencies\":[]}\n",
    "-->\n",
    "\u{feff}Replacement documentation\r\n",
    "without normalization.\n",
);
const CODE: &str = "export async function runTests() {\r\n    return true;\r\n}\n";

fn source(documentation: &str, code: &str) -> SourcePackage {
    let authority = AuthorityId::new(TxId::from_bytes([0x13; 12]));
    let family = WebFamily::new(authority, "contract-fixture").unwrap();
    let id = WebId::new(family, Version::new(1, 2, 3)).unwrap();
    let manifest = concat!(
        "{\"name\":\"contract-fixture\",\"version\":\"1.2.3\",",
        "\"entry\":\"Code.js\",\"tests\":\"Code.js\",\"dependencies\":[]}"
    );

    SourcePackage::new(
        id,
        vec![
            SourceFile::new("Documentation.md", documentation.as_bytes().to_vec()),
            SourceFile::new("k1-web.json", manifest.as_bytes().to_vec()),
            SourceFile::new("Code.js", code.as_bytes().to_vec()),
        ],
    )
    .unwrap()
}

#[test]
fn recovers_and_validates_only_the_canonical_session_source() {
    let scope = SessionScope::new();
    let current = Arc::new(source(DOCUMENTATION, CODE));
    let opened = scope.recover(USER, Arc::clone(&current)).unwrap();

    assert_eq!(opened.document().documentation(), DOCUMENTATION.as_bytes());
    assert_eq!(opened.document().code(), CODE.as_bytes());
    assert_eq!(Arc::strong_count(&current), 2);
    assert_eq!(scope.validate(USER, &opened, current.as_ref()), Ok(()));

    let foreign_scope = SessionScope::new();
    assert_eq!(
        foreign_scope.validate(USER, &opened, current.as_ref()),
        Err(SessionError::ForeignHandle)
    );
    assert_eq!(
        scope.validate(OTHER_USER, &opened, current.as_ref()),
        Err(SessionError::ForeignHandle)
    );

    let stale = source(REPLACEMENT_DOCUMENTATION, CODE);
    assert_eq!(
        scope.validate(USER, &opened, &stale),
        Err(SessionError::StaleSource)
    );
}

#[test]
fn documentation_replacement_preserves_code_and_refreshes_exactly() {
    let scope = SessionScope::new();
    let current = Arc::new(source(DOCUMENTATION, CODE));
    let opened = scope.recover(USER, Arc::clone(&current)).unwrap();
    let original_id = opened.document().id().clone();

    let replaced = scope
        .replacement(
            USER,
            &opened,
            current.as_ref(),
            DocumentPart::Documentation,
            REPLACEMENT_DOCUMENTATION.to_owned(),
        )
        .unwrap();
    let replaced = Arc::new(replaced);
    let refreshed = scope.refresh(USER, Arc::clone(&replaced)).unwrap();

    assert_eq!(refreshed.document().id(), &original_id);
    assert_eq!(
        refreshed.document().documentation(),
        REPLACEMENT_DOCUMENTATION.as_bytes()
    );
    assert_eq!(refreshed.document().code(), CODE.as_bytes());
    assert_eq!(
        scope.validate(USER, &opened, replaced.as_ref()),
        Err(SessionError::StaleSource)
    );
    assert_eq!(scope.validate(USER, &refreshed, replaced.as_ref()), Ok(()));
}

#[test]
fn code_replacement_preserves_documentation_and_refreshes_exactly() {
    let scope = SessionScope::new();
    let current = Arc::new(source(DOCUMENTATION, CODE));
    let opened = scope.recover(USER, Arc::clone(&current)).unwrap();
    let original_id = opened.document().id().clone();
    let replacement = "export async function runTests() {\n\treturn \"replacement\";\n}\n";

    let replaced = scope
        .replacement(
            USER,
            &opened,
            current.as_ref(),
            DocumentPart::Code,
            replacement.to_owned(),
        )
        .unwrap();
    let replaced = Arc::new(replaced);
    let refreshed = scope.refresh(USER, Arc::clone(&replaced)).unwrap();

    assert_eq!(refreshed.document().id(), &original_id);
    assert_eq!(
        refreshed.document().documentation(),
        DOCUMENTATION.as_bytes()
    );
    assert_eq!(refreshed.document().code(), replacement.as_bytes());
    assert_eq!(scope.validate(USER, &refreshed, replaced.as_ref()), Ok(()));
}

#[test]
fn evidence_matches_current_source_and_is_invalidated_by_replacement() {
    let scope = SessionScope::new();
    let current = Arc::new(source(DOCUMENTATION, CODE));
    let opened = scope.recover(USER, Arc::clone(&current)).unwrap();
    let evidence = scope
        .mint_evidence(USER, &opened, current.as_ref())
        .unwrap();

    assert_eq!(
        scope.evidence_matches(USER, &opened, current.as_ref(), &evidence),
        Ok(true)
    );

    let replaced = scope
        .replacement(
            USER,
            &opened,
            current.as_ref(),
            DocumentPart::Code,
            "export async function runTests() { return false; }\n".to_owned(),
        )
        .unwrap();
    let replaced = Arc::new(replaced);
    let refreshed = scope.refresh(USER, Arc::clone(&replaced)).unwrap();

    assert_eq!(
        scope.evidence_matches(USER, &refreshed, replaced.as_ref(), &evidence),
        Ok(false)
    );
}