use super::persist::{
    deserialize_definition, escape_newlines, serialize_definition,
    unescape_newlines,
};
use super::*;
use crate::hash::ContentHash;

fn parse_file(source: &str) -> ast::File {
    crate::parse_source_silent(source, "test.tri").unwrap()
}

#[test]
fn test_add_file_creates_definitions_and_names() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file(
        "program test\nfn helper(x: Field) -> Field { x + 1 }\nfn main() { pub_write(helper(pub_read())) }\n",
    );
    let result = cb.add_file(&file);

    assert_eq!(result.added, 2);
    assert_eq!(result.updated, 0);
    assert_eq!(result.unchanged, 0);

    // Both names should exist.
    assert!(cb.lookup("helper").is_some());
    assert!(cb.lookup("main").is_some());

    // Stats should reflect 2 definitions and 2 names.
    let stats = cb.stats();
    assert_eq!(stats.definitions, 2);
    assert_eq!(stats.names, 2);
    assert!(stats.total_source_bytes > 0);
}

#[test]
fn test_lookup_by_name() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file =
        parse_file("program test\nfn add(a: Field, b: Field) -> Field { a + b }\nfn main() { }\n");
    cb.add_file(&file);

    let def = cb.lookup("add").unwrap();
    assert!(def.source.contains("fn add"));
    assert_eq!(def.params.len(), 2);
    assert_eq!(def.params[0].0, "a");
    assert_eq!(def.params[0].1, "Field");
    assert_eq!(def.return_ty.as_deref(), Some("Field"));
    assert!(!def.is_pub);
}

#[test]
fn test_rename() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\nfn old_name(x: Field) -> Field { x }\nfn main() { }\n");
    cb.add_file(&file);

    assert!(cb.lookup("old_name").is_some());
    cb.rename("old_name", "new_name").unwrap();

    assert!(cb.lookup("old_name").is_none());
    assert!(cb.lookup("new_name").is_some());

    // Both old and new name should appear in history.
    let history = cb.name_history("new_name");
    assert!(!history.is_empty());
}

#[test]
fn test_alias() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\nfn original(x: Field) -> Field { x }\nfn main() { }\n");
    cb.add_file(&file);

    cb.alias("original", "shortcut").unwrap();

    // Both names should resolve to the same hash.
    let hash_orig = cb.names.get("original").unwrap();
    let hash_alias = cb.names.get("shortcut").unwrap();
    assert_eq!(hash_orig, hash_alias);

    // Both names should appear in names_for_hash.
    let names = cb.names_for_hash(hash_orig);
    assert!(names.contains(&"original"));
    assert!(names.contains(&"shortcut"));
}

#[test]
fn test_add_updated_file_rebinds_name() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    // First version.
    let file1 = parse_file("program test\nfn helper(x: Field) -> Field { x + 1 }\nfn main() { }\n");
    cb.add_file(&file1);
    let hash1 = *cb.names.get("helper").unwrap();

    // Updated version (different body).
    let file2 = parse_file("program test\nfn helper(x: Field) -> Field { x + 2 }\nfn main() { }\n");
    let result = cb.add_file(&file2);

    let hash2 = *cb.names.get("helper").unwrap();
    assert_ne!(hash1, hash2, "hash should change when body changes");
    assert!(result.updated >= 1);

    // History should have both entries.
    let history = cb.name_history("helper");
    assert!(history.len() >= 2);
}

#[test]
fn test_stats_empty() {
    let tmp = tempfile::tempdir().unwrap();
    let cb = Codebase::open_at(tmp.path()).unwrap();

    let stats = cb.stats();
    assert_eq!(stats.definitions, 0);
    assert_eq!(stats.names, 0);
    assert_eq!(stats.total_source_bytes, 0);
}

#[test]
fn test_empty_lookups_return_none() {
    let tmp = tempfile::tempdir().unwrap();
    let cb = Codebase::open_at(tmp.path()).unwrap();

    assert!(cb.lookup("nonexistent").is_none());
    assert!(cb.lookup_hash(&ContentHash::zero()).is_none());
    assert!(cb.view("nonexistent").is_none());
}

