1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
//! This provides testing functionality for building tests.
//!
//! **Feature:** `test` (*disabled by default*)
//!
//! If the sentry crate has been compiled with the test support feature this
//! module becomes available and provides functionality to capture events
//! in a block.
//!
//! # Example usage
//!
//! ```
//! use sentry::test::with_captured_events;
//! use sentry::{capture_message, Level};
//!
//! let events = with_captured_events(|| {
//!     capture_message("Hello World!", Level::Warning);
//! });
//! assert_eq!(events.len(), 1);
//! assert_eq!(events[0].message.as_ref().unwrap(), "Hello World!");
//! ```

use std::sync::{Arc, Mutex};

use once_cell::sync::Lazy;

use crate::protocol::Event;
use crate::types::Dsn;
use crate::{ClientOptions, Envelope, Hub, Transport};

static TEST_DSN: Lazy<Dsn> = Lazy::new(|| "https://public@sentry.invalid/1".parse().unwrap());

/// Collects events instead of sending them.
///
/// # Examples
///
/// ```
/// use sentry::test::TestTransport;
/// use sentry::{ClientOptions, Hub};
/// use std::sync::Arc;
///
/// let transport = TestTransport::new();
/// let options = ClientOptions {
///     dsn: Some("https://public@example.com/1".parse().unwrap()),
///     transport: Some(Arc::new(transport.clone())),
///     ..ClientOptions::default()
/// };
/// Hub::current().bind_client(Some(Arc::new(options.into())));
/// ```
pub struct TestTransport {
    collected: Mutex<Vec<Envelope>>,
}

impl TestTransport {
    /// Creates a new test transport.
    #[allow(clippy::new_ret_no_self)]
    pub fn new() -> Arc<TestTransport> {
        Arc::new(TestTransport {
            collected: Mutex::new(vec![]),
        })
    }

    /// Fetches and clears the contained events.
    pub fn fetch_and_clear_events(&self) -> Vec<Event<'static>> {
        self.fetch_and_clear_envelopes()
            .into_iter()
            .filter_map(|envelope| envelope.event().cloned())
            .collect()
    }

    /// Fetches and clears the contained envelopes.
    pub fn fetch_and_clear_envelopes(&self) -> Vec<Envelope> {
        let mut guard = self.collected.lock().unwrap();
        std::mem::take(&mut *guard)
    }
}

impl Transport for TestTransport {
    fn send_envelope(&self, envelope: Envelope) {
        self.collected.lock().unwrap().push(envelope);
    }
}

/// Runs some code with the default test hub and returns the captured events.
///
/// See [`with_captured_envelopes_options`](fn.with_captured_envelopes_options.html)
pub fn with_captured_events<F: FnOnce()>(f: F) -> Vec<Event<'static>> {
    with_captured_events_options(f, ClientOptions::default())
}

/// Runs some code with the default test hub with the given options and
/// returns the captured events.
///
/// See [`with_captured_envelopes_options`](fn.with_captured_envelopes_options.html)
pub fn with_captured_events_options<F: FnOnce(), O: Into<ClientOptions>>(
    f: F,
    options: O,
) -> Vec<Event<'static>> {
    with_captured_envelopes_options(f, options)
        .into_iter()
        .filter_map(|envelope| envelope.event().cloned())
        .collect()
}

/// Runs some code with the default test hub and returns the captured envelopes.
///
/// See [`with_captured_envelopes_options`](fn.with_captured_envelopes_options.html)
pub fn with_captured_envelopes<F: FnOnce()>(f: F) -> Vec<Envelope> {
    with_captured_envelopes_options(f, ClientOptions::default())
}

/// Runs some code with the default test hub with the given options and
/// returns the captured envelopes.
///
/// If no DSN is set on the options a default test DSN is inserted.  The
/// transport on the options is also overridden with a `TestTransport`.
///
/// This is a shortcut for creating a testable client with the supplied options
/// and `TestTransport`, and bind it to a newly created hub for the duration of
/// the call.
pub fn with_captured_envelopes_options<F: FnOnce(), O: Into<ClientOptions>>(
    f: F,
    options: O,
) -> Vec<Envelope> {
    let transport = TestTransport::new();
    let mut options = options.into();
    options.dsn = Some(options.dsn.unwrap_or_else(|| TEST_DSN.clone()));
    options.transport = Some(Arc::new(transport.clone()));
    Hub::run(
        Arc::new(Hub::new(
            Some(Arc::new(options.into())),
            Arc::new(Default::default()),
        )),
        f,
    );
    transport.fetch_and_clear_envelopes()
}