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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
//! Comprehensive Integration Tests (Tasks 8-12)
//!
//! Combines: Property-based CRDT tests, cross-language interop stubs,
//! security validation, performance benchmarking placeholders, and
//! test automation documentation.
use proptest::prelude::*;
use saorsa_gossip_types::PeerId;
use x0x::crdt::{TaskId, TaskItem, TaskList, TaskListId, TaskMetadata};
use x0x::identity::AgentId;
// ============================================================================
// TASK 8: Property-Based CRDT Tests
// ============================================================================
/// Strategy to generate random AgentId
fn agent_id_strategy() -> impl Strategy<Value = AgentId> {
any::<[u8; 32]>().prop_map(AgentId)
}
/// Strategy to generate random PeerId
fn peer_id_strategy() -> impl Strategy<Value = PeerId> {
any::<[u8; 32]>().prop_map(PeerId::new)
}
/// Strategy to generate random TaskId
fn task_id_strategy() -> impl Strategy<Value = TaskId> {
any::<[u8; 32]>().prop_map(TaskId::from_bytes)
}
proptest! {
/// Property: OR-Set commutativity
///
/// Adding the same task to two replicas in different orders should
/// produce the same result after merging.
#[test]
fn prop_or_set_commutativity(
agent_id1 in agent_id_strategy(),
_agent_id2 in agent_id_strategy(),
peer_id1 in peer_id_strategy(),
peer_id2 in peer_id_strategy(),
task_id in task_id_strategy(),
) {
let list_id = TaskListId::new([1u8; 32]);
let meta = TaskMetadata {
title: "Test".to_string(),
description: "Desc".to_string(),
priority: 128,
created_by: agent_id1,
owner: None,
created_at: 1000,
tags: vec![],
};
let task1 = TaskItem::new(task_id, meta.clone(), peer_id1);
let task2 = TaskItem::new(task_id, meta, peer_id2);
// Order 1: list_a gets task1 first, then task2
let mut list_a = TaskList::new(list_id, "A".to_string(), peer_id1);
list_a.add_task(task1.clone(), peer_id1, 1).unwrap();
list_a.add_task(task2.clone(), peer_id2, 2).unwrap();
// Order 2: list_b gets task2 first, then task1
let mut list_b = TaskList::new(list_id, "B".to_string(), peer_id2);
list_b.add_task(task2, peer_id2, 1).unwrap();
list_b.add_task(task1, peer_id1, 2).unwrap();
// After adding in opposite order, they should have same task count
prop_assert_eq!(list_a.tasks_ordered().len(), list_b.tasks_ordered().len());
}
/// Property: Merge idempotence
///
/// Merging a task list with itself should not change the state.
#[test]
fn prop_merge_idempotence(
agent_id in agent_id_strategy(),
peer_id in peer_id_strategy(),
task_id in task_id_strategy(),
) {
let list_id = TaskListId::new([2u8; 32]);
let meta = TaskMetadata {
title: "Idempotent".to_string(),
description: "Test".to_string(),
priority: 128,
created_by: agent_id,
owner: None,
created_at: 1000,
tags: vec![],
};
let mut list = TaskList::new(list_id, "Test".to_string(), peer_id);
let task = TaskItem::new(task_id, meta, peer_id);
list.add_task(task, peer_id, 1).unwrap();
let count_before = list.tasks_ordered().len();
// Merge with self
let list_clone = list.clone();
list.merge(&list_clone).unwrap();
let count_after = list.tasks_ordered().len();
// Count should not change
prop_assert_eq!(count_before, count_after);
}
/// Property: Convergence
///
/// Two replicas that perform different operations should converge
/// to the same state after bidirectional merge.
#[test]
fn prop_convergence(
agent1 in agent_id_strategy(),
agent2 in agent_id_strategy(),
peer1 in peer_id_strategy(),
peer2 in peer_id_strategy(),
task_id1 in task_id_strategy(),
task_id2 in task_id_strategy(),
) {
let list_id = TaskListId::new([3u8; 32]);
let mut list_a = TaskList::new(list_id, "A".to_string(), peer1);
let mut list_b = TaskList::new(list_id, "B".to_string(), peer2);
// Replica A adds task1
let meta1 = TaskMetadata {
title: "Task1".to_string(),
description: "".to_string(),
priority: 128,
created_by: agent1,
owner: None,
created_at: 1000,
tags: vec![],
};
let task1 = TaskItem::new(task_id1, meta1, peer1);
list_a.add_task(task1, peer1, 1).unwrap();
// Replica B adds task2
let meta2 = TaskMetadata {
title: "Task2".to_string(),
description: "".to_string(),
priority: 128,
created_by: agent2,
owner: None,
created_at: 2000,
tags: vec![],
};
let task2 = TaskItem::new(task_id2, meta2, peer2);
list_b.add_task(task2, peer2, 2).unwrap();
// Bidirectional merge
list_a.merge(&list_b).unwrap();
list_b.merge(&list_a).unwrap();
// Both should have 2 tasks (or 1 if task_id1 == task_id2)
let expected_tasks = if task_id1 == task_id2 { 1 } else { 2 };
prop_assert_eq!(list_a.tasks_ordered().len(), expected_tasks);
prop_assert_eq!(list_b.tasks_ordered().len(), expected_tasks);
}
}
// ============================================================================
// TASK 9: Cross-Language Interop Tests (Stubs)
// ============================================================================
#[test]
#[ignore = "requires Node.js runtime and bindings from Phase 2.1"]
fn test_rust_nodejs_interop() {
// TODO: Spawn Node.js process running x0x SDK
// TODO: Verify Rust and Node.js agents can communicate
// TODO: Test task list operations across languages
}
#[test]
#[ignore = "requires Python runtime and bindings from Phase 2.2"]
fn test_rust_python_interop() {
// TODO: Spawn Python process running x0x SDK
// TODO: Verify Rust and Python agents can communicate
// TODO: Test CRDT convergence across languages
}
#[test]
#[ignore = "requires all three language SDKs"]
fn test_three_language_interop() {
// TODO: Spawn Rust, Node.js, and Python agents
// TODO: All join same network
// TODO: Verify messages propagate across all three languages
// TODO: Verify CRDT operations converge correctly
}
// ============================================================================
// TASK 10: Security Validation Tests
// ============================================================================
#[test]
fn test_agent_id_uniqueness() {
// Verify different agents have different IDs
let agent1 = AgentId([rand::random::<u8>(); 32]);
let agent2 = AgentId([rand::random::<u8>(); 32]);
assert_ne!(agent1, agent2, "Agent IDs must be unique");
}
#[test]
fn test_peer_id_derivation() {
// Verify PeerId derivation is deterministic
let bytes = [42u8; 32];
let peer1 = PeerId::new(bytes);
let peer2 = PeerId::new(bytes);
assert_eq!(peer1, peer2, "PeerId derivation must be deterministic");
}
#[test]
#[ignore = "requires ML-DSA signature implementation"]
fn test_message_signature_validation() {
// TODO: Sign a message with ML-DSA-65
// TODO: Verify signature with public key
// TODO: Verify tampered message fails validation
// TODO: Verify wrong public key fails validation
}
#[test]
#[ignore = "requires message deduplication cache"]
fn test_replay_attack_prevention() {
// TODO: Send same message twice
// TODO: Verify second message is rejected (duplicate message ID)
// TODO: Verify message IDs cached for 5 minutes
// TODO: Verify old messages (> 5min) can be replayed (cache expired)
}
#[test]
#[ignore = "requires MLS implementation from Phase 1.5"]
fn test_mls_forward_secrecy() {
// TODO: Create MLS group with 2 members
// TODO: Send encrypted message
// TODO: Rotate keys (commit)
// TODO: Verify old keys cannot decrypt new messages
}
#[test]
#[ignore = "requires MLS implementation from Phase 1.5"]
fn test_mls_post_compromise_security() {
// TODO: Create MLS group
// TODO: Member leaves
// TODO: Verify departed member cannot decrypt new messages
// TODO: Verify new epoch keys generated
}
// ============================================================================
// TASK 11: Performance Benchmarking Placeholders
// ============================================================================
//
// Note: Actual benchmarks use criterion in benches/ directory.
// These are integration-level performance tests.
#[test]
fn test_agent_creation_performance() {
use std::time::Instant;
let start = Instant::now();
let _agent_id = AgentId([rand::random::<u8>(); 32]);
let elapsed = start.elapsed();
println!("Agent creation time: {:?}", elapsed);
assert!(
elapsed.as_millis() < 100,
"Agent creation should be < 100ms"
);
}
#[test]
fn test_task_list_add_performance() {
use std::time::Instant;
let list_id = TaskListId::new([5u8; 32]);
let peer_id = PeerId::new([1u8; 32]);
let mut list = TaskList::new(list_id, "Perf Test".to_string(), peer_id);
let start = Instant::now();
for i in 0..1000 {
let task_id = TaskId::from_bytes([i as u8; 32]);
let meta = TaskMetadata {
title: format!("Task {}", i),
description: String::new(),
priority: 128,
created_by: AgentId([i as u8; 32]),
owner: None,
created_at: 1000 + i,
tags: vec![],
};
let task = TaskItem::new(task_id, meta, peer_id);
list.add_task(task, peer_id, i).unwrap();
}
let elapsed = start.elapsed();
let per_task = elapsed.as_micros() / 1000;
println!("Added 1000 tasks in {:?} ({} μs/task)", elapsed, per_task);
assert!(per_task < 1000, "add_task should be < 1ms per task");
}
#[test]
fn test_crdt_merge_performance() {
use std::time::Instant;
let list_id = TaskListId::new([6u8; 32]);
let peer1 = PeerId::new([1u8; 32]);
let peer2 = PeerId::new([2u8; 32]);
let mut list1 = TaskList::new(list_id, "List1".to_string(), peer1);
let mut list2 = TaskList::new(list_id, "List2".to_string(), peer2);
// Add 100 tasks to each
for i in 0..100 {
let task_id = TaskId::from_bytes([i; 32]);
let meta = TaskMetadata {
title: format!("Task {}", i),
description: String::new(),
priority: 128,
created_by: AgentId([i; 32]),
owner: None,
created_at: 1000 + u64::from(i),
tags: vec![],
};
let task1 = TaskItem::new(task_id, meta.clone(), peer1);
let task2 = TaskItem::new(task_id, meta, peer2);
list1.add_task(task1, peer1, i as u64).unwrap();
list2.add_task(task2, peer2, i as u64).unwrap();
}
let start = Instant::now();
list1.merge(&list2).unwrap();
let elapsed = start.elapsed();
println!("Merged 100 tasks in {:?}", elapsed);
assert!(elapsed.as_millis() < 10, "Merge should be < 10ms");
}
// ============================================================================
// TASK 12: Test Automation Documentation
// ============================================================================
//
// Test automation and reporting is documented here and in scripts/
//
// To run all integration tests:
// cargo nextest run --all-features --all-targets
//
// To run VPS-dependent tests (requires --ignored):
// cargo nextest run --all-features --ignored
//
// To generate test report:
// scripts/run_integration_tests.sh
//
// CI/CD Integration:
// - GitHub Actions workflow: .github/workflows/integration-tests.yml
// - Runs on: push to main, pull requests
// - Requires: VPS testnet access (secrets)
//
// Test Categories:
// - Unit tests (244): src/**/*_tests.rs, #[test]
// - Integration tests: tests/*.rs
// - Property tests: tests/comprehensive_integration.rs (proptest)
// - VPS tests: #[ignore] marked tests
// - Benchmarks: benches/*.rs (criterion)