1use serde_json::Value;
2use std::time::Duration;
3use syncular_runtime::client::SyncularClient;
4use syncular_runtime::diesel_sqlite::DieselSqliteStore;
5use syncular_runtime::store::{ConflictSummary, OutboxSummary};
6use syncular_runtime::transport::{SyncAuthHeaders, SyncTransport};
7
8use crate::app_server::{AppTestServer, AppTestServerCommit};
9use crate::http::TestHttpRequest;
10use crate::transport::{BlobUploadRecord, TestTransportHandle};
11
12pub fn assert_outbox_empty<T>(
13 client: &mut SyncularClient<DieselSqliteStore, T>,
14) -> Vec<OutboxSummary>
15where
16 T: SyncTransport,
17{
18 let summaries = client.outbox_summaries().expect("outbox summaries");
19 assert_eq!(summaries.len(), 0, "expected empty outbox: {summaries:?}");
20 summaries
21}
22
23pub fn assert_outbox_statuses<T>(
24 client: &mut SyncularClient<DieselSqliteStore, T>,
25 expected: &[&str],
26) -> Vec<OutboxSummary>
27where
28 T: SyncTransport,
29{
30 let summaries = client.outbox_summaries().expect("outbox summaries");
31 let actual = summaries
32 .iter()
33 .map(|summary| summary.status.as_str())
34 .collect::<Vec<_>>();
35 assert_eq!(actual, expected, "unexpected outbox statuses");
36 summaries
37}
38
39pub fn assert_outbox_count<T>(
40 client: &mut SyncularClient<DieselSqliteStore, T>,
41 expected: usize,
42) -> Vec<OutboxSummary>
43where
44 T: SyncTransport,
45{
46 let summaries = client.outbox_summaries().expect("outbox summaries");
47 assert_eq!(
48 summaries.len(),
49 expected,
50 "unexpected outbox count: {summaries:?}"
51 );
52 summaries
53}
54
55pub fn assert_latest_outbox_status<T>(
56 client: &mut SyncularClient<DieselSqliteStore, T>,
57 expected: &str,
58) -> OutboxSummary
59where
60 T: SyncTransport,
61{
62 let summaries = client.outbox_summaries().expect("outbox summaries");
63 let latest = summaries.last().expect("latest outbox summary").clone();
64 assert_eq!(latest.status, expected, "unexpected latest outbox status");
65 latest
66}
67
68pub fn assert_conflict_count<T>(
69 client: &mut SyncularClient<DieselSqliteStore, T>,
70 expected: usize,
71) -> Vec<ConflictSummary>
72where
73 T: SyncTransport,
74{
75 let conflicts = client.conflict_summaries().expect("conflict summaries");
76 assert_eq!(
77 conflicts.len(),
78 expected,
79 "unexpected conflict count: {conflicts:?}"
80 );
81 conflicts
82}
83
84pub fn assert_no_conflicts<T>(
85 client: &mut SyncularClient<DieselSqliteStore, T>,
86) -> Vec<ConflictSummary>
87where
88 T: SyncTransport,
89{
90 assert_conflict_count(client, 0)
91}
92
93pub fn assert_table_row_count<T>(
94 client: &mut SyncularClient<DieselSqliteStore, T>,
95 table: &str,
96 expected: usize,
97) -> Vec<Value>
98where
99 T: SyncTransport,
100{
101 let rows_json = client.list_table_json(table).expect("table rows");
102 let rows: Vec<Value> = serde_json::from_str(&rows_json).expect("table rows json");
103 assert_eq!(rows.len(), expected, "unexpected row count for {table}");
104 rows
105}
106
107pub fn assert_table_has_row<T>(
108 client: &mut SyncularClient<DieselSqliteStore, T>,
109 table: &str,
110 primary_key: &str,
111 row_id: &str,
112) -> Value
113where
114 T: SyncTransport,
115{
116 let rows_json = client.list_table_json(table).expect("table rows");
117 let rows: Vec<Value> = serde_json::from_str(&rows_json).expect("table rows json");
118 rows.into_iter()
119 .find(|row| row.get(primary_key).and_then(Value::as_str) == Some(row_id))
120 .unwrap_or_else(|| panic!("expected row {table}.{primary_key}={row_id}"))
121}
122
123pub fn assert_blob_upload_queue<T>(
124 client: &mut SyncularClient<DieselSqliteStore, T>,
125 pending: i64,
126 uploading: i64,
127 failed: i64,
128) where
129 T: SyncTransport,
130{
131 let stats = client.blob_upload_queue_stats().expect("blob queue stats");
132 assert_eq!(stats.pending, pending, "unexpected pending blob uploads");
133 assert_eq!(
134 stats.uploading, uploading,
135 "unexpected uploading blob uploads"
136 );
137 assert_eq!(stats.failed, failed, "unexpected failed blob uploads");
138}
139
140pub fn assert_blob_cache<T>(
141 client: &mut SyncularClient<DieselSqliteStore, T>,
142 count: i64,
143 total_bytes: i64,
144) where
145 T: SyncTransport,
146{
147 let stats = client.blob_cache_stats().expect("blob cache stats");
148 assert_eq!(stats.count, count, "unexpected cached blob count");
149 assert_eq!(
150 stats.total_bytes, total_bytes,
151 "unexpected cached blob bytes"
152 );
153}
154
155pub fn assert_blob_upload_count(
156 handle: &TestTransportHandle,
157 expected: usize,
158) -> Vec<BlobUploadRecord> {
159 let uploads = handle.blob_uploads();
160 assert_eq!(
161 uploads.len(),
162 expected,
163 "unexpected blob upload count: {uploads:?}"
164 );
165 uploads
166}
167
168pub fn assert_blob_uploaded(handle: &TestTransportHandle, hash: &str) -> BlobUploadRecord {
169 handle
170 .blob_uploads()
171 .into_iter()
172 .find(|upload| upload.blob.hash == hash)
173 .unwrap_or_else(|| panic!("expected uploaded blob {hash}"))
174}
175
176pub fn assert_app_server_row_count(
177 server: &AppTestServer,
178 table: &str,
179 expected: usize,
180) -> Vec<Value> {
181 let rows = server.rows(table);
182 assert_eq!(
183 rows.len(),
184 expected,
185 "unexpected AppTestServer row count for {table}: {rows:?}"
186 );
187 rows
188}
189
190pub fn assert_app_server_has_row(server: &AppTestServer, table: &str, row_id: &str) -> Value {
191 server
192 .row(table, row_id)
193 .unwrap_or_else(|| panic!("expected AppTestServer row {table}.{row_id}"))
194}
195
196pub fn assert_app_server_missing_row(server: &AppTestServer, table: &str, row_id: &str) {
197 assert!(
198 server.row(table, row_id).is_none(),
199 "expected missing AppTestServer row {table}.{row_id}"
200 );
201}
202
203pub fn assert_app_server_commit_count(
204 server: &AppTestServer,
205 expected: usize,
206 timeout: Duration,
207) -> Vec<AppTestServerCommit> {
208 let commits = server.wait_for_commit_count(expected, timeout);
209 assert_eq!(
210 commits.len(),
211 expected,
212 "unexpected AppTestServer commit count: {commits:?}"
213 );
214 commits
215}
216
217pub fn assert_app_server_auth_header(
218 server: &AppTestServer,
219 name: &str,
220 expected: &str,
221) -> SyncAuthHeaders {
222 let name = name.to_ascii_lowercase();
223 server
224 .auth_headers()
225 .into_iter()
226 .find(|headers| headers.get(&name).map(String::as_str) == Some(expected))
227 .unwrap_or_else(|| panic!("expected AppTestServer auth header {name}={expected}"))
228}
229
230pub fn assert_http_request_count(
231 requests: &[TestHttpRequest],
232 expected: usize,
233) -> &[TestHttpRequest] {
234 assert_eq!(
235 requests.len(),
236 expected,
237 "unexpected HTTP request count: {requests:?}"
238 );
239 requests
240}
241
242pub fn assert_http_request_header(
243 request: &TestHttpRequest,
244 name: &str,
245 expected: &str,
246) -> TestHttpRequest {
247 assert_eq!(
248 request.header(name),
249 Some(expected),
250 "unexpected HTTP request header {name} on {} {}",
251 request.method,
252 request.path
253 );
254 request.clone()
255}