1use serde_json::{json, Value};
2use syncular_runtime::client::{CrdtFieldMaterialization, CrdtFieldWriteReceipt, SyncularClient};
3use syncular_runtime::crdt_field::CrdtFieldId;
4use syncular_runtime::diesel_sqlite::DieselSqliteStore;
5use syncular_runtime::native::NativeSyncularClient;
6use syncular_runtime::transport::SyncTransport;
7
8pub fn crdt_field_id(
9 table: impl Into<String>,
10 row_id: impl Into<String>,
11 field: impl Into<String>,
12) -> CrdtFieldId {
13 CrdtFieldId::new(table, row_id, field)
14}
15
16pub fn crdt_field_request_json(table: &str, row_id: &str, field: &str) -> String {
17 json!({
18 "table": table,
19 "rowId": row_id,
20 "field": field
21 })
22 .to_string()
23}
24
25pub fn crdt_field_text_request_json(
26 table: &str,
27 row_id: &str,
28 field: &str,
29 next_text: &str,
30) -> String {
31 json!({
32 "table": table,
33 "rowId": row_id,
34 "field": field,
35 "nextText": next_text
36 })
37 .to_string()
38}
39
40pub fn apply_crdt_field_text<T>(
41 client: &mut SyncularClient<DieselSqliteStore, T>,
42 table: &str,
43 row_id: &str,
44 field_name: &str,
45 next_text: &str,
46) -> CrdtFieldWriteReceipt
47where
48 T: SyncTransport,
49{
50 let field = client
51 .open_crdt_field(crdt_field_id(table, row_id, field_name))
52 .expect("open CRDT field");
53 client
54 .apply_crdt_field_text(&field, next_text)
55 .expect("apply CRDT field text")
56}
57
58pub fn assert_crdt_field_materializes<T>(
59 client: &mut SyncularClient<DieselSqliteStore, T>,
60 table: &str,
61 row_id: &str,
62 field_name: &str,
63 expected: Value,
64) -> CrdtFieldMaterialization
65where
66 T: SyncTransport,
67{
68 let field = client
69 .open_crdt_field(crdt_field_id(table, row_id, field_name))
70 .expect("open CRDT field");
71 let materialized = client
72 .materialize_crdt_field(&field)
73 .expect("materialize CRDT field");
74 assert_eq!(materialized.value, expected, "unexpected CRDT value");
75 assert!(
76 !materialized.state_vector_base64.is_empty(),
77 "CRDT state vector should not be empty"
78 );
79 materialized
80}
81
82pub fn assert_crdt_field_text_nonblank<T>(
83 client: &mut SyncularClient<DieselSqliteStore, T>,
84 table: &str,
85 row_id: &str,
86 field_name: &str,
87) -> CrdtFieldMaterialization
88where
89 T: SyncTransport,
90{
91 let field = client
92 .open_crdt_field(crdt_field_id(table, row_id, field_name))
93 .expect("open CRDT field");
94 let materialized = client
95 .materialize_crdt_field(&field)
96 .expect("materialize CRDT field");
97 let text = materialized
98 .value
99 .as_str()
100 .expect("CRDT field should materialize to text");
101 assert!(!text.is_empty(), "CRDT text field should not blank");
102 materialized
103}
104
105pub fn apply_native_crdt_field_text(
106 client: &mut NativeSyncularClient,
107 table: &str,
108 row_id: &str,
109 field_name: &str,
110 next_text: &str,
111) -> Value {
112 let json = client
113 .apply_crdt_field_text_json(&crdt_field_text_request_json(
114 table, row_id, field_name, next_text,
115 ))
116 .expect("apply native CRDT field text");
117 serde_json::from_str(&json).expect("native CRDT write receipt JSON")
118}
119
120pub fn assert_native_crdt_field_materializes(
121 client: &mut NativeSyncularClient,
122 table: &str,
123 row_id: &str,
124 field_name: &str,
125 expected: Value,
126) -> Value {
127 let json = client
128 .materialize_crdt_field_json(&crdt_field_request_json(table, row_id, field_name))
129 .expect("materialize native CRDT field");
130 let materialized: Value =
131 serde_json::from_str(&json).expect("native CRDT materialization JSON");
132 assert_eq!(
133 materialized["value"], expected,
134 "unexpected native CRDT value"
135 );
136 assert!(
137 materialized["stateVectorBase64"]
138 .as_str()
139 .is_some_and(|value| !value.is_empty()),
140 "native CRDT state vector should not be empty"
141 );
142 materialized
143}