a73x

tests/cache_test.rs

Ref:   Size: 6.5 KiB

mod common;

use common::{alice, bob, add_comment, close_issue, init_repo, open_issue, create_patch, add_review};
use git_collab::cache;
use git_collab::event::ReviewVerdict;
use git_collab::state::{IssueState, IssueStatus, PatchState, PatchStatus};
use tempfile::TempDir;

#[test]
fn cache_miss_walks_dag_and_caches() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = open_issue(&repo, &alice(), "Cache test issue");

    // No cache exists yet — should return None
    let cached: Option<IssueState> = cache::get_cached_state(&repo, &ref_name);
    assert!(cached.is_none());

    // from_ref should walk the DAG and populate the cache
    let state = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state.title, "Cache test issue");
    assert_eq!(state.status, IssueStatus::Open);

    // Now the cache should have an entry
    let cached: Option<IssueState> = cache::get_cached_state(&repo, &ref_name);
    assert!(cached.is_some());
    let cached = cached.unwrap();
    assert_eq!(cached.title, "Cache test issue");
    assert_eq!(cached.id, id);
}

#[test]
fn cache_hit_returns_cached_state() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = open_issue(&repo, &alice(), "Cached issue");

    // Populate cache
    let state1 = IssueState::from_ref(&repo, &ref_name, &id).unwrap();

    // Second call should hit cache and return same result
    let state2 = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state1.title, state2.title);
    assert_eq!(state1.id, state2.id);
    assert_eq!(state1.status, state2.status);
}

#[test]
fn cache_invalidation_on_new_event() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = open_issue(&repo, &alice(), "Invalidation test");

    // Populate cache
    let state1 = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state1.comments.len(), 0);

    // Append a comment — this changes the ref tip OID
    add_comment(&repo, &ref_name, &bob(), "Hello!");

    // Cache should be invalidated (tip changed), DAG should be re-walked
    let state2 = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state2.comments.len(), 1);
    assert_eq!(state2.comments[0].body, "Hello!");
}

#[test]
fn cache_invalidation_on_close() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = open_issue(&repo, &alice(), "Close test");

    let state1 = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state1.status, IssueStatus::Open);

    close_issue(&repo, &ref_name, &alice());

    let state2 = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state2.status, IssueStatus::Closed);
}

#[test]
fn no_cache_dir_falls_back_gracefully() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = open_issue(&repo, &alice(), "No cache dir");

    // Ensure no cache directory exists (it shouldn't by default)
    let cache_path = repo.path().join("collab").join("cache");
    if cache_path.exists() {
        std::fs::remove_dir_all(&cache_path).unwrap();
    }

    // Should still work via DAG walk
    let state = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state.title, "No cache dir");
}

#[test]
fn corrupted_cache_falls_back_to_dag_walk() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = open_issue(&repo, &alice(), "Corrupted cache");

    // Populate cache
    IssueState::from_ref(&repo, &ref_name, &id).unwrap();

    // Corrupt the cache file
    let cache_dir = repo.path().join("collab").join("cache");
    let sanitized = ref_name.replace('/', "_");
    let cache_file = cache_dir.join(sanitized);
    std::fs::write(&cache_file, "not valid json").unwrap();

    // Should fall back gracefully
    let state = IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state.title, "Corrupted cache");
}

#[test]
fn patch_cache_miss_and_hit() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = create_patch(&repo, &alice(), "Cache patch");

    // No cache
    let cached: Option<PatchState> = cache::get_cached_state(&repo, &ref_name);
    assert!(cached.is_none());

    // from_ref populates cache
    let state = PatchState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state.title, "Cache patch");
    assert_eq!(state.status, PatchStatus::Open);

    // Cache hit
    let cached: Option<PatchState> = cache::get_cached_state(&repo, &ref_name);
    assert!(cached.is_some());
    assert_eq!(cached.unwrap().title, "Cache patch");
}

#[test]
fn patch_cache_invalidation_on_review() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = create_patch(&repo, &alice(), "Review patch");

    let state1 = PatchState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state1.reviews.len(), 0);

    add_review(&repo, &ref_name, &bob(), ReviewVerdict::Approve);

    let state2 = PatchState::from_ref(&repo, &ref_name, &id).unwrap();
    assert_eq!(state2.reviews.len(), 1);
}

#[test]
fn cache_invalidate_removes_entry() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    let (ref_name, id) = open_issue(&repo, &alice(), "Invalidate test");

    // Populate cache
    IssueState::from_ref(&repo, &ref_name, &id).unwrap();
    assert!(cache::get_cached_state::<IssueState>(&repo, &ref_name).is_some());

    // Explicitly invalidate
    cache::invalidate(&repo, &ref_name);
    assert!(cache::get_cached_state::<IssueState>(&repo, &ref_name).is_none());
}

#[test]
fn list_issues_uses_cache() {
    let dir = TempDir::new().unwrap();
    let repo = init_repo(dir.path(), &alice());
    open_issue(&repo, &alice(), "List issue 1");
    open_issue(&repo, &alice(), "List issue 2");

    // First call walks DAGs and populates cache
    let issues1 = git_collab::state::list_issues(&repo).unwrap();
    assert_eq!(issues1.len(), 2);

    // Second call should use cache
    let issues2 = git_collab::state::list_issues(&repo).unwrap();
    assert_eq!(issues2.len(), 2);

    // Titles should match (order may differ)
    let mut titles1: Vec<_> = issues1.iter().map(|i| &i.title).collect();
    let mut titles2: Vec<_> = issues2.iter().map(|i| &i.title).collect();
    titles1.sort();
    titles2.sort();
    assert_eq!(titles1, titles2);
}