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}