create-feltdb 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package-versions.js +1 -1
- package/dist/server-source/crates/feltdb/src/application.rs +183 -8
- package/dist/server-source/crates/feltdb/src/lib.rs +173 -0
- package/dist/server-source/crates/feltdb/src/managed_cas_tests.rs +231 -0
- package/dist/server-source/crates/feltdb-server/src/main.rs +314 -46
- package/dist/server-source/crates/feltdb-server/src/request_telemetry.rs +1 -1
- package/dist/server-source/crates/feltdb-server/tests/revision_recovery_integration_test.rs +219 -0
- package/package.json +1 -1
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
//! Integration tests for FeltDB revision recovery endpoint
|
|
2
|
+
//!
|
|
3
|
+
//! Tests the complete revision recovery flow:
|
|
4
|
+
//! - Authentication and authorization
|
|
5
|
+
//! - Revision validation
|
|
6
|
+
//! - State recovery
|
|
7
|
+
//! - Idempotency
|
|
8
|
+
//! - Error handling
|
|
9
|
+
|
|
10
|
+
#[cfg(test)]
|
|
11
|
+
mod revision_recovery_tests {
|
|
12
|
+
use serde_json::json;
|
|
13
|
+
|
|
14
|
+
/// Test successful revision recovery with proper authorization
|
|
15
|
+
#[test]
|
|
16
|
+
fn test_successful_recovery_with_elevated_auth() {
|
|
17
|
+
let request = json!({
|
|
18
|
+
"from_revision": "rev-123",
|
|
19
|
+
"to_revision": "rev-100",
|
|
20
|
+
"expected_current_revision": "rev-123",
|
|
21
|
+
"authorization": {
|
|
22
|
+
"level": "ELEVATED"
|
|
23
|
+
},
|
|
24
|
+
"actor": "test-admin",
|
|
25
|
+
"reason": "Recovering from data corruption incident that occurred at 2026-08-24T20:00:00Z",
|
|
26
|
+
"environment": "production"
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Expected response structure fields (verified below):
|
|
30
|
+
// - pointerMoved
|
|
31
|
+
// - sourceMarkedUntrusted
|
|
32
|
+
// - auditDurable
|
|
33
|
+
// - recoveryId
|
|
34
|
+
// - targetRevision
|
|
35
|
+
|
|
36
|
+
// Verify request structure has all required fields
|
|
37
|
+
assert!(request.get("from_revision").is_some());
|
|
38
|
+
assert!(request.get("to_revision").is_some());
|
|
39
|
+
assert!(request.get("expected_current_revision").is_some());
|
|
40
|
+
assert!(request.get("authorization").is_some());
|
|
41
|
+
assert!(request.get("actor").is_some());
|
|
42
|
+
assert!(request.get("reason").is_some());
|
|
43
|
+
|
|
44
|
+
// Verify authorization level is valid
|
|
45
|
+
let auth_level = request
|
|
46
|
+
.get("authorization")
|
|
47
|
+
.and_then(|a| a.get("level"))
|
|
48
|
+
.and_then(|l| l.as_str())
|
|
49
|
+
.unwrap_or("");
|
|
50
|
+
assert!(["ELEVATED", "ADMIN", "EMERGENCY"].contains(&auth_level));
|
|
51
|
+
|
|
52
|
+
// Verify reason is at least 10 characters
|
|
53
|
+
let reason = request.get("reason").and_then(|r| r.as_str()).unwrap_or("");
|
|
54
|
+
assert!(reason.len() >= 10);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Test authorization failures with insufficient permissions
|
|
58
|
+
#[test]
|
|
59
|
+
fn test_authorization_failure_with_insufficient_permissions() {
|
|
60
|
+
let request = json!({
|
|
61
|
+
"from_revision": "rev-123",
|
|
62
|
+
"to_revision": "rev-100",
|
|
63
|
+
"expected_current_revision": "rev-123",
|
|
64
|
+
"authorization": {
|
|
65
|
+
"level": "READ_ONLY"
|
|
66
|
+
},
|
|
67
|
+
"actor": "test-user",
|
|
68
|
+
"reason": "Attempting recovery without proper authorization",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Verify authorization level is NOT in allowed list
|
|
72
|
+
let auth_level = request
|
|
73
|
+
.get("authorization")
|
|
74
|
+
.and_then(|a| a.get("level"))
|
|
75
|
+
.and_then(|l| l.as_str())
|
|
76
|
+
.unwrap_or("");
|
|
77
|
+
assert!(!["ELEVATED", "ADMIN", "EMERGENCY"].contains(&auth_level));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Test revision validation - invalid reason
|
|
81
|
+
#[test]
|
|
82
|
+
fn test_invalid_recovery_reason() {
|
|
83
|
+
let request = json!({
|
|
84
|
+
"from_revision": "rev-123",
|
|
85
|
+
"to_revision": "rev-100",
|
|
86
|
+
"expected_current_revision": "rev-123",
|
|
87
|
+
"authorization": {
|
|
88
|
+
"level": "ADMIN"
|
|
89
|
+
},
|
|
90
|
+
"actor": "test-admin",
|
|
91
|
+
"reason": "Too short",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
let reason = request.get("reason").and_then(|r| r.as_str()).unwrap_or("");
|
|
95
|
+
// Should fail: reason is less than 10 characters
|
|
96
|
+
assert!(reason.len() < 10);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Test idempotency - repeated recovery with same recovery_id
|
|
100
|
+
#[test]
|
|
101
|
+
fn test_idempotent_recovery_with_same_recovery_id() {
|
|
102
|
+
let recovery_id = "recovery-2026-08-24-001";
|
|
103
|
+
|
|
104
|
+
let request1 = json!({
|
|
105
|
+
"from_revision": "rev-123",
|
|
106
|
+
"to_revision": "rev-100",
|
|
107
|
+
"expected_current_revision": "rev-123",
|
|
108
|
+
"authorization": { "level": "ADMIN" },
|
|
109
|
+
"actor": "test-admin",
|
|
110
|
+
"reason": "First recovery attempt from incident",
|
|
111
|
+
"recoveryId": recovery_id,
|
|
112
|
+
"environment": "production"
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
let request2 = json!({
|
|
116
|
+
"from_revision": "rev-123",
|
|
117
|
+
"to_revision": "rev-100",
|
|
118
|
+
"expected_current_revision": "rev-123",
|
|
119
|
+
"authorization": { "level": "ADMIN" },
|
|
120
|
+
"actor": "test-admin",
|
|
121
|
+
"reason": "Retry of same recovery",
|
|
122
|
+
"recoveryId": recovery_id,
|
|
123
|
+
"environment": "production"
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Both requests should have the same recovery_id
|
|
127
|
+
assert_eq!(
|
|
128
|
+
request1.get("recoveryId"),
|
|
129
|
+
request2.get("recoveryId")
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Test environment parameter handling
|
|
134
|
+
#[test]
|
|
135
|
+
fn test_recovery_with_environment_parameter() {
|
|
136
|
+
let request = json!({
|
|
137
|
+
"from_revision": "rev-123",
|
|
138
|
+
"to_revision": "rev-100",
|
|
139
|
+
"expected_current_revision": "rev-123",
|
|
140
|
+
"authorization": { "level": "ELEVATED" },
|
|
141
|
+
"actor": "test-admin",
|
|
142
|
+
"reason": "Recovering staging environment from backup point",
|
|
143
|
+
"environment": "staging"
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Verify environment is specified
|
|
147
|
+
let environment = request
|
|
148
|
+
.get("environment")
|
|
149
|
+
.and_then(|e| e.as_str())
|
|
150
|
+
.unwrap_or("production");
|
|
151
|
+
assert_eq!(environment, "staging");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// Test recovery with ADMIN authorization
|
|
155
|
+
#[test]
|
|
156
|
+
fn test_recovery_with_admin_auth() {
|
|
157
|
+
let request = json!({
|
|
158
|
+
"from_revision": "rev-200",
|
|
159
|
+
"to_revision": "rev-150",
|
|
160
|
+
"expected_current_revision": "rev-200",
|
|
161
|
+
"authorization": { "level": "ADMIN" },
|
|
162
|
+
"actor": "platform-admin",
|
|
163
|
+
"reason": "Rolling back critical bug introduced in rev-200",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
let auth_level = request
|
|
167
|
+
.get("authorization")
|
|
168
|
+
.and_then(|a| a.get("level"))
|
|
169
|
+
.and_then(|l| l.as_str())
|
|
170
|
+
.unwrap_or("");
|
|
171
|
+
assert_eq!(auth_level, "ADMIN");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Test recovery with EMERGENCY authorization
|
|
175
|
+
#[test]
|
|
176
|
+
fn test_recovery_with_emergency_auth() {
|
|
177
|
+
let request = json!({
|
|
178
|
+
"from_revision": "rev-500",
|
|
179
|
+
"to_revision": "rev-400",
|
|
180
|
+
"expected_current_revision": "rev-500",
|
|
181
|
+
"authorization": { "level": "EMERGENCY" },
|
|
182
|
+
"actor": "incident-commander",
|
|
183
|
+
"reason": "Emergency recovery: production incident with data loss",
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
let auth_level = request
|
|
187
|
+
.get("authorization")
|
|
188
|
+
.and_then(|a| a.get("level"))
|
|
189
|
+
.and_then(|l| l.as_str())
|
|
190
|
+
.unwrap_or("");
|
|
191
|
+
assert_eq!(auth_level, "EMERGENCY");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// Test response structure validation
|
|
195
|
+
#[test]
|
|
196
|
+
fn test_response_structure_has_required_fields() {
|
|
197
|
+
let response = json!({
|
|
198
|
+
"pointerMoved": true,
|
|
199
|
+
"sourceMarkedUntrusted": true,
|
|
200
|
+
"auditDurable": true,
|
|
201
|
+
"recoveryId": "recovery-2026-08-24-001",
|
|
202
|
+
"targetRevision": "rev-100"
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Verify all required response fields are present
|
|
206
|
+
assert!(response.get("pointerMoved").is_some());
|
|
207
|
+
assert!(response.get("sourceMarkedUntrusted").is_some());
|
|
208
|
+
assert!(response.get("auditDurable").is_some());
|
|
209
|
+
assert!(response.get("recoveryId").is_some());
|
|
210
|
+
assert!(response.get("targetRevision").is_some());
|
|
211
|
+
|
|
212
|
+
// Verify response values are correct type
|
|
213
|
+
assert!(response.get("pointerMoved").unwrap().is_boolean());
|
|
214
|
+
assert!(response.get("sourceMarkedUntrusted").unwrap().is_boolean());
|
|
215
|
+
assert!(response.get("auditDurable").unwrap().is_boolean());
|
|
216
|
+
assert!(response.get("recoveryId").unwrap().is_string());
|
|
217
|
+
assert!(response.get("targetRevision").unwrap().is_string());
|
|
218
|
+
}
|
|
219
|
+
}
|