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
This commit is contained in:
Dylan Knutson
2025-08-28 22:02:52 +00:00
parent d4ccbb884c
commit a81308d577

276
src/dptree_utils.rs Normal file
View File

@@ -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<T>(
handler: Handler<'static, T, DpHandlerDescription>,
input_variant: TestEnum,
expected: Option<T>,
) 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(_))),
}
}
}