#[test]
fn test_persistence_save_and_reload() {
    let tmp = tempfile::tempdir().unwrap();

    // Create and populate a codebase.
    {
        let mut cb = Codebase::open_at(tmp.path()).unwrap();
        let file = parse_file(
            "program test\npub fn add(a: Field, b: Field) -> Field { a + b }\nfn main() { pub_write(add(pub_read(), pub_read())) }\n",
        );
        cb.add_file(&file);
        cb.save().unwrap();
    }

    // Reload from disk.
    {
        let cb = Codebase::open_at(tmp.path()).unwrap();
        assert_eq!(cb.stats().definitions, 2);
        assert_eq!(cb.stats().names, 2);

        let def = cb.lookup("add").unwrap();
        assert!(def.source.contains("fn add"));
        assert_eq!(def.module, "test");
        assert!(def.is_pub);
        assert_eq!(def.params.len(), 2);
        assert_eq!(def.return_ty.as_deref(), Some("Field"));
    }
}

#[test]
fn test_dependencies_extracted() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file(
        "program test\nfn helper(x: Field) -> Field { x + 1 }\nfn main() { pub_write(helper(pub_read())) }\n",
    );
    cb.add_file(&file);

    let main_hash = cb.names.get("main").unwrap();
    let main_def = cb.lookup_hash(main_hash).unwrap();

    // main should depend on helper.
    let helper_hash = cb.names.get("helper").unwrap();
    assert!(
        main_def.dependencies.contains(helper_hash),
        "main should depend on helper"
    );

    // helper should have no dependencies (only calls builtins).
    let helper_def = cb.lookup_hash(helper_hash).unwrap();
    assert!(
        helper_def.dependencies.is_empty(),
        "helper should have no function deps"
    );
}

#[test]
fn test_dependents() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file(
        "program test\nfn helper(x: Field) -> Field { x + 1 }\nfn main() { pub_write(helper(pub_read())) }\n",
    );
    cb.add_file(&file);

    let helper_hash = cb.names.get("helper").unwrap();
    let dependents = cb.dependents(helper_hash);
    assert!(
        dependents.iter().any(|(name, _)| *name == "main"),
        "main should be a dependent of helper"
    );
}

#[test]
fn test_view_output() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\nfn id(x: Field) -> Field { x }\nfn main() { }\n");
    cb.add_file(&file);

    let view = cb.view("id").unwrap();
    assert!(view.contains("-- id #"));
    assert!(view.contains("fn id"));
}

#[test]
fn test_rename_errors() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\nfn a() { }\nfn b() { }\nfn main() { }\n");
    cb.add_file(&file);

    // Rename nonexistent.
    assert!(cb.rename("nonexistent", "c").is_err());

    // Rename to existing name.
    assert!(cb.rename("a", "b").is_err());
}

#[test]
fn test_alias_errors() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\nfn a() { }\nfn b() { }\nfn main() { }\n");
    cb.add_file(&file);

    // Alias nonexistent.
    assert!(cb.alias("nonexistent", "c").is_err());

    // Alias to existing name.
    assert!(cb.alias("a", "b").is_err());
}

#[test]
fn test_escape_unescape_roundtrip() {
    let original = "fn main() {\n    pub_write(pub_read())\n}";
    let escaped = escape_newlines(original);
    assert!(!escaped.contains('\n'));
    let unescaped = unescape_newlines(&escaped);
    assert_eq!(unescaped, original);
}

#[test]
fn test_escape_backslashes() {
    let original = "line1\\nstill_line1\nline2";
    let escaped = escape_newlines(original);
    let unescaped = unescape_newlines(&escaped);
    assert_eq!(unescaped, original);
}

#[test]
fn test_parse_hex_hash() {
    let hash = ContentHash([0xAB; 32]);
    let hex = hash.to_hex();
    let parsed = ContentHash::from_hex(&hex).unwrap();
    assert_eq!(parsed, hash);

    // Invalid: too short.
    assert!(ContentHash::from_hex("abcd").is_none());
    // Invalid: wrong chars.
    assert!(ContentHash::from_hex(&"zz".repeat(32)).is_none());
}

#[test]
fn test_definition_serialization_roundtrip() {
    let def = Definition {
        source: "fn add(a: Field, b: Field) -> Field {\n    a + b\n}".to_string(),
        module: "test".to_string(),
        is_pub: true,
        params: vec![
            ("a".to_string(), "Field".to_string()),
            ("b".to_string(), "Field".to_string()),
        ],
        return_ty: Some("Field".to_string()),
        dependencies: vec![ContentHash([0x01; 32])],
        requires: vec!["a > 0".to_string()],
        ensures: vec!["result == a + b".to_string()],
        first_seen: 1707580000,
    };

    let serialized = serialize_definition(&def);
    let deserialized = deserialize_definition(&serialized).unwrap();

    assert_eq!(deserialized.source, def.source);
    assert_eq!(deserialized.module, def.module);
    assert_eq!(deserialized.is_pub, def.is_pub);
    assert_eq!(deserialized.params, def.params);
    assert_eq!(deserialized.return_ty, def.return_ty);
    assert_eq!(deserialized.dependencies.len(), 1);
    assert_eq!(deserialized.dependencies[0], ContentHash([0x01; 32]));
    assert_eq!(deserialized.requires, def.requires);
    assert_eq!(deserialized.ensures, def.ensures);
    assert_eq!(deserialized.first_seen, def.first_seen);
}

