wlk 0.1.0

File-centric, event-sourced version control system with implicit branching
Documentation
use proptest::prelude::*;
use wlk::{DeltaOperation, ShadowFile};

proptest! {
    #[test]
    fn test_insert_never_panics(
        base in "[a-zA-Z0-9 ]{0,100}",
        index in 0usize..100,
        value in "[a-zA-Z0-9 ]{1,20}"
    ) {
        let mut shadow = ShadowFile::new("test.txt", base.clone());
        let actual_index = index.min(base.len());

        let result = shadow.apply_delta(
            DeltaOperation::Insert {
                index: actual_index,
                value: value.clone(),
            },
            None,
        );

        // Should either succeed or fail gracefully
        let _ = result;
    }

    #[test]
    fn test_delete_never_panics(
        base in "[a-zA-Z0-9 ]{1,100}",
        index in 0usize..100,
        length in 1usize..50
    ) {
        let mut shadow = ShadowFile::new("test.txt", base.clone());
        let actual_index = index.min(base.len().saturating_sub(1));
        let actual_length = length.min(base.len() - actual_index);

        let result = shadow.apply_delta(
            DeltaOperation::Delete {
                index: actual_index,
                length: actual_length,
            },
            None,
        );

        // Should either succeed or fail gracefully
        let _ = result;
    }

    #[test]
    fn test_replace_never_panics(
        base in "[a-zA-Z0-9 ]{1,100}",
        index in 0usize..100,
        length in 1usize..50,
        value in "[a-zA-Z0-9 ]{1,20}"
    ) {
        let mut shadow = ShadowFile::new("test.txt", base.clone());
        let actual_index = index.min(base.len().saturating_sub(1));
        let actual_length = length.min(base.len() - actual_index);

        let result = shadow.apply_delta(
            DeltaOperation::Replace {
                index: actual_index,
                length: actual_length,
                value: value.clone(),
            },
            None,
        );

        // Should either succeed or fail gracefully
        let _ = result;
    }

    #[test]
    fn test_snapshot_always_works(base in "[a-zA-Z0-9 ]{0,1000}") {
        let mut shadow = ShadowFile::new("test.txt", base.clone());

        let result = shadow.apply_delta(
            DeltaOperation::Snapshot { content: base.clone() },
            None,
        );

        prop_assert!(result.is_ok());
    }

    #[test]
    fn test_reconstruction_is_consistent(
        base in "[a-zA-Z0-9 ]{10,50}",
        insert_value in "[a-zA-Z0-9 ]{5,15}"
    ) {
        let mut shadow = ShadowFile::new("test.txt", base.clone());

        // Apply an insert
        shadow.apply_delta(
            DeltaOperation::Insert {
                index: base.len() / 2,
                value: insert_value.clone(),
            },
            Some("test".to_string()),
        ).unwrap();

        let delta_id = shadow.current_head.clone();

        // Reconstruct multiple times
        let content1 = shadow.reconstruct_at(&delta_id).unwrap();
        let content2 = shadow.reconstruct_at(&delta_id).unwrap();
        let content3 = shadow.reconstruct_at(&delta_id).unwrap();

        // Should always get the same result
        prop_assert_eq!(&content1, &content2);
        prop_assert_eq!(&content2, &content3);
    }

    #[test]
    fn test_multiple_operations_compose(
        base in "[a-zA-Z ]{20,50}"
    ) {
        let mut shadow = ShadowFile::new("test.txt", base.clone());

        // Apply multiple operations
        for i in 0..5 {
            shadow.apply_delta(
                DeltaOperation::Insert {
                    index: shadow.current_content().unwrap().len(),
                    value: format!(" {}", i),
                },
                Some(format!("Add {}", i)),
            ).unwrap();
        }

        // Should be able to reconstruct at each point
        let history = shadow.get_history();
        for delta in history {
            let content = shadow.reconstruct_at(&delta.id);
            prop_assert!(content.is_ok());
        }
    }
}

#[cfg(test)]
mod edge_case_tests {
    use super::*;
    use wlk::ShadowFile;

    #[test]
    fn test_empty_file() {
        let shadow = ShadowFile::new("empty.txt", String::new());
        assert_eq!(shadow.current_content().unwrap(), "");
    }

    #[test]
    fn test_very_long_file() {
        let content = "x".repeat(100_000);
        let shadow = ShadowFile::new("long.txt", content.clone());
        assert_eq!(shadow.current_content().unwrap(), content);
    }

    #[test]
    fn test_unicode_content() {
        let content = "Hello 世界 🌍 Rust!";
        let mut shadow = ShadowFile::new("unicode.txt", content.to_string());

        shadow
            .apply_delta(
                DeltaOperation::Insert {
                    index: 6,
                    value: "नमस्ते ".to_string(),
                },
                None,
            )
            .unwrap();

        let result = shadow.current_content().unwrap();
        assert!(result.contains("नमस्ते"));
    }

    #[test]
    fn test_newlines_and_special_chars() {
        let content = "Line 1\nLine 2\r\nLine 3\tTabbed";
        let shadow = ShadowFile::new("special.txt", content.to_string());
        assert_eq!(shadow.current_content().unwrap(), content);
    }

    #[test]
    fn test_deep_history() {
        let mut shadow = ShadowFile::new("deep.txt", "start".to_string());

        // Create 1000 deltas
        for i in 0..1000 {
            shadow
                .apply_delta(
                    DeltaOperation::Insert {
                        index: shadow.current_content().unwrap().len(),
                        value: "x".to_string(),
                    },
                    Some(format!("Delta {}", i)),
                )
                .unwrap();
        }

        // Should be able to get history
        let history = shadow.get_history();
        assert_eq!(history.len(), 1000);

        // Should be able to reconstruct at any point
        let mid_delta = &history[500];
        let content = shadow.reconstruct_at(&mid_delta.id).unwrap();
        assert!(content.len() > 500);
    }
}