use sdivi_config::PatternsConfig;
use sdivi_parsing::feature_record::{FeatureRecord, PatternHint};
use sdivi_patterns::build_catalog;
use std::path::PathBuf;
fn go_hint(node_kind: &str, text: &str, row: usize) -> PatternHint {
PatternHint {
node_kind: node_kind.to_string(),
start_byte: 0,
end_byte: text.len(),
start_row: row,
start_col: 0,
text: text.to_string(),
}
}
fn go_record(file: &str, hints: Vec<PatternHint>) -> FeatureRecord {
FeatureRecord {
path: PathBuf::from(file),
language: "go".to_string(),
imports: vec!["fmt".to_string()],
exports: vec![],
signatures: vec![],
pattern_hints: hints,
}
}
fn catalog_config_min1() -> PatternsConfig {
PatternsConfig {
min_pattern_nodes: 1,
..PatternsConfig::default()
}
}
#[test]
fn go_fmt_println_routes_to_logging_in_catalog() {
let records = vec![go_record(
"main.go",
vec![go_hint("call_expression", "fmt.Println(os.Args)", 10)],
)];
let catalog = build_catalog(&records, &catalog_config_min1());
assert!(
catalog.entries.contains_key("logging"),
"build_catalog must produce a `logging` bucket for Go fmt.Println calls (M33). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
let log_total: u32 = catalog.entries["logging"].values().map(|s| s.count).sum();
assert_eq!(
log_total, 1,
"logging bucket must contain exactly 1 instance for fmt.Println, got {log_total}"
);
assert!(
!catalog.entries.contains_key("data_access"),
"fmt.Println must NOT produce a data_access entry in M33; it routes to logging. \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn go_fmt_printf_routes_to_logging_in_catalog() {
let records = vec![go_record(
"main.go",
vec![go_hint("call_expression", "fmt.Printf(\"%v\", x)", 5)],
)];
let catalog = build_catalog(&records, &catalog_config_min1());
assert!(
catalog.entries.contains_key("logging"),
"build_catalog must produce a `logging` bucket for Go fmt.Printf calls (M33). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
assert!(
!catalog.entries.contains_key("data_access"),
"fmt.Printf must NOT produce a data_access entry — it routes to logging via GO_RE"
);
}
#[test]
fn go_db_query_routes_to_data_access_in_catalog() {
let records = vec![go_record(
"repo.go",
vec![go_hint("call_expression", "db.query(sql)", 20)],
)];
let catalog = build_catalog(&records, &catalog_config_min1());
assert!(
catalog.entries.contains_key("data_access"),
"build_catalog must produce a `data_access` bucket for Go db.query calls (M33). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
let da_total: u32 = catalog.entries["data_access"]
.values()
.map(|s| s.count)
.sum();
assert_eq!(
da_total, 1,
"data_access bucket must contain exactly 1 instance for db.query, got {da_total}"
);
assert!(
!catalog.entries.contains_key("logging"),
"db.query must NOT produce a logging entry — it routes to data_access via TS_JS_GO_RE"
);
}
#[test]
fn go_nonmatching_call_is_dropped_from_catalog() {
let records = vec![go_record(
"main.go",
vec![go_hint("call_expression", "os.Exit(1)", 3)],
)];
let catalog = build_catalog(&records, &catalog_config_min1());
assert!(
!catalog.entries.contains_key("logging"),
"os.Exit(1) must NOT appear in logging — it matches no logging regex for Go. \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
assert!(
!catalog.entries.contains_key("data_access"),
"os.Exit(1) must NOT appear in data_access — it matches no data_access regex for Go. \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
assert!(
catalog.entries.is_empty(),
"os.Exit(1) should produce an empty catalog (dropped hint). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn go_strings_join_is_dropped_from_catalog() {
let records = vec![go_record(
"util.go",
vec![go_hint("call_expression", "strings.Join(parts, \", \")", 7)],
)];
let catalog = build_catalog(&records, &catalog_config_min1());
assert!(
catalog.entries.is_empty(),
"strings.Join must produce an empty catalog (no matching regex for Go). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn go_mixed_calls_route_correctly_by_callee_in_catalog() {
let records = vec![go_record(
"app.go",
vec![
go_hint("call_expression", "fmt.Println(\"handled:\", event)", 10),
go_hint("call_expression", "fmt.Printf(\"%v\", x)", 11),
go_hint("call_expression", "db.Query(ctx, sql)", 15),
go_hint("call_expression", "sql.Open(\"postgres\", dsn)", 16),
go_hint("call_expression", "os.Exit(1)", 20),
go_hint("call_expression", "strings.Join(parts, \", \")", 21),
],
)];
let catalog = build_catalog(&records, &catalog_config_min1());
assert!(
catalog.entries.contains_key("logging"),
"logging bucket must be present for Go fmt.Print* calls (M33 acceptance criterion). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
assert!(
catalog.entries.contains_key("data_access"),
"data_access bucket must be present for Go db.*/sql.* calls (M33 acceptance criterion). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
let log_total: u32 = catalog.entries["logging"].values().map(|s| s.count).sum();
let da_total: u32 = catalog.entries["data_access"]
.values()
.map(|s| s.count)
.sum();
assert_eq!(
log_total, 2,
"logging must contain 2 instances (fmt.Println + fmt.Printf), got {log_total}"
);
assert_eq!(
da_total, 2,
"data_access must contain 2 instances (db.Query + sql.Open), got {da_total}"
);
let total_classified = log_total + da_total;
assert_eq!(
total_classified, 4,
"total classified instances must be 4 (2 logging + 2 data_access); \
os.Exit and strings.Join must be dropped. Got {total_classified}."
);
}
#[test]
fn go_fmt_errorf_routes_to_logging_not_data_access() {
let records = vec![go_record(
"err.go",
vec![go_hint(
"call_expression",
"fmt.Errorf(\"bad: %w\", err)",
5,
)],
)];
let catalog = build_catalog(&records, &catalog_config_min1());
assert!(
catalog.entries.contains_key("logging"),
"fmt.Errorf must route to logging (matches GO_RE ^fmt\\.Errorf). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
assert!(
!catalog.entries.contains_key("data_access"),
"fmt.Errorf must NOT appear in data_access (M33 routes it to logging). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn go_sql_open_routes_to_data_access_in_catalog() {
let records = vec![go_record(
"db.go",
vec![go_hint("call_expression", "sql.Open(\"postgres\", dsn)", 3)],
)];
let catalog = build_catalog(&records, &catalog_config_min1());
assert!(
catalog.entries.contains_key("data_access"),
"sql.Open must produce a data_access entry (matches TS_JS_GO_RE \\b(db|sql)\\.). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
assert!(
!catalog.entries.contains_key("logging"),
"sql.Open must NOT appear in logging. \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}
#[test]
fn go_min_pattern_nodes_filter_drops_single_instance_logging() {
let records = vec![go_record(
"main.go",
vec![go_hint("call_expression", "fmt.Println(\"x\")", 1)],
)];
let config = PatternsConfig {
min_pattern_nodes: 2, ..PatternsConfig::default()
};
let catalog = build_catalog(&records, &config);
assert!(
!catalog.entries.contains_key("logging"),
"logging must be absent when the only fmt.Println instance count (1) < min_pattern_nodes (2). \
Present categories: {:?}",
catalog.entries.keys().collect::<Vec<_>>()
);
}