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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! Test-only fault-injection seams.
//!
//! This module is gated behind the `test-fault-injection` cargo
//! feature and is intended exclusively for integration tests in
//! downstream crates (notably `velesdb-server`) that need to force
//! specific internal failures without touching the real file system.
//!
//! Never enable the `test-fault-injection` feature in production
//! builds. The hooks are implemented as process-wide atomic flags so
//! they are cheap when disabled (a single `AtomicBool::load` per
//! call) but they would otherwise leak failures across unrelated
//! code paths if compiled into a running server.
//!
//! # Example
//!
//! ```ignore
//! use velesdb_core::fault_injection::SaveConfigFaultGuard;
//!
//! // `activate(fail_at)` schedules the Nth call to
//! // `Collection::save_config()` to return a synthetic
//! // `PermissionDenied` error; earlier and later calls succeed
//! // normally. Use `activate(0)` to fail the very first call.
//! // The guard fires exactly once and the counter+threshold are
//! // cleared on drop so tests can never leak fault state across
//! // test cases.
//! {
//! let _guard = SaveConfigFaultGuard::activate(0);
//! // Exercise the rollback path of apply_advanced_config,
//! // upsert_points, or any other caller of save_config().
//! }
//! // Guard dropped → normal operation resumes.
//! ```
use ;
/// Sentinel value meaning "no fault injection scheduled".
/// `save_config` never reaches this call count in any realistic
/// scenario so the comparison is effectively disabled at rest.
const SAVE_CONFIG_FAIL_DISABLED: usize = usizeMAX;
/// Process-wide counter of every `Collection::save_config()` call
/// since the most recent guard activation. Compared against
/// `SAVE_CONFIG_FAIL_AT` on every call — the first call whose
/// zero-based index reaches `SAVE_CONFIG_FAIL_AT` returns a
/// synthetic `Error::Io(PermissionDenied)` and subsequent calls
/// pass through untouched (the guard "fires once").
pub static SAVE_CONFIG_CALL_COUNT: AtomicUsize = new;
/// Process-wide threshold at which `save_config` starts failing.
/// Set to `usize::MAX` (via `SAVE_CONFIG_FAIL_DISABLED`) at rest so
/// the check is effectively a no-op when no guard is active.
/// A guard activation stores a finite value here; dropping the
/// guard resets it back to the sentinel.
pub static SAVE_CONFIG_FAIL_AT: AtomicUsize = new;
/// RAII guard that schedules the Nth call to
/// `Collection::save_config()` on this process to return a
/// synthetic `Error::Io(PermissionDenied)` instead of touching the
/// file system. All preceding and following calls succeed normally.
///
/// The "fail after N" semantics are what makes this guard useful for
/// Phase-2 rollback tests: when a REST handler first creates a
/// collection (Phase 1) and then applies advanced config (Phase 2),
/// the test needs Phase 1's save_config() calls to succeed and only
/// Phase 2's to fail. Activate the guard with
/// `fail_at = <count of Phase 1 calls>` — Phase 1 then completes
/// normally, Phase 2 immediately hits the injected failure, and the
/// rollback logic can be exercised end-to-end.
///
/// Dropping the guard resets both the threshold and the counter, so
/// tests that construct a guard inside a scope can never leak state
/// into unrelated tests — even if they panic in between. This
/// matters because the state is process-wide (atomic) rather than
/// thread-local: without RAII semantics a flaky test could poison
/// the whole test binary.
///
/// Always bind the guard to a named variable (`let _guard = ...`)
/// rather than `let _ = ...` — the latter drops the guard
/// immediately and defeats the purpose.
;
/// Called by `Collection::save_config()` at the top of the function
/// to decide whether to return a synthetic error. Returns `true` if
/// the caller should fail.
///
/// The counter is incremented unconditionally (regardless of whether
/// a guard is active) so tests can read `SAVE_CONFIG_CALL_COUNT`
/// between operations to measure how many `save_config` calls a
/// given code path produces — essential for calibrating the
/// `fail_at` threshold of subsequent fault injection.
pub