|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 Cu.import("resource://services-sync/engines.js"); |
|
5 Cu.import("resource://services-sync/service.js"); |
|
6 Cu.import("resource://services-sync/util.js"); |
|
7 Cu.import("resource://testing-common/services/sync/rotaryengine.js"); |
|
8 Cu.import("resource://testing-common/services/sync/utils.js"); |
|
9 |
|
10 // Track HMAC error counts. |
|
11 let hmacErrorCount = 0; |
|
12 (function () { |
|
13 let hHE = Service.handleHMACEvent; |
|
14 Service.handleHMACEvent = function () { |
|
15 hmacErrorCount++; |
|
16 return hHE.call(Service); |
|
17 }; |
|
18 })(); |
|
19 |
|
20 function shared_setup() { |
|
21 hmacErrorCount = 0; |
|
22 |
|
23 // Do not instantiate SyncTestingInfrastructure; we need real crypto. |
|
24 ensureLegacyIdentityManager(); |
|
25 setBasicCredentials("foo", "foo", "aabcdeabcdeabcdeabcdeabcde"); |
|
26 |
|
27 // Make sure RotaryEngine is the only one we sync. |
|
28 Service.engineManager._engines = {}; |
|
29 Service.engineManager.register(RotaryEngine); |
|
30 let engine = Service.engineManager.get("rotary"); |
|
31 engine.enabled = true; |
|
32 engine.lastSync = 123; // Needs to be non-zero so that tracker is queried. |
|
33 engine._store.items = {flying: "LNER Class A3 4472", |
|
34 scotsman: "Flying Scotsman"}; |
|
35 engine._tracker.addChangedID('scotsman', 0); |
|
36 do_check_eq(1, Service.engineManager.getEnabled().length); |
|
37 |
|
38 let engines = {rotary: {version: engine.version, |
|
39 syncID: engine.syncID}, |
|
40 clients: {version: Service.clientsEngine.version, |
|
41 syncID: Service.clientsEngine.syncID}}; |
|
42 |
|
43 // Common server objects. |
|
44 let global = new ServerWBO("global", {engines: engines}); |
|
45 let keysWBO = new ServerWBO("keys"); |
|
46 let rotaryColl = new ServerCollection({}, true); |
|
47 let clientsColl = new ServerCollection({}, true); |
|
48 |
|
49 return [engine, rotaryColl, clientsColl, keysWBO, global]; |
|
50 } |
|
51 |
|
52 add_test(function hmac_error_during_404() { |
|
53 _("Attempt to replicate the HMAC error setup."); |
|
54 let [engine, rotaryColl, clientsColl, keysWBO, global] = shared_setup(); |
|
55 |
|
56 // Hand out 404s for crypto/keys. |
|
57 let keysHandler = keysWBO.handler(); |
|
58 let key404Counter = 0; |
|
59 let keys404Handler = function (request, response) { |
|
60 if (key404Counter > 0) { |
|
61 let body = "Not Found"; |
|
62 response.setStatusLine(request.httpVersion, 404, body); |
|
63 response.bodyOutputStream.write(body, body.length); |
|
64 key404Counter--; |
|
65 return; |
|
66 } |
|
67 keysHandler(request, response); |
|
68 }; |
|
69 |
|
70 let collectionsHelper = track_collections_helper(); |
|
71 let upd = collectionsHelper.with_updated_collection; |
|
72 let collections = collectionsHelper.collections; |
|
73 let handlers = { |
|
74 "/1.1/foo/info/collections": collectionsHelper.handler, |
|
75 "/1.1/foo/storage/meta/global": upd("meta", global.handler()), |
|
76 "/1.1/foo/storage/crypto/keys": upd("crypto", keys404Handler), |
|
77 "/1.1/foo/storage/clients": upd("clients", clientsColl.handler()), |
|
78 "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler()) |
|
79 }; |
|
80 |
|
81 let server = sync_httpd_setup(handlers); |
|
82 Service.serverURL = server.baseURI; |
|
83 |
|
84 try { |
|
85 _("Syncing."); |
|
86 Service.sync(); |
|
87 _("Partially resetting client, as if after a restart, and forcing redownload."); |
|
88 Service.collectionKeys.clear(); |
|
89 engine.lastSync = 0; // So that we redownload records. |
|
90 key404Counter = 1; |
|
91 _("---------------------------"); |
|
92 Service.sync(); |
|
93 _("---------------------------"); |
|
94 |
|
95 // Two rotary items, one client record... no errors. |
|
96 do_check_eq(hmacErrorCount, 0) |
|
97 } finally { |
|
98 Svc.Prefs.resetBranch(""); |
|
99 Service.recordManager.clearCache(); |
|
100 server.stop(run_next_test); |
|
101 } |
|
102 }); |
|
103 |
|
104 add_test(function hmac_error_during_node_reassignment() { |
|
105 _("Attempt to replicate an HMAC error during node reassignment."); |
|
106 let [engine, rotaryColl, clientsColl, keysWBO, global] = shared_setup(); |
|
107 |
|
108 let collectionsHelper = track_collections_helper(); |
|
109 let upd = collectionsHelper.with_updated_collection; |
|
110 |
|
111 // We'll provide a 401 mid-way through the sync. This function |
|
112 // simulates shifting to a node which has no data. |
|
113 function on401() { |
|
114 _("Deleting server data..."); |
|
115 global.delete(); |
|
116 rotaryColl.delete(); |
|
117 keysWBO.delete(); |
|
118 clientsColl.delete(); |
|
119 delete collectionsHelper.collections.rotary; |
|
120 delete collectionsHelper.collections.crypto; |
|
121 delete collectionsHelper.collections.clients; |
|
122 _("Deleted server data."); |
|
123 } |
|
124 |
|
125 let should401 = false; |
|
126 function upd401(coll, handler) { |
|
127 return function (request, response) { |
|
128 if (should401 && (request.method != "DELETE")) { |
|
129 on401(); |
|
130 should401 = false; |
|
131 let body = "\"reassigned!\""; |
|
132 response.setStatusLine(request.httpVersion, 401, "Node reassignment."); |
|
133 response.bodyOutputStream.write(body, body.length); |
|
134 return; |
|
135 } |
|
136 handler(request, response); |
|
137 }; |
|
138 } |
|
139 |
|
140 function sameNodeHandler(request, response) { |
|
141 // Set this so that _setCluster will think we've really changed. |
|
142 let url = Service.serverURL.replace("localhost", "LOCALHOST"); |
|
143 _("Client requesting reassignment; pointing them to " + url); |
|
144 response.setStatusLine(request.httpVersion, 200, "OK"); |
|
145 response.bodyOutputStream.write(url, url.length); |
|
146 } |
|
147 |
|
148 let handlers = { |
|
149 "/user/1.0/foo/node/weave": sameNodeHandler, |
|
150 "/1.1/foo/info/collections": collectionsHelper.handler, |
|
151 "/1.1/foo/storage/meta/global": upd("meta", global.handler()), |
|
152 "/1.1/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()), |
|
153 "/1.1/foo/storage/clients": upd401("clients", clientsColl.handler()), |
|
154 "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler()) |
|
155 }; |
|
156 |
|
157 let server = sync_httpd_setup(handlers); |
|
158 Service.serverURL = server.baseURI; |
|
159 _("Syncing."); |
|
160 // First hit of clients will 401. This will happen after meta/global and |
|
161 // keys -- i.e., in the middle of the sync, but before RotaryEngine. |
|
162 should401 = true; |
|
163 |
|
164 // Use observers to perform actions when our sync finishes. |
|
165 // This allows us to observe the automatic next-tick sync that occurs after |
|
166 // an abort. |
|
167 function onSyncError() { |
|
168 do_throw("Should not get a sync error!"); |
|
169 } |
|
170 function onSyncFinished() {} |
|
171 let obs = { |
|
172 observe: function observe(subject, topic, data) { |
|
173 switch (topic) { |
|
174 case "weave:service:sync:error": |
|
175 onSyncError(); |
|
176 break; |
|
177 case "weave:service:sync:finish": |
|
178 onSyncFinished(); |
|
179 break; |
|
180 } |
|
181 } |
|
182 }; |
|
183 |
|
184 Svc.Obs.add("weave:service:sync:finish", obs); |
|
185 Svc.Obs.add("weave:service:sync:error", obs); |
|
186 |
|
187 // This kicks off the actual test. Split into a function here to allow this |
|
188 // source file to broadly follow actual execution order. |
|
189 function onwards() { |
|
190 _("== Invoking first sync."); |
|
191 Service.sync(); |
|
192 _("We should not simultaneously have data but no keys on the server."); |
|
193 let hasData = rotaryColl.wbo("flying") || |
|
194 rotaryColl.wbo("scotsman"); |
|
195 let hasKeys = keysWBO.modified; |
|
196 |
|
197 _("We correctly handle 401s by aborting the sync and starting again."); |
|
198 do_check_true(!hasData == !hasKeys); |
|
199 |
|
200 _("Be prepared for the second (automatic) sync..."); |
|
201 } |
|
202 |
|
203 _("Make sure that syncing again causes recovery."); |
|
204 onSyncFinished = function() { |
|
205 _("== First sync done."); |
|
206 _("---------------------------"); |
|
207 onSyncFinished = function() { |
|
208 _("== Second (automatic) sync done."); |
|
209 hasData = rotaryColl.wbo("flying") || |
|
210 rotaryColl.wbo("scotsman"); |
|
211 hasKeys = keysWBO.modified; |
|
212 do_check_true(!hasData == !hasKeys); |
|
213 |
|
214 // Kick off another sync. Can't just call it, because we're inside the |
|
215 // lock... |
|
216 Utils.nextTick(function() { |
|
217 _("Now a fresh sync will get no HMAC errors."); |
|
218 _("Partially resetting client, as if after a restart, and forcing redownload."); |
|
219 Service.collectionKeys.clear(); |
|
220 engine.lastSync = 0; |
|
221 hmacErrorCount = 0; |
|
222 |
|
223 onSyncFinished = function() { |
|
224 // Two rotary items, one client record... no errors. |
|
225 do_check_eq(hmacErrorCount, 0) |
|
226 |
|
227 Svc.Obs.remove("weave:service:sync:finish", obs); |
|
228 Svc.Obs.remove("weave:service:sync:error", obs); |
|
229 |
|
230 Svc.Prefs.resetBranch(""); |
|
231 Service.recordManager.clearCache(); |
|
232 server.stop(run_next_test); |
|
233 }; |
|
234 |
|
235 Service.sync(); |
|
236 }, |
|
237 this); |
|
238 }; |
|
239 }; |
|
240 |
|
241 onwards(); |
|
242 }); |
|
243 |
|
244 function run_test() { |
|
245 initTestLogging("Trace"); |
|
246 run_next_test(); |
|
247 } |