|
| 1 | +// SPDX-License-Identifier: PMPL-1.0-or-later |
| 2 | +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> |
| 3 | + |
| 4 | +//! Unit tests for core types — Language detection, family classification, |
| 5 | +//! WeakPointCategory coverage, and serialization contracts. |
| 6 | +
|
| 7 | +use panic_attack::types::*; |
| 8 | + |
| 9 | +// ─── Language Detection (47 languages) ──────────────────────────────────── |
| 10 | + |
| 11 | +#[test] |
| 12 | +fn language_detect_rust() { |
| 13 | + assert_eq!(Language::detect("src/main.rs"), Language::Rust); |
| 14 | + assert_eq!(Language::detect("lib.rs"), Language::Rust); |
| 15 | +} |
| 16 | + |
| 17 | +#[test] |
| 18 | +fn language_detect_c_family() { |
| 19 | + assert_eq!(Language::detect("foo.c"), Language::C); |
| 20 | + assert_eq!(Language::detect("foo.h"), Language::C); |
| 21 | + assert_eq!(Language::detect("bar.cpp"), Language::Cpp); |
| 22 | + assert_eq!(Language::detect("bar.hpp"), Language::Cpp); |
| 23 | +} |
| 24 | + |
| 25 | +#[test] |
| 26 | +fn language_detect_beam_family() { |
| 27 | + assert_eq!(Language::detect("server.ex"), Language::Elixir); |
| 28 | + assert_eq!(Language::detect("test.exs"), Language::Elixir); |
| 29 | + assert_eq!(Language::detect("gen.erl"), Language::Erlang); |
| 30 | + assert_eq!(Language::detect("app.gleam"), Language::Gleam); |
| 31 | +} |
| 32 | + |
| 33 | +#[test] |
| 34 | +fn language_detect_ml_family() { |
| 35 | + assert_eq!(Language::detect("Component.res"), Language::ReScript); |
| 36 | + assert_eq!(Language::detect("types.resi"), Language::ReScript); |
| 37 | + assert_eq!(Language::detect("parser.ml"), Language::OCaml); |
| 38 | + assert_eq!(Language::detect("sig.mli"), Language::OCaml); |
| 39 | + assert_eq!(Language::detect("main.sml"), Language::StandardML); |
| 40 | +} |
| 41 | + |
| 42 | +#[test] |
| 43 | +fn language_detect_proof_assistants() { |
| 44 | + assert_eq!(Language::detect("Types.idr"), Language::Idris); |
| 45 | + assert_eq!(Language::detect("proof.lean"), Language::Lean); |
| 46 | + assert_eq!(Language::detect("module.agda"), Language::Agda); |
| 47 | +} |
| 48 | + |
| 49 | +#[test] |
| 50 | +fn language_detect_logic_programming() { |
| 51 | + assert_eq!(Language::detect("rules.lgt"), Language::Logtalk); |
| 52 | + assert_eq!(Language::detect("facts.pl"), Language::Prolog); |
| 53 | + assert_eq!(Language::detect("query.dl"), Language::Datalog); |
| 54 | +} |
| 55 | + |
| 56 | +#[test] |
| 57 | +fn language_detect_systems_languages() { |
| 58 | + assert_eq!(Language::detect("build.zig"), Language::Zig); |
| 59 | + assert_eq!(Language::detect("main.adb"), Language::Ada); |
| 60 | + assert_eq!(Language::detect("spec.ads"), Language::Ada); |
| 61 | + assert_eq!(Language::detect("app.odin"), Language::Odin); |
| 62 | + assert_eq!(Language::detect("config.nim"), Language::Nim); |
| 63 | + assert_eq!(Language::detect("actor.pony"), Language::Pony); |
| 64 | +} |
| 65 | + |
| 66 | +#[test] |
| 67 | +fn language_detect_config_languages() { |
| 68 | + assert_eq!(Language::detect("config.ncl"), Language::Nickel); |
| 69 | + assert_eq!(Language::detect("flake.nix"), Language::Nix); |
| 70 | +} |
| 71 | + |
| 72 | +#[test] |
| 73 | +fn language_detect_scripting() { |
| 74 | + assert_eq!(Language::detect("script.sh"), Language::Shell); |
| 75 | + assert_eq!(Language::detect("deploy.bash"), Language::Shell); |
| 76 | + assert_eq!(Language::detect("model.jl"), Language::Julia); |
| 77 | + assert_eq!(Language::detect("plugin.lua"), Language::Lua); |
| 78 | +} |
| 79 | + |
| 80 | +#[test] |
| 81 | +fn language_detect_nextgen_dsls() { |
| 82 | + assert_eq!(Language::detect("module.ecl"), Language::Eclexia); |
| 83 | + assert_eq!(Language::detect("linear.eph"), Language::Ephapax); |
| 84 | + assert_eq!(Language::detect("game.bet"), Language::BetLang); |
| 85 | + assert_eq!(Language::detect("script.woke"), Language::WokeLang); |
| 86 | + assert_eq!(Language::detect("query.vql"), Language::VQL); |
| 87 | + assert_eq!(Language::detect("types.aff"), Language::AffineScript); |
| 88 | +} |
| 89 | + |
| 90 | +#[test] |
| 91 | +fn language_detect_javascript_variants() { |
| 92 | + // TS/TSX/JSX all map to JavaScript (ReScript is the replacement) |
| 93 | + assert_eq!(Language::detect("app.js"), Language::JavaScript); |
| 94 | + assert_eq!(Language::detect("module.mjs"), Language::JavaScript); |
| 95 | + assert_eq!(Language::detect("legacy.ts"), Language::JavaScript); |
| 96 | + assert_eq!(Language::detect("component.tsx"), Language::JavaScript); |
| 97 | + assert_eq!(Language::detect("widget.jsx"), Language::JavaScript); |
| 98 | +} |
| 99 | + |
| 100 | +#[test] |
| 101 | +fn language_detect_unknown() { |
| 102 | + assert_eq!(Language::detect("readme.md"), Language::Unknown); |
| 103 | + assert_eq!(Language::detect("data.csv"), Language::Unknown); |
| 104 | + assert_eq!(Language::detect("image.png"), Language::Unknown); |
| 105 | + assert_eq!(Language::detect("noext"), Language::Unknown); |
| 106 | +} |
| 107 | + |
| 108 | +// ─── Language Family Classification ─────────────────────────────────────── |
| 109 | + |
| 110 | +#[test] |
| 111 | +fn language_family_beam() { |
| 112 | + assert_eq!(Language::Elixir.family(), "beam"); |
| 113 | + assert_eq!(Language::Erlang.family(), "beam"); |
| 114 | + assert_eq!(Language::Gleam.family(), "beam"); |
| 115 | +} |
| 116 | + |
| 117 | +#[test] |
| 118 | +fn language_family_ml() { |
| 119 | + assert_eq!(Language::ReScript.family(), "ml"); |
| 120 | + assert_eq!(Language::OCaml.family(), "ml"); |
| 121 | + assert_eq!(Language::StandardML.family(), "ml"); |
| 122 | +} |
| 123 | + |
| 124 | +#[test] |
| 125 | +fn language_family_proof() { |
| 126 | + assert_eq!(Language::Idris.family(), "proof"); |
| 127 | + assert_eq!(Language::Lean.family(), "proof"); |
| 128 | + assert_eq!(Language::Agda.family(), "proof"); |
| 129 | +} |
| 130 | + |
| 131 | +#[test] |
| 132 | +fn language_family_systems() { |
| 133 | + assert_eq!(Language::Zig.family(), "systems"); |
| 134 | + assert_eq!(Language::Ada.family(), "systems"); |
| 135 | + assert_eq!(Language::Nim.family(), "systems"); |
| 136 | +} |
| 137 | + |
| 138 | +#[test] |
| 139 | +fn language_family_config() { |
| 140 | + assert_eq!(Language::Nickel.family(), "config"); |
| 141 | + assert_eq!(Language::Nix.family(), "config"); |
| 142 | +} |
| 143 | + |
| 144 | +// ─── Serialization Contracts ────────────────────────────────────────────── |
| 145 | + |
| 146 | +#[test] |
| 147 | +fn language_serializes_lowercase() { |
| 148 | + let json = serde_json::to_string(&Language::Rust).unwrap(); |
| 149 | + assert_eq!(json, "\"rust\""); |
| 150 | + |
| 151 | + let json = serde_json::to_string(&Language::Elixir).unwrap(); |
| 152 | + assert_eq!(json, "\"elixir\""); |
| 153 | + |
| 154 | + let json = serde_json::to_string(&Language::ReScript).unwrap(); |
| 155 | + assert_eq!(json, "\"rescript\""); |
| 156 | +} |
| 157 | + |
| 158 | +#[test] |
| 159 | +fn language_deserializes_from_lowercase() { |
| 160 | + let lang: Language = serde_json::from_str("\"rust\"").unwrap(); |
| 161 | + assert_eq!(lang, Language::Rust); |
| 162 | + |
| 163 | + let lang: Language = serde_json::from_str("\"idris\"").unwrap(); |
| 164 | + assert_eq!(lang, Language::Idris); |
| 165 | +} |
| 166 | + |
| 167 | +#[test] |
| 168 | +fn language_roundtrip_serde() { |
| 169 | + let languages = vec![ |
| 170 | + Language::Rust, Language::Elixir, Language::Gleam, |
| 171 | + Language::ReScript, Language::Idris, Language::Zig, |
| 172 | + Language::Haskell, Language::Nickel, Language::Ephapax, |
| 173 | + ]; |
| 174 | + |
| 175 | + for lang in languages { |
| 176 | + let json = serde_json::to_string(&lang).unwrap(); |
| 177 | + let deserialized: Language = serde_json::from_str(&json).unwrap(); |
| 178 | + assert_eq!(lang, deserialized, "Roundtrip failed for {:?}", lang); |
| 179 | + } |
| 180 | +} |
| 181 | + |
| 182 | +// ─── WeakPointCategory Coverage ─────────────────────────────────────────── |
| 183 | + |
| 184 | +#[test] |
| 185 | +fn weak_point_category_serializes() { |
| 186 | + let json = serde_json::to_string(&WeakPointCategory::UnsafeCode).unwrap(); |
| 187 | + assert!(!json.is_empty()); |
| 188 | + |
| 189 | + let json = serde_json::to_string(&WeakPointCategory::PanicPath).unwrap(); |
| 190 | + assert!(!json.is_empty()); |
| 191 | +} |
| 192 | + |
| 193 | +// ─── AssailReport Structure ─────────────────────────────────────────────── |
| 194 | + |
| 195 | +#[test] |
| 196 | +fn assail_report_serializes_to_json() { |
| 197 | + let report = AssailReport { |
| 198 | + program_path: std::path::PathBuf::from("test/target"), |
| 199 | + language: Language::Rust, |
| 200 | + frameworks: vec![], |
| 201 | + weak_points: vec![], |
| 202 | + statistics: Default::default(), |
| 203 | + file_statistics: vec![], |
| 204 | + recommended_attacks: vec![], |
| 205 | + dependency_graph: Default::default(), |
| 206 | + taint_matrix: Default::default(), |
| 207 | + migration_metrics: None, |
| 208 | + }; |
| 209 | + |
| 210 | + let json = serde_json::to_string(&report).unwrap(); |
| 211 | + assert!(json.contains("\"language\":\"rust\"")); |
| 212 | + |
| 213 | + // Verify it can be deserialized back |
| 214 | + let _: AssailReport = serde_json::from_str(&json).unwrap(); |
| 215 | +} |
0 commit comments