bitwarden_test_macro/
lib.rs

1//! Proc macro for Play test framework
2//!
3//! Provides the `#[play_test]` attribute macro for writing E2E tests with automatic
4//! Play instance setup and cleanup.
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{ItemFn, parse_macro_input};
9
10/// Attribute macro for Play framework tests
11///
12/// Transforms an async test function that takes a `Play` parameter into a properly
13/// configured tokio test with automatic cleanup.
14///
15/// # Example
16///
17/// ```ignore
18/// use bitwarden_test::play::{play_test, Play, SingleUserArgs, SingleUserScene};
19///
20/// #[play_test]
21/// async fn test_user_login(play: Play) {
22///     let args = SingleUserArgs {
23///         email: "[email protected]".to_string(),
24///         ..Default::default()
25///     };
26///     let scene = play.scene::<SingleUserScene>(&args).await.unwrap();
27///     // Cleanup happens automatically when the test completes
28/// }
29/// ```
30///
31/// The macro transforms the above into:
32///
33/// ```ignore
34/// #[tokio::test]
35/// async fn test_user_login() {
36///     ::bitwarden_test::play::Play::builder()
37///         .run(|play: Play| async move {
38///             // original test body
39///         })
40///         .await;
41/// }
42/// ```
43#[proc_macro_attribute]
44pub fn play_test(_attr: TokenStream, item: TokenStream) -> TokenStream {
45    let input = parse_macro_input!(item as ItemFn);
46
47    let fn_name = &input.sig.ident;
48    let fn_body = &input.block;
49    let fn_vis = &input.vis;
50    let fn_attrs = &input.attrs;
51
52    // Extract the play parameter name and type from the function signature
53    let (play_param, play_type) = input
54        .sig
55        .inputs
56        .first()
57        .and_then(|arg| {
58            if let syn::FnArg::Typed(pat_type) = arg {
59                if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
60                    return Some((pat_ident.ident.clone(), pat_type.ty.clone()));
61                }
62            }
63            None
64        })
65        .unwrap_or_else(|| {
66            let ident = syn::Ident::new("play", proc_macro2::Span::call_site());
67            let ty: syn::Type = syn::parse_quote!(::bitwarden_test::play::Play);
68            (ident, Box::new(ty))
69        });
70
71    let expanded = quote! {
72        #(#fn_attrs)*
73        #[::tokio::test]
74        #fn_vis async fn #fn_name() {
75            ::bitwarden_test::play::Play::builder()
76                .run(|#play_param: #play_type| async move #fn_body)
77                .await;
78        }
79    };
80
81    TokenStream::from(expanded)
82}