use std::collections::HashMap;
use std::path::{Path, PathBuf};
use textfsm_rust::{CliTable, Index};
fn ntc_templates_path() -> Option<PathBuf> {
std::env::var("NTC_TEMPLATES_DIR")
.ok()
.map(|dir| Path::new(&dir).join("ntc_templates/templates"))
}
fn ntc_index_path() -> Option<PathBuf> {
ntc_templates_path().map(|p| p.join("index"))
}
fn have_ntc_templates() -> bool {
ntc_index_path().is_some_and(|p| p.exists())
}
#[test]
fn test_parse_ntc_index() {
if !have_ntc_templates() {
eprintln!("Skipping test: ntc-templates not found");
return;
}
let index = Index::from_file(ntc_index_path().unwrap()).expect("should parse ntc-templates index");
assert!(index.entries().len() > 500, "ntc-templates should have 500+ entries");
assert!(index.columns().contains(&"Template".to_string()));
assert!(index.columns().contains(&"Platform".to_string()));
assert!(index.columns().contains(&"Command".to_string()));
eprintln!("Parsed {} entries from ntc-templates index", index.entries().len());
}
#[test]
fn test_ntc_template_matching() {
if !have_ntc_templates() {
eprintln!("Skipping test: ntc-templates not found");
return;
}
let index = Index::from_file(ntc_index_path().unwrap()).expect("should parse ntc-templates index");
let test_cases = vec![
("cisco_ios", "show version", "cisco_ios_show_version.textfsm"),
("cisco_ios", "show interfaces", "cisco_ios_show_interfaces.textfsm"),
("cisco_ios", "show ip route", "cisco_ios_show_ip_route.textfsm"),
("cisco_nxos", "show version", "cisco_nxos_show_version.textfsm"),
("arista_eos", "show version", "arista_eos_show_version.textfsm"),
("juniper_junos", "show version", "juniper_junos_show_version.textfsm"),
];
for (platform, command, expected_template) in test_cases {
let mut attrs = HashMap::new();
attrs.insert("Platform".to_string(), platform.to_string());
attrs.insert("Command".to_string(), command.to_string());
let result = index.find_match(&attrs);
assert!(
result.is_some(),
"Should find match for {} '{}'",
platform,
command
);
let entry = result.unwrap();
assert!(
entry.templates().contains(&expected_template.to_string()),
"Expected template {} for {} '{}', got {:?}",
expected_template,
platform,
command,
entry.templates()
);
}
}
#[test]
fn test_ntc_abbreviated_commands() {
if !have_ntc_templates() {
eprintln!("Skipping test: ntc-templates not found");
return;
}
let index = Index::from_file(ntc_index_path().unwrap()).expect("should parse ntc-templates index");
let test_cases = vec![
("cisco_ios", "sh ver"), ("cisco_ios", "sh int"), ("cisco_ios", "sh ip ro"), ("arista_eos", "sh ver"),
];
for (platform, command) in test_cases {
let mut attrs = HashMap::new();
attrs.insert("Platform".to_string(), platform.to_string());
attrs.insert("Command".to_string(), command.to_string());
let result = index.find_match(&attrs);
assert!(
result.is_some(),
"Should find match for abbreviated command {} '{}'",
platform,
command
);
}
}
#[test]
fn test_clitable_with_ntc_templates() {
if !have_ntc_templates() {
eprintln!("Skipping test: ntc-templates not found");
return;
}
let templates_path = ntc_templates_path().unwrap();
let cli_table = CliTable::new(ntc_index_path().unwrap(), &templates_path)
.expect("should create CliTable from ntc-templates");
let mut attrs = HashMap::new();
attrs.insert("Platform".to_string(), "cisco_ios".to_string());
attrs.insert("Command".to_string(), "show version".to_string());
let templates = cli_table.find_templates(&attrs).expect("should find templates");
assert!(!templates.is_empty(), "Should find at least one template");
assert!(
templates[0].to_string_lossy().contains("cisco_ios_show_version.textfsm"),
"Should find cisco_ios_show_version.textfsm"
);
}
#[test]
fn test_parse_cisco_show_version() {
if !have_ntc_templates() {
eprintln!("Skipping test: ntc-templates not found");
return;
}
let templates_path = ntc_templates_path().unwrap();
let cli_table = CliTable::new(ntc_index_path().unwrap(), &templates_path)
.expect("should create CliTable from ntc-templates");
let cli_output = r#"
Cisco IOS Software, C3750 Software (C3750-IPSERVICESK9-M), Version 15.0(2)SE11, RELEASE SOFTWARE (fc3)
Technical Support: http://www.cisco.com/techsupport
Copyright (c) 1986-2017 by Cisco Systems, Inc.
Compiled Sun 01-Oct-17 06:26 by prod_rel_team
ROM: Bootstrap program is C3750 boot loader
BOOTLDR: C3750 Boot Loader (C3750-HBOOT-M) Version 12.2(44)SE5, RELEASE SOFTWARE (fc1)
Switch uptime is 1 year, 2 weeks, 3 days, 4 hours, 5 minutes
System returned to ROM by power-on
System image file is "flash:c3750-ipservicesk9-mz.150-2.SE11.bin"
cisco WS-C3750G-24TS-1U (PowerPC405) processor (revision K0) with 262144K bytes of memory.
Processor board ID CAT1234A5BC
Last reset from power-on
1 Virtual Ethernet interface
24 Gigabit Ethernet interfaces
The password-recovery mechanism is enabled.
512K bytes of flash-simulated non-volatile configuration memory.
Base ethernet MAC Address : 00:1B:2B:3C:4D:5E
Motherboard assembly number : 73-10390-05
Power supply part number : 341-0029-05
Motherboard serial number : CAT12345ABC
Power supply serial number : ABC12345678
Model revision number : K0
Motherboard revision number : B0
Model number : WS-C3750G-24TS-S1U
System serial number : CAT1234A5BC
Top Assembly Part Number : 800-26858-02
Top Assembly Revision Number : D0
Version ID : V04
CLEI Code Number : ABCD1234567
Hardware Board Revision Number : 0x06
"#;
let mut attrs = HashMap::new();
attrs.insert("Platform".to_string(), "cisco_ios".to_string());
attrs.insert("Command".to_string(), "show version".to_string());
let result = cli_table.parse_cmd(cli_output, &attrs);
match result {
Ok(table) => {
eprintln!("Parsed {} rows", table.len());
eprintln!("Headers: {:?}", table.header());
if !table.is_empty() {
eprintln!("First row: {}", table.get(0).unwrap());
}
assert!(!table.is_empty(), "Should parse at least one row from show version output");
}
Err(e) => {
eprintln!("Parse error (may be expected if template format differs): {}", e);
}
}
}
#[test]
fn test_all_templates_count() {
if !have_ntc_templates() {
eprintln!("Skipping test: ntc-templates not found");
return;
}
let index = Index::from_file(ntc_index_path().unwrap()).expect("should parse ntc-templates index");
let all_templates = index.all_templates();
eprintln!("ntc-templates has {} unique templates", all_templates.len());
assert!(
all_templates.len() > 300,
"Expected 300+ unique templates, got {}",
all_templates.len()
);
let expected = vec![
"cisco_ios_show_version.textfsm",
"cisco_nxos_show_version.textfsm",
"arista_eos_show_version.textfsm",
];
for template in expected {
assert!(
all_templates.contains(&template),
"Expected template {} not found",
template
);
}
}