nyx/tests/class_method_corpus.rs

201 lines
6.8 KiB
Rust

//! Phase 19 (Track M.1) — `ClassMethod` end-to-end acceptance.
//!
//! Asserts the new `EntryKind::ClassMethod { class, method }` variant
//! is supported by every per-language emitter so the
//! `Inconclusive(EntryKindUnsupported { attempted: ClassMethod })`
//! rate drops to 0% across the ten supported languages. Each
//! sub-test constructs a `HarnessSpec` whose `entry_kind` is
//! `ClassMethod`, drives it through `lang::emit`, and checks the
//! harness source carries the matching `class` + `method` literal
//! plus the per-lang structural marker (probe shim, build command,
//! mock-class declaration when applicable).
//!
//! `cargo nextest run --features dynamic --test class_method_corpus`.
#![cfg(feature = "dynamic")]
use nyx_scanner::dynamic::lang;
use nyx_scanner::dynamic::spec::{EntryKind, EntryKindTag, HarnessSpec, PayloadSlot};
use nyx_scanner::dynamic::stubs::{mock_source, MockKind};
use nyx_scanner::labels::Cap;
use nyx_scanner::symbol::Lang;
const LANGS: &[Lang] = &[
Lang::Python,
Lang::JavaScript,
Lang::TypeScript,
Lang::Java,
Lang::Php,
Lang::Ruby,
Lang::Go,
Lang::Rust,
Lang::C,
Lang::Cpp,
];
fn entry_file(lang: Lang) -> &'static str {
match lang {
Lang::Python => "tests/dynamic_fixtures/class_method/python/vuln.py",
Lang::JavaScript => "tests/dynamic_fixtures/class_method/javascript/vuln.js",
Lang::TypeScript => "tests/dynamic_fixtures/class_method/typescript/vuln.ts",
Lang::Java => "tests/dynamic_fixtures/class_method/java/Vuln.java",
Lang::Php => "tests/dynamic_fixtures/class_method/php/vuln.php",
Lang::Ruby => "tests/dynamic_fixtures/class_method/ruby/vuln.rb",
Lang::Go => "tests/dynamic_fixtures/class_method/go/vuln.go",
Lang::Rust => "tests/dynamic_fixtures/class_method/rust/vuln.rs",
Lang::C => "tests/dynamic_fixtures/class_method/c/vuln.c",
Lang::Cpp => "tests/dynamic_fixtures/class_method/cpp/vuln.cpp",
}
}
fn class_for(lang: Lang) -> (&'static str, &'static str) {
match lang {
Lang::Python => ("UserRepository", "find_by_name"),
Lang::Java => ("UserRepository", "findByName"),
Lang::C => ("UserService", "run"),
_ => ("UserService", "run"),
}
}
fn make_spec(lang: Lang) -> HarnessSpec {
let (class, method) = class_for(lang);
HarnessSpec {
finding_id: "phase19classmth1".into(),
entry_file: entry_file(lang).into(),
entry_name: method.into(),
entry_kind: EntryKind::ClassMethod {
class: class.into(),
method: method.into(),
},
lang,
toolchain_id: "phase19".into(),
payload_slot: PayloadSlot::Param(0),
expected_cap: Cap::CODE_EXEC,
constraint_hints: vec![],
sink_file: entry_file(lang).into(),
sink_line: 1,
spec_hash: "phase19classmth1".into(),
derivation: nyx_scanner::dynamic::spec::SpecDerivationStrategy::FromFlowSteps,
stubs_required: vec![],
framework: None,
java_toolchain: nyx_scanner::dynamic::spec::JavaToolchain::default(),
}
}
#[test]
fn class_method_supported_by_every_lang_emitter() {
for lang in LANGS {
let supported = lang::entry_kinds_supported(*lang);
assert!(
supported.contains(&EntryKindTag::ClassMethod),
"{lang:?} must advertise ClassMethod after Phase 19; supported = {supported:?}",
);
}
}
#[test]
fn class_method_emit_does_not_short_circuit_to_entry_kind_unsupported() {
for lang in LANGS {
let spec = make_spec(*lang);
let result = lang::emit(&spec);
assert!(
result.is_ok(),
"{lang:?} emit returned {result:?} for ClassMethod spec"
);
}
}
#[test]
fn class_method_harness_carries_class_and_method_literal() {
for lang in LANGS {
let spec = make_spec(*lang);
let h = lang::emit(&spec).expect("emit ok");
let (class, method) = class_for(*lang);
assert!(
h.source.contains(class),
"{lang:?} harness source must reference class {class:?}",
);
assert!(
h.source.contains(method),
"{lang:?} harness source must reference method {method:?}",
);
}
}
#[test]
fn class_method_harness_splices_phase_19_mock_classes_where_lang_has_classes() {
// Languages with a class system embed the MockHttpClient /
// MockDatabaseConnection / MockLogger declarations the
// `stubs::mocks` registry publishes. Go uses a struct registry
// routed through the entry package and does not splice the
// doubles into the harness source; C has no class system.
// Rust's ClassMethod path uses Default::default() — no mocks.
let class_system_langs = [
Lang::Python,
Lang::JavaScript,
Lang::TypeScript,
Lang::Java,
Lang::Php,
Lang::Ruby,
];
for lang in class_system_langs {
let spec = make_spec(lang);
let h = lang::emit(&spec).expect("emit ok");
let mock_http = mock_source(MockKind::HttpClient, lang);
assert!(
h.source.contains("MockHttpClient"),
"{lang:?} harness must splice MockHttpClient",
);
assert!(!mock_http.is_empty());
}
}
#[test]
fn class_method_python_dispatch_reads_payload_and_invokes_method() {
let spec = make_spec(Lang::Python);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("NYX_PAYLOAD"));
assert!(h.source.contains("UserRepository"));
assert!(h.source.contains("find_by_name"));
assert!(h.source.contains("_nyx_build_receiver"));
}
#[test]
fn class_method_java_emits_reflective_dispatch() {
let spec = make_spec(Lang::Java);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("Class.forName"));
assert!(h.source.contains("nyxBuildReceiver"));
assert!(h.source.contains("UserRepository"));
}
#[test]
fn class_method_go_uses_reflect_receivers_registry() {
let spec = make_spec(Lang::Go);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("entry.NyxReceivers"));
assert!(h.source.contains("MethodByName"));
}
#[test]
fn class_method_rust_uses_default_constructor() {
let spec = make_spec(Lang::Rust);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("UserService::default()"));
assert!(h.source.contains("instance.run"));
}
#[test]
fn class_method_c_collapses_to_class_underscore_method_symbol() {
let spec = make_spec(Lang::C);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("UserService_run"));
}
#[test]
fn class_method_cpp_constructs_default_then_calls_method() {
let spec = make_spec(Lang::Cpp);
let h = lang::emit(&spec).expect("emit ok");
assert!(h.source.contains("UserService instance;"));
assert!(h.source.contains("instance.run"));
}