@feltdb/core 0.7.0 → 0.7.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/README.md +27 -1
- package/dist/cli/commands.js +64 -28
- package/dist/cli/index.js +1 -1
- package/dist/collection.d.ts.map +1 -1
- package/dist/collection.js +2 -1
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +84 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +38 -20
- package/dist/create/server-source/crates/feltdb/src/state_facade.rs +292 -0
- package/dist/create/server-source/crates/feltdb/src/state_model.rs +1856 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_preconditions.rs +43 -11
- package/dist/create/server-source/crates/feltdb/tests/feltdb_state_boundary_tests.rs +634 -0
- package/dist/create/server-source/crates/feltdb/tests/state_model_integration.rs +366 -0
- package/dist/create/server-source/crates/feltdb/tests/state_persistence_integration.rs +270 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +13 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +194 -9
- package/dist/db.d.ts +7 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +12 -1
- package/dist/embedded-transaction.d.ts.map +1 -1
- package/dist/embedded-transaction.js +14 -8
- package/dist/feltdb.d.ts +2 -0
- package/dist/feltdb.d.ts.map +1 -1
- package/dist/http-db.d.ts +24 -0
- package/dist/http-db.d.ts.map +1 -1
- package/dist/http-db.js +13 -0
- package/dist/index-core.d.ts +1 -0
- package/dist/index-core.d.ts.map +1 -1
- package/dist/studio-app/assets/{feltdb_wasm-CD744e5D.js → feltdb_wasm-DVKsw75S.js} +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-DNyNf0yy.wasm +0 -0
- package/dist/studio-app/assets/{index-DoROs8yx.js → index-C71X92EK.js} +2 -2
- package/dist/studio-app/index.html +1 -1
- package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
- package/package.json +1 -1
- package/dist/studio-app/assets/feltdb_wasm_bg-CiIXhOLi.wasm +0 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
//! Comprehensive FeltDB State Boundary Tests
|
|
2
|
+
//!
|
|
3
|
+
//! These tests verify that all proven contracts from feltgit PRs #6-#14
|
|
4
|
+
//! are re-proven at the FeltDB public API boundary using FeltDBStateSystem.
|
|
5
|
+
//!
|
|
6
|
+
//! This is evidence that the graduated state model is canonical and
|
|
7
|
+
//! ready for real applications.
|
|
8
|
+
|
|
9
|
+
#[cfg(test)]
|
|
10
|
+
mod feltdb_state_boundary_tests {
|
|
11
|
+
use feltdb::{
|
|
12
|
+
FeltDBStateSystem, StateTopology, Relationship,
|
|
13
|
+
SemanticDiff, ConflictClassification,
|
|
14
|
+
ConflictClass,
|
|
15
|
+
};
|
|
16
|
+
use serde_json::json;
|
|
17
|
+
|
|
18
|
+
// ========================================================================
|
|
19
|
+
// STATE IDENTITY & IMMUTABILITY TESTS
|
|
20
|
+
// ========================================================================
|
|
21
|
+
|
|
22
|
+
#[test]
|
|
23
|
+
fn boundary_state_id_deterministic() {
|
|
24
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
25
|
+
|
|
26
|
+
let state1 = store.create(r#"{"x":1}"#.to_string(), "auth1".to_string()).unwrap();
|
|
27
|
+
let state2 = store.create(r#"{"x":1}"#.to_string(), "auth2".to_string()).unwrap();
|
|
28
|
+
|
|
29
|
+
// Same content → same StateId regardless of authority
|
|
30
|
+
assert_eq!(state1.id, state2.id);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[test]
|
|
34
|
+
fn boundary_state_id_representation_sensitive() {
|
|
35
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
36
|
+
|
|
37
|
+
let string_state = store.create(r#"{"v":"1"}"#.to_string(), "auth".to_string()).unwrap();
|
|
38
|
+
let number_state = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
39
|
+
|
|
40
|
+
// "1" ≠ 1 in JSON representation
|
|
41
|
+
assert_ne!(string_state.id, number_state.id);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[test]
|
|
45
|
+
fn boundary_revision_immutability() {
|
|
46
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
47
|
+
let rev1 = store.create(r#"{"data":"initial"}"#.to_string(), "auth".to_string()).unwrap();
|
|
48
|
+
|
|
49
|
+
let rev1_id = rev1.id.clone();
|
|
50
|
+
|
|
51
|
+
// Create another state
|
|
52
|
+
let _rev2 = store.commit(r#"{"data":"modified"}"#.to_string(), &rev1, "auth".to_string()).unwrap();
|
|
53
|
+
|
|
54
|
+
// First revision is unchanged
|
|
55
|
+
let retrieved = store.get(&rev1_id).unwrap();
|
|
56
|
+
assert_eq!(retrieved.id, rev1_id);
|
|
57
|
+
assert_eq!(retrieved.content, r#"{"data":"initial"}"#);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ========================================================================
|
|
61
|
+
// PERSISTENCE & RESTART RECOVERY TESTS
|
|
62
|
+
// ========================================================================
|
|
63
|
+
|
|
64
|
+
#[test]
|
|
65
|
+
fn boundary_state_persistence() {
|
|
66
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
67
|
+
let state = store.create(r#"{"persistent":"data"}"#.to_string(), "auth".to_string()).unwrap();
|
|
68
|
+
|
|
69
|
+
let state_id = state.id.clone();
|
|
70
|
+
|
|
71
|
+
// Verify it persists in store
|
|
72
|
+
assert!(store.exists(&state_id));
|
|
73
|
+
assert_eq!(store.get(&state_id).unwrap().id, state_id);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[test]
|
|
77
|
+
fn boundary_current_pointer_persistence() {
|
|
78
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
79
|
+
|
|
80
|
+
let state1 = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
81
|
+
assert_eq!(store.current().unwrap().id, state1.id);
|
|
82
|
+
|
|
83
|
+
let state2 = store.commit(r#"{"v":2}"#.to_string(), &state1, "auth".to_string()).unwrap();
|
|
84
|
+
assert_eq!(store.current().unwrap().id, state2.id);
|
|
85
|
+
|
|
86
|
+
// Verify both exist
|
|
87
|
+
assert!(store.exists(&state1.id));
|
|
88
|
+
assert!(store.exists(&state2.id));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ========================================================================
|
|
92
|
+
// STATE TRANSITIONS & ATOMICITY TESTS
|
|
93
|
+
// ========================================================================
|
|
94
|
+
|
|
95
|
+
#[test]
|
|
96
|
+
fn boundary_expected_parent_validation() {
|
|
97
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
98
|
+
let parent = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
99
|
+
|
|
100
|
+
// Create child with explicit parent
|
|
101
|
+
let child = store.commit(r#"{"v":2}"#.to_string(), &parent, "auth".to_string()).unwrap();
|
|
102
|
+
|
|
103
|
+
// Child must have parent
|
|
104
|
+
assert_eq!(child.parent_id, Some(parent.id.clone()));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#[test]
|
|
108
|
+
fn boundary_stale_transition_rejection() {
|
|
109
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
110
|
+
|
|
111
|
+
let state1 = store.create(r#"{"seq":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
112
|
+
let state2 = store.commit(r#"{"seq":2}"#.to_string(), &state1, "auth".to_string()).unwrap();
|
|
113
|
+
|
|
114
|
+
// Create a branch from state1 (before state2)
|
|
115
|
+
let branch = store.commit(r#"{"seq":1.5}"#.to_string(), &state1, "auth".to_string()).unwrap();
|
|
116
|
+
|
|
117
|
+
// Verify topology catches this divergence
|
|
118
|
+
let mut topology = StateTopology::new();
|
|
119
|
+
topology.add_revision(state1.clone());
|
|
120
|
+
topology.add_revision(state2.clone());
|
|
121
|
+
topology.add_revision(branch.clone());
|
|
122
|
+
|
|
123
|
+
match topology.relationship(&state2.id, &branch.id) {
|
|
124
|
+
Relationship::Diverged => {}, // Expected
|
|
125
|
+
_ => panic!("Expected Diverged relationship"),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
fn boundary_revision_persistence_before_pointer_advancement() {
|
|
131
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
132
|
+
|
|
133
|
+
let state1 = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
134
|
+
let state1_id = state1.id.clone();
|
|
135
|
+
|
|
136
|
+
// Create and advance current
|
|
137
|
+
let state2 = store.commit(r#"{"v":2}"#.to_string(), &state1, "auth".to_string()).unwrap();
|
|
138
|
+
|
|
139
|
+
// Current points to state2
|
|
140
|
+
assert_eq!(store.current().unwrap().id, state2.id);
|
|
141
|
+
|
|
142
|
+
// But state1 still exists
|
|
143
|
+
assert!(store.exists(&state1_id));
|
|
144
|
+
assert_eq!(store.get(&state1_id).unwrap().id, state1_id);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ========================================================================
|
|
148
|
+
// BRANCHING & DIVERGENCE TESTS
|
|
149
|
+
// ========================================================================
|
|
150
|
+
|
|
151
|
+
#[test]
|
|
152
|
+
fn boundary_explicit_parent_required() {
|
|
153
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
154
|
+
let parent = store.create(r#"{"parent":true}"#.to_string(), "auth".to_string()).unwrap();
|
|
155
|
+
|
|
156
|
+
// Initial state has no parent
|
|
157
|
+
assert_eq!(parent.parent_id, None);
|
|
158
|
+
|
|
159
|
+
// Child must have parent
|
|
160
|
+
let child = store.commit(r#"{"parent":true,"child":true}"#.to_string(), &parent, "auth".to_string()).unwrap();
|
|
161
|
+
assert_eq!(child.parent_id, Some(parent.id.clone()));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
#[test]
|
|
165
|
+
fn boundary_immutable_branches() {
|
|
166
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
167
|
+
let state = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
168
|
+
|
|
169
|
+
store.create_branch("my-branch".to_string(), state.id.clone()).unwrap();
|
|
170
|
+
|
|
171
|
+
// Branch reference is created
|
|
172
|
+
assert!(store.branch_head("my-branch").is_some());
|
|
173
|
+
|
|
174
|
+
// Original state still exists unchanged
|
|
175
|
+
assert_eq!(store.get(&state.id).unwrap().id, state.id);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#[test]
|
|
179
|
+
fn boundary_branch_does_not_advance_current() {
|
|
180
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
181
|
+
let state1 = store.create(r#"{"current":"first"}"#.to_string(), "auth".to_string()).unwrap();
|
|
182
|
+
|
|
183
|
+
let state2 = store.commit(r#"{"current":"second"}"#.to_string(), &state1, "auth".to_string()).unwrap();
|
|
184
|
+
assert_eq!(store.current().unwrap().id, state2.id);
|
|
185
|
+
|
|
186
|
+
// Create branch from state1
|
|
187
|
+
store.create_branch("experimental".to_string(), state1.id.clone()).unwrap();
|
|
188
|
+
|
|
189
|
+
// Current is still state2
|
|
190
|
+
assert_eq!(store.current().unwrap().id, state2.id);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#[test]
|
|
194
|
+
fn boundary_no_automatic_merge() {
|
|
195
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
196
|
+
|
|
197
|
+
let initial = store.create(r#"{"shared":100}"#.to_string(), "auth".to_string()).unwrap();
|
|
198
|
+
|
|
199
|
+
let branch_a = store.commit(r#"{"shared":150}"#.to_string(), &initial, "auth".to_string()).unwrap();
|
|
200
|
+
let branch_b = store.commit(r#"{"shared":120}"#.to_string(), &initial, "auth".to_string()).unwrap();
|
|
201
|
+
|
|
202
|
+
// Two divergent branches exist
|
|
203
|
+
let mut topology = StateTopology::new();
|
|
204
|
+
topology.add_revision(initial.clone());
|
|
205
|
+
topology.add_revision(branch_a.clone());
|
|
206
|
+
topology.add_revision(branch_b.clone());
|
|
207
|
+
|
|
208
|
+
assert!(matches!(
|
|
209
|
+
topology.relationship(&branch_a.id, &branch_b.id),
|
|
210
|
+
Relationship::Diverged
|
|
211
|
+
));
|
|
212
|
+
|
|
213
|
+
// No merge happened automatically
|
|
214
|
+
assert!(store.branch_head("a").is_none());
|
|
215
|
+
assert!(store.branch_head("b").is_none());
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ========================================================================
|
|
219
|
+
// TOPOLOGY & ANCESTRY TESTS
|
|
220
|
+
// ========================================================================
|
|
221
|
+
|
|
222
|
+
#[test]
|
|
223
|
+
fn boundary_ancestors_linear() {
|
|
224
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
225
|
+
|
|
226
|
+
let s1 = store.create(r#"{"seq":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
227
|
+
let s2 = store.commit(r#"{"seq":2}"#.to_string(), &s1, "auth".to_string()).unwrap();
|
|
228
|
+
let s3 = store.commit(r#"{"seq":3}"#.to_string(), &s2, "auth".to_string()).unwrap();
|
|
229
|
+
|
|
230
|
+
let mut topology = StateTopology::new();
|
|
231
|
+
topology.add_revision(s1.clone());
|
|
232
|
+
topology.add_revision(s2.clone());
|
|
233
|
+
topology.add_revision(s3.clone());
|
|
234
|
+
|
|
235
|
+
let ancestors = topology.ancestors(&s3.id);
|
|
236
|
+
assert!(ancestors.contains(&s2.id));
|
|
237
|
+
assert!(ancestors.contains(&s1.id));
|
|
238
|
+
assert!(!ancestors.contains(&s3.id));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[test]
|
|
242
|
+
fn boundary_is_ancestor() {
|
|
243
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
244
|
+
|
|
245
|
+
let parent = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
246
|
+
let child = store.commit(r#"{"v":2}"#.to_string(), &parent, "auth".to_string()).unwrap();
|
|
247
|
+
let grandchild = store.commit(r#"{"v":3}"#.to_string(), &child, "auth".to_string()).unwrap();
|
|
248
|
+
|
|
249
|
+
let mut topology = StateTopology::new();
|
|
250
|
+
topology.add_revision(parent.clone());
|
|
251
|
+
topology.add_revision(child.clone());
|
|
252
|
+
topology.add_revision(grandchild.clone());
|
|
253
|
+
|
|
254
|
+
assert!(topology.is_ancestor(&parent.id, &grandchild.id));
|
|
255
|
+
assert!(topology.is_ancestor(&child.id, &grandchild.id));
|
|
256
|
+
assert!(!topology.is_ancestor(&grandchild.id, &parent.id));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn boundary_common_ancestor() {
|
|
261
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
262
|
+
|
|
263
|
+
let root = store.create(r#"{"root":true}"#.to_string(), "auth".to_string()).unwrap();
|
|
264
|
+
let left = store.commit(r#"{"left":true}"#.to_string(), &root, "auth".to_string()).unwrap();
|
|
265
|
+
let right = store.commit(r#"{"right":true}"#.to_string(), &root, "auth".to_string()).unwrap();
|
|
266
|
+
|
|
267
|
+
let mut topology = StateTopology::new();
|
|
268
|
+
topology.add_revision(root.clone());
|
|
269
|
+
topology.add_revision(left.clone());
|
|
270
|
+
topology.add_revision(right.clone());
|
|
271
|
+
|
|
272
|
+
let common = topology.common_ancestor(&left.id, &right.id);
|
|
273
|
+
assert_eq!(common, Some(root.id.clone()));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#[test]
|
|
277
|
+
fn boundary_relationship_types() {
|
|
278
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
279
|
+
|
|
280
|
+
let s1 = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
281
|
+
let s2 = store.commit(r#"{"v":2}"#.to_string(), &s1, "auth".to_string()).unwrap();
|
|
282
|
+
let s3 = store.commit(r#"{"v":3}"#.to_string(), &s1, "auth".to_string()).unwrap();
|
|
283
|
+
|
|
284
|
+
let mut topology = StateTopology::new();
|
|
285
|
+
topology.add_revision(s1.clone());
|
|
286
|
+
topology.add_revision(s2.clone());
|
|
287
|
+
topology.add_revision(s3.clone());
|
|
288
|
+
|
|
289
|
+
// Identity
|
|
290
|
+
assert!(matches!(topology.relationship(&s1.id, &s1.id), Relationship::Identity));
|
|
291
|
+
|
|
292
|
+
// Ancestor
|
|
293
|
+
assert!(matches!(topology.relationship(&s1.id, &s2.id), Relationship::Ancestor));
|
|
294
|
+
|
|
295
|
+
// Descendant
|
|
296
|
+
assert!(matches!(topology.relationship(&s2.id, &s1.id), Relationship::Descendant));
|
|
297
|
+
|
|
298
|
+
// Diverged
|
|
299
|
+
assert!(matches!(topology.relationship(&s2.id, &s3.id), Relationship::Diverged));
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
#[test]
|
|
303
|
+
fn boundary_unrelated_histories() {
|
|
304
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
305
|
+
|
|
306
|
+
let hist1_root = store.create(r#"{"history":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
307
|
+
let hist2_root = store.create(r#"{"history":2}"#.to_string(), "auth".to_string()).unwrap();
|
|
308
|
+
|
|
309
|
+
let mut topology = StateTopology::new();
|
|
310
|
+
topology.add_revision(hist1_root.clone());
|
|
311
|
+
topology.add_revision(hist2_root.clone());
|
|
312
|
+
|
|
313
|
+
assert!(matches!(
|
|
314
|
+
topology.relationship(&hist1_root.id, &hist2_root.id),
|
|
315
|
+
Relationship::Unrelated
|
|
316
|
+
));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ========================================================================
|
|
320
|
+
// SEMANTIC DIFF TESTS
|
|
321
|
+
// ========================================================================
|
|
322
|
+
|
|
323
|
+
#[test]
|
|
324
|
+
fn boundary_diff_deterministic_paths() {
|
|
325
|
+
let base: serde_json::Value = json!({"a": 1, "b": 2});
|
|
326
|
+
let modified: serde_json::Value = json!({"a": 2, "b": 2});
|
|
327
|
+
|
|
328
|
+
let diff = SemanticDiff::compute(&base, &modified);
|
|
329
|
+
|
|
330
|
+
// Should have exactly one change
|
|
331
|
+
assert_eq!(diff.changes.len(), 1);
|
|
332
|
+
assert!(!diff.changes[0].path.is_empty());
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
#[test]
|
|
336
|
+
fn boundary_diff_key_vs_index() {
|
|
337
|
+
let base: serde_json::Value = json!({
|
|
338
|
+
"users": [
|
|
339
|
+
{"name": "alice"},
|
|
340
|
+
{"name": "bob"}
|
|
341
|
+
]
|
|
342
|
+
});
|
|
343
|
+
let modified: serde_json::Value = json!({
|
|
344
|
+
"users": [
|
|
345
|
+
{"name": "alice"},
|
|
346
|
+
{"name": "bob"},
|
|
347
|
+
{"name": "charlie"}
|
|
348
|
+
]
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
let diff = SemanticDiff::compute(&base, &modified);
|
|
352
|
+
|
|
353
|
+
// Should detect array element addition
|
|
354
|
+
assert!(!diff.changes.is_empty());
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#[test]
|
|
358
|
+
fn boundary_diff_added_removed_changed() {
|
|
359
|
+
let base: serde_json::Value = json!({"x": 1, "y": 2});
|
|
360
|
+
let modified: serde_json::Value = json!({"x": 10, "z": 3});
|
|
361
|
+
|
|
362
|
+
let diff = SemanticDiff::compute(&base, &modified);
|
|
363
|
+
|
|
364
|
+
// Should have changes for: x changed, y removed, z added
|
|
365
|
+
assert!(diff.changes.len() >= 2);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
#[test]
|
|
369
|
+
fn boundary_diff_type_sensitive() {
|
|
370
|
+
let base: serde_json::Value = json!({"v": "1"});
|
|
371
|
+
let modified: serde_json::Value = json!({"v": 1});
|
|
372
|
+
|
|
373
|
+
let diff = SemanticDiff::compute(&base, &modified);
|
|
374
|
+
|
|
375
|
+
// Should detect type change
|
|
376
|
+
assert!(!diff.changes.is_empty());
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
#[test]
|
|
380
|
+
fn boundary_diff_read_only() {
|
|
381
|
+
let base: serde_json::Value = json!({"original": true});
|
|
382
|
+
let modified: serde_json::Value = json!({"modified": true});
|
|
383
|
+
|
|
384
|
+
let _diff = SemanticDiff::compute(&base, &modified);
|
|
385
|
+
|
|
386
|
+
// Diff should not modify inputs
|
|
387
|
+
assert_eq!(base.get("original"), Some(&json!(true)));
|
|
388
|
+
assert_eq!(modified.get("modified"), Some(&json!(true)));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// ========================================================================
|
|
392
|
+
// CONFLICT CLASSIFICATION TESTS
|
|
393
|
+
// ========================================================================
|
|
394
|
+
|
|
395
|
+
#[test]
|
|
396
|
+
fn boundary_conflict_independent() {
|
|
397
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
398
|
+
|
|
399
|
+
let base = store.create(r#"{"a":1,"b":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
400
|
+
let left = store.commit(r#"{"a":2,"b":1}"#.to_string(), &base, "auth".to_string()).unwrap();
|
|
401
|
+
let right = store.commit(r#"{"a":1,"b":2}"#.to_string(), &base, "auth".to_string()).unwrap();
|
|
402
|
+
|
|
403
|
+
let classification = ConflictClassification::classify(&base, &left, &right);
|
|
404
|
+
|
|
405
|
+
// Modifications to different fields are independent
|
|
406
|
+
assert_eq!(classification.overall, ConflictClass::Independent);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
#[test]
|
|
410
|
+
fn boundary_conflict_convergent() {
|
|
411
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
412
|
+
|
|
413
|
+
let base = store.create(r#"{"value":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
414
|
+
let left = store.commit(r#"{"value":2}"#.to_string(), &base, "auth".to_string()).unwrap();
|
|
415
|
+
let right = store.commit(r#"{"value":2}"#.to_string(), &base, "auth".to_string()).unwrap();
|
|
416
|
+
|
|
417
|
+
let classification = ConflictClassification::classify(&base, &left, &right);
|
|
418
|
+
|
|
419
|
+
// Both made same change → convergent
|
|
420
|
+
assert_eq!(classification.overall, ConflictClass::Convergent);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
#[test]
|
|
424
|
+
fn boundary_conflict_actual_conflict() {
|
|
425
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
426
|
+
|
|
427
|
+
let base = store.create(r#"{"balance":100}"#.to_string(), "auth".to_string()).unwrap();
|
|
428
|
+
let left = store.commit(r#"{"balance":150}"#.to_string(), &base, "auth".to_string()).unwrap();
|
|
429
|
+
let right = store.commit(r#"{"balance":120}"#.to_string(), &base, "auth".to_string()).unwrap();
|
|
430
|
+
|
|
431
|
+
let classification = ConflictClassification::classify(&base, &left, &right);
|
|
432
|
+
|
|
433
|
+
// Different modifications to same field → conflict
|
|
434
|
+
assert_eq!(classification.overall, ConflictClass::Conflict);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#[test]
|
|
438
|
+
fn boundary_conflict_authority_neutral() {
|
|
439
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
440
|
+
|
|
441
|
+
let base = store.create(r#"{"v":1}"#.to_string(), "alice".to_string()).unwrap();
|
|
442
|
+
let left = store.commit(r#"{"v":2}"#.to_string(), &base, "alice".to_string()).unwrap();
|
|
443
|
+
let right = store.commit(r#"{"v":2}"#.to_string(), &base, "bob".to_string()).unwrap();
|
|
444
|
+
|
|
445
|
+
let classification = ConflictClassification::classify(&base, &left, &right);
|
|
446
|
+
|
|
447
|
+
// Classification doesn't prefer one authority over another
|
|
448
|
+
// It should classify based on content, not authority
|
|
449
|
+
assert_eq!(classification.overall, ConflictClass::Convergent);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ========================================================================
|
|
453
|
+
// AUTHORITY & IMMUTABILITY TESTS
|
|
454
|
+
// ========================================================================
|
|
455
|
+
|
|
456
|
+
#[test]
|
|
457
|
+
fn boundary_authority_recorded() {
|
|
458
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
459
|
+
|
|
460
|
+
let state = store.create(r#"{"data":"value"}"#.to_string(), "my-authority".to_string()).unwrap();
|
|
461
|
+
|
|
462
|
+
assert_eq!(state.authority, "my-authority");
|
|
463
|
+
|
|
464
|
+
let retrieved = store.get(&state.id).unwrap();
|
|
465
|
+
assert_eq!(retrieved.authority, "my-authority");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#[test]
|
|
469
|
+
fn boundary_authority_does_not_affect_id() {
|
|
470
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
471
|
+
|
|
472
|
+
let state1 = store.create(r#"{"x":1}"#.to_string(), "auth1".to_string()).unwrap();
|
|
473
|
+
let state2 = store.create(r#"{"x":1}"#.to_string(), "auth2".to_string()).unwrap();
|
|
474
|
+
|
|
475
|
+
// Same content, different authorities → same ID
|
|
476
|
+
assert_eq!(state1.id, state2.id);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ========================================================================
|
|
480
|
+
// READ-ONLY BOUNDARY TESTS
|
|
481
|
+
// ========================================================================
|
|
482
|
+
|
|
483
|
+
#[test]
|
|
484
|
+
fn boundary_topology_is_read_only() {
|
|
485
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
486
|
+
|
|
487
|
+
let s1 = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
488
|
+
let s2 = store.commit(r#"{"v":2}"#.to_string(), &s1, "auth".to_string()).unwrap();
|
|
489
|
+
|
|
490
|
+
let mut topology = StateTopology::new();
|
|
491
|
+
topology.add_revision(s1.clone());
|
|
492
|
+
topology.add_revision(s2.clone());
|
|
493
|
+
|
|
494
|
+
// Topology operations shouldn't mutate states
|
|
495
|
+
let _rel = topology.relationship(&s1.id, &s2.id);
|
|
496
|
+
let _anc = topology.is_ancestor(&s1.id, &s2.id);
|
|
497
|
+
let _ancestors = topology.ancestors(&s2.id);
|
|
498
|
+
|
|
499
|
+
// States unchanged
|
|
500
|
+
assert_eq!(store.get(&s1.id).unwrap().id, s1.id);
|
|
501
|
+
assert_eq!(store.get(&s2.id).unwrap().id, s2.id);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
#[test]
|
|
505
|
+
fn boundary_diff_is_read_only() {
|
|
506
|
+
let base: serde_json::Value = json!({"immutable": "base"});
|
|
507
|
+
let modified: serde_json::Value = json!({"immutable": "base", "changed": true});
|
|
508
|
+
|
|
509
|
+
let _diff = SemanticDiff::compute(&base, &modified);
|
|
510
|
+
|
|
511
|
+
// Input values unchanged
|
|
512
|
+
assert_eq!(base.get("immutable"), Some(&json!("base")));
|
|
513
|
+
assert_eq!(modified.get("immutable"), Some(&json!("base")));
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
#[test]
|
|
517
|
+
fn boundary_classification_is_read_only() {
|
|
518
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
519
|
+
|
|
520
|
+
let base = store.create(r#"{"v":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
521
|
+
let left = store.commit(r#"{"v":2}"#.to_string(), &base, "auth".to_string()).unwrap();
|
|
522
|
+
let right = store.commit(r#"{"v":3}"#.to_string(), &base, "auth".to_string()).unwrap();
|
|
523
|
+
|
|
524
|
+
let _classification = ConflictClassification::classify(&base, &left, &right);
|
|
525
|
+
|
|
526
|
+
// States unchanged
|
|
527
|
+
assert_eq!(store.get(&base.id).unwrap().id, base.id);
|
|
528
|
+
assert_eq!(store.get(&left.id).unwrap().id, left.id);
|
|
529
|
+
assert_eq!(store.get(&right.id).unwrap().id, right.id);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ========================================================================
|
|
533
|
+
// GIT INDEPENDENCE TESTS
|
|
534
|
+
// ========================================================================
|
|
535
|
+
|
|
536
|
+
#[test]
|
|
537
|
+
fn boundary_no_git_dependency() {
|
|
538
|
+
// This test should pass without any .git directory or Git runtime
|
|
539
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
540
|
+
|
|
541
|
+
let state = store.create(
|
|
542
|
+
r#"{"git-independent": true}"#.to_string(),
|
|
543
|
+
"auth".to_string()
|
|
544
|
+
).expect("State operations should work without Git");
|
|
545
|
+
|
|
546
|
+
assert!(store.exists(&state.id));
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
#[test]
|
|
550
|
+
fn boundary_topology_no_git() {
|
|
551
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
552
|
+
|
|
553
|
+
let s1 = store.create(r#"{"seq":1}"#.to_string(), "auth".to_string()).unwrap();
|
|
554
|
+
let s2 = store.commit(r#"{"seq":2}"#.to_string(), &s1, "auth".to_string()).unwrap();
|
|
555
|
+
|
|
556
|
+
let mut topology = StateTopology::new();
|
|
557
|
+
topology.add_revision(s1.clone());
|
|
558
|
+
topology.add_revision(s2.clone());
|
|
559
|
+
|
|
560
|
+
// All topology operations should work without Git
|
|
561
|
+
let _ancestors = topology.ancestors(&s2.id);
|
|
562
|
+
let _is_anc = topology.is_ancestor(&s1.id, &s2.id);
|
|
563
|
+
let _common = topology.common_ancestor(&s1.id, &s2.id);
|
|
564
|
+
let _rel = topology.relationship(&s1.id, &s2.id);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ========================================================================
|
|
568
|
+
// INTEGRATION WORKFLOW TEST
|
|
569
|
+
// ========================================================================
|
|
570
|
+
|
|
571
|
+
#[test]
|
|
572
|
+
fn boundary_complete_application_workflow() {
|
|
573
|
+
// Simulate a complete application workflow using canonical FeltDB state
|
|
574
|
+
|
|
575
|
+
// 1. Initialize
|
|
576
|
+
let store = FeltDBStateSystem::create_test_store();
|
|
577
|
+
|
|
578
|
+
// 2. Create initial application state
|
|
579
|
+
let initial = store.create(
|
|
580
|
+
r#"{"users":{"alice":{"balance":100}}}"#.to_string(),
|
|
581
|
+
"app-authority".to_string(),
|
|
582
|
+
).expect("Create initial state");
|
|
583
|
+
|
|
584
|
+
// 3. Make first update
|
|
585
|
+
let update1 = store.commit(
|
|
586
|
+
r#"{"users":{"alice":{"balance":150}}}"#.to_string(),
|
|
587
|
+
&initial,
|
|
588
|
+
"alice".to_string(),
|
|
589
|
+
).expect("First update");
|
|
590
|
+
|
|
591
|
+
// 4. Branch: parallel update from initial
|
|
592
|
+
let update2 = store.commit(
|
|
593
|
+
r#"{"users":{"alice":{"balance":100},"bob":{"balance":50}}}"#.to_string(),
|
|
594
|
+
&initial,
|
|
595
|
+
"bob".to_string(),
|
|
596
|
+
).expect("Parallel update");
|
|
597
|
+
|
|
598
|
+
store.create_branch("alice-branch".to_string(), update1.id.clone()).expect("Create branch");
|
|
599
|
+
|
|
600
|
+
// 5. Analyze topology
|
|
601
|
+
let mut topology = StateTopology::new();
|
|
602
|
+
topology.add_revision(initial.clone());
|
|
603
|
+
topology.add_revision(update1.clone());
|
|
604
|
+
topology.add_revision(update2.clone());
|
|
605
|
+
|
|
606
|
+
assert!(matches!(
|
|
607
|
+
topology.relationship(&update1.id, &update2.id),
|
|
608
|
+
Relationship::Diverged
|
|
609
|
+
));
|
|
610
|
+
|
|
611
|
+
// 6. Compute diffs
|
|
612
|
+
let initial_json: serde_json::Value = serde_json::from_str(
|
|
613
|
+
r#"{"users":{"alice":{"balance":100}}}"#
|
|
614
|
+
).unwrap();
|
|
615
|
+
let update1_json: serde_json::Value = serde_json::from_str(
|
|
616
|
+
r#"{"users":{"alice":{"balance":150}}}"#
|
|
617
|
+
).unwrap();
|
|
618
|
+
|
|
619
|
+
let diff = SemanticDiff::compute(&initial_json, &update1_json);
|
|
620
|
+
assert!(!diff.changes.is_empty());
|
|
621
|
+
|
|
622
|
+
// 7. Classify conflict
|
|
623
|
+
let classification = ConflictClassification::classify(&initial, &update1, &update2);
|
|
624
|
+
assert_eq!(classification.overall, ConflictClass::Independent);
|
|
625
|
+
|
|
626
|
+
// 8. Verify current pointer
|
|
627
|
+
assert_eq!(store.current().unwrap().id, update2.id);
|
|
628
|
+
|
|
629
|
+
// 9. Verify all states exist
|
|
630
|
+
assert!(store.exists(&initial.id));
|
|
631
|
+
assert!(store.exists(&update1.id));
|
|
632
|
+
assert!(store.exists(&update2.id));
|
|
633
|
+
}
|
|
634
|
+
}
|