//! Ruby fixture integration tests (Phase 15 acceptance gate). //! //! Per-shape acceptance for the Ruby emitter shapes shipped in Phase 15 //! (Track B Ruby vertical): Sinatra route, Rails action, Rack middleware, //! and generic controller method. Each shape ships a `vuln.rb` + `benign.rb` //! pair under `tests/dynamic_fixtures/ruby//`. //! //! Prerequisites: skips cleanly when `ruby` is unavailable on the host. //! //! Run with: `cargo nextest run --features dynamic --test ruby_fixtures` mod common; #[cfg(feature = "dynamic")] mod phase15_shape_tests { use crate::common::fixture_harness::{Prerequisite, run_shape_fixture_lang_or_skip}; use nyx_scanner::dynamic::spec::PayloadSlot; use nyx_scanner::evidence::{EntryKind, VerifyResult, VerifyStatus}; use nyx_scanner::labels::Cap; use nyx_scanner::symbol::Lang; fn assert_confirmed(shape: &str, result: &VerifyResult) { assert_eq!( result.status, VerifyStatus::Confirmed, "{shape}/vuln: expected Confirmed, got {:?} ({:?})", result.status, result.detail, ); } fn assert_not_confirmed(shape: &str, result: &VerifyResult) { assert!( matches!( result.status, VerifyStatus::NotConfirmed | VerifyStatus::Inconclusive ), "{shape}/benign: expected NotConfirmed (or Inconclusive), got {:?} ({:?})", result.status, result.detail, ); assert_ne!( result.status, VerifyStatus::Confirmed, "{shape}/benign: must not confirm", ); } fn run( shape: &str, file: &str, func: &str, cap: Cap, sink_line: u32, kind: EntryKind, slot: PayloadSlot, ) -> Option { // Phase 29 (Track I): structured prerequisite gating replaces // the bespoke `ruby_available()` + per-test // `eprintln!("SKIP ..."); return;` pattern. let mut requires = vec![Prerequisite::CommandAvailable("ruby")]; match shape { "sinatra_route" => { requires.push(Prerequisite::CommandAvailable("bundle")); requires.push(Prerequisite::RubyRequireAvailable("sinatra/base")); } "rails_action" => { requires.push(Prerequisite::CommandAvailable("bundle")); requires.push(Prerequisite::RubyRequireAvailable("action_controller")); } "hanami_action" => { requires.push(Prerequisite::CommandAvailable("bundle")); requires.push(Prerequisite::RubyRequireAvailable("hanami/action")); } "rack_middleware" => { requires.push(Prerequisite::CommandAvailable("bundle")); requires.push(Prerequisite::RubyRequireAvailable("rack/mock")); } _ => {} } run_shape_fixture_lang_or_skip( &requires, Lang::Ruby, "ruby", shape, file, func, cap, sink_line, kind, slot, ) } // ── sinatra_route ──────────────────────────────────────────────────────── #[test] fn sinatra_route_vuln_is_confirmed() { let Some(r) = run( "sinatra_route", "vuln.rb", "run", Cap::CODE_EXEC, 12, EntryKind::HttpRoute, PayloadSlot::Param(0), ) else { return; }; assert_confirmed("sinatra_route", &r); } #[test] fn sinatra_route_benign_not_confirmed() { let Some(r) = run( "sinatra_route", "benign.rb", "run", Cap::CODE_EXEC, 15, EntryKind::HttpRoute, PayloadSlot::Param(0), ) else { return; }; assert_not_confirmed("sinatra_route", &r); } // ── rails_action ───────────────────────────────────────────────────────── #[test] fn rails_action_vuln_is_confirmed() { let Some(r) = run( "rails_action", "vuln.rb", "index", Cap::CODE_EXEC, 14, EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()), ) else { return; }; assert_confirmed("rails_action", &r); } #[test] fn rails_action_benign_not_confirmed() { let Some(r) = run( "rails_action", "benign.rb", "index", Cap::CODE_EXEC, 17, EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()), ) else { return; }; assert_not_confirmed("rails_action", &r); } // ── hanami_action ─────────────────────────────────────────────────────── #[test] fn hanami_action_vuln_is_confirmed() { let Some(r) = run( "hanami_action", "vuln.rb", "call", Cap::CODE_EXEC, 19, EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()), ) else { return; }; assert_confirmed("hanami_action", &r); } #[test] fn hanami_action_benign_not_confirmed() { let Some(r) = run( "hanami_action", "benign.rb", "call", Cap::CODE_EXEC, 21, EntryKind::HttpRoute, PayloadSlot::QueryParam("payload".into()), ) else { return; }; assert_not_confirmed("hanami_action", &r); } // ── rack_middleware ────────────────────────────────────────────────────── #[test] fn rack_middleware_vuln_is_confirmed() { let Some(r) = run( "rack_middleware", "vuln.rb", "call", Cap::CODE_EXEC, 10, EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()), ) else { return; }; assert_confirmed("rack_middleware", &r); } #[test] fn rack_middleware_benign_not_confirmed() { let Some(r) = run( "rack_middleware", "benign.rb", "call", Cap::CODE_EXEC, 11, EntryKind::HttpRoute, PayloadSlot::EnvVar("NYX_PAYLOAD".into()), ) else { return; }; assert_not_confirmed("rack_middleware", &r); } // ── controller_method ──────────────────────────────────────────────────── #[test] fn controller_method_vuln_is_confirmed() { let Some(r) = run( "controller_method", "vuln.rb", "authenticate", Cap::CODE_EXEC, 7, EntryKind::Function, PayloadSlot::Param(0), ) else { return; }; assert_confirmed("controller_method", &r); } #[test] fn controller_method_benign_not_confirmed() { let Some(r) = run( "controller_method", "benign.rb", "authenticate", Cap::CODE_EXEC, 10, EntryKind::Function, PayloadSlot::Param(0), ) else { return; }; assert_not_confirmed("controller_method", &r); } }