From a81308d57776a1c9e5d04c126830b18e3f0f220c Mon Sep 17 00:00:00 2001 From: Dylan Knutson Date: Thu, 28 Aug 2025 22:02:52 +0000 Subject: [PATCH] refactor: improve case! macro tests with rstest parametrization - Fix failing tests by correcting test expectations for parameter extraction - Add comprehensive coverage including InnerWithMultiStruct variant - Reduce test duplication using rstest parametrization - Create assert_handler_result helper function to eliminate repetitive code - Consolidate similar test patterns into unified test functions - Ensure all macro functionality is properly tested with clear test case names All 20 parameterized test cases now pass, covering: - Simple variant filtering - Single/multiple parameter extraction from enums - Nested enum parameter extraction - Struct field extraction (single and multiple fields) - Proper negative test cases for non-matching variants --- src/dptree_utils.rs | 276 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 src/dptree_utils.rs diff --git a/src/dptree_utils.rs b/src/dptree_utils.rs new file mode 100644 index 0000000..5d8a356 --- /dev/null +++ b/src/dptree_utils.rs @@ -0,0 +1,276 @@ +#[macro_export] +macro_rules! case { + // Basic variant matching without parameters + ($($variant:ident)::+) => { + dptree::filter(|x| matches!(x, $($variant)::+)) + }; + + // Single parameter extraction + ($($variant:ident)::+ ($param:ident)) => { + dptree::filter_map(|x| match x { + $($variant)::+($param) => Some($param), + _ => None, + }) + }; + + // Nested variant matching without parameter extraction + ($($variant:ident)::+ ($($inner_variant:ident)::+)) => { + dptree::filter(|x| matches!(x, $($variant)::+($($inner_variant)::+))) + }; + + // Nested variant matching with single parameter extraction + ($($variant:ident)::+ ($($inner_variant:ident)::+ ($param:ident))) => { + dptree::filter_map(|x| match x { + $($variant)::+($($inner_variant)::+($param)) => Some($param), + _ => None, + }) + }; + + // Multiple parameter extraction (tuple destructuring) + ($($variant:ident)::+ ($($param:ident),+ $(,)?)) => { + dptree::filter_map(|x| match x { + $($variant)::+($($param),+) => Some(($($param),+)), + _ => None, + }) + }; + + // Nested variant matching with multiple parameter extraction + ($($variant:ident)::+ ($($inner_variant:ident)::+ ($($param:ident),+ $(,)?))) => { + dptree::filter_map(|x| match x { + $($variant)::+($($inner_variant)::+($($param),+)) => Some(($($param),+)), + _ => None, + }) + }; + + // Nested variant matching with struct pattern (single field) + ($($variant:ident)::+ ($($inner_variant:ident)::+ { $field:ident })) => { + dptree::filter_map(|x| match x { + $($variant)::+($($inner_variant)::+ { $field }) => Some($field), + _ => None, + }) + }; + + // Nested variant matching with struct pattern (multiple fields) + ($($variant:ident)::+ ($($inner_variant:ident)::+ { $($field:ident),+ $(,)? })) => { + dptree::filter_map(|x| match x { + $($variant)::+($($inner_variant)::+ { $($field),+ }) => Some(($($field),+)), + _ => None, + }) + }; + + // Nested variant matching with struct pattern without extraction (filter only) + ($($variant:ident)::+ ($($inner_variant:ident)::+ { .. })) => { + dptree::filter(|x| matches!(x, $($variant)::+($($inner_variant)::+ { .. }))) + }; + + // Triple-nested variant matching with struct pattern + ($($variant:ident)::+ ($($inner1:ident)::+ ($($inner2:ident)::+ { $($field:ident),+ $(,)? }))) => { + dptree::filter_map(|x| match x { + $($variant)::+($($inner1)::+($($inner2)::+ { $($field),+ })) => Some(($($field),+)), + _ => None, + }) + }; +} + +#[cfg(test)] +mod tests { + use std::ops::ControlFlow; + + use teloxide::{ + dispatching::DpHandlerDescription, + dptree::{self, deps, Handler}, + }; + + // Test enums to verify macro functionality + #[derive(Debug, Clone, PartialEq, Default)] + enum TestEnum { + #[default] + DefaultVariant, + SimpleVariant, + SingleParam(&'static str), + MultipleParams(&'static str, i32), + NestedVariant(InnerEnum), + } + + #[derive(Debug, Clone, PartialEq)] + enum InnerEnum { + InnerSimple, + InnerWithParam(&'static str), + InnerWithMultiple(&'static str, i32), + InnerWithStruct { field: &'static str }, + InnerWithMultiStruct { field: &'static str, number: i32 }, + } + + // Helper function for testing handlers with expected results + async fn assert_handler_result( + handler: Handler<'static, T, DpHandlerDescription>, + input_variant: TestEnum, + expected: Option, + ) where + T: std::fmt::Debug + PartialEq + 'static, + { + let input = deps![input_variant]; + let result = handler.dispatch(input).await; + + match expected { + Some(expected_value) => assert_eq!(result, ControlFlow::Break(expected_value)), + None => assert!(matches!(result, ControlFlow::Continue(_))), + } + } + + // Comprehensive tests for single value extraction (strings) + #[rstest::rstest] + // Simple variant filtering (no parameter extraction) + #[case::simple_variant_match( + case![TestEnum::SimpleVariant].endpoint(|| async { "matched" }), + TestEnum::SimpleVariant, + Some("matched") + )] + #[case::simple_variant_no_match( + case![TestEnum::SimpleVariant].endpoint(|| async { "matched" }), + TestEnum::DefaultVariant, + None + )] + // Single parameter extraction from simple enum + #[case::single_param_match( + case![TestEnum::SingleParam(p)].endpoint(|p: &'static str| async move { p }), + TestEnum::SingleParam("extracted"), + Some("extracted") + )] + #[case::single_param_no_match( + case![TestEnum::SingleParam(p)].endpoint(|p: &'static str| async move { p }), + TestEnum::DefaultVariant, + None + )] + // Single parameter extraction from nested enum + #[case::nested_single_match( + case![TestEnum::NestedVariant(InnerEnum::InnerWithParam(p))].endpoint(|p: &'static str| async move { p }), + TestEnum::NestedVariant(InnerEnum::InnerWithParam("nested")), + Some("nested") + )] + #[case::nested_single_wrong_inner_variant( + case![TestEnum::NestedVariant(InnerEnum::InnerWithParam(p))].endpoint(|p: &'static str| async move { p }), + TestEnum::NestedVariant(InnerEnum::InnerSimple), + None + )] + #[case::nested_single_wrong_outer_variant( + case![TestEnum::NestedVariant(InnerEnum::InnerWithParam(p))].endpoint(|p: &'static str| async move { p }), + TestEnum::DefaultVariant, + None + )] + // Single field extraction from nested struct + #[case::struct_field_match( + case![TestEnum::NestedVariant(InnerEnum::InnerWithStruct { field })].endpoint(|field: &'static str| async move { field }), + TestEnum::NestedVariant(InnerEnum::InnerWithStruct { field: "struct_value" }), + Some("struct_value") + )] + #[case::struct_field_no_match( + case![TestEnum::NestedVariant(InnerEnum::InnerWithStruct { field })].endpoint(|field: &'static str| async move { field }), + TestEnum::NestedVariant(InnerEnum::InnerSimple), + None + )] + #[tokio::test] + async fn test_single_value_extraction( + #[case] handler: Handler<'static, &'static str, DpHandlerDescription>, + #[case] input_variant: TestEnum, + #[case] expected: Option<&'static str>, + ) { + assert_handler_result(handler, input_variant, expected).await; + } + + // Test cases for multiple parameter extraction + #[rstest::rstest] + #[case(TestEnum::MultipleParams("multi", 42), Some(("multi", 42)))] + #[case(TestEnum::DefaultVariant, None)] + #[tokio::test] + async fn test_multiple_parameter_extraction( + #[case] input_variant: TestEnum, + #[case] expected_params: Option<(&'static str, i32)>, + ) { + let handler: Handler<'static, (&str, i32), DpHandlerDescription> = + case![TestEnum::MultipleParams(s, n)] + .endpoint(|params: (&'static str, i32)| async move { params }); + + let input = deps![input_variant]; + let result = handler.dispatch(input).await; + + match expected_params { + Some(params) => assert_eq!(result, ControlFlow::Break(params)), + None => assert!(matches!(result, ControlFlow::Continue(_))), + } + } + + // Test cases for nested multiple parameter extraction + #[rstest::rstest] + #[case(TestEnum::NestedVariant(InnerEnum::InnerWithMultiple("nested", 123)), Some(("nested", 123)))] + #[case(TestEnum::NestedVariant(InnerEnum::InnerSimple), None)] + #[case(TestEnum::DefaultVariant, None)] + #[tokio::test] + async fn test_nested_multiple_parameter_extraction( + #[case] input_variant: TestEnum, + #[case] expected_params: Option<(&'static str, i32)>, + ) { + let handler: Handler<'static, (&str, i32), DpHandlerDescription> = + case![TestEnum::NestedVariant(InnerEnum::InnerWithMultiple(s, n))] + .endpoint(|params: (&'static str, i32)| async move { params }); + + let input = deps![input_variant]; + let result = handler.dispatch(input).await; + + match expected_params { + Some(params) => assert_eq!(result, ControlFlow::Break(params)), + None => assert!(matches!(result, ControlFlow::Continue(_))), + } + } + + // Test cases for struct pattern extraction + #[rstest::rstest] + #[case(TestEnum::NestedVariant(InnerEnum::InnerWithStruct { field: "struct_field" }), Some("struct_field"))] + #[case(TestEnum::NestedVariant(InnerEnum::InnerSimple), None)] + #[case(TestEnum::DefaultVariant, None)] + #[tokio::test] + async fn test_struct_pattern_extraction( + #[case] input_variant: TestEnum, + #[case] expected_field: Option<&'static str>, + ) { + let handler: Handler<'static, &str, DpHandlerDescription> = + case![TestEnum::NestedVariant(InnerEnum::InnerWithStruct { + field + })] + .endpoint(|field: &'static str| async move { field }); + + let input = deps![input_variant]; + let result = handler.dispatch(input).await; + + match expected_field { + Some(field) => assert_eq!(result, ControlFlow::Break(field)), + None => assert!(matches!(result, ControlFlow::Continue(_))), + } + } + + // Test cases for multi-field struct pattern extraction + #[rstest::rstest] + #[case(TestEnum::NestedVariant(InnerEnum::InnerWithMultiStruct { field: "multi_field", number: 42 }), Some(("multi_field", 42)))] + #[case(TestEnum::NestedVariant(InnerEnum::InnerSimple), None)] + #[case(TestEnum::DefaultVariant, None)] + #[tokio::test] + async fn test_multi_struct_pattern_extraction( + #[case] input_variant: TestEnum, + #[case] expected_fields: Option<(&'static str, i32)>, + ) { + let handler: Handler<'static, (&str, i32), DpHandlerDescription> = + case![TestEnum::NestedVariant(InnerEnum::InnerWithMultiStruct { + field, + number + })] + .endpoint(|fields: (&'static str, i32)| async move { fields }); + + let input = deps![input_variant]; + let result = handler.dispatch(input).await; + + match expected_fields { + Some(fields) => assert_eq!(result, ControlFlow::Break(fields)), + None => assert!(matches!(result, ControlFlow::Continue(_))), + } + } +}