sentry_options/
testing.rs1use std::cell::RefCell;
20use std::collections::HashMap;
21
22use serde_json::Value;
23
24use crate::{GLOBAL_OPTIONS, Result};
25
26thread_local! {
27 static OVERRIDES: RefCell<HashMap<String, HashMap<String, Value>>> = RefCell::new(HashMap::new());
28}
29
30pub fn set_override(namespace: &str, key: &str, value: Value) {
32 OVERRIDES.with(|o| {
33 o.borrow_mut()
34 .entry(namespace.to_string())
35 .or_default()
36 .insert(key.to_string(), value);
37 });
38}
39
40pub fn get_override(namespace: &str, key: &str) -> Option<Value> {
42 OVERRIDES.with(|o| {
43 o.borrow()
44 .get(namespace)
45 .and_then(|ns| ns.get(key).cloned())
46 })
47}
48
49pub fn clear_override(namespace: &str, key: &str) {
51 OVERRIDES.with(|o| {
52 if let Some(ns_map) = o.borrow_mut().get_mut(namespace) {
53 ns_map.remove(key);
54 }
55 });
56}
57
58pub struct OverrideGuard {
60 previous: Vec<(String, String, Option<Value>)>,
61}
62
63impl Drop for OverrideGuard {
64 fn drop(&mut self) {
65 OVERRIDES.with(|o| {
66 let mut map = o.borrow_mut();
67 for (ns, key, prev_value) in self.previous.drain(..) {
68 match prev_value {
69 Some(v) => {
70 map.entry(ns).or_default().insert(key, v);
71 }
72 None => {
73 if let Some(ns_map) = map.get_mut(&ns) {
74 ns_map.remove(&key);
75 }
76 }
77 }
78 }
79 });
80 }
81}
82
83pub fn override_options(overrides: &[(&str, &str, Value)]) -> Result<OverrideGuard> {
117 let opts = GLOBAL_OPTIONS
119 .get()
120 .ok_or(crate::OptionsError::NotInitialized)?;
121 for (ns, key, value) in overrides {
122 opts.validate_override(ns, key, value)?;
123 }
124
125 let mut previous = Vec::with_capacity(overrides.len());
126
127 OVERRIDES.with(|o| {
128 let mut map = o.borrow_mut();
129 for (ns, key, value) in overrides {
130 let prev = map.get(*ns).and_then(|m| m.get(*key).cloned());
131 previous.push((ns.to_string(), key.to_string(), prev));
132 map.entry(ns.to_string())
133 .or_default()
134 .insert(key.to_string(), value.clone());
135 }
136 });
137
138 Ok(OverrideGuard { previous })
139}
140
141pub fn always_on() -> Value {
153 serde_json::json!({
154 "owner": {"team": "testing"},
155 "created_at": "1970-01-01",
156 "segments": [{"name": "always-on", "rollout": 100, "conditions": []}]
157 })
158}
159
160pub fn always_off() -> Value {
164 serde_json::json!({
165 "owner": {"team": "testing"},
166 "created_at": "1970-01-01",
167 "enabled": false,
168 "segments": []
169 })
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use serde_json::json;
176
177 #[test]
178 fn test_set_get_clear_override() {
179 set_override("ns", "key", json!(true));
180 assert_eq!(get_override("ns", "key"), Some(json!(true)));
181 clear_override("ns", "key");
182 assert_eq!(get_override("ns", "key"), None);
183 }
184
185 #[test]
186 fn test_override_guard_restores() {
187 crate::init().unwrap();
188 set_override("sentry-options-testing", "int-option", json!(1));
189
190 {
191 let _guard =
192 override_options(&[("sentry-options-testing", "int-option", json!(2))]).unwrap();
193 assert_eq!(
194 get_override("sentry-options-testing", "int-option"),
195 Some(json!(2))
196 );
197 }
198
199 assert_eq!(
200 get_override("sentry-options-testing", "int-option"),
201 Some(json!(1))
202 );
203 clear_override("sentry-options-testing", "int-option");
204 }
205
206 #[test]
207 fn test_override_guard_clears_new_key() {
208 crate::init().unwrap();
209 assert_eq!(get_override("sentry-options-testing", "bool-option"), None);
210
211 {
212 let _guard =
213 override_options(&[("sentry-options-testing", "bool-option", json!(true))])
214 .unwrap();
215 assert_eq!(
216 get_override("sentry-options-testing", "bool-option"),
217 Some(json!(true))
218 );
219 }
220
221 assert_eq!(get_override("sentry-options-testing", "bool-option"), None);
222 }
223
224 #[test]
225 fn test_nested_overrides() {
226 crate::init().unwrap();
227 {
228 let _outer =
229 override_options(&[("sentry-options-testing", "int-option", json!(100))]).unwrap();
230 assert_eq!(
231 get_override("sentry-options-testing", "int-option"),
232 Some(json!(100))
233 );
234
235 {
236 let _inner =
237 override_options(&[("sentry-options-testing", "int-option", json!(200))])
238 .unwrap();
239 assert_eq!(
240 get_override("sentry-options-testing", "int-option"),
241 Some(json!(200))
242 );
243 }
244
245 assert_eq!(
246 get_override("sentry-options-testing", "int-option"),
247 Some(json!(100))
248 );
249 }
250
251 assert_eq!(get_override("sentry-options-testing", "int-option"), None);
252 }
253
254 #[test]
255 fn test_override_feature_flag() {
256 crate::init().unwrap();
257
258 let mut ctx = crate::FeatureContext::new();
259 ctx.insert("organization_id", json!(1));
260 ctx.identity_fields(vec!["organization_id"]);
261
262 assert!(
264 !crate::features("sentry-options-testing").has("organizations:enabled-feature", &ctx)
265 );
266
267 {
268 let _guard = override_options(&[(
269 "sentry-options-testing",
270 "feature.organizations:enabled-feature",
271 always_on(),
272 )])
273 .unwrap();
274
275 assert!(
276 crate::features("sentry-options-testing")
277 .has("organizations:enabled-feature", &ctx)
278 );
279 }
280
281 assert!(
283 !crate::features("sentry-options-testing").has("organizations:enabled-feature", &ctx)
284 );
285 }
286
287 #[test]
288 fn test_always_off_forces_feature_off() {
289 crate::init().unwrap();
290
291 let mut ctx = crate::FeatureContext::new();
293 ctx.insert("organization_id", json!(123));
294 ctx.identity_fields(vec!["organization_id"]);
295 assert!(
296 crate::features("sentry-options-testing").has("organizations:enabled-feature", &ctx)
297 );
298
299 {
300 let _guard = override_options(&[(
301 "sentry-options-testing",
302 "feature.organizations:enabled-feature",
303 always_off(),
304 )])
305 .unwrap();
306
307 assert!(
308 !crate::features("sentry-options-testing")
309 .has("organizations:enabled-feature", &ctx)
310 );
311 }
312 }
313}