Skip to main content

max / makenotwork

synckit-client: pin native keyring backends, point tests at /api/v1 - keyring optional dep gains apple-native, linux-native, windows-native features so the native backends are explicit when consumers enable the feature. - Integration tests follow the server's /api/sync → /api/v1/sync path rename. Mock paths only — no client behavior change. - Add OTA publish module to the post-beta queue (planned for mnw-cli). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-05-15 17:14 UTC
Commit: 3251f458665da24fa040ef530b77d40640293ea4
Parent: 6fc1e1e
4 files changed, +100 insertions, -65 deletions
@@ -113,6 +113,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
113 113 checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
114 114
115 115 [[package]]
116 + name = "byteorder"
117 + version = "1.5.0"
118 + source = "registry+https://github.com/rust-lang/crates.io-index"
119 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
120 +
121 + [[package]]
116 122 name = "bytes"
117 123 version = "1.11.1"
118 124 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -838,7 +844,12 @@ version = "3.6.3"
838 844 source = "registry+https://github.com/rust-lang/crates.io-index"
839 845 checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
840 846 dependencies = [
847 + "byteorder",
848 + "linux-keyutils",
841 849 "log",
850 + "security-framework 2.11.1",
851 + "security-framework 3.7.0",
852 + "windows-sys 0.60.2",
842 853 "zeroize",
843 854 ]
844 855
@@ -861,6 +872,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
861 872 checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
862 873
863 874 [[package]]
875 + name = "linux-keyutils"
876 + version = "0.2.5"
877 + source = "registry+https://github.com/rust-lang/crates.io-index"
878 + checksum = "83270a18e9f90d0707c41e9f35efada77b64c0e6f3f1810e71c8368a864d5590"
879 + dependencies = [
880 + "bitflags",
881 + "libc",
882 + ]
883 +
884 + [[package]]
864 885 name = "linux-raw-sys"
865 886 version = "0.12.1"
866 887 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -922,7 +943,7 @@ dependencies = [
922 943 "openssl-probe",
923 944 "openssl-sys",
924 945 "schannel",
925 - "security-framework",
946 + "security-framework 3.7.0",
926 947 "security-framework-sys",
927 948 "tempfile",
928 949 ]
@@ -1328,6 +1349,19 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1328 1349
1329 1350 [[package]]
1330 1351 name = "security-framework"
1352 + version = "2.11.1"
1353 + source = "registry+https://github.com/rust-lang/crates.io-index"
1354 + checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
1355 + dependencies = [
1356 + "bitflags",
1357 + "core-foundation 0.9.4",
1358 + "core-foundation-sys",
1359 + "libc",
1360 + "security-framework-sys",
1361 + ]
1362 +
1363 + [[package]]
1364 + name = "security-framework"
1331 1365 version = "3.7.0"
1332 1366 source = "registry+https://github.com/rust-lang/crates.io-index"
1333 1367 checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
@@ -30,7 +30,7 @@ chrono = { version = "0.4", features = ["serde"] }
30 30 uuid = { version = "1", features = ["v4", "serde"] }
31 31
32 32 # OS keychain (optional)
33 - keyring = { version = "3", optional = true }
33 + keyring = { version = "3", optional = true, features = ["apple-native", "linux-native", "windows-native"] }
34 34
35 35 # URL encoding
36 36 urlencoding = "2"
@@ -24,6 +24,7 @@ v0.3.1. Audit grade A. 340 tests (249 unit + integration). Rust 2024 edition (20
24 24
25 25 ## Deferred (Post-Beta)
26 26
27 + - [ ] **OTA publish module** (`src/client/ota.rs`) — typed client for the server's existing OTA endpoints (`/api/sync/ota/apps/{app_id}/releases`, `/artifacts`, presigned upload, updater verify). Backs the `mnw ota publish` subcommand. Replaces `MNW/server/deploy/ota-publish.sh`. Full plan in `MNW/mnw-cli/docs/todo.md` § OTA Publish Subcommand. Estimate: ~4 hours including tests.
27 28 - [ ] Conflict resolution helpers — LWW, field-level merge, custom resolver callback in the SDK. Reduces client-side boilerplate. (Gap vs Ditto, Couchbase)
28 29 - [ ] WASM web client — compile SyncKit to WASM for browser use. Only if a consumer app ships a web companion.
29 30 - [ ] C FFI layer — enables Swift/Kotlin/Python bindings. Only if non-Tauri consumers appear.
@@ -87,7 +87,7 @@ async fn authenticate_success_stores_session() {
87 87 let server = MockServer::start().await;
88 88
89 89 Mock::given(method("POST"))
90 - .and(path("/api/sync/auth"))
90 + .and(path("/api/v1/sync/auth"))
91 91 .respond_with(ResponseTemplate::new(200).set_body_json(auth_response_json()))
92 92 .mount(&server)
93 93 .await;
@@ -108,7 +108,7 @@ async fn authenticate_wrong_password_no_retry() {
108 108 let server = MockServer::start().await;
109 109
110 110 Mock::given(method("POST"))
111 - .and(path("/api/sync/auth"))
111 + .and(path("/api/v1/sync/auth"))
112 112 .respond_with(ResponseTemplate::new(401).set_body_string("Unauthorized"))
113 113 .expect(1) // Must be called exactly once (no retry)
114 114 .mount(&server)
@@ -131,14 +131,14 @@ async fn authenticate_retries_on_503() {
131 131 let server = MockServer::start().await;
132 132
133 133 Mock::given(method("POST"))
134 - .and(path("/api/sync/auth"))
134 + .and(path("/api/v1/sync/auth"))
135 135 .respond_with(ResponseTemplate::new(503).set_body_string("Service Unavailable"))
136 136 .up_to_n_times(1)
137 137 .mount(&server)
138 138 .await;
139 139
140 140 Mock::given(method("POST"))
141 - .and(path("/api/sync/auth"))
141 + .and(path("/api/v1/sync/auth"))
142 142 .respond_with(ResponseTemplate::new(200).set_body_json(auth_response_json()))
143 143 .mount(&server)
144 144 .await;
@@ -183,7 +183,7 @@ async fn register_device_success() {
183 183 let server = MockServer::start().await;
184 184
185 185 Mock::given(method("POST"))
186 - .and(path("/api/sync/devices"))
186 + .and(path("/api/v1/sync/devices"))
187 187 .respond_with(ResponseTemplate::new(200).set_body_json(device_json()))
188 188 .mount(&server)
189 189 .await;
@@ -198,14 +198,14 @@ async fn register_device_retries_on_transient() {
198 198 let server = MockServer::start().await;
199 199
200 200 Mock::given(method("POST"))
201 - .and(path("/api/sync/devices"))
201 + .and(path("/api/v1/sync/devices"))
202 202 .respond_with(ResponseTemplate::new(502).set_body_string("Bad Gateway"))
203 203 .up_to_n_times(1)
204 204 .mount(&server)
205 205 .await;
206 206
207 207 Mock::given(method("POST"))
208 - .and(path("/api/sync/devices"))
208 + .and(path("/api/v1/sync/devices"))
209 209 .respond_with(ResponseTemplate::new(200).set_body_json(device_json()))
210 210 .mount(&server)
211 211 .await;
@@ -220,7 +220,7 @@ async fn list_devices_success() {
220 220 let server = MockServer::start().await;
221 221
222 222 Mock::given(method("GET"))
223 - .and(path("/api/sync/devices"))
223 + .and(path("/api/v1/sync/devices"))
224 224 .respond_with(ResponseTemplate::new(200).set_body_json(json!([device_json()])))
225 225 .mount(&server)
226 226 .await;
@@ -238,7 +238,7 @@ async fn push_encrypts_data() {
238 238 let server = MockServer::start().await;
239 239
240 240 Mock::given(method("POST"))
241 - .and(path("/api/sync/push"))
241 + .and(path("/api/v1/sync/push"))
242 242 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 1})))
243 243 .mount(&server)
244 244 .await;
@@ -268,7 +268,7 @@ async fn push_encrypts_data() {
268 268 let requests = server.received_requests().await.unwrap();
269 269 let push_req = requests
270 270 .iter()
271 - .find(|r| r.url.path() == "/api/sync/push")
271 + .find(|r| r.url.path() == "/api/v1/sync/push")
272 272 .unwrap();
273 273 let body: serde_json::Value = serde_json::from_slice(&push_req.body).unwrap();
274 274 let wire_data = body["changes"][0]["data"].as_str().unwrap();
@@ -292,7 +292,7 @@ async fn pull_decrypts_data() {
292 292
293 293 let device_id = Uuid::new_v4();
294 294 Mock::given(method("POST"))
295 - .and(path("/api/sync/pull"))
295 + .and(path("/api/v1/sync/pull"))
296 296 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
297 297 "changes": [{
298 298 "seq": 1,
@@ -321,14 +321,14 @@ async fn push_retries_on_503() {
321 321 let server = MockServer::start().await;
322 322
323 323 Mock::given(method("POST"))
324 - .and(path("/api/sync/push"))
324 + .and(path("/api/v1/sync/push"))
325 325 .respond_with(ResponseTemplate::new(503))
326 326 .up_to_n_times(1)
327 327 .mount(&server)
328 328 .await;
329 329
330 330 Mock::given(method("POST"))
331 - .and(path("/api/sync/push"))
331 + .and(path("/api/v1/sync/push"))
332 332 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 5})))
333 333 .mount(&server)
334 334 .await;
@@ -346,7 +346,7 @@ async fn push_fails_immediately_on_401() {
346 346 let server = MockServer::start().await;
347 347
348 348 Mock::given(method("POST"))
349 - .and(path("/api/sync/push"))
349 + .and(path("/api/v1/sync/push"))
350 350 .respond_with(ResponseTemplate::new(401).set_body_string("Unauthorized"))
351 351 .expect(1)
352 352 .mount(&server)
@@ -372,7 +372,7 @@ async fn pull_with_has_more_pagination() {
372 372
373 373 // First pull: has_more = true
374 374 Mock::given(method("POST"))
375 - .and(path("/api/sync/pull"))
375 + .and(path("/api/v1/sync/pull"))
376 376 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
377 377 "changes": [],
378 378 "cursor": 50,
@@ -389,7 +389,7 @@ async fn pull_with_has_more_pagination() {
389 389
390 390 // Second pull from cursor 50: has_more = false
391 391 Mock::given(method("POST"))
392 - .and(path("/api/sync/pull"))
392 + .and(path("/api/v1/sync/pull"))
393 393 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
394 394 "changes": [],
395 395 "cursor": 100,
@@ -410,7 +410,7 @@ async fn blob_upload_url_success() {
410 410 let server = MockServer::start().await;
411 411
412 412 Mock::given(method("POST"))
413 - .and(path("/api/sync/blobs/upload"))
413 + .and(path("/api/v1/sync/blobs/upload"))
414 414 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
415 415 "upload_url": "https://s3.example.com/put",
416 416 "already_exists": false,
@@ -557,7 +557,7 @@ async fn restore_then_clear_session() {
557 557 let server = MockServer::start().await;
558 558
559 559 Mock::given(method("GET"))
560 - .and(path("/api/sync/status"))
560 + .and(path("/api/v1/sync/status"))
561 561 .respond_with(
562 562 ResponseTemplate::new(200)
563 563 .set_body_json(json!({"total_changes": 0, "latest_cursor": null})),
@@ -604,7 +604,7 @@ async fn has_server_key_true_on_200() {
604 604 let server = MockServer::start().await;
605 605
606 606 Mock::given(method("GET"))
607 - .and(path("/api/sync/keys"))
607 + .and(path("/api/v1/sync/keys"))
608 608 .respond_with(
609 609 ResponseTemplate::new(200)
610 610 .set_body_json(json!({"encrypted_key": "envelope-data"})),
@@ -621,7 +621,7 @@ async fn has_server_key_false_on_404() {
621 621 let server = MockServer::start().await;
622 622
623 623 Mock::given(method("GET"))
624 - .and(path("/api/sync/keys"))
624 + .and(path("/api/v1/sync/keys"))
625 625 .respond_with(ResponseTemplate::new(404))
626 626 .mount(&server)
627 627 .await;
@@ -635,14 +635,14 @@ async fn has_server_key_retries_on_500() {
635 635 let server = MockServer::start().await;
636 636
637 637 Mock::given(method("GET"))
638 - .and(path("/api/sync/keys"))
638 + .and(path("/api/v1/sync/keys"))
639 639 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
640 640 .up_to_n_times(1)
641 641 .mount(&server)
642 642 .await;
643 643
644 644 Mock::given(method("GET"))
645 - .and(path("/api/sync/keys"))
645 + .and(path("/api/v1/sync/keys"))
646 646 .respond_with(
647 647 ResponseTemplate::new(200)
648 648 .set_body_json(json!({"encrypted_key": "envelope"})),
@@ -661,14 +661,14 @@ async fn concurrent_push_pull_no_panics() {
661 661 let server = MockServer::start().await;
662 662
663 663 Mock::given(method("POST"))
664 - .and(path("/api/sync/push"))
664 + .and(path("/api/v1/sync/push"))
665 665 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 1})))
666 666 .mount(&server)
667 667 .await;
668 668
669 669 let device_id = Uuid::new_v4();
670 670 Mock::given(method("POST"))
671 - .and(path("/api/sync/pull"))
671 + .and(path("/api/v1/sync/pull"))
672 672 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
673 673 "changes": [],
674 674 "cursor": 0,
@@ -703,14 +703,14 @@ async fn error_429_is_retried() {
703 703 let server = MockServer::start().await;
704 704
705 705 Mock::given(method("GET"))
706 - .and(path("/api/sync/status"))
706 + .and(path("/api/v1/sync/status"))
707 707 .respond_with(ResponseTemplate::new(429).set_body_string("Too Many Requests"))
708 708 .up_to_n_times(1)
709 709 .mount(&server)
710 710 .await;
711 711
712 712 Mock::given(method("GET"))
713 - .and(path("/api/sync/status"))
713 + .and(path("/api/v1/sync/status"))
714 714 .respond_with(
715 715 ResponseTemplate::new(200)
716 716 .set_body_json(json!({"total_changes": 10, "latest_cursor": 5})),
@@ -728,7 +728,7 @@ async fn error_400_not_retried() {
728 728 let server = MockServer::start().await;
729 729
730 730 Mock::given(method("POST"))
731 - .and(path("/api/sync/devices"))
731 + .and(path("/api/v1/sync/devices"))
732 732 .respond_with(ResponseTemplate::new(400).set_body_string("Bad Request"))
733 733 .expect(1)
734 734 .mount(&server)
@@ -749,7 +749,7 @@ async fn status_success() {
749 749 let server = MockServer::start().await;
750 750
751 751 Mock::given(method("GET"))
752 - .and(path("/api/sync/status"))
752 + .and(path("/api/v1/sync/status"))
753 753 .respond_with(
754 754 ResponseTemplate::new(200)
755 755 .set_body_json(json!({"total_changes": 42, "latest_cursor": 100})),
@@ -768,14 +768,14 @@ async fn status_retries_on_transient() {
768 768 let server = MockServer::start().await;
769 769
770 770 Mock::given(method("GET"))
771 - .and(path("/api/sync/status"))
771 + .and(path("/api/v1/sync/status"))
772 772 .respond_with(ResponseTemplate::new(504).set_body_string("Gateway Timeout"))
773 773 .up_to_n_times(1)
774 774 .mount(&server)
775 775 .await;
776 776
777 777 Mock::given(method("GET"))
778 - .and(path("/api/sync/status"))
778 + .and(path("/api/v1/sync/status"))
779 779 .respond_with(
780 780 ResponseTemplate::new(200)
781 781 .set_body_json(json!({"total_changes": 0, "latest_cursor": null})),
@@ -795,7 +795,7 @@ async fn blob_confirm_success() {
795 795 let server = MockServer::start().await;
796 796
797 797 Mock::given(method("POST"))
798 - .and(path("/api/sync/blobs/confirm"))
798 + .and(path("/api/v1/sync/blobs/confirm"))
799 799 .respond_with(ResponseTemplate::new(200))
800 800 .mount(&server)
801 801 .await;
@@ -811,7 +811,7 @@ async fn blob_download_url_success() {
811 811 let server = MockServer::start().await;
812 812
813 813 Mock::given(method("POST"))
814 - .and(path("/api/sync/blobs/download"))
814 + .and(path("/api/v1/sync/blobs/download"))
815 815 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
816 816 "download_url": "https://s3.example.com/get",
817 817 })))
@@ -849,7 +849,7 @@ async fn change_password_wrong_old_password_with_cached_key_fails() {
849 849
850 850 // Server returns the envelope on GET
851 851 Mock::given(method("GET"))
852 - .and(path("/api/sync/keys"))
852 + .and(path("/api/v1/sync/keys"))
853 853 .respond_with(
854 854 ResponseTemplate::new(200)
855 855 .set_body_json(json!({"encrypted_key": envelope})),
@@ -881,7 +881,7 @@ async fn change_password_correct_old_password_with_cached_key_succeeds() {
881 881
882 882 // Server returns the envelope on GET
883 883 Mock::given(method("GET"))
884 - .and(path("/api/sync/keys"))
884 + .and(path("/api/v1/sync/keys"))
885 885 .respond_with(
886 886 ResponseTemplate::new(200)
887 887 .set_body_json(json!({"encrypted_key": envelope})),
@@ -891,7 +891,7 @@ async fn change_password_correct_old_password_with_cached_key_succeeds() {
891 891
892 892 // Server accepts the new envelope on PUT
893 893 Mock::given(method("PUT"))
894 - .and(path("/api/sync/keys"))
894 + .and(path("/api/v1/sync/keys"))
895 895 .respond_with(ResponseTemplate::new(200))
896 896 .mount(&server)
897 897 .await;
@@ -905,7 +905,7 @@ async fn change_password_correct_old_password_with_cached_key_succeeds() {
905 905 let requests = server.received_requests().await.unwrap();
906 906 let put_requests: Vec<_> = requests
907 907 .iter()
908 - .filter(|r| r.method.as_str() == "PUT" && r.url.path() == "/api/sync/keys")
908 + .filter(|r| r.method.as_str() == "PUT" && r.url.path() == "/api/v1/sync/keys")
909 909 .collect();
910 910 assert_eq!(put_requests.len(), 1, "Should have sent exactly one PUT");
911 911
@@ -928,7 +928,7 @@ async fn change_password_wrong_old_password_without_cached_key_fails() {
928 928 // Deliberately NOT setting master key -- no cached key
929 929
930 930 Mock::given(method("GET"))
931 - .and(path("/api/sync/keys"))
931 + .and(path("/api/v1/sync/keys"))
932 932 .respond_with(
933 933 ResponseTemplate::new(200)
934 934 .set_body_json(json!({"encrypted_key": envelope})),
@@ -957,7 +957,7 @@ async fn change_password_old_envelope_invalid_with_new_password() {
957 957 setup_change_password_test(&server, "old-pass").await;
958 958
959 959 Mock::given(method("GET"))
960 - .and(path("/api/sync/keys"))
960 + .and(path("/api/v1/sync/keys"))
961 961 .respond_with(
962 962 ResponseTemplate::new(200)
963 963 .set_body_json(json!({"encrypted_key": envelope})),
@@ -966,7 +966,7 @@ async fn change_password_old_envelope_invalid_with_new_password() {
966 966 .await;
967 967
968 968 Mock::given(method("PUT"))
969 - .and(path("/api/sync/keys"))
969 + .and(path("/api/v1/sync/keys"))
970 970 .respond_with(ResponseTemplate::new(200))
971 971 .mount(&server)
972 972 .await;
@@ -980,7 +980,7 @@ async fn change_password_old_envelope_invalid_with_new_password() {
980 980 let requests = server.received_requests().await.unwrap();
981 981 let put_req = requests
982 982 .iter()
983 - .find(|r| r.method.as_str() == "PUT" && r.url.path() == "/api/sync/keys")
983 + .find(|r| r.method.as_str() == "PUT" && r.url.path() == "/api/v1/sync/keys")
984 984 .unwrap();
985 985 let body: serde_json::Value = serde_json::from_slice(&put_req.body).unwrap();
986 986 let new_envelope = body["encrypted_key"].as_str().unwrap();
@@ -999,7 +999,7 @@ async fn push_empty_changes_succeeds() {
999 999 let server = MockServer::start().await;
1000 1000
1001 1001 Mock::given(method("POST"))
1002 - .and(path("/api/sync/push"))
1002 + .and(path("/api/v1/sync/push"))
1003 1003 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 0})))
1004 1004 .mount(&server)
1005 1005 .await;
@@ -1019,7 +1019,7 @@ async fn push_malformed_json_response_handled() {
1019 1019 let server = MockServer::start().await;
1020 1020
1021 1021 Mock::given(method("POST"))
1022 - .and(path("/api/sync/push"))
1022 + .and(path("/api/v1/sync/push"))
1023 1023 .respond_with(
1024 1024 ResponseTemplate::new(200).set_body_string("not valid json at all"),
1025 1025 )
@@ -1045,7 +1045,7 @@ async fn pull_malformed_json_response_handled() {
1045 1045 let server = MockServer::start().await;
1046 1046
1047 1047 Mock::given(method("POST"))
1048 - .and(path("/api/sync/pull"))
1048 + .and(path("/api/v1/sync/pull"))
1049 1049 .respond_with(
1050 1050 ResponseTemplate::new(200).set_body_string("{invalid json}"),
1051 1051 )
@@ -1065,7 +1065,7 @@ async fn status_malformed_json_response_handled() {
1065 1065 let server = MockServer::start().await;
1066 1066
1067 1067 Mock::given(method("GET"))
1068 - .and(path("/api/sync/status"))
1068 + .and(path("/api/v1/sync/status"))
1069 1069 .respond_with(
1070 1070 ResponseTemplate::new(200).set_body_string("this is not json"),
1071 1071 )
@@ -1084,7 +1084,7 @@ async fn server_error_message_preserved() {
1084 1084 let server = MockServer::start().await;
1085 1085
1086 1086 Mock::given(method("GET"))
1087 - .and(path("/api/sync/status"))
1087 + .and(path("/api/v1/sync/status"))
1088 1088 .respond_with(
1089 1089 ResponseTemplate::new(422).set_body_string("Validation failed: missing field"),
1090 1090 )
@@ -1112,7 +1112,7 @@ async fn concurrent_push_operations_no_data_corruption() {
1112 1112 let server = MockServer::start().await;
1113 1113
1114 1114 Mock::given(method("POST"))
1115 - .and(path("/api/sync/push"))
1115 + .and(path("/api/v1/sync/push"))
1116 1116 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 1})))
1117 1117 .mount(&server)
1118 1118 .await;
@@ -1149,13 +1149,13 @@ async fn concurrent_push_and_pull_interleaved() {
1149 1149 let device_id = Uuid::new_v4();
1150 1150
1151 1151 Mock::given(method("POST"))
1152 - .and(path("/api/sync/push"))
1152 + .and(path("/api/v1/sync/push"))
1153 1153 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 10})))
1154 1154 .mount(&server)
1155 1155 .await;
1156 1156
1157 1157 Mock::given(method("POST"))
1158 - .and(path("/api/sync/pull"))
1158 + .and(path("/api/v1/sync/pull"))
1159 1159 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1160 1160 "changes": [],
1161 1161 "cursor": 10,
@@ -1195,7 +1195,7 @@ async fn push_many_changes_succeeds() {
1195 1195 let server = MockServer::start().await;
1196 1196
1197 1197 Mock::given(method("POST"))
1198 - .and(path("/api/sync/push"))
1198 + .and(path("/api/v1/sync/push"))
1199 1199 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 1000})))
1200 1200 .mount(&server)
1201 1201 .await;
@@ -1392,7 +1392,7 @@ async fn push_delete_without_master_key_succeeds() {
1392 1392 let server = MockServer::start().await;
1393 1393
1394 1394 Mock::given(method("POST"))
1395 - .and(path("/api/sync/push"))
1395 + .and(path("/api/v1/sync/push"))
1396 1396 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 1})))
1397 1397 .mount(&server)
1398 1398 .await;
@@ -1453,14 +1453,14 @@ async fn double_push_same_data_both_succeed() {
1453 1453
1454 1454 // Server returns incrementing cursors
1455 1455 Mock::given(method("POST"))
1456 - .and(path("/api/sync/push"))
1456 + .and(path("/api/v1/sync/push"))
1457 1457 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 1})))
1458 1458 .up_to_n_times(1)
1459 1459 .mount(&server)
1460 1460 .await;
1461 1461
1462 1462 Mock::given(method("POST"))
1463 - .and(path("/api/sync/push"))
1463 + .and(path("/api/v1/sync/push"))
1464 1464 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 2})))
1465 1465 .mount(&server)
1466 1466 .await;
@@ -1494,7 +1494,7 @@ async fn setup_encryption_new_stores_key_and_uploads_envelope() {
1494 1494 let server = MockServer::start().await;
1495 1495
1496 1496 Mock::given(method("PUT"))
1497 - .and(path("/api/sync/keys"))
1497 + .and(path("/api/v1/sync/keys"))
1498 1498 .respond_with(ResponseTemplate::new(200))
1499 1499 .expect(1)
1500 1500 .mount(&server)
@@ -1512,7 +1512,7 @@ async fn setup_encryption_new_stores_key_and_uploads_envelope() {
1512 1512 let requests = server.received_requests().await.unwrap();
1513 1513 let put_req = requests
1514 1514 .iter()
1515 - .find(|r| r.method.as_str() == "PUT" && r.url.path() == "/api/sync/keys")
1515 + .find(|r| r.method.as_str() == "PUT" && r.url.path() == "/api/v1/sync/keys")
1516 1516 .unwrap();
1517 1517 let body: serde_json::Value = serde_json::from_slice(&put_req.body).unwrap();
1518 1518 let envelope_str = body["encrypted_key"].as_str().unwrap();
@@ -1538,14 +1538,14 @@ async fn setup_encryption_new_retries_on_server_error() {
1538 1538 let server = MockServer::start().await;
1539 1539
1540 1540 Mock::given(method("PUT"))
1541 - .and(path("/api/sync/keys"))
1541 + .and(path("/api/v1/sync/keys"))
1542 1542 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
1543 1543 .up_to_n_times(1)
1544 1544 .mount(&server)
1545 1545 .await;
1546 1546
1547 1547 Mock::given(method("PUT"))
1548 - .and(path("/api/sync/keys"))
1548 + .and(path("/api/v1/sync/keys"))
1549 1549 .respond_with(ResponseTemplate::new(200))
1550 1550 .mount(&server)
1551 1551 .await;
@@ -1565,7 +1565,7 @@ async fn setup_encryption_existing_recovers_key() {
1565 1565 synckit_client::crypto::wrap_master_key(&master_key, "my-password").unwrap();
1566 1566
1567 1567 Mock::given(method("GET"))
1568 - .and(path("/api/sync/keys"))
1568 + .and(path("/api/v1/sync/keys"))
1569 1569 .respond_with(
1570 1570 ResponseTemplate::new(200)
1571 1571 .set_body_json(json!({"encrypted_key": envelope})),
@@ -1593,7 +1593,7 @@ async fn setup_encryption_existing_wrong_password_fails() {
1593 1593 synckit_client::crypto::wrap_master_key(&master_key, "correct-password").unwrap();
1594 1594
1595 1595 Mock::given(method("GET"))
1596 - .and(path("/api/sync/keys"))
1596 + .and(path("/api/v1/sync/keys"))
1597 1597 .respond_with(
1598 1598 ResponseTemplate::new(200)
1599 1599 .set_body_json(json!({"encrypted_key": envelope})),
@@ -1634,14 +1634,14 @@ async fn setup_encryption_existing_retries_on_server_error() {
1634 1634 synckit_client::crypto::wrap_master_key(&master_key, "password").unwrap();
1635 1635
1636 1636 Mock::given(method("GET"))
1637 - .and(path("/api/sync/keys"))
1637 + .and(path("/api/v1/sync/keys"))
1638 1638 .respond_with(ResponseTemplate::new(502).set_body_string("Bad Gateway"))
1639 1639 .up_to_n_times(1)
1640 1640 .mount(&server)
1641 1641 .await;
1642 1642
1643 1643 Mock::given(method("GET"))
1644 - .and(path("/api/sync/keys"))
1644 + .and(path("/api/v1/sync/keys"))
1645 1645 .respond_with(
1646 1646 ResponseTemplate::new(200)
1647 1647 .set_body_json(json!({"encrypted_key": envelope})),
@@ -1660,7 +1660,7 @@ async fn setup_encryption_existing_no_server_key_returns_error() {
1660 1660 let server = MockServer::start().await;
1661 1661
1662 1662 Mock::given(method("GET"))
1663 - .and(path("/api/sync/keys"))
1663 + .and(path("/api/v1/sync/keys"))
1664 1664 .respond_with(ResponseTemplate::new(404).set_body_string("Not Found"))
1665 1665 .mount(&server)
1666 1666 .await;
@@ -1685,13 +1685,13 @@ async fn encryption_setup_cross_device_roundtrip() {
1685 1685
1686 1686 // Device 1: setup_encryption_new
1687 1687 Mock::given(method("PUT"))
1688 - .and(path("/api/sync/keys"))
1688 + .and(path("/api/v1/sync/keys"))
1689 1689 .respond_with(ResponseTemplate::new(200))
1690 1690 .mount(&server)
1691 1691 .await;
1692 1692
1693 1693 Mock::given(method("POST"))
1694 - .and(path("/api/sync/push"))
1694 + .and(path("/api/v1/sync/push"))
1695 1695 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"cursor": 1})))
Lines truncated