autofill/sync/
bridge.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use crate::Store;
6use std::sync::Arc;
7
8impl Store {
9    /// Returns a bridged sync engine for addresses, for use by Desktop's Sync
10    /// framework. Constructing a `ConfigSyncEngine` only assembles structs and
11    /// never touches the DB, so this cannot fail.
12    pub fn addresses_bridged_engine(self: Arc<Self>) -> Arc<AddressesBridgedEngine> {
13        let engine = crate::sync::address::create_engine(self);
14        Arc::new(AddressesBridgedEngine::new(Box::new(engine)))
15    }
16}
17
18// Generates the UniFFI-exposed `AddressesBridgedEngine`, a newtype around
19// `sync15::engine::BridgedEngineWrapper`.
20sync15::uniffi_bridged_engine!(AddressesBridgedEngine);
21
22#[cfg(test)]
23mod tests {
24    use super::*;
25    use crate::db::models::address::UpdatableAddressFields;
26    use std::collections::HashMap;
27
28    // Exercises the sync metadata the bridge owns: last_sync, sync_id and reset.
29    #[test]
30    fn test_sync_meta() {
31        error_support::init_for_tests();
32
33        let store = Arc::new(Store::new_shared_memory("addresses-bridge").unwrap());
34        let bridge = store.addresses_bridged_engine();
35
36        bridge.sync_started().unwrap();
37        // Fresh DB: never synced.
38        assert_eq!(bridge.last_sync().unwrap(), 0);
39        bridge.set_uploaded(3, vec![]).unwrap();
40        assert_eq!(bridge.last_sync().unwrap(), 3);
41
42        assert!(bridge.sync_id().unwrap().is_none());
43
44        bridge.ensure_current_sync_id("some_guid").unwrap();
45        assert_eq!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
46        // changing the sync ID resets the timestamp
47        assert_eq!(bridge.last_sync().unwrap(), 0);
48        bridge.set_uploaded(3, vec![]).unwrap();
49
50        bridge.reset_sync_id().unwrap();
51        assert_ne!(bridge.sync_id().unwrap(), Some("some_guid".to_string()));
52        assert_eq!(bridge.last_sync().unwrap(), 0);
53        bridge.set_uploaded(3, vec![]).unwrap();
54
55        // `reset` clears the guid and the timestamp.
56        bridge.reset().unwrap();
57        assert_eq!(bridge.last_sync().unwrap(), 0);
58        assert!(bridge.sync_id().unwrap().is_none());
59    }
60
61    // A roundtrip through the bridge's data path: stage an incoming remote
62    // address, apply it, and confirm the local-only address comes back out for
63    // upload. Unlike `test_sync_meta` this exercises the JSON (de)serialization
64    // of BSOs and the sync staging tables, mirroring the logins and tabs
65    // `test_sync_via_bridge` tests.
66    #[test]
67    fn test_sync_via_bridge() {
68        error_support::init_for_tests();
69
70        let store = Arc::new(Store::new_shared_memory("addresses-bridge-roundtrip").unwrap());
71
72        // A local-only address: nothing on the server knows about it yet, so it
73        // should be uploaded.
74        let local = store
75            .add_address(UpdatableAddressFields {
76                name: "Local Person".to_string(),
77                street_address: "1 Local Lane".to_string(),
78                address_level2: "Seattle, WA".to_string(),
79                country: "US".to_string(),
80                ..Default::default()
81            })
82            .expect("should add local address");
83
84        let bridge = store.clone().addresses_bridged_engine();
85
86        // `sync_started` is what creates the sync staging tables.
87        bridge.sync_started().expect("should prepare for sync");
88
89        // An incoming remote address that isn't known locally. We build the
90        // envelope as raw JSON, exactly as the JS bridge hands it to us.
91        let incoming = vec![serde_json::json!({
92            "id": "remote-only-bbbb",
93            "modified": 0,
94            "payload": serde_json::json!({
95                "id": "remote-only-bbbb",
96                "entry": {
97                    "name": "Remote Person",
98                    "street-address": "99 Remote Road",
99                    "address-level2": "Portland, OR",
100                    "country": "US",
101                    "version": 1,
102                },
103            })
104            .to_string(),
105        })
106        .to_string()];
107        bridge
108            .store_incoming(incoming)
109            .expect("should store incoming");
110
111        // Applying stores the remote record locally and returns the local-only
112        // address for upload.
113        let outgoing = bridge.apply(1234).expect("should apply");
114        let changes: HashMap<String, serde_json::Value> = outgoing
115            .into_iter()
116            .map(|s| {
117                let bso: serde_json::Value = serde_json::from_str(&s).unwrap();
118                let payload: serde_json::Value =
119                    serde_json::from_str(bso["payload"].as_str().unwrap()).unwrap();
120                (payload["id"].as_str().unwrap().to_string(), payload)
121            })
122            .collect();
123
124        // Only the local address is outgoing; the just-applied remote one is not
125        // re-uploaded.
126        assert_eq!(changes.len(), 1);
127        assert_eq!(
128            changes[&local.guid]["entry"]["street-address"],
129            "1 Local Lane"
130        );
131
132        // The incoming remote address was actually persisted.
133        let stored = store
134            .get_address("remote-only-bbbb".to_string())
135            .expect("remote address should have been stored");
136        assert_eq!(stored.street_address, "99 Remote Road");
137
138        assert_eq!(bridge.last_sync().unwrap(), 1234);
139        bridge.set_uploaded(5678, vec![local.guid.clone()]).unwrap();
140        bridge.sync_finished().unwrap();
141        assert_eq!(bridge.last_sync().unwrap(), 5678);
142
143        // Acknowledging the upload cleared the record's change counter, so a
144        // subsequent sync has nothing to send.
145        assert!(bridge.apply(5678).expect("should apply again").is_empty());
146    }
147}