// Copyright (C) 2025  The Software Heritage developers
// See the AUTHORS file at the top-level directory of this distribution
// License: GNU General Public License version 3, or any later version
// See top-level LICENSE file for more information

// Test cases adapted from Charly Reux's and Romain Lefeuvre's Java tests

use anyhow::{Context, Result, bail};
use itertools::Itertools;
use osv::schema::{Affected, Event, Range, RangeType, Vulnerability};
use swh_graph::graph::NodeId;
use swh_graph::graph_builder::{BuiltGraph, GraphBuilder};
use swh_graph::swhid;

use swh_osv::git::*;

#[path = "./graphs.rs"]
mod graphs;

/// ```
/// rev:301 <- rev:302 <- rev:303
///                ^
///                 \
///                  +--- rev:304
/// ```
fn checkout_graph() -> BuiltGraph {
    let mut builder = GraphBuilder::default();

    // placeholder to make numbers of actual nodes start at 1 like in the original test cases
    let s0 = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000300))
        .unwrap()
        .done();

    let r1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000301))
        .unwrap()
        .done();
    let r2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000302))
        .unwrap()
        .done();
    let r3 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000303))
        .unwrap()
        .done();
    let r4 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000304))
        .unwrap()
        .done();
    builder.arc(r2, r1);
    builder.arc(r3, r2);
    builder.arc(r4, r2);
    builder.arc(s0, r4);
    builder.done().unwrap()
}

/// ```
/// rev:301 <- rev:302 <- rev:304
///     ^
///      \
///       +--- rev:303
/// ```
fn checkout_early_graph() -> BuiltGraph {
    let mut builder = GraphBuilder::default();

    // placeholder to make numbers of actual nodes start at 1 like in the original test cases
    let s0 = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000300))
        .unwrap()
        .done();

    let r1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000301))
        .unwrap()
        .done();
    let r2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000302))
        .unwrap()
        .done();
    let r3 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000303))
        .unwrap()
        .done();
    let r4 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000304))
        .unwrap()
        .done();
    builder.arc(r2, r1);
    builder.arc(r3, r1);
    builder.arc(r4, r2);
    builder.arc(s0, r4);
    builder.done().unwrap()
}

/// ```
/// rev:301 <- rev:302 <- rev:303
/// ```
fn linear_graph() -> BuiltGraph {
    let mut builder = GraphBuilder::default();

    // placeholder to make numbers of actual nodes start at 1 like in the original test cases
    let s0 = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000300))
        .unwrap()
        .done();

    let r1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000301))
        .unwrap()
        .done();
    let r2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000302))
        .unwrap()
        .done();
    let r3 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000303))
        .unwrap()
        .done();
    builder.arc(r2, r1);
    builder.arc(r3, r2);
    builder.arc(s0, r3);
    builder.done().unwrap()
}

/// ```
/// rev:301 <- rev:302 <- rev:303 <- rev:304
/// ```
fn linear4_graph() -> BuiltGraph {
    let mut builder = GraphBuilder::default();

    // placeholder to make numbers of actual nodes start at 1 like in the original test cases
    let s0 = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000300))
        .unwrap()
        .done();

    let r1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000301))
        .unwrap()
        .done();
    let r2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000302))
        .unwrap()
        .done();
    let r3 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000303))
        .unwrap()
        .done();
    let r4 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000304))
        .unwrap()
        .done();
    builder.arc(r2, r1);
    builder.arc(r3, r2);
    builder.arc(r4, r3);
    builder.arc(s0, r4);
    builder.done().unwrap()
}

/// ```
/// rev:301 <- rev:303 <- rev:304
///               /
///              /
/// rev:302 <---+
/// ```
fn merge_graph() -> BuiltGraph {
    let mut builder = GraphBuilder::default();

    // placeholder to make numbers of actual nodes start at 1 like in the original test cases
    let s0 = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000300))
        .unwrap()
        .done();

    let r1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000301))
        .unwrap()
        .done();
    let r2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000302))
        .unwrap()
        .done();
    let r3 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000303))
        .unwrap()
        .done();
    let r4 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000304))
        .unwrap()
        .done();
    builder.arc(r3, r1);
    builder.arc(r3, r2);
    builder.arc(r4, r3);
    builder.arc(s0, r4);
    builder.done().unwrap()
}

/// ```
/// rev:301 <- rev:302 <- rev:304 <- rev:305
///                          /
///                         /
/// rev:303 <--------------+
/// ```
fn merge4_graph() -> BuiltGraph {
    let mut builder = GraphBuilder::default();

    // placeholder to make numbers of actual nodes start at 1 like in the original test cases
    let s0 = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000300))
        .unwrap()
        .done();

    let r1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000301))
        .unwrap()
        .done();
    let r2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000302))
        .unwrap()
        .done();
    let r3 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000303))
        .unwrap()
        .done();
    let r4 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000304))
        .unwrap()
        .done();
    let r5 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000305))
        .unwrap()
        .done();
    builder.arc(r2, r1);
    builder.arc(r4, r2);
    builder.arc(r5, r3);
    builder.arc(r5, r4);
    builder.arc(s0, r5);
    builder.done().unwrap()
}