#[test]
fn test_lookup_by_prefix() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\nfn id(x: Field) -> Field { x }\nfn main() { }\n");
    cb.add_file(&file);

    let hash = *cb.names.get("id").unwrap();
    let hex = hash.to_hex();

    // Lookup by hex prefix.
    let (found_hash, _) = cb.lookup_by_prefix(&hex[..8]).unwrap();
    assert_eq!(*found_hash, hash);

    // Lookup by short hash.
    let short = hash.to_short();
    let (found_hash, _) = cb.lookup_by_prefix(&short).unwrap();
    assert_eq!(*found_hash, hash);
}

#[test]
fn test_unchanged_on_readd() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\nfn id(x: Field) -> Field { x }\nfn main() { }\n");
    cb.add_file(&file);

    // Adding the same file again should show unchanged.
    let result = cb.add_file(&file);
    assert_eq!(result.added, 0);
    assert_eq!(result.updated, 0);
    assert_eq!(result.unchanged, 2);
}

#[test]
fn test_list_names_sorted() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\nfn zzz() { }\nfn aaa() { }\nfn main() { }\n");
    cb.add_file(&file);

    let names: Vec<&str> = cb.list_names().iter().map(|(n, _)| *n).collect();
    assert_eq!(names, vec!["aaa", "main", "zzz"]);
}

#[test]
fn test_pub_function_stored() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file("program test\npub fn visible(x: Field) -> Field { x }\nfn main() { }\n");
    cb.add_file(&file);

    let def = cb.lookup("visible").unwrap();
    assert!(def.is_pub);
}

#[test]
fn test_spec_annotations_stored() {
    let tmp = tempfile::tempdir().unwrap();
    let mut cb = Codebase::open_at(tmp.path()).unwrap();

    let file = parse_file(
        "program test\n#[requires(a > 0)]\n#[ensures(result == a + 1)]\nfn inc(a: Field) -> Field { a + 1 }\nfn main() { }\n",
    );
    cb.add_file(&file);

    let def = cb.lookup("inc").unwrap();
    assert_eq!(def.requires, vec!["a > 0"]);
    assert_eq!(def.ensures, vec!["result == a + 1"]);
}

Dimensions

trident/src/deploy/tests.rs
cw-cyber/contracts/hub-skills/src/tests.rs
trident/src/lsp/semantic/tests.rs
trident/src/syntax/format/tests.rs
cw-cyber/contracts/hub-libs/src/tests.rs
cw-cyber/contracts/hub-channels/src/tests.rs
trident/src/package/registry/tests.rs
trident/src/syntax/lexer/tests.rs
trident/src/cost/stack_verifier/tests.rs
cw-cyber/contracts/hub-networks/src/tests.rs
trident/src/verify/sym/tests.rs
cw-cyber/contracts/cw-cyber-subgraph/src/tests.rs
cw-cyber/contracts/cw-cyber-gift/src/tests.rs
trident/src/verify/report/tests.rs
cw-cyber/contracts/hub-tokens/src/tests.rs
trident/src/config/scaffold/tests.rs
trident/src/verify/solve/tests.rs
trident/src/verify/smt/tests.rs
cw-cyber/contracts/graph-filter/src/tests.rs
trident/src/package/manifest/tests.rs
trident/src/verify/synthesize/tests.rs
cw-cyber/contracts/cw-cyber-passport/src/tests.rs
trident/src/verify/equiv/tests.rs
trident/src/lsp/util/tests.rs
trident/src/config/resolve/tests.rs
trident/src/package/hash/tests.rs
trident/src/ir/lir/tests.rs
cw-cyber/contracts/hub-protocols/src/tests.rs
trident/src/syntax/grammar/tests.rs
trident/src/ir/tir/optimize/tests.rs
trident/src/neural/data/tir_graph/tests.rs
trident/src/ir/tir/lower/tests.rs
trident/src/ir/tir/stack/tests.rs

Local Graph