notedthat-write 0.7.0

Shared write path (commit, patch, replace) for NotedThat HTTP API and WebDAV surfaces
Documentation
use bytes::Bytes;

use super::support::{
    ReplaceArgs, TestStorage, conditionals, expect_replace_err, run_replace, run_replace_with,
};

#[tokio::test]
async fn replace_all_true_with_zero_matches_returns_no_match() {
    let storage = TestStorage::with_body(b"foo");
    let err = expect_replace_err(run_replace(&storage, ReplaceArgs::all("bar", "baz")).await);

    assert!(matches!(err, crate::WriteError::ReplaceNoMatch));
    assert_eq!(storage.body(), Bytes::from_static(b"foo"));
}

#[tokio::test]
async fn ambiguous_match_fires_at_exactly_two_matches() {
    let storage = TestStorage::with_body(b"aa");
    let err = expect_replace_err(run_replace(&storage, ReplaceArgs::one("a", "Z")).await);

    assert!(matches!(
        err,
        crate::WriteError::ReplaceAmbiguous { count: 2 }
    ));
    assert_eq!(storage.body(), Bytes::from_static(b"aa"));
}

#[tokio::test]
async fn nul_byte_in_old_string_matches_correctly() {
    let storage = TestStorage::with_body(b"a\0b");
    let outcome = run_replace(&storage, ReplaceArgs::one("\0", "X"))
        .await
        .expect("NUL byte old_string matches");

    assert_eq!(outcome.match_count, 1);
    assert_eq!(storage.body(), Bytes::from_static(b"aXb"));
}

#[tokio::test]
async fn content_type_preserved_from_get_response() {
    let storage = TestStorage::with_body_and_content_type(b"hello world", "text/markdown");
    run_replace(&storage, ReplaceArgs::one("world", "planet"))
        .await
        .expect("replace preserves GET content type when caller omits one");

    let after = storage.read().await;
    assert_eq!(after.meta.content_type.as_deref(), Some("text/markdown"));
    assert_eq!(after.bytes, Bytes::from_static(b"hello planet"));
}

#[tokio::test]
async fn single_pass_splice_o_n_scaling() {
    let mut body = vec![b'a'; 1_048_576];
    for index in 0..1000 {
        body[index * 1024] = b'x';
    }
    let storage = TestStorage::with_bytes(Bytes::from(body), Some("text/plain"));

    let outcome = run_replace_with(
        &storage,
        ReplaceArgs::all("x", "Y"),
        conditionals(Some("etag1")),
        2_097_152,
        None,
    )
    .await
    .expect("large replace_all completes in one pass");

    let after = storage.body();
    assert_eq!(outcome.match_count, 1000);
    assert!(!after.contains(&b'x'));
    let replacement_count = after
        .iter()
        .fold(0usize, |count, byte| count + usize::from(*byte == b'Y'));
    assert_eq!(replacement_count, 1000);
}