fn make_vulnerability(events: Vec<Event>) -> (Vulnerability, Range) {
    let range = Range {
        range_type: RangeType::Git,
        repo: None,
        events,
    };
    (
        Vulnerability {
            schema_version: None,
            id: "EXAMPLE-001".to_string(),
            published: None,
            modified: Default::default(),
            withdrawn: None,
            aliases: None,
            related: None,
            summary: None,
            details: None,
            affected: Some(vec![Affected {
                package: None,
                severity: None,
                ranges: Some(vec![range.clone()]),
                versions: None,
                ecosystem_specific: None,
                database_specific: None,
            }]),
            upstream: None,
            references: None,
            severity: None,
            credits: None,
            database_specific: None,
        },
        range,
    )
}

fn check_process_git_range<const N: usize>(
    graph: &BuiltGraph,
    vuln: &Vulnerability,
    range: &Range,
    expected_nodes: [NodeId; N],
) -> Result<()> {
    let stats = Default::default();

    let got_nodes = list_commits_in_git_range(
        "EXAMPLE-001.json",
        vuln,
        range,
        &GitRangeParser::new(&graph),
        &stats,
    )
    .context("Error while evaluating")?;
    let Some(got_nodes) = got_nodes else {
        bail!("expand_git_range returned None. {stats:#?}");
    };
    let got_nodes = got_nodes.into_iter().sorted().collect::<Vec<_>>();
    let expected_nodes = expected_nodes.into_iter().collect::<Vec<_>>();
    if got_nodes != expected_nodes {
        bail!("Expected vulnerable nodes to be {expected_nodes:?}, got {got_nodes:?}. {stats:#?}",);
    }

    Ok(())
}

/****************************************
 * introduced + fixed
 ****************************************/

#[test]
fn introduced_fixed_checkout() -> Result<()> {
    let graph = checkout_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Fixed("0000000000000000000000000000000000000303".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 2, 4])
}

#[test]
fn introduced_fixed_linear() -> Result<()> {
    let graph = linear_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Fixed("0000000000000000000000000000000000000303".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 2])
}

#[test]
fn introduced_fixed_linear4() -> Result<()> {
    let graph = linear4_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Fixed("0000000000000000000000000000000000000302".into()),
        Event::Introduced("0000000000000000000000000000000000000303".into()),
        Event::Fixed("0000000000000000000000000000000000000304".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 3])
}

#[test]
fn introduced_fixed_merge() -> Result<()> {
    let graph = merge_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Fixed("0000000000000000000000000000000000000304".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 3])
}

#[test]
fn introduced_fixed_merge4() -> Result<()> {
    let graph = merge4_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Fixed("0000000000000000000000000000000000000302".into()),
        Event::Introduced("0000000000000000000000000000000000000303".into()),
        Event::Fixed("0000000000000000000000000000000000000305".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 3])
}

/****************************************
 * introduced + limit
 ****************************************/

#[test]
fn introduced_limit_checkout() -> Result<()> {
    let graph = checkout_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Limit("0000000000000000000000000000000000000303".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 2])
}

#[test]
fn introduced_limit_linear() -> Result<()> {
    let graph = linear_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Limit("0000000000000000000000000000000000000303".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 2])
}

#[test]
fn introduced_limit_linear4() -> Result<()> {
    let graph = linear4_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Limit("0000000000000000000000000000000000000302".into()),
        Event::Introduced("0000000000000000000000000000000000000303".into()),
        Event::Limit("0000000000000000000000000000000000000304".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 3])
}

#[test]
fn introduced_limit_merge() -> Result<()> {
    let graph = merge_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Limit("0000000000000000000000000000000000000304".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 3])
}

/****************************************
 * introduced + last_affected
 ****************************************/

#[test]
fn introduced_last_affected_checkout() -> Result<()> {
    let graph = checkout_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::LastAffected("0000000000000000000000000000000000000303".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 2, 3, 4])
}

#[test]
fn introduced_last_affected_linear() -> Result<()> {
    let graph = linear_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::LastAffected("0000000000000000000000000000000000000303".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 2, 3])
}

#[test]
fn introduced_last_affected_linear4() -> Result<()> {
    let graph = linear4_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::LastAffected("0000000000000000000000000000000000000302".into()),
        Event::Introduced("0000000000000000000000000000000000000303".into()),
        Event::LastAffected("0000000000000000000000000000000000000304".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 2, 3, 4])
}

