Skip to main content

syncular_testkit/
conformance.rs

1use std::collections::BTreeMap;
2
3use serde::Deserialize;
4use serde_json::Value;
5
6#[derive(Debug, Clone, Deserialize)]
7#[serde(rename_all = "camelCase")]
8pub struct SyncScenarioFixture {
9    pub actors: SyncScenarioActors,
10    pub subscription: SyncScenarioSubscription,
11    pub owner_conflict: OwnerConflictScenario,
12    pub revoked_subscription: RevokedSubscriptionScenario,
13    pub retry_backoff: RetryBackoffScenario,
14    pub snapshot_chunk: SnapshotChunkScenario,
15    pub repeated_pull: RepeatedPullScenario,
16    pub duplicate_push: DuplicatePushScenario,
17    pub conflict_keep_local: ConflictKeepLocalScenario,
18    pub realtime: RealtimeScenario,
19    pub live_query: LiveQueryScenario,
20    pub worker_auth: WorkerAuthScenario,
21    pub auth_refresh: AuthRefreshScenario,
22    pub revoked_session: RevokedSessionScenario,
23    pub schema_version: SchemaVersionScenario,
24    pub e2ee: E2eeScenario,
25    pub blob: BlobScenario,
26}
27
28#[derive(Debug, Clone, Deserialize)]
29pub struct SyncScenarioActors {
30    #[serde(rename = "ownerA")]
31    pub owner_a: SyncScenarioActor,
32    #[serde(rename = "ownerB")]
33    pub owner_b: SyncScenarioActor,
34    pub rust: RustSyncScenarioActor,
35}
36
37#[derive(Debug, Clone, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct SyncScenarioActor {
40    pub actor_id: String,
41    pub token: String,
42}
43
44#[derive(Debug, Clone, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct RustSyncScenarioActor {
47    pub actor_id: String,
48    pub project_id: String,
49    pub token: String,
50}
51
52#[derive(Debug, Clone, Deserialize)]
53pub struct SyncScenarioSubscription {
54    pub id: String,
55    pub table: String,
56}
57
58#[derive(Debug, Clone, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct OwnerConflictScenario {
61    pub client_id: String,
62    pub first_file_name: String,
63    pub second_file_name: String,
64    pub expected_error_pattern: String,
65    pub expected_refresh_count: i64,
66}
67
68#[derive(Debug, Clone, Deserialize)]
69#[serde(rename_all = "camelCase")]
70pub struct RevokedSubscriptionScenario {
71    pub client_id: String,
72    pub revoked_actor_id: String,
73    pub seed_task: SyncScenarioVersionedTask,
74    pub expected_status: String,
75    pub expected_scopes: BTreeMap<String, Value>,
76    pub expected_cursor_sequence: Vec<i64>,
77}
78
79#[derive(Debug, Clone, Deserialize)]
80#[serde(rename_all = "camelCase")]
81pub struct RetryBackoffScenario {
82    pub client_id: String,
83    pub local_row: SyncScenarioTaskRow,
84    pub expected_sync_post_counts: Vec<i64>,
85    pub expected_pending_pushes: i64,
86}
87
88#[derive(Debug, Clone, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct SnapshotChunkScenario {
91    pub client_id: String,
92    pub failure_client_id: String,
93    pub chunk_id: String,
94    pub byte_length: i64,
95    pub sha256: String,
96    pub encoding: String,
97    pub compression: String,
98    pub expected_error_pattern: String,
99    pub server_task: SyncScenarioVersionedTask,
100    pub browser_server_task: SyncScenarioTaskInput,
101    pub local_row: SyncScenarioTaskRow,
102}
103
104#[derive(Debug, Clone, Deserialize)]
105#[serde(rename_all = "camelCase")]
106pub struct RepeatedPullScenario {
107    pub client_id: String,
108    pub task: SyncScenarioVersionedTask,
109    pub expected_cursor: i64,
110    pub expected_browser_cursor: i64,
111    pub expected_row_count: i64,
112    pub expected_pull_count: i64,
113}
114
115#[derive(Debug, Clone, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct DuplicatePushScenario {
118    pub client_id: String,
119    pub task: SyncScenarioTaskRow,
120    pub expected_first_push_commits: i64,
121    pub expected_second_push_commits: i64,
122    pub expected_server_row_count: i64,
123    pub expected_outbox_status: String,
124    pub expected_conflict_count: i64,
125}
126
127#[derive(Debug, Clone, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct ConflictKeepLocalScenario {
130    pub client_id: String,
131    pub keep_server_client_id: String,
132    pub dismiss_client_id: String,
133    pub row_id: String,
134    pub local_title: String,
135    pub server_title: String,
136    pub stale_base_version: i64,
137    pub server_version: i64,
138    pub conflict_code: String,
139    pub conflict_message: String,
140    pub browser_conflict_message: String,
141    pub keep_server_resolution: String,
142    pub dismiss_resolution: String,
143    pub expected_initial_conflict_count: i64,
144    pub expected_after_resolve_conflict_count: i64,
145    pub expected_after_retry_conflict_count: i64,
146    pub expected_retry_push_commits: i64,
147    pub retry_base_version: i64,
148}
149
150#[derive(Debug, Clone, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct RealtimeScenario {
153    pub client_a_id: String,
154    pub client_b_id: String,
155    pub auth_refresh_client_id: String,
156    pub websocket_token: String,
157    pub refreshed_websocket_token: String,
158    pub expected_auth_tokens: Vec<String>,
159    pub expected_connection_count: i64,
160    pub presence_event: String,
161    pub expected_event_debug: Vec<String>,
162    pub task: SyncScenarioVersionedTask,
163}
164
165#[derive(Debug, Clone, Deserialize)]
166#[serde(rename_all = "camelCase")]
167pub struct LiveQueryScenario {
168    pub client_a_id: String,
169    pub client_b_id: String,
170    pub query_sql: String,
171    pub tables: Vec<String>,
172    pub expected_initial_rows: i64,
173    pub expected_events_before_unsubscribe: i64,
174    pub expected_events_after_unsubscribe: i64,
175    pub first_task: SyncScenarioTaskInput,
176    pub second_task: SyncScenarioTaskInput,
177    pub third_task: SyncScenarioTaskInput,
178}
179
180#[derive(Debug, Clone, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct WorkerAuthScenario {
183    pub client_id: String,
184    pub authorization: String,
185}
186
187#[derive(Debug, Clone, Deserialize)]
188#[serde(rename_all = "camelCase")]
189pub struct AuthRefreshScenario {
190    pub client_id: String,
191    pub initial_authorization: String,
192    pub refreshed_authorization: String,
193    pub expected_refresh_count: i64,
194    pub expected_auth_headers: Vec<String>,
195}
196
197#[derive(Debug, Clone, Deserialize)]
198#[serde(rename_all = "camelCase")]
199pub struct RevokedSessionScenario {
200    pub client_id: String,
201    pub authorization: String,
202    pub expected_status: u16,
203    pub expected_refresh_count: i64,
204    pub expected_retry_count: i64,
205    pub expected_error_pattern: String,
206}
207
208#[derive(Debug, Clone, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct SchemaVersionScenario {
211    pub required_future_client_id: String,
212    pub latest_future_client_id: String,
213    pub invalid_outbox_client_id: String,
214    pub future_version_offset: i32,
215    pub expected_required_error_pattern: String,
216    pub expected_invalid_outbox_error_pattern: String,
217}
218
219#[derive(Debug, Clone, Deserialize)]
220#[serde(rename_all = "camelCase")]
221pub struct E2eeScenario {
222    pub client_id: String,
223    pub pull_client_id: String,
224    pub key_base64: String,
225    pub envelope_prefix: String,
226    pub rule: E2eeRuleScenario,
227    pub task: SyncScenarioTaskInput,
228    pub conflict: E2eeConflictScenario,
229    pub chunk: E2eeChunkScenario,
230    pub server_version: i64,
231    pub expected_decrypted_row_count: i64,
232}
233
234#[derive(Debug, Clone, Deserialize)]
235pub struct E2eeRuleScenario {
236    pub scope: String,
237    pub table: String,
238    pub fields: Vec<String>,
239}
240
241#[derive(Debug, Clone, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct E2eeConflictScenario {
244    pub seed_client_id: String,
245    pub client_id: String,
246    pub row_id: String,
247    pub server_title: String,
248    pub local_title: String,
249    pub stale_base_version: i64,
250    pub expected_conflict_count: i64,
251}
252
253#[derive(Debug, Clone, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct E2eeChunkScenario {
256    pub seed_client_id: String,
257    pub client_id: String,
258}
259
260#[derive(Debug, Clone, Deserialize)]
261#[serde(rename_all = "camelCase")]
262pub struct BlobScenario {
263    pub client_id: String,
264    pub browser_client_id: String,
265    pub streaming_client_id: String,
266    pub dedupe_client_id: String,
267    pub auth_failure_client_id: String,
268    pub interrupted_upload_client_id: String,
269    pub missing_client_id: String,
270    pub cache_prune_client_id: String,
271    pub actor_id: String,
272    pub browser_actor_id: String,
273    pub authorization: String,
274    pub stale_authorization: String,
275    pub mime_type: String,
276    pub text_mime_type: String,
277    pub bytes: Vec<u8>,
278    pub browser_text: String,
279    pub reference_sync: BlobReferenceSyncScenario,
280    pub dedupe_text: String,
281    pub auth_failure_text: String,
282    pub interrupted_upload_text: String,
283    pub cache_prune_old_text: String,
284    pub cache_prune_new_text: String,
285    pub streaming_byte_count: usize,
286    pub upload_token: String,
287    pub upload_path: String,
288    pub download_path: String,
289    pub expected_upload_queue_before: BlobQueueStats,
290    pub expected_upload_queue_after: BlobQueueStats,
291    pub expected_failed_queue: BlobQueueStats,
292    pub cache_prune_max_bytes: i64,
293    pub expected_cache_before_prune: BlobCacheStats,
294    pub expected_cache_pruned_bytes: i64,
295    pub expected_cache_after_prune: BlobCacheStats,
296    pub expected_process_uploaded: BlobProcessResult,
297    pub expected_process_retryable_failure: BlobProcessResult,
298    pub expected_process_permanent_failure: BlobProcessResult,
299    pub expected_auth_header_count: i64,
300}
301
302#[derive(Debug, Clone, Deserialize)]
303#[serde(rename_all = "camelCase")]
304pub struct BlobReferenceSyncScenario {
305    pub source_client_id: String,
306    pub reader_client_id: String,
307    pub task: SyncScenarioTaskInput,
308    pub image: SyncScenarioBlobRef,
309}
310
311#[derive(Debug, Clone, Deserialize)]
312#[serde(rename_all = "camelCase")]
313pub struct SyncScenarioBlobRef {
314    pub hash: String,
315    pub size: i64,
316    pub mime_type: String,
317    #[serde(default)]
318    pub encrypted: Option<bool>,
319    #[serde(default)]
320    pub key_id: Option<String>,
321}
322
323#[derive(Debug, Clone, Deserialize)]
324#[serde(rename_all = "camelCase")]
325pub struct BlobQueueStats {
326    pub pending: i64,
327    pub uploading: i64,
328    pub failed: i64,
329}
330
331#[derive(Debug, Clone, Deserialize)]
332#[serde(rename_all = "camelCase")]
333pub struct BlobCacheStats {
334    pub count: i64,
335    pub total_bytes: i64,
336}
337
338#[derive(Debug, Clone, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct BlobProcessResult {
341    pub uploaded: i32,
342    pub failed: i32,
343}
344
345#[derive(Debug, Clone, Deserialize)]
346#[serde(rename_all = "camelCase")]
347pub struct SyncScenarioVersionedTask {
348    pub id: String,
349    pub title: String,
350    pub server_version: i64,
351}
352
353#[derive(Debug, Clone, Deserialize)]
354pub struct SyncScenarioTaskInput {
355    pub id: String,
356    pub title: String,
357    #[serde(default)]
358    pub description: Option<String>,
359}
360
361#[derive(Debug, Clone, Deserialize)]
362pub struct SyncScenarioTaskRow {
363    pub id: String,
364    pub title: String,
365    pub completed: i64,
366    #[serde(default)]
367    pub user_id: Option<String>,
368    pub project_id: Option<String>,
369    pub server_version: i64,
370    pub image: Option<String>,
371    pub title_yjs_state: Option<String>,
372}
373
374pub fn sync_conformance_fixture() -> SyncScenarioFixture {
375    serde_json::from_str(include_str!("../conformance/sync-scenarios.json"))
376        .expect("typed sync conformance JSON")
377}
378
379pub fn sync_conformance() -> Value {
380    serde_json::from_str(include_str!("../conformance/sync-scenarios.json"))
381        .expect("sync conformance JSON")
382}
383
384pub fn sync_conformance_str(path: &[&str]) -> String {
385    sync_conformance_value(path)
386        .as_str()
387        .unwrap_or_else(|| panic!("sync conformance path {path:?} must be a string"))
388        .to_string()
389}
390
391pub fn sync_conformance_i64(path: &[&str]) -> i64 {
392    sync_conformance_value(path)
393        .as_i64()
394        .unwrap_or_else(|| panic!("sync conformance path {path:?} must be an integer"))
395}
396
397pub fn sync_conformance_i32(path: &[&str]) -> i32 {
398    sync_conformance_i64(path)
399        .try_into()
400        .unwrap_or_else(|_| panic!("sync conformance path {path:?} must fit in i32"))
401}
402
403pub fn sync_conformance_usize(path: &[&str]) -> usize {
404    sync_conformance_i64(path)
405        .try_into()
406        .unwrap_or_else(|_| panic!("sync conformance path {path:?} must fit in usize"))
407}
408
409pub fn sync_conformance_bytes(path: &[&str]) -> Vec<u8> {
410    sync_conformance_value(path)
411        .as_array()
412        .unwrap_or_else(|| panic!("sync conformance path {path:?} must be an array"))
413        .iter()
414        .map(|value| {
415            value
416                .as_u64()
417                .and_then(|byte| byte.try_into().ok())
418                .unwrap_or_else(|| panic!("sync conformance path {path:?} must contain bytes"))
419        })
420        .collect()
421}
422
423pub fn sync_conformance_value(path: &[&str]) -> Value {
424    let mut value = sync_conformance();
425    for segment in path {
426        value = value
427            .get(segment)
428            .unwrap_or_else(|| panic!("missing sync conformance path {path:?}"))
429            .clone();
430    }
431    value
432}