use super::action::{DagAction, DagActionHash};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncRequest {
pub local_tips: Vec<DagActionHash>,
#[serde(default)]
pub want: Vec<DagActionHash>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncResponse {
pub actions: Vec<DagAction>,
pub remote_tips: Vec<DagActionHash>,
pub action_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PullResult {
pub ingested: usize,
pub already_had: usize,
pub remote_tips: Vec<DagActionHash>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dag::{DagPayload, DagStore, TripleInsertPayload};
use crate::NodeId;
use chrono::Utc;
fn make_action(seq: u64, subject: &str, parents: Vec<DagActionHash>) -> DagAction {
DagAction {
parents,
author: NodeId::named("node:1"),
seq,
timestamp: Utc::now(),
payload: DagPayload::TripleInsert {
triples: vec![TripleInsertPayload {
subject: subject.into(),
predicate: "knows".into(),
object: serde_json::json!("x"),
provenance: None,
}],
},
signature: None,
}
}
#[test]
fn test_compute_missing_linear() {
let store_b = DagStore::new();
let a1 = make_action(1, "s1", vec![]);
let h1 = store_b.put(&a1).unwrap();
let a2 = make_action(2, "s2", vec![h1]);
let h2 = store_b.put(&a2).unwrap();
let a3 = make_action(3, "s3", vec![h2]);
store_b.put(&a3).unwrap();
let missing = store_b.compute_missing(&[h2]).unwrap();
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].seq, 3);
}
#[test]
fn test_compute_missing_branching() {
let store_b = DagStore::new();
let a1 = make_action(1, "s1", vec![]);
let h1 = store_b.put(&a1).unwrap();
let a2 = make_action(2, "s2", vec![h1]);
let h2 = store_b.put(&a2).unwrap();
let a3 = make_action(3, "s3", vec![h1]);
store_b.put(&a3).unwrap();
let missing = store_b.compute_missing(&[h2]).unwrap();
assert_eq!(missing.len(), 1);
assert_eq!(missing[0].seq, 3);
}
#[test]
fn test_compute_missing_fully_synced() {
let store = DagStore::new();
let a1 = make_action(1, "s1", vec![]);
let h1 = store.put(&a1).unwrap();
let a2 = make_action(2, "s2", vec![h1]);
let h2 = store.put(&a2).unwrap();
let missing = store.compute_missing(&[h2]).unwrap();
assert!(missing.is_empty());
}
#[test]
fn test_compute_missing_unknown_remote_tip() {
let store = DagStore::new();
let a1 = make_action(1, "s1", vec![]);
store.put(&a1).unwrap();
let unknown = DagActionHash([0xFF; 32]);
let missing = store.compute_missing(&[unknown]).unwrap();
assert_eq!(missing.len(), 1);
}
#[test]
fn test_ingest_stores_without_touching_tips() {
let store = DagStore::new();
let a1 = make_action(1, "s1", vec![]);
let h1 = store.put(&a1).unwrap();
assert_eq!(store.tip_count().unwrap(), 1);
let a0 = DagAction {
parents: vec![],
author: NodeId::named("node:1"),
seq: 0,
timestamp: Utc::now(),
payload: DagPayload::Noop,
signature: None,
};
let h0 = store.ingest(&a0).unwrap();
assert_ne!(h0, h1);
let tips = store.tips().unwrap();
assert_eq!(tips.len(), 1);
assert_eq!(tips[0], h1);
assert!(store.get(&h0).unwrap().is_some());
assert_eq!(store.action_count(), 2);
}
#[test]
fn test_ingest_skips_duplicates() {
let store = DagStore::new();
let a1 = make_action(1, "s1", vec![]);
let h1 = store.put(&a1).unwrap();
let h1_again = store.ingest(&a1).unwrap();
assert_eq!(h1, h1_again);
assert_eq!(store.action_count(), 1); }
#[test]
fn test_sync_request_serialization() {
let req = SyncRequest {
local_tips: vec![DagActionHash([1; 32])],
want: vec![],
};
let json = serde_json::to_string(&req).unwrap();
let back: SyncRequest = serde_json::from_str(&json).unwrap();
assert_eq!(back.local_tips.len(), 1);
}
#[test]
fn test_sync_response_serialization() {
let resp = SyncResponse {
actions: vec![],
remote_tips: vec![DagActionHash([2; 32])],
action_count: 0,
};
let json = serde_json::to_string(&resp).unwrap();
let back: SyncResponse = serde_json::from_str(&json).unwrap();
assert_eq!(back.remote_tips.len(), 1);
}
}