#[test]
fn introduced_last_affected_merge() -> Result<()> {
    let graph = merge_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::LastAffected("0000000000000000000000000000000000000304".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1, 3, 4])
}

/****************************************
 * introduced + limit + fixed
 ****************************************/

#[test]
fn introduced_limit_fixed_checkout_early() -> Result<()> {
    let graph = checkout_early_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Limit("0000000000000000000000000000000000000302".into()),
        Event::Fixed("0000000000000000000000000000000000000304".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1])
}

#[test]
fn introduced_limit_fixed_linear_lf() -> Result<()> {
    let graph = linear_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Limit("0000000000000000000000000000000000000302".into()),
        Event::Fixed("0000000000000000000000000000000000000303".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1])
}

#[test]
fn introduced_limit_fixed_linear_fl() -> Result<()> {
    let graph = linear_graph();
    let (vuln, range) = make_vulnerability(vec![
        Event::Introduced("0000000000000000000000000000000000000301".into()),
        Event::Fixed("0000000000000000000000000000000000000302".into()),
        Event::Limit("0000000000000000000000000000000000000303".into()),
    ]);

    check_process_git_range(&graph, &vuln, &range, [1])
}

/****************************************
 * tags
 ****************************************/

/// ```
/// rel:001 (v1.0.0) --> rev:001
///                        ^
///                        |
/// snp:020 ---------------+--- refs/tags/snapshot-tag-1.0
///   |                    |
///   |                    +--- refs/tags/snapshot-tag-1.1
///   |                    |
///   |                    +--- refs/heads/main
///   |
///   +--------------------+--- refs/tags/snapshot-tag-2.0
///                        |
///                        v
/// rel:002 (v2.0.0) --> rev:002
/// ```
fn tag_test_graph() -> BuiltGraph {
    let mut builder = GraphBuilder::default();

    let snp = builder
        .node(swhid!(swh:1:snp:0000000000000000000000000000000000000020))
        .unwrap()
        .done();

    let rev1 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .unwrap()
        .done();

    let rev2 = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000002))
        .unwrap()
        .done();

    // Release pointing to rev1 with tag name "v1.0.0"
    let rel1 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000001))
        .unwrap()
        .tag_name(b"v1.0.0".to_vec())
        .done();
    builder.arc(rel1, rev1);

    // Release pointing to rev2 with tag name "v2.0.0"
    let rel2 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000002))
        .unwrap()
        .tag_name(b"v2.0.0".to_vec())
        .done();
    builder.arc(rel2, rev2);

    builder.snp_arc(snp, rev1, b"refs/tags/snapshot-tag-1.0");
    builder.snp_arc(snp, rev1, b"refs/tags/snapshot-tag-1.1");
    builder.snp_arc(snp, rev2, b"refs/tags/snapshot-tag-2.0");
    builder.snp_arc(snp, rev1, b"refs/heads/main"); // not a tag

    builder.done().unwrap()
}

#[test]
fn test_get_tag_names_from_release() {
    let graph = tag_test_graph();
    let rev1 = 1; // First revision

    let mut tags = get_tag_names(&graph, rev1);
    tags.sort();

    // Should get tag from release (v1.0.0) and from snapshot branches (snapshot-tag-1.0, snapshot-tag-1.1)
    let expected = vec!["snapshot-tag-1.0", "snapshot-tag-1.1", "v1.0.0"];
    assert_eq!(tags, expected);
}

#[test]
fn test_get_tag_names_from_snapshot() {
    let graph = tag_test_graph();
    let rev2 = 2; // Second revision

    let mut tags = get_tag_names(&graph, rev2);
    tags.sort();

    // Should get tag from release (v2.0.0) and from snapshot branch (snapshot-tag-2.0)
    let expected = vec!["snapshot-tag-2.0", "v2.0.0"];
    assert_eq!(tags, expected);
}

#[test]
fn test_get_tag_names_no_tags() {
    let mut builder = GraphBuilder::default();

    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .unwrap()
        .done();
    let dir = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000011))
        .unwrap()
        .done();

    // Add arc (required by VecGraph)
    builder.arc(rev, dir);

    let graph = builder.done().unwrap();

    let tags = get_tag_names(&graph, rev);
    assert_eq!(tags, Vec::<String>::new());
}

#[test]
fn test_get_tag_names_duplicate_tags() {
    let mut builder = GraphBuilder::default();

    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .unwrap()
        .done();

    // Two releases with the same tag name pointing to the same revision
    let rel1 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000011))
        .unwrap()
        .tag_name(b"v1.0.0".to_vec())
        .done();
    builder.arc(rel1, rev);

    let rel2 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000012))
        .unwrap()
        .tag_name(b"v1.0.0".to_vec())
        .done();
    builder.arc(rel2, rev);

    let graph = builder.done().unwrap();

    let tags = get_tag_names(&graph, rev);
    // Should deduplicate
    assert_eq!(tags, vec!["v1.0.0"]);
}

