relay-knowledge 1.1.16

Graph-database-based knowledge graph project.
Documentation
//! Route-projection tests for route-to-handler association.

use crate::domain::CodeRepositoryRegistration;

use super::*;
use std::collections::BTreeSet;

#[test]
fn record_routes_links_records_to_handler_symbols() {
    let mut build = route_test_build();
    build.symbols.push(route_symbol("list-users-symbol", 3, 5));

    record_routes(
        &mut build,
        "src/routes.ts",
        "routes-file",
        "typescript",
        "app.get('/users', listUsers);\napp.post('/users', listUsers);\nfunction listUsers() {}\n",
    );

    assert_eq!(build.routes.len(), 2);
    assert!(
        build
            .routes
            .iter()
            .all(|route| route.handler_symbol_snapshot_id.as_deref() == Some("list-users-symbol"))
    );
    let Some(SymbolRole::RouteHandlers { routes }) = &build.symbols[0].symbol_role else {
        panic!("shared handler should preserve every route role");
    };
    assert!(
        routes
            .iter()
            .any(|route| route.url == "/users" && route.http_method == "get")
    );
    assert!(
        routes
            .iter()
            .any(|route| route.url == "/users" && route.http_method == "post")
    );
}

#[test]
fn record_routes_links_handler_symbols_declared_before_route_registration() {
    let mut build = route_test_build();
    build.symbols.push(route_symbol("before-route", 1, 1));

    record_routes(
        &mut build,
        "src/routes.ts",
        "routes-file",
        "typescript",
        "function listUsers() {}\napp.get('/users', listUsers);\n",
    );

    assert_eq!(
        build.routes[0].handler_symbol_snapshot_id.as_deref(),
        Some("before-route")
    );
    assert!(build.symbols[0].symbol_role.is_some());
}

#[test]
fn record_routes_leaves_anonymous_callbacks_unresolved() {
    let mut build = route_test_build();
    let mut symbol = route_symbol("anonymous-symbol", 1, 1);
    symbol.name = "anonymous".to_owned();
    build.symbols.push(symbol);

    record_routes(
        &mut build,
        "src/routes.ts",
        "routes-file",
        "typescript",
        "app.get('/health', (req, res) => res.end());\n",
    );

    assert!(build.routes[0].handler_symbol_snapshot_id.is_none());
    assert!(build.symbols[0].symbol_role.is_none());
}

#[test]
fn record_routes_links_member_expression_handlers_by_leaf_name() {
    let mut build = route_test_build();
    let mut symbol = route_symbol("bare-list-symbol", 3, 3);
    symbol.name = "list".to_owned();
    symbol.qualified_name = "list".to_owned();
    symbol.signature = "function list()".to_owned();
    build.symbols.push(symbol);

    record_routes(
        &mut build,
        "src/routes.ts",
        "routes-file",
        "typescript",
        "router.get('/users', usersController.list);\nfunction list() {}\n",
    );

    assert_eq!(build.routes[0].handler_name, "usersController.list");
    assert_eq!(
        build.routes[0].handler_symbol_snapshot_id.as_deref(),
        Some("bare-list-symbol")
    );
    assert!(build.symbols[0].symbol_role.is_some());
}

#[test]
fn record_routes_keeps_same_line_route_chain_ids_distinct() {
    let mut build = route_test_build();

    record_routes(
        &mut build,
        "src/routes.ts",
        "routes-file",
        "typescript",
        "router.route('/health').get(requireAuth).get(health);\n",
    );

    assert_eq!(build.routes.len(), 2);
    assert!(build.routes.iter().any(|route| {
        route.url == "/health" && route.http_method == "get" && route.handler_name == "requireAuth"
    }));
    assert!(build.routes.iter().any(|route| {
        route.url == "/health" && route.http_method == "get" && route.handler_name == "health"
    }));
    let route_ids = build
        .routes
        .iter()
        .map(|route| route.route_id.as_str())
        .collect::<BTreeSet<_>>();
    assert_eq!(route_ids.len(), build.routes.len());
}

fn route_test_build() -> SnapshotBuild {
    let registration =
        CodeRepositoryRegistration::new("repo", "fixture", "/tmp/repo", Vec::new(), Vec::new())
            .expect("registration should validate");
    SnapshotBuild::new(
        &registration,
        "commit".to_owned(),
        "tree".to_owned(),
        true,
        1,
        0,
    )
}

fn route_symbol(
    symbol_snapshot_id: &str,
    line_start: u32,
    line_end: u32,
) -> RepositoryCodeSymbolRecord {
    RepositoryCodeSymbolRecord {
        repository_id: "repo".to_owned(),
        source_scope: "scope".to_owned(),
        symbol_snapshot_id: symbol_snapshot_id.to_owned(),
        canonical_symbol_id: "repo://repo/src::routes.ts::listUsers".to_owned(),
        file_id: "routes-file".to_owned(),
        path: "src/routes.ts".to_owned(),
        language_id: "typescript".to_owned(),
        name: "listUsers".to_owned(),
        qualified_name: "listUsers".to_owned(),
        kind: "function".to_owned(),
        signature: "function listUsers()".to_owned(),
        doc_comment: None,
        byte_range: RepositoryCodeRange { start: 0, end: 1 },
        line_range: RepositoryCodeRange {
            start: line_start,
            end: line_end,
        },
        symbol_role: None,
    }
}