#[test]
fn test_get_tag_names_release_without_tag_name() {
    let mut builder = GraphBuilder::default();

    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .unwrap()
        .done();

    let rel = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000011))
        .unwrap()
        .done(); // No .tag_name() call
    builder.arc(rel, rev);

    let graph = builder.done().unwrap();

    let tags = get_tag_names(&graph, rev);
    assert_eq!(tags, Vec::<String>::new());
}

#[test]
fn test_get_tag_names_release_not_in_snapshot() {
    let mut builder = GraphBuilder::default();

    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .unwrap()
        .done();

    // Release with tag name but not referenced by any snapshot
    let rel = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000011))
        .unwrap()
        .tag_name(b"v1.0.0".to_vec())
        .done();
    builder.arc(rel, rev);

    // Add a directory to satisfy VecGraph arc requirement
    let dir = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000021))
        .unwrap()
        .done();
    builder.arc(rev, dir);

    let graph = builder.done().unwrap();

    let mut tags = get_tag_names(&graph, rev);
    tags.sort();

    // Should still get tag from release even without snapshot
    let expected = vec!["v1.0.0"];
    assert_eq!(tags, expected);
}

#[test]
fn test_get_tag_names_multiple_releases_same_revision() {
    let mut builder = GraphBuilder::default();

    let rev = builder
        .node(swhid!(swh:1:rev:0000000000000000000000000000000000000001))
        .unwrap()
        .done();

    // Multiple releases pointing to the same revision with different tag names
    let rel1 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000011))
        .unwrap()
        .tag_name(b"v1.0.0".to_vec())
        .done();
    builder.arc(rel1, rev);

    let rel2 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000012))
        .unwrap()
        .tag_name(b"v1.0.1".to_vec())
        .done();
    builder.arc(rel2, rev);

    let rel3 = builder
        .node(swhid!(swh:1:rel:0000000000000000000000000000000000000013))
        .unwrap()
        .tag_name(b"stable".to_vec())
        .done();
    builder.arc(rel3, rev);

    let dir = builder
        .node(swhid!(swh:1:dir:0000000000000000000000000000000000000021))
        .unwrap()
        .done();
    builder.arc(rev, dir);

    let graph = builder.done().unwrap();

    let mut tags = get_tag_names(&graph, rev);
    tags.sort();

    // Should get all tags from all releases
    let expected = vec!["stable", "v1.0.0", "v1.0.1"];
    assert_eq!(tags, expected);
}

#[test]
fn test_get_all_tag_names() {
    let graph = tag_test_graph();

    let revs = vec![1, 2]; // Both revisions
    let mut tags = get_all_tag_names(&graph, revs);
    tags.sort();

    // Should get all tags from both revisions
    let expected = vec![
        "snapshot-tag-1.0",
        "snapshot-tag-1.1",
        "snapshot-tag-2.0",
        "v1.0.0",
        "v2.0.0",
    ];
    assert_eq!(tags, expected);
}

#[test]
fn test_get_all_tag_names_empty() {
    let graph = tag_test_graph();

    let revs: Vec<NodeId> = vec![];
    let tags = get_all_tag_names(&graph, revs);

    assert_eq!(tags, Vec::<String>::new());
}

#[test]
fn test_get_all_tag_names_with_duplicates() {
    let graph = tag_test_graph();

    // Same revision twice
    let revs = vec![1, 1];
    let mut tags = get_all_tag_names(&graph, revs);
    tags.sort();

    // Should return duplicates (get_all_tag_names doesn't deduplicate across revisions)
    let expected = vec![
        "snapshot-tag-1.0",
        "snapshot-tag-1.0",
        "snapshot-tag-1.1",
        "snapshot-tag-1.1",
        "v1.0.0",
        "v1.0.0",
    ];
    assert_eq!(tags, expected);
}

#[test]
fn test_filter_non_merge_revisions_linear() {
    let graph = graphs::get_10rev_linear_graph();

    // Test with rev1, rev2, rev3 (node ids 1, 2, 3)
    let revs = vec![1, 2, 3];
    let mut filtered = filter_non_merge_revisions(&graph, revs.clone());
    filtered.sort();

    assert_eq!(filtered, vec![1, 2, 3]);
}

#[test]
fn test_filter_non_merge_revisions_merge() {
    let graph = graphs::get_merge_graph();

    // Test with rev1, rev2, rev3 (node ids 1, 2, 3)
    // rev3 is a merge of rev1 and rev2, so it should be filtered out
    let revs = vec![1, 2, 3];
    let mut filtered = filter_non_merge_revisions(&graph, revs);
    filtered.sort();

    assert_eq!(filtered, vec![1, 2]);
}
