@capgo/cordova-updater 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +373 -0
- package/README.md +82 -0
- package/dist/esm/definitions.d.ts +2684 -0
- package/dist/esm/definitions.js +109 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/exec.d.ts +4 -0
- package/dist/esm/exec.js +25 -0
- package/dist/esm/exec.js.map +1 -0
- package/dist/esm/history.d.ts +1 -0
- package/dist/esm/history.js +283 -0
- package/dist/esm/history.js.map +1 -0
- package/dist/esm/index.d.ts +57 -0
- package/dist/esm/index.js +68 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/plugin.d.ts +57 -0
- package/dist/esm/plugin.js +15 -0
- package/dist/esm/plugin.js.map +1 -0
- package/dist/plugin.cjs.js +217 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +222 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +96 -0
- package/plugin.xml +207 -0
- package/src/android/CordovaUpdaterPlugin.java +5662 -0
- package/src/android/UpdaterPathHandler.java +99 -0
- package/src/android/app/capgo/cordova/updater/AndroidAppExitReporter.java +92 -0
- package/src/android/app/capgo/cordova/updater/AppLifecycleObserver.java +115 -0
- package/src/android/app/capgo/cordova/updater/BundleInfo.java +246 -0
- package/src/android/app/capgo/cordova/updater/BundleStatus.java +45 -0
- package/src/android/app/capgo/cordova/updater/Callback.java +13 -0
- package/src/android/app/capgo/cordova/updater/CapgoUpdater.java +3404 -0
- package/src/android/app/capgo/cordova/updater/CordovaUpdaterConfig.java +56 -0
- package/src/android/app/capgo/cordova/updater/CryptoCipher.java +462 -0
- package/src/android/app/capgo/cordova/updater/DataManager.java +44 -0
- package/src/android/app/capgo/cordova/updater/DelayCondition.java +56 -0
- package/src/android/app/capgo/cordova/updater/DelayUntilNext.java +14 -0
- package/src/android/app/capgo/cordova/updater/DelayUpdateUtils.java +307 -0
- package/src/android/app/capgo/cordova/updater/DeviceIdHelper.java +230 -0
- package/src/android/app/capgo/cordova/updater/DownloadService.java +1219 -0
- package/src/android/app/capgo/cordova/updater/DownloadWorkerManager.java +260 -0
- package/src/android/app/capgo/cordova/updater/InternalUtils.java +54 -0
- package/src/android/app/capgo/cordova/updater/Logger.java +345 -0
- package/src/android/app/capgo/cordova/updater/ShakeDetector.java +72 -0
- package/src/android/app/capgo/cordova/updater/ShakeMenu.java +39 -0
- package/src/android/app/capgo/cordova/updater/ThreeFingerPinchDetector.java +323 -0
- package/src/android/app/capgo/cordova/updater/compat/JSArray.java +9 -0
- package/src/android/app/capgo/cordova/updater/compat/JSObject.java +83 -0
- package/src/android/app/capgo/cordova/updater/compat/PluginCall.java +165 -0
- package/src/android/capgo-cordova-updater.gradle +19 -0
- package/src/android/capgo-updater.xml +3 -0
- package/src/android/test/app/capgo/cordova/updater/CapacitorUpdaterUnitTest.java.skip +3039 -0
- package/src/android/test/app/capgo/cordova/updater/DelayUpdateUtilsTest.java +177 -0
- package/src/android/test/app/capgo/cordova/updater/NativeContractTest.java +105 -0
- package/src/ios/AES.swift +216 -0
- package/src/ios/AppHealthTracker.swift +82 -0
- package/src/ios/BundleInfo.swift +185 -0
- package/src/ios/BundleStatus.swift +127 -0
- package/src/ios/CapgoRawRsa.swift +36 -0
- package/src/ios/CapgoUpdater.swift +3544 -0
- package/src/ios/CordovaCompat.swift +11 -0
- package/src/ios/CordovaPluginCall.swift +190 -0
- package/src/ios/CordovaUpdaterCommands.swift +270 -0
- package/src/ios/CordovaUpdaterPlugin.swift +4706 -0
- package/src/ios/CryptoCipher.swift +326 -0
- package/src/ios/DelayCondition.swift +74 -0
- package/src/ios/DelayUntilNext.swift +30 -0
- package/src/ios/DelayUpdateUtils.swift +257 -0
- package/src/ios/DeviceIdHelper.swift +120 -0
- package/src/ios/Info.plist +28 -0
- package/src/ios/InternalUtils.swift +471 -0
- package/src/ios/Logger.swift +283 -0
- package/src/ios/RSA.swift +96 -0
- package/src/ios/ShakeMenu.swift +52 -0
- package/src/ios/UpdaterSchemeHandler.swift +81 -0
- package/src/ios/UserDefaultsExtension.swift +46 -0
- package/src/ios/WebViewStatsReporter.swift +303 -0
|
@@ -0,0 +1,3039 @@
|
|
|
1
|
+
package app.capgo.cordova.updater;
|
|
2
|
+
|
|
3
|
+
import static org.junit.Assert.*;
|
|
4
|
+
import static org.mockito.Mockito.*;
|
|
5
|
+
|
|
6
|
+
import android.app.ApplicationExitInfo;
|
|
7
|
+
import android.content.SharedPreferences;
|
|
8
|
+
import android.content.pm.PackageInfo;
|
|
9
|
+
import android.os.Handler;
|
|
10
|
+
import android.os.Looper;
|
|
11
|
+
import android.webkit.WebView;
|
|
12
|
+
import androidx.appcompat.app.AppCompatActivity;
|
|
13
|
+
import com.getcapacitor.Bridge;
|
|
14
|
+
import com.getcapacitor.CapConfig;
|
|
15
|
+
import com.getcapacitor.JSArray;
|
|
16
|
+
import com.getcapacitor.JSObject;
|
|
17
|
+
import com.getcapacitor.PluginCall;
|
|
18
|
+
import com.getcapacitor.PluginConfig;
|
|
19
|
+
import com.getcapacitor.PluginHandle;
|
|
20
|
+
import io.github.g00fy2.versioncompare.Version;
|
|
21
|
+
import java.io.File;
|
|
22
|
+
import java.io.IOException;
|
|
23
|
+
import java.lang.reflect.Field;
|
|
24
|
+
import java.lang.reflect.InvocationTargetException;
|
|
25
|
+
import java.lang.reflect.Method;
|
|
26
|
+
import java.nio.charset.StandardCharsets;
|
|
27
|
+
import java.nio.file.Files;
|
|
28
|
+
import java.nio.file.Path;
|
|
29
|
+
import java.util.ArrayList;
|
|
30
|
+
import java.util.Date;
|
|
31
|
+
import java.util.HashMap;
|
|
32
|
+
import java.util.List;
|
|
33
|
+
import java.util.Map;
|
|
34
|
+
import java.util.concurrent.Phaser;
|
|
35
|
+
import java.util.concurrent.TimeUnit;
|
|
36
|
+
import java.util.function.BooleanSupplier;
|
|
37
|
+
import java.util.zip.ZipEntry;
|
|
38
|
+
import java.util.zip.ZipOutputStream;
|
|
39
|
+
import org.json.JSONArray;
|
|
40
|
+
import org.json.JSONException;
|
|
41
|
+
import org.json.JSONObject;
|
|
42
|
+
import org.junit.Test;
|
|
43
|
+
import org.mockito.ArgumentCaptor;
|
|
44
|
+
import org.mockito.MockedConstruction;
|
|
45
|
+
import org.mockito.MockedStatic;
|
|
46
|
+
|
|
47
|
+
public class CapacitorUpdaterUnitTest {
|
|
48
|
+
|
|
49
|
+
private static class TestableCordovaUpdaterPlugin extends CordovaUpdaterPlugin {
|
|
50
|
+
|
|
51
|
+
private final ArrayList<String> notifiedEventNames = new ArrayList<>();
|
|
52
|
+
private final Map<String, JSObject> notifiedEventPayloads = new HashMap<>();
|
|
53
|
+
|
|
54
|
+
@Override
|
|
55
|
+
public void notifyListeners(String eventName, JSObject data) {
|
|
56
|
+
this.notifiedEventNames.add(eventName);
|
|
57
|
+
this.notifiedEventPayloads.put(eventName, data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Override
|
|
61
|
+
public void notifyListeners(String eventName, JSObject data, boolean retainUntilConsumed) {
|
|
62
|
+
this.notifiedEventNames.add(eventName);
|
|
63
|
+
this.notifiedEventPayloads.put(eventName, data);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
boolean hasNotifiedEvent(final String eventName) {
|
|
67
|
+
return this.notifiedEventNames.contains(eventName);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
JSObject getNotifiedEventPayload(final String eventName) {
|
|
71
|
+
return this.notifiedEventPayloads.get(eventName);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private static final class ImmediateThreadCordovaUpdaterPlugin extends TestableCordovaUpdaterPlugin {
|
|
76
|
+
|
|
77
|
+
private boolean versionDownloadInProgress = false;
|
|
78
|
+
|
|
79
|
+
@Override
|
|
80
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
81
|
+
function.run();
|
|
82
|
+
return new Thread();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
@Override
|
|
86
|
+
public Thread startNewThread(final Runnable function) {
|
|
87
|
+
function.run();
|
|
88
|
+
return new Thread();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Override
|
|
92
|
+
boolean isVersionDownloadInProgress(final String version) {
|
|
93
|
+
return this.versionDownloadInProgress;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private static final class DirectUpdateDispatchPlugin extends TestableCordovaUpdaterPlugin {
|
|
98
|
+
|
|
99
|
+
private final AppCompatActivity activity = mock(AppCompatActivity.class);
|
|
100
|
+
private boolean startNewThreadCalled = false;
|
|
101
|
+
private boolean reloadCalled = false;
|
|
102
|
+
|
|
103
|
+
@Override
|
|
104
|
+
public AppCompatActivity getActivity() {
|
|
105
|
+
return this.activity;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@Override
|
|
109
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
110
|
+
this.startNewThreadCalled = true;
|
|
111
|
+
function.run();
|
|
112
|
+
return new Thread();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Override
|
|
116
|
+
public Thread startNewThread(final Runnable function) {
|
|
117
|
+
this.startNewThreadCalled = true;
|
|
118
|
+
function.run();
|
|
119
|
+
return new Thread();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@Override
|
|
123
|
+
protected boolean _reload() {
|
|
124
|
+
this.reloadCalled = true;
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static final class InstallNextDispatchPlugin extends TestableCordovaUpdaterPlugin {
|
|
130
|
+
|
|
131
|
+
private boolean startNewThreadCalled = false;
|
|
132
|
+
private boolean reloadCalled = false;
|
|
133
|
+
|
|
134
|
+
@Override
|
|
135
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
136
|
+
return this.startNewThread(function);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@Override
|
|
140
|
+
public Thread startNewThread(final Runnable function) {
|
|
141
|
+
this.startNewThreadCalled = true;
|
|
142
|
+
function.run();
|
|
143
|
+
return new Thread();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
@Override
|
|
147
|
+
protected boolean _reload() {
|
|
148
|
+
this.reloadCalled = true;
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private static final class ReloadDispatchPlugin extends TestableCordovaUpdaterPlugin {
|
|
154
|
+
|
|
155
|
+
private boolean startNewThreadCalled = false;
|
|
156
|
+
private boolean reloadCalled = false;
|
|
157
|
+
|
|
158
|
+
@Override
|
|
159
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
160
|
+
return this.startNewThread(function);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@Override
|
|
164
|
+
public Thread startNewThread(final Runnable function) {
|
|
165
|
+
this.startNewThreadCalled = true;
|
|
166
|
+
function.run();
|
|
167
|
+
return new Thread();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
@Override
|
|
171
|
+
protected boolean _reload() {
|
|
172
|
+
this.reloadCalled = true;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private static final class InstallNextCapgoUpdater extends CapgoUpdater {
|
|
178
|
+
|
|
179
|
+
private BundleInfo currentBundle = new BundleInfo(
|
|
180
|
+
BundleInfo.ID_BUILTIN,
|
|
181
|
+
"builtin",
|
|
182
|
+
BundleStatus.SUCCESS,
|
|
183
|
+
BundleInfo.DOWNLOADED_BUILTIN,
|
|
184
|
+
"builtin"
|
|
185
|
+
);
|
|
186
|
+
private BundleInfo nextBundle = new BundleInfo("next-bundle-id", "2.0.0", BundleStatus.PENDING, new Date(), "abc123");
|
|
187
|
+
private int setCalls = 0;
|
|
188
|
+
private String lastSetNextBundleId = "next-bundle-id";
|
|
189
|
+
|
|
190
|
+
InstallNextCapgoUpdater() {
|
|
191
|
+
super(null);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@Override
|
|
195
|
+
public BundleInfo getCurrentBundle() {
|
|
196
|
+
return this.currentBundle;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@Override
|
|
200
|
+
public BundleInfo getNextBundle() {
|
|
201
|
+
return this.nextBundle;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@Override
|
|
205
|
+
public Boolean set(final BundleInfo bundle) {
|
|
206
|
+
this.setCalls++;
|
|
207
|
+
this.currentBundle = bundle;
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@Override
|
|
212
|
+
public boolean setNextBundle(final String next) {
|
|
213
|
+
this.lastSetNextBundleId = next;
|
|
214
|
+
this.nextBundle = next == null ? null : this.nextBundle;
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private static final class ReloadCapgoUpdater extends CapgoUpdater {
|
|
220
|
+
|
|
221
|
+
private final BundleInfo currentBundle = new BundleInfo("current-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
222
|
+
|
|
223
|
+
ReloadCapgoUpdater() {
|
|
224
|
+
super(null);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
@Override
|
|
228
|
+
public BundleInfo getCurrentBundle() {
|
|
229
|
+
return this.currentBundle;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@Override
|
|
233
|
+
public BundleInfo getNextBundle() {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private static final class StatsIgnoringCapgoUpdater extends CapgoUpdater {
|
|
239
|
+
|
|
240
|
+
StatsIgnoringCapgoUpdater() {
|
|
241
|
+
super(mock(Logger.class));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@Override
|
|
245
|
+
public void sendStats(final String action) {}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private static final class FreshDownloadCapgoUpdater extends CapgoUpdater {
|
|
249
|
+
|
|
250
|
+
private final BundleInfo currentBundle = new BundleInfo("current-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
251
|
+
private BundleInfo existingLatestBundle;
|
|
252
|
+
private BooleanSupplier consumedStateSupplier = () -> false;
|
|
253
|
+
private BooleanSupplier directUpdateStateSupplier = () -> false;
|
|
254
|
+
private boolean downloadBackgroundCalled = false;
|
|
255
|
+
private boolean downloadBackgroundSetNext = true;
|
|
256
|
+
private boolean consumedWhenDownloadStarted = false;
|
|
257
|
+
private boolean directUpdateWhenDownloadStarted = false;
|
|
258
|
+
private boolean setNextBundleCalled = false;
|
|
259
|
+
private String lastSetNextBundleId;
|
|
260
|
+
private java.util.function.Consumer<String> updateAvailableNotifier = (version) -> {};
|
|
261
|
+
|
|
262
|
+
FreshDownloadCapgoUpdater() {
|
|
263
|
+
super(null);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@Override
|
|
267
|
+
public void getLatest(final String updateUrl, final String channel, final Callback callback) {
|
|
268
|
+
final Map<String, Object> response = new HashMap<>();
|
|
269
|
+
response.put("version", "2.0.0");
|
|
270
|
+
response.put("url", "https://example.com/update.zip");
|
|
271
|
+
callback.callback(response);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
@Override
|
|
275
|
+
public BundleInfo getCurrentBundle() {
|
|
276
|
+
return this.currentBundle;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
@Override
|
|
280
|
+
public BundleInfo getBundleInfoByName(final String version) {
|
|
281
|
+
return this.existingLatestBundle;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@Override
|
|
285
|
+
public void downloadBackground(
|
|
286
|
+
final String url,
|
|
287
|
+
final String version,
|
|
288
|
+
final String sessionKey,
|
|
289
|
+
final String checksum,
|
|
290
|
+
final JSONArray manifest
|
|
291
|
+
) {
|
|
292
|
+
this.downloadBackground(url, version, sessionKey, checksum, manifest, true);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
@Override
|
|
296
|
+
public void downloadBackground(
|
|
297
|
+
final String url,
|
|
298
|
+
final String version,
|
|
299
|
+
final String sessionKey,
|
|
300
|
+
final String checksum,
|
|
301
|
+
final JSONArray manifest,
|
|
302
|
+
final boolean setNext
|
|
303
|
+
) {
|
|
304
|
+
this.downloadBackgroundCalled = true;
|
|
305
|
+
this.downloadBackgroundSetNext = setNext;
|
|
306
|
+
this.consumedWhenDownloadStarted = this.consumedStateSupplier.getAsBoolean();
|
|
307
|
+
this.directUpdateWhenDownloadStarted = this.directUpdateStateSupplier.getAsBoolean();
|
|
308
|
+
this.updateAvailableNotifier.accept(version);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@Override
|
|
312
|
+
public boolean setNextBundle(final String next) {
|
|
313
|
+
this.setNextBundleCalled = true;
|
|
314
|
+
this.lastSetNextBundleId = next;
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private static final class BuiltinLatestCapgoUpdater extends CapgoUpdater {
|
|
320
|
+
|
|
321
|
+
private final BundleInfo currentBundle;
|
|
322
|
+
private final BundleInfo builtinBundle = new BundleInfo(
|
|
323
|
+
BundleInfo.ID_BUILTIN,
|
|
324
|
+
"builtin",
|
|
325
|
+
BundleStatus.SUCCESS,
|
|
326
|
+
BundleInfo.DOWNLOADED_BUILTIN,
|
|
327
|
+
"builtin"
|
|
328
|
+
);
|
|
329
|
+
private boolean setNextBundleCalled = false;
|
|
330
|
+
private String lastSetNextBundleId;
|
|
331
|
+
|
|
332
|
+
BuiltinLatestCapgoUpdater() {
|
|
333
|
+
this(new BundleInfo("current-id", "2.0.0", BundleStatus.SUCCESS, new Date(), "abc123"));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
BuiltinLatestCapgoUpdater(final BundleInfo currentBundle) {
|
|
337
|
+
super(null);
|
|
338
|
+
this.currentBundle = currentBundle;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
@Override
|
|
342
|
+
public void getLatest(final String updateUrl, final String channel, final Callback callback) {
|
|
343
|
+
final Map<String, Object> response = new HashMap<>();
|
|
344
|
+
response.put("version", "builtin");
|
|
345
|
+
callback.callback(response);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
@Override
|
|
349
|
+
public BundleInfo getCurrentBundle() {
|
|
350
|
+
return this.currentBundle;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@Override
|
|
354
|
+
public BundleInfo getBundleInfo(final String id) {
|
|
355
|
+
if (BundleInfo.ID_BUILTIN.equals(id)) {
|
|
356
|
+
return this.builtinBundle;
|
|
357
|
+
}
|
|
358
|
+
return this.currentBundle;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
@Override
|
|
362
|
+
public boolean setNextBundle(final String next) {
|
|
363
|
+
this.setNextBundleCalled = true;
|
|
364
|
+
this.lastSetNextBundleId = next;
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private static final class NoNewVersionCapgoUpdater extends CapgoUpdater {
|
|
370
|
+
|
|
371
|
+
private final BundleInfo currentBundle = new BundleInfo("current-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
372
|
+
private boolean sendStatsCalled = false;
|
|
373
|
+
private final boolean includeKind;
|
|
374
|
+
|
|
375
|
+
NoNewVersionCapgoUpdater() {
|
|
376
|
+
this(true);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
NoNewVersionCapgoUpdater(final boolean includeKind) {
|
|
380
|
+
super(null);
|
|
381
|
+
this.includeKind = includeKind;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
@Override
|
|
385
|
+
public void getLatest(final String updateUrl, final String channel, final Callback callback) {
|
|
386
|
+
final Map<String, Object> response = new HashMap<>();
|
|
387
|
+
response.put("error", "no_new_version_available");
|
|
388
|
+
if (this.includeKind) {
|
|
389
|
+
response.put("kind", "up_to_date");
|
|
390
|
+
}
|
|
391
|
+
response.put("message", "No new version available");
|
|
392
|
+
response.put("statusCode", 200);
|
|
393
|
+
callback.callback(response);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
@Override
|
|
397
|
+
public BundleInfo getCurrentBundle() {
|
|
398
|
+
return this.currentBundle;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
@Override
|
|
402
|
+
public void sendStats(final String action, final String versionName, final String oldVersionName) {
|
|
403
|
+
this.sendStatsCalled = true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
private static final class BlockedUpdateCapgoUpdater extends CapgoUpdater {
|
|
408
|
+
|
|
409
|
+
private final BundleInfo currentBundle = new BundleInfo("current-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
410
|
+
private boolean sendStatsCalled = false;
|
|
411
|
+
|
|
412
|
+
BlockedUpdateCapgoUpdater() {
|
|
413
|
+
super(null);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@Override
|
|
417
|
+
public void getLatest(final String updateUrl, final String channel, final Callback callback) {
|
|
418
|
+
final Map<String, Object> response = new HashMap<>();
|
|
419
|
+
response.put("error", "disable_auto_update_to_major");
|
|
420
|
+
response.put("kind", "blocked");
|
|
421
|
+
response.put("message", "Cannot upgrade major version");
|
|
422
|
+
response.put("statusCode", 200);
|
|
423
|
+
callback.callback(response);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
@Override
|
|
427
|
+
public BundleInfo getCurrentBundle() {
|
|
428
|
+
return this.currentBundle;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@Override
|
|
432
|
+
public void sendStats(final String action, final String versionName, final String oldVersionName) {
|
|
433
|
+
this.sendStatsCalled = true;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private static final class BreakingNoUrlCapgoUpdater extends CapgoUpdater {
|
|
438
|
+
|
|
439
|
+
private final BundleInfo currentBundle = new BundleInfo("current-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
440
|
+
private boolean downloadFailStatsCalled = false;
|
|
441
|
+
|
|
442
|
+
BreakingNoUrlCapgoUpdater() {
|
|
443
|
+
super(null);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
@Override
|
|
447
|
+
public void getLatest(final String updateUrl, final String channel, final Callback callback) {
|
|
448
|
+
final Map<String, Object> response = new HashMap<>();
|
|
449
|
+
response.put("version", "1.0.17");
|
|
450
|
+
response.put("breaking", true);
|
|
451
|
+
response.put("message", "store_update_required");
|
|
452
|
+
response.put("statusCode", 200);
|
|
453
|
+
callback.callback(response);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
@Override
|
|
457
|
+
public BundleInfo getCurrentBundle() {
|
|
458
|
+
return this.currentBundle;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
@Override
|
|
462
|
+
public void sendStats(final String action, final String versionName, final String oldVersionName) {
|
|
463
|
+
this.downloadFailStatsCalled = "download_fail".equals(action);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private static final class FailedUpdateCapgoUpdater extends CapgoUpdater {
|
|
468
|
+
|
|
469
|
+
private final BundleInfo currentBundle = new BundleInfo("current-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
470
|
+
private boolean downloadFailStatsCalled = false;
|
|
471
|
+
|
|
472
|
+
FailedUpdateCapgoUpdater() {
|
|
473
|
+
super(null);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
@Override
|
|
477
|
+
public void getLatest(final String updateUrl, final String channel, final Callback callback) {
|
|
478
|
+
final Map<String, Object> response = new HashMap<>();
|
|
479
|
+
response.put("error", "response_error");
|
|
480
|
+
response.put("kind", "failed");
|
|
481
|
+
response.put("message", "Error getting Latest");
|
|
482
|
+
response.put("statusCode", 500);
|
|
483
|
+
callback.callback(response);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
@Override
|
|
487
|
+
public BundleInfo getCurrentBundle() {
|
|
488
|
+
return this.currentBundle;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
@Override
|
|
492
|
+
public void sendStats(final String action, final String versionName, final String oldVersionName) {
|
|
493
|
+
this.downloadFailStatsCalled = "download_fail".equals(action);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
private static final class ResetTrackingCapgoUpdater extends CapgoUpdater {
|
|
498
|
+
|
|
499
|
+
private BundleInfo currentBundle = new BundleInfo("current-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
500
|
+
private final List<BundleInfo> listedBundles = new ArrayList<>();
|
|
501
|
+
private BundleInfo fallbackBundle = new BundleInfo(
|
|
502
|
+
BundleInfo.ID_BUILTIN,
|
|
503
|
+
"builtin",
|
|
504
|
+
BundleStatus.SUCCESS,
|
|
505
|
+
BundleInfo.DOWNLOADED_BUILTIN,
|
|
506
|
+
"builtin"
|
|
507
|
+
);
|
|
508
|
+
private BundleInfo nextBundle;
|
|
509
|
+
private BundleInfo previewFallbackBundle;
|
|
510
|
+
private BundleInfo stagedPreviewFallbackBundle;
|
|
511
|
+
private boolean resetCalled = false;
|
|
512
|
+
private boolean prepareResetStateForTransitionCalled = false;
|
|
513
|
+
private int prepareResetStateForTransitionCalls = 0;
|
|
514
|
+
private boolean finalizeResetTransitionCalled = false;
|
|
515
|
+
private int finalizeResetTransitionCalls = 0;
|
|
516
|
+
private String finalizeResetTransitionPreviousBundleName;
|
|
517
|
+
private boolean finalizeResetTransitionInternal = true;
|
|
518
|
+
private boolean canSetResult = true;
|
|
519
|
+
private boolean setResult = true;
|
|
520
|
+
private int canSetCalls = 0;
|
|
521
|
+
private int setCalls = 0;
|
|
522
|
+
private boolean stagePendingReloadResult = true;
|
|
523
|
+
private int stagePendingReloadCalls = 0;
|
|
524
|
+
private boolean stagePreviewFallbackReloadResult = true;
|
|
525
|
+
private int stagePreviewFallbackReloadCalls = 0;
|
|
526
|
+
private int finalizePendingReloadCalls = 0;
|
|
527
|
+
private BundleInfo finalizedPendingReloadBundle;
|
|
528
|
+
private String finalizePendingReloadPreviousBundleName;
|
|
529
|
+
private int setPreviewFallbackBundleCalls = 0;
|
|
530
|
+
private String lastPreviewFallbackBundle;
|
|
531
|
+
private int restoreResetStateCalls = 0;
|
|
532
|
+
private final ResetState capturedState = new ResetState("/stored/current", "fallback-id", "next-id");
|
|
533
|
+
private ResetState restoredState;
|
|
534
|
+
|
|
535
|
+
ResetTrackingCapgoUpdater() {
|
|
536
|
+
super(null);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
@Override
|
|
540
|
+
public BundleInfo getCurrentBundle() {
|
|
541
|
+
return this.currentBundle;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
@Override
|
|
545
|
+
public BundleInfo getFallbackBundle() {
|
|
546
|
+
return this.fallbackBundle;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@Override
|
|
550
|
+
public BundleInfo getNextBundle() {
|
|
551
|
+
return this.nextBundle;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
@Override
|
|
555
|
+
public List<BundleInfo> list(final boolean rawList) {
|
|
556
|
+
return this.listedBundles;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
@Override
|
|
560
|
+
public BundleInfo getPreviewFallbackBundle() {
|
|
561
|
+
return this.previewFallbackBundle;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
@Override
|
|
565
|
+
public BundleInfo getBundleInfo(final String id) {
|
|
566
|
+
if (BundleInfo.ID_BUILTIN.equals(id)) {
|
|
567
|
+
return new BundleInfo(BundleInfo.ID_BUILTIN, "builtin", BundleStatus.SUCCESS, BundleInfo.DOWNLOADED_BUILTIN, "builtin");
|
|
568
|
+
}
|
|
569
|
+
if (this.currentBundle != null && this.currentBundle.getId().equals(id)) {
|
|
570
|
+
return this.currentBundle;
|
|
571
|
+
}
|
|
572
|
+
if (this.fallbackBundle != null && this.fallbackBundle.getId().equals(id)) {
|
|
573
|
+
return this.fallbackBundle;
|
|
574
|
+
}
|
|
575
|
+
if (this.previewFallbackBundle != null && this.previewFallbackBundle.getId().equals(id)) {
|
|
576
|
+
return this.previewFallbackBundle;
|
|
577
|
+
}
|
|
578
|
+
return new BundleInfo(id, id, BundleStatus.PENDING, new Date(), "");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
@Override
|
|
582
|
+
boolean canSet(final BundleInfo bundle) {
|
|
583
|
+
this.canSetCalls++;
|
|
584
|
+
return this.canSetResult;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
@Override
|
|
588
|
+
ResetState captureResetState() {
|
|
589
|
+
return this.capturedState;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
@Override
|
|
593
|
+
void restoreResetState(final ResetState state) {
|
|
594
|
+
this.restoreResetStateCalls++;
|
|
595
|
+
this.restoredState = state;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
@Override
|
|
599
|
+
public Boolean set(final BundleInfo bundle) {
|
|
600
|
+
this.setCalls++;
|
|
601
|
+
return this.setResult;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
@Override
|
|
605
|
+
public boolean setNextBundle(final String next) {
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
@Override
|
|
610
|
+
boolean stagePendingReload(final BundleInfo bundle) {
|
|
611
|
+
this.stagePendingReloadCalls++;
|
|
612
|
+
return this.stagePendingReloadResult;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
@Override
|
|
616
|
+
boolean stagePreviewFallbackReload(final BundleInfo bundle) {
|
|
617
|
+
this.stagePreviewFallbackReloadCalls++;
|
|
618
|
+
this.stagedPreviewFallbackBundle = bundle;
|
|
619
|
+
return this.stagePreviewFallbackReloadResult;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
@Override
|
|
623
|
+
public boolean setPreviewFallbackBundle(final String fallback) {
|
|
624
|
+
this.setPreviewFallbackBundleCalls++;
|
|
625
|
+
this.lastPreviewFallbackBundle = fallback;
|
|
626
|
+
if (fallback == null) {
|
|
627
|
+
this.previewFallbackBundle = null;
|
|
628
|
+
}
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
@Override
|
|
633
|
+
public Boolean delete(final String id, final Boolean removeInfo) {
|
|
634
|
+
return true;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
@Override
|
|
638
|
+
void finalizePendingReload(final BundleInfo bundle, final String previousBundleName) {
|
|
639
|
+
this.finalizePendingReloadCalls++;
|
|
640
|
+
this.finalizedPendingReloadBundle = bundle;
|
|
641
|
+
this.finalizePendingReloadPreviousBundleName = previousBundleName;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
@Override
|
|
645
|
+
public void reset(final boolean internal) {
|
|
646
|
+
this.resetCalled = true;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
@Override
|
|
650
|
+
void prepareResetStateForTransition() {
|
|
651
|
+
this.prepareResetStateForTransitionCalled = true;
|
|
652
|
+
this.prepareResetStateForTransitionCalls++;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
@Override
|
|
656
|
+
void finalizeResetTransition(final String previousBundleName, final boolean internal) {
|
|
657
|
+
this.finalizeResetTransitionCalled = true;
|
|
658
|
+
this.finalizeResetTransitionCalls++;
|
|
659
|
+
this.finalizeResetTransitionPreviousBundleName = previousBundleName;
|
|
660
|
+
this.finalizeResetTransitionInternal = internal;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private static final class ReloadBypassCordovaUpdaterPlugin extends TestableCordovaUpdaterPlugin {
|
|
665
|
+
|
|
666
|
+
@Override
|
|
667
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
668
|
+
return this.startNewThread(function);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
@Override
|
|
672
|
+
public Thread startNewThread(final Runnable function) {
|
|
673
|
+
function.run();
|
|
674
|
+
return new Thread();
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
@Override
|
|
678
|
+
protected boolean _reload() {
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
@Override
|
|
683
|
+
protected boolean reloadWithoutWaitingForAppReady() {
|
|
684
|
+
return true;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private static final class ReloadFailureCordovaUpdaterPlugin extends TestableCordovaUpdaterPlugin {
|
|
689
|
+
|
|
690
|
+
private int restoreLiveBundleStateAfterFailedReloadCalls = 0;
|
|
691
|
+
|
|
692
|
+
@Override
|
|
693
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
694
|
+
return this.startNewThread(function);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
@Override
|
|
698
|
+
public Thread startNewThread(final Runnable function) {
|
|
699
|
+
function.run();
|
|
700
|
+
return new Thread();
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
@Override
|
|
704
|
+
protected boolean _reload() {
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
@Override
|
|
709
|
+
protected boolean reloadWithoutWaitingForAppReady() {
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
@Override
|
|
714
|
+
protected void restoreLiveBundleStateAfterFailedReload() {
|
|
715
|
+
this.restoreLiveBundleStateAfterFailedReloadCalls++;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private static final class SequenceReloadCordovaUpdaterPlugin extends TestableCordovaUpdaterPlugin {
|
|
720
|
+
|
|
721
|
+
private final boolean[] reloadResults;
|
|
722
|
+
private int reloadCallCount = 0;
|
|
723
|
+
private int restoreLiveBundleStateAfterFailedReloadCalls = 0;
|
|
724
|
+
|
|
725
|
+
SequenceReloadCordovaUpdaterPlugin(final boolean... reloadResults) {
|
|
726
|
+
this.reloadResults = reloadResults;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
@Override
|
|
730
|
+
protected boolean _reload() {
|
|
731
|
+
final int resultIndex = Math.min(this.reloadCallCount, this.reloadResults.length - 1);
|
|
732
|
+
this.reloadCallCount++;
|
|
733
|
+
return this.reloadResults[resultIndex];
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
@Override
|
|
737
|
+
protected boolean reloadWithoutWaitingForAppReady() {
|
|
738
|
+
final int resultIndex = Math.min(this.reloadCallCount, this.reloadResults.length - 1);
|
|
739
|
+
this.reloadCallCount++;
|
|
740
|
+
return this.reloadResults[resultIndex];
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
@Override
|
|
744
|
+
protected void restoreLiveBundleStateAfterFailedReload() {
|
|
745
|
+
this.restoreLiveBundleStateAfterFailedReloadCalls++;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private static final class PendingReloadFinalizeCapgoUpdater extends CapgoUpdater {
|
|
750
|
+
|
|
751
|
+
private final Map<String, BundleInfo> bundleInfos = new HashMap<>();
|
|
752
|
+
private final BundleInfo currentBundle = new BundleInfo("current-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
753
|
+
private String lastStatsAction;
|
|
754
|
+
private String lastStatsVersionName;
|
|
755
|
+
private final List<String> sentStatsActions = new ArrayList<>();
|
|
756
|
+
private final List<Map<String, String>> sentStatsMetadata = new ArrayList<>();
|
|
757
|
+
private String lastStatsOldVersionName;
|
|
758
|
+
private Map<String, String> lastStatsMetadata;
|
|
759
|
+
private boolean acknowledgeStats = true;
|
|
760
|
+
|
|
761
|
+
PendingReloadFinalizeCapgoUpdater() {
|
|
762
|
+
super(null);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
@Override
|
|
766
|
+
public BundleInfo getCurrentBundle() {
|
|
767
|
+
return this.currentBundle;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
@Override
|
|
771
|
+
public BundleInfo getBundleInfo(final String id) {
|
|
772
|
+
return this.bundleInfos.get(id);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
@Override
|
|
776
|
+
public void saveBundleInfo(final String id, final BundleInfo info) {
|
|
777
|
+
this.bundleInfos.put(id, info);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
@Override
|
|
781
|
+
public void sendStats(final String action, final String versionName, final String oldVersionName) {
|
|
782
|
+
this.sentStatsActions.add(action);
|
|
783
|
+
this.sentStatsMetadata.add(null);
|
|
784
|
+
this.lastStatsAction = action;
|
|
785
|
+
this.lastStatsVersionName = versionName;
|
|
786
|
+
this.lastStatsOldVersionName = oldVersionName;
|
|
787
|
+
this.lastStatsMetadata = null;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
@Override
|
|
791
|
+
public void sendStats(
|
|
792
|
+
final String action,
|
|
793
|
+
final String versionName,
|
|
794
|
+
final String oldVersionName,
|
|
795
|
+
final Map<String, String> metadata
|
|
796
|
+
) {
|
|
797
|
+
this.sentStatsActions.add(action);
|
|
798
|
+
this.sentStatsMetadata.add(metadata);
|
|
799
|
+
this.lastStatsAction = action;
|
|
800
|
+
this.lastStatsVersionName = versionName;
|
|
801
|
+
this.lastStatsOldVersionName = oldVersionName;
|
|
802
|
+
this.lastStatsMetadata = metadata;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
@Override
|
|
806
|
+
public void sendStats(
|
|
807
|
+
final String action,
|
|
808
|
+
final String versionName,
|
|
809
|
+
final String oldVersionName,
|
|
810
|
+
final Map<String, String> metadata,
|
|
811
|
+
final Runnable onSent
|
|
812
|
+
) {
|
|
813
|
+
this.sendStats(action, versionName, oldVersionName, metadata);
|
|
814
|
+
if (this.acknowledgeStats && onSent != null) {
|
|
815
|
+
onSent.run();
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
private static final class NoOpThreadCordovaUpdaterPlugin extends TestableCordovaUpdaterPlugin {
|
|
821
|
+
|
|
822
|
+
@Override
|
|
823
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
824
|
+
return this.startNewThread(function);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
@Override
|
|
828
|
+
public Thread startNewThread(final Runnable function) {
|
|
829
|
+
return new Thread();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
private static final class ConfigurableTimeoutCordovaUpdaterPlugin extends TestableCordovaUpdaterPlugin {
|
|
834
|
+
|
|
835
|
+
private final long minimumPendingBundleAppReadyTimeoutMs;
|
|
836
|
+
|
|
837
|
+
ConfigurableTimeoutCordovaUpdaterPlugin(final long minimumPendingBundleAppReadyTimeoutMs) {
|
|
838
|
+
this.minimumPendingBundleAppReadyTimeoutMs = minimumPendingBundleAppReadyTimeoutMs;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
@Override
|
|
842
|
+
public Thread startNewThread(final Runnable function, Number waitTime) {
|
|
843
|
+
return this.startNewThread(function);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
@Override
|
|
847
|
+
public Thread startNewThread(final Runnable function) {
|
|
848
|
+
return new Thread();
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
@Override
|
|
852
|
+
protected long getMinimumPendingBundleAppReadyTimeoutMs() {
|
|
853
|
+
return this.minimumPendingBundleAppReadyTimeoutMs;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
private static final class AutoResetNativeVersionCapgoUpdater extends CapgoUpdater {
|
|
858
|
+
|
|
859
|
+
private boolean resetCalled = false;
|
|
860
|
+
|
|
861
|
+
AutoResetNativeVersionCapgoUpdater() {
|
|
862
|
+
super(mock(Logger.class));
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
@Override
|
|
866
|
+
public void reset() {
|
|
867
|
+
this.resetCalled = true;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
@Override
|
|
871
|
+
public void reset(final boolean internal) {
|
|
872
|
+
this.resetCalled = true;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
private static final class FixedPathCapgoUpdater extends CapgoUpdater {
|
|
877
|
+
|
|
878
|
+
private final String currentBundlePath;
|
|
879
|
+
private final boolean usingBuiltin;
|
|
880
|
+
private final BundleInfo currentBundle;
|
|
881
|
+
|
|
882
|
+
FixedPathCapgoUpdater(final String currentBundlePath, final boolean usingBuiltin) {
|
|
883
|
+
this(
|
|
884
|
+
currentBundlePath,
|
|
885
|
+
usingBuiltin,
|
|
886
|
+
usingBuiltin
|
|
887
|
+
? new BundleInfo(BundleInfo.ID_BUILTIN, "builtin", BundleStatus.SUCCESS, BundleInfo.DOWNLOADED_BUILTIN, "builtin")
|
|
888
|
+
: new BundleInfo("current-bundle-id", "current-bundle", BundleStatus.SUCCESS, new Date(), "current-bundle-checksum")
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
FixedPathCapgoUpdater(final String currentBundlePath, final boolean usingBuiltin, final BundleInfo currentBundle) {
|
|
893
|
+
super(null);
|
|
894
|
+
this.currentBundlePath = currentBundlePath;
|
|
895
|
+
this.usingBuiltin = usingBuiltin;
|
|
896
|
+
this.currentBundle = currentBundle;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
@Override
|
|
900
|
+
public String getCurrentBundlePath() {
|
|
901
|
+
return this.currentBundlePath;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
@Override
|
|
905
|
+
public Boolean isUsingBuiltin() {
|
|
906
|
+
return this.usingBuiltin;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
@Override
|
|
910
|
+
public BundleInfo getCurrentBundle() {
|
|
911
|
+
return this.currentBundle;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
private static Path createExistingBundleDirectory(final String prefix, final String bundleId) throws Exception {
|
|
916
|
+
final Path tempDir = Files.createTempDirectory(prefix);
|
|
917
|
+
tempDir.toFile().deleteOnExit();
|
|
918
|
+
final Path versionsDir = tempDir.resolve("versions");
|
|
919
|
+
final Path bundleDir = versionsDir.resolve(bundleId);
|
|
920
|
+
Files.createDirectories(bundleDir);
|
|
921
|
+
versionsDir.toFile().deleteOnExit();
|
|
922
|
+
bundleDir.toFile().deleteOnExit();
|
|
923
|
+
final Path indexFile = bundleDir.resolve("index.html");
|
|
924
|
+
Files.createFile(indexFile);
|
|
925
|
+
indexFile.toFile().deleteOnExit();
|
|
926
|
+
return tempDir;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
private static Path createZipWithEntry(final String entryName) throws Exception {
|
|
930
|
+
final Path zipPath = Files.createTempFile("capgo-zip-path", ".zip");
|
|
931
|
+
zipPath.toFile().deleteOnExit();
|
|
932
|
+
try (ZipOutputStream zip = new ZipOutputStream(Files.newOutputStream(zipPath))) {
|
|
933
|
+
zip.putNextEntry(new ZipEntry(entryName));
|
|
934
|
+
zip.write("owned".getBytes(StandardCharsets.UTF_8));
|
|
935
|
+
zip.closeEntry();
|
|
936
|
+
}
|
|
937
|
+
return zipPath;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
private static File invokeUnzip(final CapgoUpdater updater, final String id, final Path zipPath, final String dest) throws Exception {
|
|
941
|
+
final Method method = CapgoUpdater.class.getDeclaredMethod("unzip", String.class, File.class, String.class);
|
|
942
|
+
method.setAccessible(true);
|
|
943
|
+
try {
|
|
944
|
+
return (File) method.invoke(updater, id, zipPath.toFile(), dest);
|
|
945
|
+
} catch (InvocationTargetException e) {
|
|
946
|
+
final Throwable cause = e.getCause();
|
|
947
|
+
if (cause instanceof IOException) {
|
|
948
|
+
throw (IOException) cause;
|
|
949
|
+
}
|
|
950
|
+
if (cause instanceof RuntimeException) {
|
|
951
|
+
throw (RuntimeException) cause;
|
|
952
|
+
}
|
|
953
|
+
throw e;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
private static void invokeBackgroundDownload(final CordovaUpdaterPlugin plugin) throws Exception {
|
|
958
|
+
final Method backgroundDownload = CordovaUpdaterPlugin.class.getDeclaredMethod("backgroundDownload");
|
|
959
|
+
backgroundDownload.setAccessible(true);
|
|
960
|
+
backgroundDownload.invoke(plugin);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
private static FreshDownloadCapgoUpdater configureOnlyDownloadBackgroundDownload(final ImmediateThreadCordovaUpdaterPlugin plugin) {
|
|
964
|
+
final FreshDownloadCapgoUpdater updater = new FreshDownloadCapgoUpdater();
|
|
965
|
+
|
|
966
|
+
plugin.implementation = updater;
|
|
967
|
+
plugin.setAutoUpdateModeForTesting("onlyDownload");
|
|
968
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
969
|
+
updater.updateAvailableNotifier = (version) -> {
|
|
970
|
+
final JSObject ret = new JSObject();
|
|
971
|
+
final BundleInfo downloaded = new BundleInfo("downloaded-id", version, BundleStatus.PENDING, new Date(), "checksum");
|
|
972
|
+
ret.put("bundle", InternalUtils.mapToJSObject(downloaded.toJSONMap()));
|
|
973
|
+
plugin.notifyListeners("updateAvailable", ret);
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
return updater;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
private static void invokePrivateVoidMethod(final CordovaUpdaterPlugin plugin, final String methodName) throws Exception {
|
|
980
|
+
final Method method = CordovaUpdaterPlugin.class.getDeclaredMethod(methodName);
|
|
981
|
+
method.setAccessible(true);
|
|
982
|
+
method.invoke(plugin);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
private static boolean invokePrivateResetMethod(
|
|
986
|
+
final CordovaUpdaterPlugin plugin,
|
|
987
|
+
final Boolean toLastSuccessful,
|
|
988
|
+
final Boolean usePendingBundle
|
|
989
|
+
) throws Exception {
|
|
990
|
+
final Method method = CordovaUpdaterPlugin.class.getDeclaredMethod("_reset", Boolean.class, Boolean.class);
|
|
991
|
+
method.setAccessible(true);
|
|
992
|
+
return (boolean) method.invoke(plugin, toLastSuccessful, usePendingBundle);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
private static boolean invokePrivateInternalResetMethod(
|
|
996
|
+
final CordovaUpdaterPlugin plugin,
|
|
997
|
+
final Boolean toLastSuccessful,
|
|
998
|
+
final Boolean usePendingBundle,
|
|
999
|
+
final boolean internal
|
|
1000
|
+
) throws Exception {
|
|
1001
|
+
final Method method = CordovaUpdaterPlugin.class.getDeclaredMethod("performReset", Boolean.class, Boolean.class, boolean.class);
|
|
1002
|
+
method.setAccessible(true);
|
|
1003
|
+
return (boolean) method.invoke(plugin, toLastSuccessful, usePendingBundle, internal);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
private static boolean invokePrivatePreviewFallbackResetMethod(final CordovaUpdaterPlugin plugin) throws Exception {
|
|
1007
|
+
final Method method = CordovaUpdaterPlugin.class.getDeclaredMethod("resetToPreviewFallbackBundle");
|
|
1008
|
+
method.setAccessible(true);
|
|
1009
|
+
return (boolean) method.invoke(plugin);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
private static void invokePrivateSplashMethod(
|
|
1013
|
+
final CordovaUpdaterPlugin plugin,
|
|
1014
|
+
final String methodName,
|
|
1015
|
+
final JSObject options,
|
|
1016
|
+
final int retriesRemaining,
|
|
1017
|
+
final int requestToken
|
|
1018
|
+
) throws Exception {
|
|
1019
|
+
final Method method = CordovaUpdaterPlugin.class.getDeclaredMethod(
|
|
1020
|
+
"invokeSplashScreenPluginMethod",
|
|
1021
|
+
String.class,
|
|
1022
|
+
JSObject.class,
|
|
1023
|
+
Integer.TYPE,
|
|
1024
|
+
Integer.TYPE
|
|
1025
|
+
);
|
|
1026
|
+
method.setAccessible(true);
|
|
1027
|
+
method.invoke(plugin, methodName, options, retriesRemaining, requestToken);
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
private static void setPrivateField(final Object target, final String fieldName, final Object value) throws Exception {
|
|
1031
|
+
Class<?> current = target.getClass();
|
|
1032
|
+
while (current != null) {
|
|
1033
|
+
try {
|
|
1034
|
+
final Field field = current.getDeclaredField(fieldName);
|
|
1035
|
+
field.setAccessible(true);
|
|
1036
|
+
field.set(target, value);
|
|
1037
|
+
return;
|
|
1038
|
+
} catch (final NoSuchFieldException ignored) {
|
|
1039
|
+
current = current.getSuperclass();
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
throw new NoSuchFieldException(fieldName);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private static Object getPrivateField(final Object target, final String fieldName) throws Exception {
|
|
1046
|
+
Class<?> current = target.getClass();
|
|
1047
|
+
while (current != null) {
|
|
1048
|
+
try {
|
|
1049
|
+
final Field field = current.getDeclaredField(fieldName);
|
|
1050
|
+
field.setAccessible(true);
|
|
1051
|
+
return field.get(target);
|
|
1052
|
+
} catch (final NoSuchFieldException ignored) {
|
|
1053
|
+
current = current.getSuperclass();
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
throw new NoSuchFieldException(fieldName);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
@Test
|
|
1060
|
+
public void mapsHealthExitReasonsToStatsActions() {
|
|
1061
|
+
assertEquals("app_crash", CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_CRASH));
|
|
1062
|
+
assertEquals(
|
|
1063
|
+
"app_crash_native",
|
|
1064
|
+
CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_CRASH_NATIVE)
|
|
1065
|
+
);
|
|
1066
|
+
assertEquals("app_anr", CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_ANR));
|
|
1067
|
+
assertEquals(
|
|
1068
|
+
"app_killed_low_memory",
|
|
1069
|
+
CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_LOW_MEMORY)
|
|
1070
|
+
);
|
|
1071
|
+
assertEquals(
|
|
1072
|
+
"app_killed_excessive_resource_usage",
|
|
1073
|
+
CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE)
|
|
1074
|
+
);
|
|
1075
|
+
assertEquals(
|
|
1076
|
+
"app_initialization_failure",
|
|
1077
|
+
CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_INITIALIZATION_FAILURE)
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
@Test
|
|
1082
|
+
public void pluginMethodsDoNotExposeApplicationExitInfoToReflection() {
|
|
1083
|
+
final String applicationExitInfoClassName = ApplicationExitInfo.class.getName();
|
|
1084
|
+
for (final Method method : CordovaUpdaterPlugin.class.getDeclaredMethods()) {
|
|
1085
|
+
assertFalse(exposesApplicationExitInfoType(method.getReturnType(), applicationExitInfoClassName));
|
|
1086
|
+
for (final Class<?> parameterType : method.getParameterTypes()) {
|
|
1087
|
+
assertFalse(exposesApplicationExitInfoType(parameterType, applicationExitInfoClassName));
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
private static boolean exposesApplicationExitInfoType(final Class<?> type, final String applicationExitInfoClassName) {
|
|
1093
|
+
Class<?> currentType = type;
|
|
1094
|
+
while (currentType.isArray()) {
|
|
1095
|
+
currentType = currentType.getComponentType();
|
|
1096
|
+
}
|
|
1097
|
+
return applicationExitInfoClassName.equals(currentType.getName());
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
@Test
|
|
1101
|
+
public void listChannelsResponseKeepsNumericChannelIds() throws Exception {
|
|
1102
|
+
final Map<String, Object> result = CapgoUpdater.parseListChannelsResponse(
|
|
1103
|
+
"[{\"id\":123,\"name\":\"Production\",\"public\":true,\"allow_self_set\":true}]"
|
|
1104
|
+
);
|
|
1105
|
+
|
|
1106
|
+
final Object channelsValue = result.get("channels");
|
|
1107
|
+
assertTrue(channelsValue instanceof List<?>);
|
|
1108
|
+
final Object channelValue = ((List<?>) channelsValue).get(0);
|
|
1109
|
+
assertTrue(channelValue instanceof Map<?, ?>);
|
|
1110
|
+
final Object id = ((Map<?, ?>) channelValue).get("id");
|
|
1111
|
+
assertTrue(id instanceof Number);
|
|
1112
|
+
assertEquals(123, ((Number) id).intValue());
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
@Test
|
|
1116
|
+
public void listChannelsResponseRejectsStringChannelIds() {
|
|
1117
|
+
assertThrows(JSONException.class, () ->
|
|
1118
|
+
CapgoUpdater.parseListChannelsResponse("[{\"id\":\"123\",\"name\":\"Production\",\"public\":true,\"allow_self_set\":true}]")
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
@Test
|
|
1123
|
+
public void ignoresExpectedApplicationExitReasonsForStats() {
|
|
1124
|
+
assertNull(CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_EXIT_SELF));
|
|
1125
|
+
assertNull(CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_USER_REQUESTED));
|
|
1126
|
+
assertNull(CordovaUpdaterPlugin.statsActionForApplicationExitReason(ApplicationExitInfo.REASON_UNKNOWN));
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
@Test
|
|
1130
|
+
public void mapsApplicationExitReasonNamesForMetadata() {
|
|
1131
|
+
assertEquals("crash", CordovaUpdaterPlugin.applicationExitReasonName(ApplicationExitInfo.REASON_CRASH));
|
|
1132
|
+
assertEquals("anr", CordovaUpdaterPlugin.applicationExitReasonName(ApplicationExitInfo.REASON_ANR));
|
|
1133
|
+
assertEquals("unknown", CordovaUpdaterPlugin.applicationExitReasonName(-1));
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
@Test
|
|
1137
|
+
public void mapsWebViewErrorTypesToStatsActions() {
|
|
1138
|
+
assertEquals("webview_javascript_error", CordovaUpdaterPlugin.statsActionForWebViewErrorType("javascript_error"));
|
|
1139
|
+
assertEquals("webview_unhandled_rejection", CordovaUpdaterPlugin.statsActionForWebViewErrorType("unhandled_rejection"));
|
|
1140
|
+
assertEquals("webview_resource_error", CordovaUpdaterPlugin.statsActionForWebViewErrorType("resource_error"));
|
|
1141
|
+
assertEquals(
|
|
1142
|
+
"webview_security_policy_violation",
|
|
1143
|
+
CordovaUpdaterPlugin.statsActionForWebViewErrorType("security_policy_violation")
|
|
1144
|
+
);
|
|
1145
|
+
assertEquals("webview_unclean_restart", CordovaUpdaterPlugin.statsActionForWebViewErrorType("webview_unclean_restart"));
|
|
1146
|
+
assertEquals("webview_render_process_gone", CordovaUpdaterPlugin.statsActionForWebViewErrorType("render_process_gone"));
|
|
1147
|
+
assertEquals(
|
|
1148
|
+
"webview_content_process_terminated",
|
|
1149
|
+
CordovaUpdaterPlugin.statsActionForWebViewErrorType("web_content_process_terminated")
|
|
1150
|
+
);
|
|
1151
|
+
assertEquals("webview_javascript_error", CordovaUpdaterPlugin.statsActionForWebViewErrorType("unknown"));
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
@Test
|
|
1155
|
+
public void buildsWebViewErrorMetadataWithUsefulFields() throws Exception {
|
|
1156
|
+
final JSObject data = new JSObject();
|
|
1157
|
+
data.put("type", "javascript_error");
|
|
1158
|
+
data.put("message", "boom");
|
|
1159
|
+
data.put("source", "app.js");
|
|
1160
|
+
data.put("line", "10");
|
|
1161
|
+
data.put("column", "20");
|
|
1162
|
+
data.put("stack", "x".repeat(3000));
|
|
1163
|
+
data.put("href", "capacitor://localhost");
|
|
1164
|
+
data.put("session_id", "session-1");
|
|
1165
|
+
|
|
1166
|
+
final Map<String, String> metadata = CordovaUpdaterPlugin.buildWebViewErrorMetadata(data);
|
|
1167
|
+
|
|
1168
|
+
assertEquals("javascript_error", metadata.get("error_type"));
|
|
1169
|
+
assertEquals("boom", metadata.get("message"));
|
|
1170
|
+
assertEquals("app.js", metadata.get("source"));
|
|
1171
|
+
assertEquals("10", metadata.get("line"));
|
|
1172
|
+
assertEquals("20", metadata.get("column"));
|
|
1173
|
+
assertEquals("capacitor://localhost", metadata.get("href"));
|
|
1174
|
+
assertEquals("session-1", metadata.get("session_id"));
|
|
1175
|
+
assertEquals(2048, metadata.get("stack").length());
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
@Test
|
|
1179
|
+
public void sanitizesUrlValuesInWebViewErrorMetadata() throws Exception {
|
|
1180
|
+
final String scheme = "https";
|
|
1181
|
+
final String host = "example.com";
|
|
1182
|
+
final String userInfo = String.join(":", "user", "value") + "@";
|
|
1183
|
+
final String sourceQuery = "cache=123";
|
|
1184
|
+
final String hrefQuery = "debug=true";
|
|
1185
|
+
final JSObject data = new JSObject();
|
|
1186
|
+
data.put("source", scheme + "://" + userInfo + host + ":8443/assets/app.js?" + sourceQuery + "#L10");
|
|
1187
|
+
data.put("href", scheme + "://" + host + "/users/123456/dashboard?" + hrefQuery + "#frag");
|
|
1188
|
+
data.put("previous_href", "app.js?" + sourceQuery + "#frag");
|
|
1189
|
+
|
|
1190
|
+
final Map<String, String> metadata = CordovaUpdaterPlugin.buildWebViewErrorMetadata(data);
|
|
1191
|
+
|
|
1192
|
+
assertEquals(scheme + "://" + host + ":8443/assets/app.js", metadata.get("source"));
|
|
1193
|
+
assertEquals(scheme + "://" + host + "/users/redacted/dashboard", metadata.get("href"));
|
|
1194
|
+
assertEquals("app.js", metadata.get("previous_href"));
|
|
1195
|
+
assertFalse(metadata.get("source").contains(sourceQuery));
|
|
1196
|
+
assertFalse(metadata.get("href").contains(hrefQuery));
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
@Test
|
|
1200
|
+
public void webViewStatsReporterScriptCapturesRuntimeAndRestartSignals() {
|
|
1201
|
+
final String script = CordovaUpdaterPlugin.buildWebViewStatsReporterScript();
|
|
1202
|
+
|
|
1203
|
+
assertTrue(script.contains("unhandledrejection"));
|
|
1204
|
+
assertTrue(script.contains("resource_error"));
|
|
1205
|
+
assertTrue(script.contains("securitypolicyviolation"));
|
|
1206
|
+
assertTrue(script.contains("webview_unclean_restart"));
|
|
1207
|
+
assertTrue(script.contains("reportWebViewError"));
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// BundleInfo Tests
|
|
1211
|
+
|
|
1212
|
+
@Test
|
|
1213
|
+
public void testBundleInfoInitialization() {
|
|
1214
|
+
BundleInfo bundleInfo = new BundleInfo("test-id", "1.0.0", BundleStatus.PENDING, new Date(), "abc123");
|
|
1215
|
+
|
|
1216
|
+
assertEquals("test-id", bundleInfo.getId());
|
|
1217
|
+
assertEquals("1.0.0", bundleInfo.getVersionName());
|
|
1218
|
+
assertEquals(BundleStatus.PENDING, bundleInfo.getStatus());
|
|
1219
|
+
assertEquals("abc123", bundleInfo.getChecksum());
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
@Test
|
|
1223
|
+
public void testBundleInfoBuiltin() {
|
|
1224
|
+
BundleInfo bundleInfo = new BundleInfo(
|
|
1225
|
+
BundleInfo.ID_BUILTIN,
|
|
1226
|
+
"1.0.0",
|
|
1227
|
+
BundleStatus.SUCCESS,
|
|
1228
|
+
BundleInfo.DOWNLOADED_BUILTIN,
|
|
1229
|
+
"abc123"
|
|
1230
|
+
);
|
|
1231
|
+
|
|
1232
|
+
assertTrue(bundleInfo.isBuiltin());
|
|
1233
|
+
assertFalse(bundleInfo.isUnknown());
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
@Test
|
|
1237
|
+
public void testBundleInfoUnknown() {
|
|
1238
|
+
BundleInfo bundleInfo = new BundleInfo(
|
|
1239
|
+
BundleInfo.VERSION_UNKNOWN,
|
|
1240
|
+
"1.0.0",
|
|
1241
|
+
BundleStatus.SUCCESS,
|
|
1242
|
+
BundleInfo.DOWNLOADED_BUILTIN,
|
|
1243
|
+
"abc123"
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
assertTrue(bundleInfo.isUnknown());
|
|
1247
|
+
assertFalse(bundleInfo.isBuiltin());
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
@Test
|
|
1251
|
+
public void testBundleInfoErrorStatus() {
|
|
1252
|
+
BundleInfo bundleInfo = new BundleInfo("test-id", "1.0.0", BundleStatus.ERROR, new Date(), "abc123");
|
|
1253
|
+
|
|
1254
|
+
assertTrue(bundleInfo.isErrorStatus());
|
|
1255
|
+
assertFalse(bundleInfo.isDeleted());
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
@Test
|
|
1259
|
+
public void testBundleInfoDeleted() {
|
|
1260
|
+
BundleInfo bundleInfo = new BundleInfo("test-id", "1.0.0", BundleStatus.DELETED, new Date(), "abc123");
|
|
1261
|
+
|
|
1262
|
+
assertTrue(bundleInfo.isDeleted());
|
|
1263
|
+
assertFalse(bundleInfo.isErrorStatus());
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
@Test
|
|
1267
|
+
public void testBundleInfoIsDownloaded() {
|
|
1268
|
+
BundleInfo bundleInfo = new BundleInfo("test-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
1269
|
+
|
|
1270
|
+
assertTrue(bundleInfo.isDownloaded());
|
|
1271
|
+
|
|
1272
|
+
BundleInfo builtinBundle = new BundleInfo(BundleInfo.ID_BUILTIN, "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
1273
|
+
|
|
1274
|
+
assertFalse(builtinBundle.isDownloaded());
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
@Test
|
|
1278
|
+
public void testBundleInfoSetters() {
|
|
1279
|
+
BundleInfo original = new BundleInfo("test-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
1280
|
+
|
|
1281
|
+
BundleInfo withNewChecksum = original.setChecksum("new-checksum");
|
|
1282
|
+
assertEquals("new-checksum", withNewChecksum.getChecksum());
|
|
1283
|
+
assertEquals("test-id", withNewChecksum.getId());
|
|
1284
|
+
|
|
1285
|
+
BundleInfo withNewId = original.setId("new-id");
|
|
1286
|
+
assertEquals("new-id", withNewId.getId());
|
|
1287
|
+
assertEquals("abc123", withNewId.getChecksum());
|
|
1288
|
+
|
|
1289
|
+
BundleInfo withNewStatus = original.setStatus(BundleStatus.ERROR);
|
|
1290
|
+
assertEquals(BundleStatus.ERROR, withNewStatus.getStatus());
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
@Test
|
|
1294
|
+
public void testBundleInfoCopyConstructor() {
|
|
1295
|
+
BundleInfo original = new BundleInfo("test-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
1296
|
+
|
|
1297
|
+
BundleInfo copy = new BundleInfo(original);
|
|
1298
|
+
|
|
1299
|
+
assertEquals(original.getId(), copy.getId());
|
|
1300
|
+
assertEquals(original.getVersionName(), copy.getVersionName());
|
|
1301
|
+
assertEquals(original.getStatus(), copy.getStatus());
|
|
1302
|
+
assertEquals(original.getChecksum(), copy.getChecksum());
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// BundleStatus Tests
|
|
1306
|
+
|
|
1307
|
+
@Test
|
|
1308
|
+
public void testBundleStatusToString() {
|
|
1309
|
+
assertEquals("success", BundleStatus.SUCCESS.toString());
|
|
1310
|
+
assertEquals("error", BundleStatus.ERROR.toString());
|
|
1311
|
+
assertEquals("pending", BundleStatus.PENDING.toString());
|
|
1312
|
+
assertEquals("deleted", BundleStatus.DELETED.toString());
|
|
1313
|
+
assertEquals("downloading", BundleStatus.DOWNLOADING.toString());
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
@Test
|
|
1317
|
+
public void testBundleStatusFromString() {
|
|
1318
|
+
assertEquals(BundleStatus.SUCCESS, BundleStatus.fromString("success"));
|
|
1319
|
+
assertEquals(BundleStatus.ERROR, BundleStatus.fromString("error"));
|
|
1320
|
+
assertEquals(BundleStatus.PENDING, BundleStatus.fromString("pending"));
|
|
1321
|
+
assertEquals(BundleStatus.DELETED, BundleStatus.fromString("deleted"));
|
|
1322
|
+
assertEquals(BundleStatus.DOWNLOADING, BundleStatus.fromString("downloading"));
|
|
1323
|
+
|
|
1324
|
+
// Test null/empty string returns PENDING
|
|
1325
|
+
assertEquals(BundleStatus.PENDING, BundleStatus.fromString(null));
|
|
1326
|
+
assertEquals(BundleStatus.PENDING, BundleStatus.fromString(""));
|
|
1327
|
+
|
|
1328
|
+
// Test invalid string returns null
|
|
1329
|
+
assertNull(BundleStatus.fromString("invalid"));
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// Version Comparison Tests
|
|
1333
|
+
|
|
1334
|
+
@Test
|
|
1335
|
+
public void testVersionComparison() {
|
|
1336
|
+
Version version1 = new Version("1.0.0");
|
|
1337
|
+
Version version2 = new Version("1.0.1");
|
|
1338
|
+
Version version3 = new Version("2.0.0");
|
|
1339
|
+
Version version4 = new Version("1.0.0");
|
|
1340
|
+
|
|
1341
|
+
assertTrue(version1.isLowerThan(version2));
|
|
1342
|
+
assertTrue(version2.isLowerThan(version3));
|
|
1343
|
+
assertTrue(version1.isEqual(version4));
|
|
1344
|
+
assertFalse(version3.isLowerThan(version1));
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
@Test
|
|
1348
|
+
public void testVersionIsAtLeast() {
|
|
1349
|
+
Version version1 = new Version("1.0.0");
|
|
1350
|
+
Version version2 = new Version("1.0.1");
|
|
1351
|
+
|
|
1352
|
+
assertTrue(version2.isAtLeast("1.0.0"));
|
|
1353
|
+
assertTrue(version2.isAtLeast("1.0.1"));
|
|
1354
|
+
assertFalse(version1.isAtLeast("1.0.1"));
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// Edge Cases Tests
|
|
1358
|
+
|
|
1359
|
+
@Test
|
|
1360
|
+
public void testBundleInfoWithNullValues() {
|
|
1361
|
+
BundleInfo bundleInfo = new BundleInfo(null, null, null, (String) null, null);
|
|
1362
|
+
|
|
1363
|
+
assertNotNull(bundleInfo.getId());
|
|
1364
|
+
assertNotNull(bundleInfo.getStatus());
|
|
1365
|
+
assertNotNull(bundleInfo.getChecksum());
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
@Test
|
|
1369
|
+
public void testLargeDataHandling() {
|
|
1370
|
+
// Create a large string
|
|
1371
|
+
StringBuilder sb = new StringBuilder();
|
|
1372
|
+
for (int i = 0; i < 10000; i++) {
|
|
1373
|
+
sb.append("test-data-");
|
|
1374
|
+
}
|
|
1375
|
+
String largeData = sb.toString();
|
|
1376
|
+
|
|
1377
|
+
BundleInfo bundleInfo = new BundleInfo("test-id", largeData, BundleStatus.SUCCESS, new Date(), largeData);
|
|
1378
|
+
|
|
1379
|
+
assertNotNull(bundleInfo);
|
|
1380
|
+
assertEquals(largeData, bundleInfo.getVersionName());
|
|
1381
|
+
assertEquals(largeData, bundleInfo.getChecksum());
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
@Test
|
|
1385
|
+
public void testBuildUserAgentUsesUnknownForNullOrEmptyValues() {
|
|
1386
|
+
assertEquals("CapacitorUpdater/unknown (unknown) android/unknown", DownloadService.buildUserAgent("", null, ""));
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
@Test
|
|
1390
|
+
public void testBuildUserAgentMatchesExpectedAndroidFormat() {
|
|
1391
|
+
assertEquals(
|
|
1392
|
+
"CapacitorUpdater/8.43.9 (app.capgo.test) android/16",
|
|
1393
|
+
DownloadService.buildUserAgent("app.capgo.test", "8.43.9", "16")
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
@Test
|
|
1398
|
+
public void testShouldResetForForeignBundleWhenPathIsSetButBundleIsNotStored() {
|
|
1399
|
+
assertTrue(CapgoUpdater.shouldResetForForeignBundle("/data/user/0/app/files/versions/abc123", false, false));
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
@Test
|
|
1403
|
+
public void testShouldNotResetForForeignBundleWhenBundleIsBuiltin() {
|
|
1404
|
+
assertFalse(CapgoUpdater.shouldResetForForeignBundle("public", true, false));
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
@Test
|
|
1408
|
+
public void testShouldNotResetForForeignBundleWhenBundleIsStored() {
|
|
1409
|
+
assertFalse(CapgoUpdater.shouldResetForForeignBundle("/data/user/0/app/files/versions/abc123", false, true));
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
@Test
|
|
1413
|
+
public void testGetBackgroundRunnerLabelFromConfigExtractsLabel() {
|
|
1414
|
+
final String config =
|
|
1415
|
+
"{\"plugins\":{\"BackgroundRunner\":{\"label\":\"com.example.runner\",\"src\":\"runner.js\",\"autoStart\":true}}}";
|
|
1416
|
+
|
|
1417
|
+
assertEquals("com.example.runner", CapgoUpdater.getBackgroundRunnerLabelFromConfig(config));
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
@Test
|
|
1421
|
+
public void testGetBackgroundRunnerLabelFromConfigReturnsNullWhenMissing() {
|
|
1422
|
+
final String config = "{\"plugins\":{\"CapacitorUpdater\":{\"autoUpdate\":true}}}";
|
|
1423
|
+
|
|
1424
|
+
assertNull(CapgoUpdater.getBackgroundRunnerLabelFromConfig(config));
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
@Test
|
|
1428
|
+
public void testGetBackgroundRunnerLabelFromConfigReturnsNullForBlankLabel() {
|
|
1429
|
+
final String config = "{\"plugins\":{\"BackgroundRunner\":{\"label\":\" \",\"src\":\"runner.js\"}}}";
|
|
1430
|
+
|
|
1431
|
+
assertNull(CapgoUpdater.getBackgroundRunnerLabelFromConfig(config));
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
@Test
|
|
1435
|
+
public void testGetBundleInfoBuiltinReturnsVersionBuildWhenPresent() {
|
|
1436
|
+
CapgoUpdater updater = new CapgoUpdater(null);
|
|
1437
|
+
updater.versionBuild = "1.2.3";
|
|
1438
|
+
|
|
1439
|
+
BundleInfo bundleInfo = updater.getBundleInfo(BundleInfo.ID_BUILTIN);
|
|
1440
|
+
|
|
1441
|
+
assertEquals(BundleInfo.ID_BUILTIN, bundleInfo.getId());
|
|
1442
|
+
assertEquals("1.2.3", bundleInfo.getVersionName());
|
|
1443
|
+
assertEquals(BundleStatus.SUCCESS, bundleInfo.getStatus());
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
@Test
|
|
1447
|
+
public void testGetBundleInfoBuiltinHandlesNullVersionBuild() {
|
|
1448
|
+
CapgoUpdater updater = new CapgoUpdater(null);
|
|
1449
|
+
updater.versionBuild = null;
|
|
1450
|
+
|
|
1451
|
+
BundleInfo bundleInfo = updater.getBundleInfo(BundleInfo.ID_BUILTIN);
|
|
1452
|
+
|
|
1453
|
+
assertEquals(BundleInfo.ID_BUILTIN, bundleInfo.getId());
|
|
1454
|
+
assertEquals(BundleInfo.ID_BUILTIN, bundleInfo.getVersionName());
|
|
1455
|
+
assertEquals(BundleStatus.SUCCESS, bundleInfo.getStatus());
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
@Test
|
|
1459
|
+
public void testGetStoredNativeBuildVersionFallsBackToLegacyKey() throws Exception {
|
|
1460
|
+
try (MockedStatic<Looper> looperMock = mockStatic(Looper.class)) {
|
|
1461
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1462
|
+
|
|
1463
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1464
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
1465
|
+
|
|
1466
|
+
setPrivateField(plugin, "prefs", prefs);
|
|
1467
|
+
when(prefs.getString("LatestNativeBuildVersion", "")).thenReturn("");
|
|
1468
|
+
when(prefs.getString("LatestVersionNative", "")).thenReturn("7");
|
|
1469
|
+
|
|
1470
|
+
assertEquals("7", plugin.getStoredNativeBuildVersion());
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
@Test
|
|
1475
|
+
public void testPersistCurrentNativeBuildVersionWritesCurrentBuildKey() throws Exception {
|
|
1476
|
+
try (MockedStatic<Looper> looperMock = mockStatic(Looper.class)) {
|
|
1477
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1478
|
+
|
|
1479
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1480
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
1481
|
+
|
|
1482
|
+
setPrivateField(plugin, "editor", editor);
|
|
1483
|
+
setPrivateField(plugin, "currentBuildVersion", "8");
|
|
1484
|
+
when(editor.putString("LatestNativeBuildVersion", "8")).thenReturn(editor);
|
|
1485
|
+
|
|
1486
|
+
plugin.persistCurrentNativeBuildVersion();
|
|
1487
|
+
|
|
1488
|
+
verify(editor).putString("LatestNativeBuildVersion", "8");
|
|
1489
|
+
verify(editor).apply();
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
@Test
|
|
1494
|
+
public void testReportNativeVersionStatsPersistsFirstSnapshotWithoutEvent() throws Exception {
|
|
1495
|
+
try (MockedStatic<Looper> looperMock = mockStatic(Looper.class)) {
|
|
1496
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1497
|
+
|
|
1498
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1499
|
+
final PendingReloadFinalizeCapgoUpdater updater = new PendingReloadFinalizeCapgoUpdater();
|
|
1500
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
1501
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
1502
|
+
|
|
1503
|
+
plugin.implementation = updater;
|
|
1504
|
+
setPrivateField(plugin, "prefs", prefs);
|
|
1505
|
+
setPrivateField(plugin, "editor", editor);
|
|
1506
|
+
when(prefs.getString("CapacitorUpdater.lastVersionOs", "")).thenReturn("");
|
|
1507
|
+
when(prefs.getString("CapacitorUpdater.lastVersionBuild", "")).thenReturn("");
|
|
1508
|
+
when(prefs.getString("CapacitorUpdater.lastVersionCode", "")).thenReturn("");
|
|
1509
|
+
|
|
1510
|
+
plugin.reportNativeVersionStatsIfChanged("1.0.0", "100", "14");
|
|
1511
|
+
|
|
1512
|
+
assertTrue(updater.sentStatsActions.isEmpty());
|
|
1513
|
+
verify(editor).putString("CapacitorUpdater.lastVersionOs", "14");
|
|
1514
|
+
verify(editor).putString("CapacitorUpdater.lastVersionBuild", "1.0.0");
|
|
1515
|
+
verify(editor).putString("CapacitorUpdater.lastVersionCode", "100");
|
|
1516
|
+
verify(editor).apply();
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
@Test
|
|
1521
|
+
public void testReportNativeVersionStatsSendsChangedOsAndNativeVersionEvents() throws Exception {
|
|
1522
|
+
try (MockedStatic<Looper> looperMock = mockStatic(Looper.class)) {
|
|
1523
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1524
|
+
|
|
1525
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1526
|
+
final PendingReloadFinalizeCapgoUpdater updater = new PendingReloadFinalizeCapgoUpdater();
|
|
1527
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
1528
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
1529
|
+
|
|
1530
|
+
plugin.implementation = updater;
|
|
1531
|
+
setPrivateField(plugin, "prefs", prefs);
|
|
1532
|
+
setPrivateField(plugin, "editor", editor);
|
|
1533
|
+
when(prefs.getString("CapacitorUpdater.lastVersionOs", "")).thenReturn("13");
|
|
1534
|
+
when(prefs.getString("CapacitorUpdater.lastVersionBuild", "")).thenReturn("1.0.0");
|
|
1535
|
+
when(prefs.getString("CapacitorUpdater.lastVersionCode", "")).thenReturn("100");
|
|
1536
|
+
|
|
1537
|
+
plugin.reportNativeVersionStatsIfChanged("1.1.0", "101", "14");
|
|
1538
|
+
|
|
1539
|
+
assertEquals(List.of("os_version_changed", "native_app_version_changed"), updater.sentStatsActions);
|
|
1540
|
+
assertEquals("13", updater.sentStatsMetadata.get(0).get("previous_version_os"));
|
|
1541
|
+
assertEquals("14", updater.sentStatsMetadata.get(0).get("current_version_os"));
|
|
1542
|
+
assertEquals("1.0.0", updater.sentStatsMetadata.get(1).get("previous_version_build"));
|
|
1543
|
+
assertEquals("1.1.0", updater.sentStatsMetadata.get(1).get("current_version_build"));
|
|
1544
|
+
assertEquals("100", updater.sentStatsMetadata.get(1).get("previous_version_code"));
|
|
1545
|
+
assertEquals("101", updater.sentStatsMetadata.get(1).get("current_version_code"));
|
|
1546
|
+
verify(editor).putString("CapacitorUpdater.lastVersionOs", "14");
|
|
1547
|
+
verify(editor).putString("CapacitorUpdater.lastVersionBuild", "1.1.0");
|
|
1548
|
+
verify(editor).putString("CapacitorUpdater.lastVersionCode", "101");
|
|
1549
|
+
verify(editor, times(2)).apply();
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
@Test
|
|
1554
|
+
public void testReportNativeVersionStatsKeepsChangedSnapshotPendingUntilStatsAck() throws Exception {
|
|
1555
|
+
try (MockedStatic<Looper> looperMock = mockStatic(Looper.class)) {
|
|
1556
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1557
|
+
|
|
1558
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1559
|
+
final PendingReloadFinalizeCapgoUpdater updater = new PendingReloadFinalizeCapgoUpdater();
|
|
1560
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
1561
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
1562
|
+
updater.acknowledgeStats = false;
|
|
1563
|
+
|
|
1564
|
+
plugin.implementation = updater;
|
|
1565
|
+
setPrivateField(plugin, "prefs", prefs);
|
|
1566
|
+
setPrivateField(plugin, "editor", editor);
|
|
1567
|
+
when(prefs.getString("CapacitorUpdater.lastVersionOs", "")).thenReturn("13");
|
|
1568
|
+
when(prefs.getString("CapacitorUpdater.lastVersionBuild", "")).thenReturn("1.0.0");
|
|
1569
|
+
when(prefs.getString("CapacitorUpdater.lastVersionCode", "")).thenReturn("100");
|
|
1570
|
+
|
|
1571
|
+
plugin.reportNativeVersionStatsIfChanged("1.1.0", "101", "14");
|
|
1572
|
+
|
|
1573
|
+
assertEquals(List.of("os_version_changed", "native_app_version_changed"), updater.sentStatsActions);
|
|
1574
|
+
verify(editor, never()).putString("CapacitorUpdater.lastVersionOs", "14");
|
|
1575
|
+
verify(editor, never()).putString("CapacitorUpdater.lastVersionBuild", "1.1.0");
|
|
1576
|
+
verify(editor, never()).putString("CapacitorUpdater.lastVersionCode", "101");
|
|
1577
|
+
verify(editor, never()).apply();
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
@Test
|
|
1582
|
+
public void testAutoResetFallsBackToLegacyNativeBuildVersionKey() throws Exception {
|
|
1583
|
+
final String bundleId = "legacy-bundle-id";
|
|
1584
|
+
final Path tempDir = createExistingBundleDirectory("capgo-autoreset", bundleId);
|
|
1585
|
+
final Path bundleDir = tempDir.resolve("versions").resolve(bundleId);
|
|
1586
|
+
|
|
1587
|
+
final AutoResetNativeVersionCapgoUpdater updater = new AutoResetNativeVersionCapgoUpdater();
|
|
1588
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
1589
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
1590
|
+
final BundleInfo storedBundle = new BundleInfo(bundleId, "1.0.0", BundleStatus.SUCCESS, new Date(), "checksum");
|
|
1591
|
+
|
|
1592
|
+
updater.documentsDir = tempDir.toFile();
|
|
1593
|
+
updater.CAP_SERVER_PATH = "server-path";
|
|
1594
|
+
updater.prefs = prefs;
|
|
1595
|
+
updater.editor = editor;
|
|
1596
|
+
|
|
1597
|
+
when(prefs.getString("server-path", "public")).thenReturn(bundleDir.toString());
|
|
1598
|
+
when(prefs.getString("server-path", null)).thenReturn(bundleDir.toString());
|
|
1599
|
+
when(prefs.contains(bundleId + "_info")).thenReturn(true);
|
|
1600
|
+
when(prefs.getString(bundleId + "_info", "")).thenReturn(storedBundle.toString());
|
|
1601
|
+
when(prefs.getString("LatestNativeBuildVersion", "")).thenReturn("");
|
|
1602
|
+
when(prefs.getString("LatestVersionNative", "")).thenReturn("7");
|
|
1603
|
+
|
|
1604
|
+
updater.autoReset("8");
|
|
1605
|
+
|
|
1606
|
+
assertTrue(updater.resetCalled);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
@Test
|
|
1610
|
+
public void testAutoResetSkipsNativeBuildVersionResetWhenDisabled() throws Exception {
|
|
1611
|
+
final String bundleId = "legacy-bundle-id";
|
|
1612
|
+
final Path tempDir = createExistingBundleDirectory("capgo-autoreset-disabled", bundleId);
|
|
1613
|
+
final Path bundleDir = tempDir.resolve("versions").resolve(bundleId);
|
|
1614
|
+
|
|
1615
|
+
final AutoResetNativeVersionCapgoUpdater updater = new AutoResetNativeVersionCapgoUpdater();
|
|
1616
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
1617
|
+
final BundleInfo storedBundle = new BundleInfo(bundleId, "1.0.0", BundleStatus.SUCCESS, new Date(), "checksum");
|
|
1618
|
+
|
|
1619
|
+
updater.documentsDir = tempDir.toFile();
|
|
1620
|
+
updater.CAP_SERVER_PATH = "server-path";
|
|
1621
|
+
updater.prefs = prefs;
|
|
1622
|
+
|
|
1623
|
+
when(prefs.getString("server-path", "public")).thenReturn(bundleDir.toString());
|
|
1624
|
+
when(prefs.getString("server-path", null)).thenReturn(bundleDir.toString());
|
|
1625
|
+
when(prefs.contains(bundleId + "_info")).thenReturn(true);
|
|
1626
|
+
when(prefs.getString(bundleId + "_info", "")).thenReturn(storedBundle.toString());
|
|
1627
|
+
when(prefs.getString("LatestNativeBuildVersion", "")).thenReturn("");
|
|
1628
|
+
when(prefs.getString("LatestVersionNative", "")).thenReturn("7");
|
|
1629
|
+
|
|
1630
|
+
updater.autoReset("8", false);
|
|
1631
|
+
|
|
1632
|
+
assertFalse(updater.resetCalled);
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
@Test
|
|
1636
|
+
public void testShouldConsumeOnLaunchDirectUpdateForOnLaunchAttempt() {
|
|
1637
|
+
assertTrue(CordovaUpdaterPlugin.shouldConsumeOnLaunchDirectUpdate("onLaunch", true));
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
@Test
|
|
1641
|
+
public void testShouldNotConsumeOnLaunchDirectUpdateForNonLaunchAttempt() {
|
|
1642
|
+
assertFalse(CordovaUpdaterPlugin.shouldConsumeOnLaunchDirectUpdate("onLaunch", false));
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
@Test
|
|
1646
|
+
public void testShouldNotConsumeOnLaunchDirectUpdateForOtherModes() {
|
|
1647
|
+
assertFalse(CordovaUpdaterPlugin.shouldConsumeOnLaunchDirectUpdate("always", true));
|
|
1648
|
+
assertFalse(CordovaUpdaterPlugin.shouldConsumeOnLaunchDirectUpdate("atInstall", true));
|
|
1649
|
+
assertFalse(CordovaUpdaterPlugin.shouldConsumeOnLaunchDirectUpdate("false", true));
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
@Test
|
|
1653
|
+
public void testPeriodCheckDelayZeroDisablesPeriodicChecks() {
|
|
1654
|
+
assertEquals(0, CordovaUpdaterPlugin.normalizedPeriodCheckDelayMs(0));
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
@Test
|
|
1658
|
+
public void testPeriodCheckDelayNegativeDisablesPeriodicChecks() {
|
|
1659
|
+
assertEquals(0, CordovaUpdaterPlugin.normalizedPeriodCheckDelayMs(-1));
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
@Test
|
|
1663
|
+
public void testPeriodCheckDelayBelowMinimumClampsToTenMinutes() {
|
|
1664
|
+
assertEquals(600_000, CordovaUpdaterPlugin.normalizedPeriodCheckDelayMs(1));
|
|
1665
|
+
assertEquals(600_000, CordovaUpdaterPlugin.normalizedPeriodCheckDelayMs(599));
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
@Test
|
|
1669
|
+
public void testPeriodCheckDelayAtMinimumIsAllowed() {
|
|
1670
|
+
assertEquals(600_000, CordovaUpdaterPlugin.normalizedPeriodCheckDelayMs(600));
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
@Test
|
|
1674
|
+
public void testPeriodCheckDelayAboveMinimumIsPreserved() {
|
|
1675
|
+
assertEquals(3_600_000, CordovaUpdaterPlugin.normalizedPeriodCheckDelayMs(3600));
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
@Test
|
|
1679
|
+
public void testPeriodCheckDelayOverflowClampsToMaxInt() {
|
|
1680
|
+
assertEquals(Integer.MAX_VALUE, CordovaUpdaterPlugin.normalizedPeriodCheckDelayMs(Integer.MAX_VALUE));
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
@Test
|
|
1684
|
+
public void testCheckRevertSkipsRollbackDuringPreviewSession() throws Exception {
|
|
1685
|
+
try (
|
|
1686
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1687
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1688
|
+
) {
|
|
1689
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1690
|
+
|
|
1691
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1692
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1693
|
+
updater.currentBundle = new BundleInfo("preview-id", "preview", BundleStatus.PENDING, new Date(), "preview");
|
|
1694
|
+
|
|
1695
|
+
plugin.implementation = updater;
|
|
1696
|
+
plugin.previewSessionEnabled = true;
|
|
1697
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1698
|
+
|
|
1699
|
+
invokePrivateVoidMethod(plugin, "checkRevert");
|
|
1700
|
+
|
|
1701
|
+
assertFalse(updater.resetCalled);
|
|
1702
|
+
assertFalse(plugin.hasNotifiedEvent("updateFailed"));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
@Test
|
|
1707
|
+
public void testCheckRevertSkipsRollbackDuringPreviewTransition() throws Exception {
|
|
1708
|
+
try (
|
|
1709
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1710
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1711
|
+
) {
|
|
1712
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1713
|
+
|
|
1714
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1715
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1716
|
+
updater.currentBundle = new BundleInfo("preview-id", "preview", BundleStatus.PENDING, new Date(), "preview");
|
|
1717
|
+
updater.previewSession = true;
|
|
1718
|
+
|
|
1719
|
+
plugin.implementation = updater;
|
|
1720
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1721
|
+
|
|
1722
|
+
invokePrivateVoidMethod(plugin, "checkRevert");
|
|
1723
|
+
|
|
1724
|
+
assertFalse(updater.resetCalled);
|
|
1725
|
+
assertFalse(plugin.hasNotifiedEvent("updateFailed"));
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
@Test
|
|
1730
|
+
public void testTriggerUpdateCheckSkipsDuringPreviewSession() throws Exception {
|
|
1731
|
+
try (
|
|
1732
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1733
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1734
|
+
) {
|
|
1735
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1736
|
+
|
|
1737
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1738
|
+
plugin.implementation = new ResetTrackingCapgoUpdater();
|
|
1739
|
+
plugin.previewSessionEnabled = true;
|
|
1740
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1741
|
+
setPrivateField(plugin, "updateUrl", "https://example.com/updates");
|
|
1742
|
+
|
|
1743
|
+
assertEquals("preview_session", plugin.triggerBackgroundUpdateCheck());
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
@Test
|
|
1748
|
+
public void testResetToPendingWithoutPendingBundleDoesNotResetState() throws Exception {
|
|
1749
|
+
try (
|
|
1750
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1751
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1752
|
+
) {
|
|
1753
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1754
|
+
|
|
1755
|
+
final ReloadBypassCordovaUpdaterPlugin plugin = new ReloadBypassCordovaUpdaterPlugin();
|
|
1756
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1757
|
+
|
|
1758
|
+
plugin.implementation = updater;
|
|
1759
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1760
|
+
|
|
1761
|
+
final boolean result = invokePrivateResetMethod(plugin, false, true);
|
|
1762
|
+
|
|
1763
|
+
assertFalse(result);
|
|
1764
|
+
assertFalse(updater.resetCalled);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
@Test
|
|
1769
|
+
public void testResetToPendingWithoutInstallablePendingBundleDoesNotResetState() throws Exception {
|
|
1770
|
+
try (
|
|
1771
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1772
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1773
|
+
) {
|
|
1774
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1775
|
+
|
|
1776
|
+
final ReloadBypassCordovaUpdaterPlugin plugin = new ReloadBypassCordovaUpdaterPlugin();
|
|
1777
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1778
|
+
updater.nextBundle = new BundleInfo("pending-id", "2.0.0", BundleStatus.PENDING, new Date(), "pending");
|
|
1779
|
+
updater.canSetResult = false;
|
|
1780
|
+
|
|
1781
|
+
plugin.implementation = updater;
|
|
1782
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1783
|
+
|
|
1784
|
+
final boolean result = invokePrivateResetMethod(plugin, false, true);
|
|
1785
|
+
|
|
1786
|
+
assertFalse(result);
|
|
1787
|
+
assertEquals(1, updater.canSetCalls);
|
|
1788
|
+
assertEquals(0, updater.setCalls);
|
|
1789
|
+
assertFalse(updater.resetCalled);
|
|
1790
|
+
assertEquals(0, updater.restoreResetStateCalls);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
@Test
|
|
1795
|
+
public void testResetToPendingRestoresStateWhenSwitchFails() throws Exception {
|
|
1796
|
+
try (
|
|
1797
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1798
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1799
|
+
) {
|
|
1800
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1801
|
+
|
|
1802
|
+
final ReloadBypassCordovaUpdaterPlugin plugin = new ReloadBypassCordovaUpdaterPlugin();
|
|
1803
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1804
|
+
updater.nextBundle = new BundleInfo("pending-id", "2.0.0", BundleStatus.PENDING, new Date(), "pending");
|
|
1805
|
+
updater.setResult = false;
|
|
1806
|
+
|
|
1807
|
+
plugin.implementation = updater;
|
|
1808
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1809
|
+
|
|
1810
|
+
final boolean result = invokePrivateResetMethod(plugin, false, true);
|
|
1811
|
+
|
|
1812
|
+
assertFalse(result);
|
|
1813
|
+
assertFalse(updater.resetCalled);
|
|
1814
|
+
assertTrue(updater.prepareResetStateForTransitionCalled);
|
|
1815
|
+
assertFalse(updater.finalizeResetTransitionCalled);
|
|
1816
|
+
assertEquals(1, updater.canSetCalls);
|
|
1817
|
+
assertEquals(1, updater.setCalls);
|
|
1818
|
+
assertEquals(1, updater.restoreResetStateCalls);
|
|
1819
|
+
assertSame(updater.capturedState, updater.restoredState);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
@Test
|
|
1824
|
+
public void testResetToPendingRestoresLiveStateWhenReloadFails() throws Exception {
|
|
1825
|
+
try (
|
|
1826
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1827
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1828
|
+
) {
|
|
1829
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1830
|
+
|
|
1831
|
+
final ReloadFailureCordovaUpdaterPlugin plugin = new ReloadFailureCordovaUpdaterPlugin();
|
|
1832
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1833
|
+
updater.nextBundle = new BundleInfo("pending-id", "2.0.0", BundleStatus.PENDING, new Date(), "pending");
|
|
1834
|
+
|
|
1835
|
+
plugin.implementation = updater;
|
|
1836
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1837
|
+
|
|
1838
|
+
final boolean result = invokePrivateResetMethod(plugin, false, true);
|
|
1839
|
+
|
|
1840
|
+
assertFalse(result);
|
|
1841
|
+
assertFalse(updater.resetCalled);
|
|
1842
|
+
assertTrue(updater.prepareResetStateForTransitionCalled);
|
|
1843
|
+
assertFalse(updater.finalizeResetTransitionCalled);
|
|
1844
|
+
assertEquals(1, updater.canSetCalls);
|
|
1845
|
+
assertEquals(1, updater.setCalls);
|
|
1846
|
+
assertEquals(1, updater.restoreResetStateCalls);
|
|
1847
|
+
assertSame(updater.capturedState, updater.restoredState);
|
|
1848
|
+
assertEquals(1, plugin.restoreLiveBundleStateAfterFailedReloadCalls);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
@Test
|
|
1853
|
+
public void testResetToPendingRestoresStateWhenBuiltinPendingReloadFails() throws Exception {
|
|
1854
|
+
try (
|
|
1855
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1856
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1857
|
+
) {
|
|
1858
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1859
|
+
|
|
1860
|
+
final ReloadFailureCordovaUpdaterPlugin plugin = new ReloadFailureCordovaUpdaterPlugin();
|
|
1861
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1862
|
+
updater.nextBundle = new BundleInfo(
|
|
1863
|
+
BundleInfo.ID_BUILTIN,
|
|
1864
|
+
"builtin",
|
|
1865
|
+
BundleStatus.SUCCESS,
|
|
1866
|
+
BundleInfo.DOWNLOADED_BUILTIN,
|
|
1867
|
+
"builtin"
|
|
1868
|
+
);
|
|
1869
|
+
|
|
1870
|
+
plugin.implementation = updater;
|
|
1871
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1872
|
+
|
|
1873
|
+
final boolean result = invokePrivateResetMethod(plugin, false, true);
|
|
1874
|
+
|
|
1875
|
+
assertFalse(result);
|
|
1876
|
+
assertFalse(updater.resetCalled);
|
|
1877
|
+
assertTrue(updater.prepareResetStateForTransitionCalled);
|
|
1878
|
+
assertFalse(updater.finalizeResetTransitionCalled);
|
|
1879
|
+
assertEquals(1, updater.canSetCalls);
|
|
1880
|
+
assertEquals(0, updater.setCalls);
|
|
1881
|
+
assertEquals(1, updater.restoreResetStateCalls);
|
|
1882
|
+
assertSame(updater.capturedState, updater.restoredState);
|
|
1883
|
+
assertEquals(1, plugin.restoreLiveBundleStateAfterFailedReloadCalls);
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
@Test
|
|
1888
|
+
public void testLeavePreviewUsesBuiltinWhenPreviewFallbackIsMissing() throws Exception {
|
|
1889
|
+
try (
|
|
1890
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1891
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1892
|
+
) {
|
|
1893
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1894
|
+
|
|
1895
|
+
final ReloadBypassCordovaUpdaterPlugin plugin = new ReloadBypassCordovaUpdaterPlugin();
|
|
1896
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1897
|
+
updater.currentBundle = new BundleInfo("preview-id", "preview", BundleStatus.SUCCESS, new Date(), "preview");
|
|
1898
|
+
updater.previewFallbackBundle = null;
|
|
1899
|
+
|
|
1900
|
+
plugin.implementation = updater;
|
|
1901
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1902
|
+
|
|
1903
|
+
final boolean result = invokePrivatePreviewFallbackResetMethod(plugin);
|
|
1904
|
+
|
|
1905
|
+
assertTrue(result);
|
|
1906
|
+
assertEquals(1, updater.stagePreviewFallbackReloadCalls);
|
|
1907
|
+
assertEquals(BundleInfo.ID_BUILTIN, updater.stagedPreviewFallbackBundle.getId());
|
|
1908
|
+
assertTrue(updater.finalizeResetTransitionCalled);
|
|
1909
|
+
assertEquals("preview", updater.finalizeResetTransitionPreviousBundleName);
|
|
1910
|
+
assertFalse(updater.finalizeResetTransitionInternal);
|
|
1911
|
+
assertEquals(0, updater.restoreResetStateCalls);
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
@Test
|
|
1916
|
+
public void testLeavePreviewFromShakeMenuKeepsPreviewGuardUntilAppReady() throws Exception {
|
|
1917
|
+
try (
|
|
1918
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1919
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1920
|
+
) {
|
|
1921
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1922
|
+
|
|
1923
|
+
final ReloadBypassCordovaUpdaterPlugin plugin = new ReloadBypassCordovaUpdaterPlugin();
|
|
1924
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1925
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
1926
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
1927
|
+
final Bridge bridge = mock(Bridge.class);
|
|
1928
|
+
final CapConfig capConfig = mock(CapConfig.class);
|
|
1929
|
+
final PluginConfig pluginConfig = mock(PluginConfig.class);
|
|
1930
|
+
final PluginHandle handle = mock(PluginHandle.class);
|
|
1931
|
+
|
|
1932
|
+
updater.currentBundle = new BundleInfo("preview-id", "preview", BundleStatus.SUCCESS, new Date(), "preview");
|
|
1933
|
+
updater.previewFallbackBundle = new BundleInfo("fallback-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "fallback");
|
|
1934
|
+
updater.previewSession = true;
|
|
1935
|
+
plugin.implementation = updater;
|
|
1936
|
+
plugin.previewSessionEnabled = true;
|
|
1937
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
1938
|
+
setPrivateField(plugin, "prefs", prefs);
|
|
1939
|
+
setPrivateField(plugin, "editor", editor);
|
|
1940
|
+
setPrivateField(plugin, "bridge", bridge);
|
|
1941
|
+
plugin.setPluginHandle(handle);
|
|
1942
|
+
|
|
1943
|
+
when(handle.getId()).thenReturn("CapacitorUpdater");
|
|
1944
|
+
when(bridge.getConfig()).thenReturn(capConfig);
|
|
1945
|
+
when(capConfig.getPluginConfiguration("CapacitorUpdater")).thenReturn(pluginConfig);
|
|
1946
|
+
when(pluginConfig.getString(anyString(), nullable(String.class))).thenAnswer((invocation) -> invocation.getArgument(1));
|
|
1947
|
+
when(pluginConfig.getBoolean(anyString(), anyBoolean())).thenAnswer((invocation) -> invocation.getArgument(1));
|
|
1948
|
+
when(prefs.getString(anyString(), nullable(String.class))).thenAnswer((invocation) -> invocation.getArgument(1));
|
|
1949
|
+
when(prefs.getBoolean(anyString(), anyBoolean())).thenAnswer((invocation) -> invocation.getArgument(1));
|
|
1950
|
+
when(editor.remove(anyString())).thenReturn(editor);
|
|
1951
|
+
|
|
1952
|
+
assertTrue(plugin.leavePreviewSessionFromShakeMenu());
|
|
1953
|
+
assertFalse(plugin.hasActivePreviewSession());
|
|
1954
|
+
assertTrue(updater.previewSession);
|
|
1955
|
+
assertEquals(1, updater.stagePreviewFallbackReloadCalls);
|
|
1956
|
+
assertEquals("fallback-id", updater.stagedPreviewFallbackBundle.getId());
|
|
1957
|
+
assertTrue(updater.finalizeResetTransitionCalled);
|
|
1958
|
+
assertEquals("preview", updater.finalizeResetTransitionPreviousBundleName);
|
|
1959
|
+
assertEquals(0, updater.restoreResetStateCalls);
|
|
1960
|
+
assertNull(updater.lastPreviewFallbackBundle);
|
|
1961
|
+
assertTrue(updater.setPreviewFallbackBundleCalls > 0);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
@Test
|
|
1966
|
+
public void testPreviewMenuListsStoredPreviewsAndCleansMissingBundles() throws Exception {
|
|
1967
|
+
try (
|
|
1968
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
1969
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
1970
|
+
) {
|
|
1971
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
1972
|
+
|
|
1973
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
1974
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
1975
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
1976
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
1977
|
+
final String previewsKey = "CapacitorUpdater.previewSessions";
|
|
1978
|
+
final BundleInfo current = new BundleInfo("preview-current", "2.0.0", BundleStatus.SUCCESS, new Date(), "current");
|
|
1979
|
+
final BundleInfo other = new BundleInfo("preview-other", "1.5.0", BundleStatus.SUCCESS, new Date(), "other");
|
|
1980
|
+
final String sessions = new JSONObject()
|
|
1981
|
+
.put(
|
|
1982
|
+
"preview-current",
|
|
1983
|
+
new JSONObject()
|
|
1984
|
+
.put("name", "Current preview")
|
|
1985
|
+
.put("source", "qr")
|
|
1986
|
+
.put("createdAt", "2026-01-01T00:00:00.000Z")
|
|
1987
|
+
.put("updatedAt", "2026-01-02T00:00:00.000Z")
|
|
1988
|
+
.put("lastUsedAt", "2026-01-03T00:00:00.000Z")
|
|
1989
|
+
)
|
|
1990
|
+
.put(
|
|
1991
|
+
"preview-other",
|
|
1992
|
+
new JSONObject()
|
|
1993
|
+
.put("name", "Other preview")
|
|
1994
|
+
.put("createdAt", "2026-01-01T00:00:00.000Z")
|
|
1995
|
+
.put("updatedAt", "2026-01-01T00:00:00.000Z")
|
|
1996
|
+
.put("lastUsedAt", "2026-01-02T00:00:00.000Z")
|
|
1997
|
+
)
|
|
1998
|
+
.put("missing-preview", new JSONObject().put("name", "Missing preview").put("lastUsedAt", "2026-01-04T00:00:00.000Z"))
|
|
1999
|
+
.toString();
|
|
2000
|
+
|
|
2001
|
+
updater.currentBundle = current;
|
|
2002
|
+
updater.listedBundles.add(current);
|
|
2003
|
+
updater.listedBundles.add(other);
|
|
2004
|
+
plugin.implementation = updater;
|
|
2005
|
+
plugin.previewSessionEnabled = true;
|
|
2006
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2007
|
+
setPrivateField(plugin, "prefs", prefs);
|
|
2008
|
+
setPrivateField(plugin, "editor", editor);
|
|
2009
|
+
|
|
2010
|
+
when(prefs.getString(eq(previewsKey), nullable(String.class))).thenReturn(sessions);
|
|
2011
|
+
when(editor.putString(eq(previewsKey), anyString())).thenReturn(editor);
|
|
2012
|
+
|
|
2013
|
+
final JSArray previews = plugin.previewMenuPreviews();
|
|
2014
|
+
|
|
2015
|
+
assertEquals(2, previews.length());
|
|
2016
|
+
final JSONObject first = previews.getJSONObject(0);
|
|
2017
|
+
assertEquals("preview-current", first.getString("id"));
|
|
2018
|
+
assertEquals("Current preview", first.getString("name"));
|
|
2019
|
+
assertEquals("qr", first.getString("source"));
|
|
2020
|
+
assertTrue(first.getBoolean("isActive"));
|
|
2021
|
+
|
|
2022
|
+
final JSONObject second = previews.getJSONObject(1);
|
|
2023
|
+
assertEquals("preview-other", second.getString("id"));
|
|
2024
|
+
assertFalse(second.getBoolean("isActive"));
|
|
2025
|
+
|
|
2026
|
+
final ArgumentCaptor<String> cleanedSessions = ArgumentCaptor.forClass(String.class);
|
|
2027
|
+
verify(editor).putString(eq(previewsKey), cleanedSessions.capture());
|
|
2028
|
+
assertFalse(new JSONObject(cleanedSessions.getValue()).has("missing-preview"));
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
@Test
|
|
2033
|
+
public void testResetToLastSuccessfulWithoutInstallableFallbackFallsBackToBuiltin() throws Exception {
|
|
2034
|
+
try (
|
|
2035
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2036
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2037
|
+
) {
|
|
2038
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2039
|
+
|
|
2040
|
+
final ReloadBypassCordovaUpdaterPlugin plugin = new ReloadBypassCordovaUpdaterPlugin();
|
|
2041
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
2042
|
+
updater.fallbackBundle = new BundleInfo("fallback-id", "1.5.0", BundleStatus.SUCCESS, new Date(), "fallback");
|
|
2043
|
+
updater.canSetResult = false;
|
|
2044
|
+
|
|
2045
|
+
plugin.implementation = updater;
|
|
2046
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2047
|
+
|
|
2048
|
+
final boolean result = invokePrivateResetMethod(plugin, true, false);
|
|
2049
|
+
|
|
2050
|
+
assertTrue(result);
|
|
2051
|
+
assertEquals(1, updater.canSetCalls);
|
|
2052
|
+
assertEquals(0, updater.setCalls);
|
|
2053
|
+
assertFalse(updater.resetCalled);
|
|
2054
|
+
assertTrue(updater.prepareResetStateForTransitionCalled);
|
|
2055
|
+
assertTrue(updater.finalizeResetTransitionCalled);
|
|
2056
|
+
assertEquals("1.0.0", updater.finalizeResetTransitionPreviousBundleName);
|
|
2057
|
+
assertFalse(updater.finalizeResetTransitionInternal);
|
|
2058
|
+
assertEquals(0, updater.restoreResetStateCalls);
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
@Test
|
|
2063
|
+
public void testResetToLastSuccessfulRestoresStateWhenSwitchFails() throws Exception {
|
|
2064
|
+
try (
|
|
2065
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2066
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2067
|
+
) {
|
|
2068
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2069
|
+
|
|
2070
|
+
final ReloadBypassCordovaUpdaterPlugin plugin = new ReloadBypassCordovaUpdaterPlugin();
|
|
2071
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
2072
|
+
updater.fallbackBundle = new BundleInfo("fallback-id", "1.5.0", BundleStatus.SUCCESS, new Date(), "fallback");
|
|
2073
|
+
updater.setResult = false;
|
|
2074
|
+
|
|
2075
|
+
plugin.implementation = updater;
|
|
2076
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2077
|
+
|
|
2078
|
+
final boolean result = invokePrivateResetMethod(plugin, true, false);
|
|
2079
|
+
|
|
2080
|
+
assertFalse(result);
|
|
2081
|
+
assertFalse(updater.resetCalled);
|
|
2082
|
+
assertTrue(updater.prepareResetStateForTransitionCalled);
|
|
2083
|
+
assertFalse(updater.finalizeResetTransitionCalled);
|
|
2084
|
+
assertEquals(1, updater.canSetCalls);
|
|
2085
|
+
assertEquals(1, updater.setCalls);
|
|
2086
|
+
assertEquals(1, updater.restoreResetStateCalls);
|
|
2087
|
+
assertSame(updater.capturedState, updater.restoredState);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
@Test
|
|
2092
|
+
public void testResetToLastSuccessfulRestoresStateWhenFallbackReloadFails() throws Exception {
|
|
2093
|
+
try (
|
|
2094
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2095
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2096
|
+
) {
|
|
2097
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2098
|
+
|
|
2099
|
+
final SequenceReloadCordovaUpdaterPlugin plugin = new SequenceReloadCordovaUpdaterPlugin(false);
|
|
2100
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
2101
|
+
updater.fallbackBundle = new BundleInfo("fallback-id", "1.5.0", BundleStatus.SUCCESS, new Date(), "fallback");
|
|
2102
|
+
|
|
2103
|
+
plugin.implementation = updater;
|
|
2104
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2105
|
+
|
|
2106
|
+
final boolean result = invokePrivateResetMethod(plugin, true, false);
|
|
2107
|
+
|
|
2108
|
+
assertFalse(result);
|
|
2109
|
+
assertTrue(updater.prepareResetStateForTransitionCalled);
|
|
2110
|
+
assertEquals(1, updater.prepareResetStateForTransitionCalls);
|
|
2111
|
+
assertFalse(updater.finalizeResetTransitionCalled);
|
|
2112
|
+
assertEquals(1, updater.canSetCalls);
|
|
2113
|
+
assertEquals(1, updater.setCalls);
|
|
2114
|
+
assertEquals(1, updater.restoreResetStateCalls);
|
|
2115
|
+
assertSame(updater.capturedState, updater.restoredState);
|
|
2116
|
+
assertEquals(1, plugin.restoreLiveBundleStateAfterFailedReloadCalls);
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
@Test
|
|
2121
|
+
public void testInternalResetToLastSuccessfulFallsBackToBuiltinWhenFallbackReloadFails() throws Exception {
|
|
2122
|
+
try (
|
|
2123
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2124
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2125
|
+
) {
|
|
2126
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2127
|
+
|
|
2128
|
+
final SequenceReloadCordovaUpdaterPlugin plugin = new SequenceReloadCordovaUpdaterPlugin(false, true);
|
|
2129
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
2130
|
+
updater.currentBundle = new BundleInfo("current-id", "2.0.0", BundleStatus.ERROR, new Date(), "abc123");
|
|
2131
|
+
updater.fallbackBundle = new BundleInfo("fallback-id", "1.5.0", BundleStatus.SUCCESS, new Date(), "fallback");
|
|
2132
|
+
|
|
2133
|
+
plugin.implementation = updater;
|
|
2134
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2135
|
+
|
|
2136
|
+
final boolean result = invokePrivateInternalResetMethod(plugin, true, false, true);
|
|
2137
|
+
|
|
2138
|
+
assertTrue(result);
|
|
2139
|
+
assertTrue(updater.prepareResetStateForTransitionCalled);
|
|
2140
|
+
assertEquals(2, updater.prepareResetStateForTransitionCalls);
|
|
2141
|
+
assertTrue(updater.finalizeResetTransitionCalled);
|
|
2142
|
+
assertEquals(1, updater.finalizeResetTransitionCalls);
|
|
2143
|
+
assertEquals("2.0.0", updater.finalizeResetTransitionPreviousBundleName);
|
|
2144
|
+
assertTrue(updater.finalizeResetTransitionInternal);
|
|
2145
|
+
assertEquals(1, updater.canSetCalls);
|
|
2146
|
+
assertEquals(1, updater.setCalls);
|
|
2147
|
+
assertEquals(0, updater.restoreResetStateCalls);
|
|
2148
|
+
assertEquals(0, plugin.restoreLiveBundleStateAfterFailedReloadCalls);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
@Test
|
|
2153
|
+
public void testFinalizePendingReloadPreservesSuccessfulBundleStatus() {
|
|
2154
|
+
final PendingReloadFinalizeCapgoUpdater updater = new PendingReloadFinalizeCapgoUpdater();
|
|
2155
|
+
final BundleInfo successfulBundle = new BundleInfo("pending-id", "2.0.0", BundleStatus.SUCCESS, new Date(), "pending");
|
|
2156
|
+
final BundleInfo pendingBundle = new BundleInfo("pending-id", "2.0.0", BundleStatus.PENDING, new Date(), "pending");
|
|
2157
|
+
updater.bundleInfos.put("pending-id", successfulBundle);
|
|
2158
|
+
|
|
2159
|
+
updater.finalizePendingReload(pendingBundle, "1.0.0");
|
|
2160
|
+
|
|
2161
|
+
assertEquals(BundleStatus.SUCCESS, updater.bundleInfos.get("pending-id").getStatus());
|
|
2162
|
+
assertEquals("set", updater.lastStatsAction);
|
|
2163
|
+
assertEquals("2.0.0", updater.lastStatsVersionName);
|
|
2164
|
+
assertEquals("1.0.0", updater.lastStatsOldVersionName);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
@Test
|
|
2168
|
+
public void testReloadRestoresStateWhenPendingApplyReloadFails() {
|
|
2169
|
+
try (
|
|
2170
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2171
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2172
|
+
) {
|
|
2173
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2174
|
+
|
|
2175
|
+
final ReloadFailureCordovaUpdaterPlugin plugin = new ReloadFailureCordovaUpdaterPlugin();
|
|
2176
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
2177
|
+
final PluginCall call = mock(PluginCall.class);
|
|
2178
|
+
updater.nextBundle = new BundleInfo("pending-id", "2.0.0", BundleStatus.PENDING, new Date(), "pending");
|
|
2179
|
+
|
|
2180
|
+
plugin.implementation = updater;
|
|
2181
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2182
|
+
|
|
2183
|
+
plugin.reload(call);
|
|
2184
|
+
|
|
2185
|
+
assertEquals(0, updater.setCalls);
|
|
2186
|
+
assertEquals(1, updater.stagePendingReloadCalls);
|
|
2187
|
+
assertEquals(0, updater.finalizePendingReloadCalls);
|
|
2188
|
+
assertEquals(1, updater.restoreResetStateCalls);
|
|
2189
|
+
assertSame(updater.capturedState, updater.restoredState);
|
|
2190
|
+
assertEquals(1, plugin.restoreLiveBundleStateAfterFailedReloadCalls);
|
|
2191
|
+
verify(call).reject("Reload failed after applying pending bundle: 2.0.0");
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
@Test
|
|
2196
|
+
public void testReloadFinalizesPendingBundleSideEffectsAfterSuccess() {
|
|
2197
|
+
try (
|
|
2198
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2199
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2200
|
+
) {
|
|
2201
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2202
|
+
|
|
2203
|
+
final ReloadBypassCordovaUpdaterPlugin plugin = new ReloadBypassCordovaUpdaterPlugin();
|
|
2204
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
2205
|
+
final PluginCall call = mock(PluginCall.class);
|
|
2206
|
+
updater.nextBundle = new BundleInfo("pending-id", "2.0.0", BundleStatus.PENDING, new Date(), "pending");
|
|
2207
|
+
|
|
2208
|
+
plugin.implementation = updater;
|
|
2209
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2210
|
+
|
|
2211
|
+
plugin.reload(call);
|
|
2212
|
+
|
|
2213
|
+
assertEquals(0, updater.setCalls);
|
|
2214
|
+
assertEquals(1, updater.stagePendingReloadCalls);
|
|
2215
|
+
assertEquals(1, updater.finalizePendingReloadCalls);
|
|
2216
|
+
assertEquals("1.0.0", updater.finalizePendingReloadPreviousBundleName);
|
|
2217
|
+
assertSame(updater.nextBundle, updater.finalizedPendingReloadBundle);
|
|
2218
|
+
verify(call).resolve();
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
@Test
|
|
2223
|
+
public void testReloadRestoresStateWhenBuiltinPendingReloadFails() {
|
|
2224
|
+
try (
|
|
2225
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2226
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2227
|
+
) {
|
|
2228
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2229
|
+
|
|
2230
|
+
final ReloadFailureCordovaUpdaterPlugin plugin = new ReloadFailureCordovaUpdaterPlugin();
|
|
2231
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
2232
|
+
final PluginCall call = mock(PluginCall.class);
|
|
2233
|
+
updater.nextBundle = new BundleInfo(
|
|
2234
|
+
BundleInfo.ID_BUILTIN,
|
|
2235
|
+
"builtin",
|
|
2236
|
+
BundleStatus.SUCCESS,
|
|
2237
|
+
BundleInfo.DOWNLOADED_BUILTIN,
|
|
2238
|
+
"builtin"
|
|
2239
|
+
);
|
|
2240
|
+
|
|
2241
|
+
plugin.implementation = updater;
|
|
2242
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2243
|
+
|
|
2244
|
+
plugin.reload(call);
|
|
2245
|
+
|
|
2246
|
+
assertEquals(0, updater.setCalls);
|
|
2247
|
+
assertEquals(0, updater.stagePendingReloadCalls);
|
|
2248
|
+
assertEquals(0, updater.finalizePendingReloadCalls);
|
|
2249
|
+
assertTrue(updater.prepareResetStateForTransitionCalled);
|
|
2250
|
+
assertFalse(updater.finalizeResetTransitionCalled);
|
|
2251
|
+
assertEquals(1, updater.restoreResetStateCalls);
|
|
2252
|
+
assertSame(updater.capturedState, updater.restoredState);
|
|
2253
|
+
assertEquals(1, plugin.restoreLiveBundleStateAfterFailedReloadCalls);
|
|
2254
|
+
verify(call).reject("Reload failed after applying pending bundle: builtin");
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
@Test
|
|
2259
|
+
public void testReloadTimeoutUsesMilliseconds() throws Exception {
|
|
2260
|
+
try (
|
|
2261
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2262
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2263
|
+
) {
|
|
2264
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2265
|
+
|
|
2266
|
+
final NoOpThreadCordovaUpdaterPlugin plugin = new NoOpThreadCordovaUpdaterPlugin();
|
|
2267
|
+
final Bridge bridge = mock(Bridge.class);
|
|
2268
|
+
final WebView webView = mock(WebView.class);
|
|
2269
|
+
|
|
2270
|
+
plugin.implementation = new FixedPathCapgoUpdater("/tmp/capgo-bundle", false);
|
|
2271
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2272
|
+
plugin.setBridge(bridge);
|
|
2273
|
+
setPrivateField(plugin, "appReadyTimeout", 1);
|
|
2274
|
+
|
|
2275
|
+
when(bridge.getWebView()).thenReturn(webView);
|
|
2276
|
+
when(bridge.getAppUrl()).thenReturn("https://local-app-domain.com");
|
|
2277
|
+
when(webView.post(any(Runnable.class))).thenReturn(true);
|
|
2278
|
+
|
|
2279
|
+
final long start = System.nanoTime();
|
|
2280
|
+
assertFalse(plugin._reload());
|
|
2281
|
+
final long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
|
|
2282
|
+
|
|
2283
|
+
assertTrue("Expected millisecond timeout but reload took " + elapsedMs + "ms", elapsedMs < 900);
|
|
2284
|
+
verify(bridge).setServerBasePath("/tmp/capgo-bundle");
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
@Test
|
|
2289
|
+
public void testReloadUsesExtendedTimeoutForPendingBundleValidation() throws Exception {
|
|
2290
|
+
try (
|
|
2291
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2292
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2293
|
+
) {
|
|
2294
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2295
|
+
|
|
2296
|
+
final ConfigurableTimeoutCordovaUpdaterPlugin plugin = new ConfigurableTimeoutCordovaUpdaterPlugin(50);
|
|
2297
|
+
final Bridge bridge = mock(Bridge.class);
|
|
2298
|
+
final WebView webView = mock(WebView.class);
|
|
2299
|
+
final BundleInfo pendingBundle = new BundleInfo("pending-bundle-id", "2.0.0", BundleStatus.PENDING, new Date(), "abc123");
|
|
2300
|
+
|
|
2301
|
+
plugin.implementation = new FixedPathCapgoUpdater("/tmp/capgo-bundle", false, pendingBundle);
|
|
2302
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2303
|
+
plugin.setBridge(bridge);
|
|
2304
|
+
setPrivateField(plugin, "appReadyTimeout", 1);
|
|
2305
|
+
|
|
2306
|
+
when(bridge.getWebView()).thenReturn(webView);
|
|
2307
|
+
when(bridge.getAppUrl()).thenReturn("https://local-app-domain.com");
|
|
2308
|
+
when(webView.post(any(Runnable.class))).thenReturn(true);
|
|
2309
|
+
|
|
2310
|
+
final long start = System.nanoTime();
|
|
2311
|
+
assertFalse(plugin._reload());
|
|
2312
|
+
final long elapsedMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);
|
|
2313
|
+
|
|
2314
|
+
assertTrue("Expected pending bundle timeout extension but reload took only " + elapsedMs + "ms", elapsedMs >= 40);
|
|
2315
|
+
assertTrue("Expected bounded pending bundle timeout but reload took " + elapsedMs + "ms", elapsedMs < 1000);
|
|
2316
|
+
verify(bridge).setServerBasePath("/tmp/capgo-bundle");
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
@Test
|
|
2321
|
+
public void testReloadTimeoutCleansUpPendingSemaphoreParty() throws Exception {
|
|
2322
|
+
try (
|
|
2323
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2324
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2325
|
+
) {
|
|
2326
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2327
|
+
|
|
2328
|
+
final NoOpThreadCordovaUpdaterPlugin plugin = new NoOpThreadCordovaUpdaterPlugin();
|
|
2329
|
+
final Bridge bridge = mock(Bridge.class);
|
|
2330
|
+
final WebView webView = mock(WebView.class);
|
|
2331
|
+
|
|
2332
|
+
plugin.implementation = new FixedPathCapgoUpdater("/tmp/capgo-bundle", false);
|
|
2333
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2334
|
+
plugin.setBridge(bridge);
|
|
2335
|
+
setPrivateField(plugin, "appReadyTimeout", 1);
|
|
2336
|
+
|
|
2337
|
+
when(bridge.getWebView()).thenReturn(webView);
|
|
2338
|
+
when(bridge.getAppUrl()).thenReturn("https://local-app-domain.com");
|
|
2339
|
+
when(webView.post(any(Runnable.class))).thenReturn(true);
|
|
2340
|
+
|
|
2341
|
+
assertFalse(plugin._reload());
|
|
2342
|
+
|
|
2343
|
+
final Phaser semaphore = (Phaser) getPrivateField(plugin, "semaphoreReady");
|
|
2344
|
+
assertEquals(0, semaphore.getRegisteredParties());
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
@Test
|
|
2349
|
+
public void testNotifyAppReadyWithoutPendingReloadKeepsSemaphoreReusable() throws Exception {
|
|
2350
|
+
try (MockedStatic<Looper> looperMock = mockStatic(Looper.class)) {
|
|
2351
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2352
|
+
|
|
2353
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
2354
|
+
final PluginCall call = mock(PluginCall.class);
|
|
2355
|
+
final CapgoUpdater updater = mock(CapgoUpdater.class);
|
|
2356
|
+
final BundleInfo bundle = new BundleInfo("current-bundle-id", "current-bundle", BundleStatus.SUCCESS, new Date(), "checksum");
|
|
2357
|
+
|
|
2358
|
+
plugin.implementation = updater;
|
|
2359
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2360
|
+
|
|
2361
|
+
when(updater.getCurrentBundle()).thenReturn(bundle);
|
|
2362
|
+
|
|
2363
|
+
plugin.notifyAppReady(call);
|
|
2364
|
+
|
|
2365
|
+
final Phaser semaphore = (Phaser) getPrivateField(plugin, "semaphoreReady");
|
|
2366
|
+
assertFalse(semaphore.isTerminated());
|
|
2367
|
+
assertEquals(0, semaphore.getRegisteredParties());
|
|
2368
|
+
verify(call).resolve(any(JSObject.class));
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
@Test
|
|
2373
|
+
public void testInstallNextDispatchesReloadOffLifecycleThread() throws Exception {
|
|
2374
|
+
try (MockedStatic<Looper> looperMock = mockStatic(Looper.class)) {
|
|
2375
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2376
|
+
|
|
2377
|
+
final InstallNextDispatchPlugin plugin = new InstallNextDispatchPlugin();
|
|
2378
|
+
final InstallNextCapgoUpdater updater = new InstallNextCapgoUpdater();
|
|
2379
|
+
final SharedPreferences prefs = mock(SharedPreferences.class);
|
|
2380
|
+
final DelayUpdateUtils delayUpdateUtils = mock(DelayUpdateUtils.class);
|
|
2381
|
+
|
|
2382
|
+
plugin.implementation = updater;
|
|
2383
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2384
|
+
|
|
2385
|
+
when(prefs.getString(DelayUpdateUtils.DELAY_CONDITION_PREFERENCES, "[]")).thenReturn("[]");
|
|
2386
|
+
when(delayUpdateUtils.parseDelayConditions("[]")).thenReturn(new ArrayList<>());
|
|
2387
|
+
|
|
2388
|
+
setPrivateField(plugin, "prefs", prefs);
|
|
2389
|
+
setPrivateField(plugin, "delayUpdateUtils", delayUpdateUtils);
|
|
2390
|
+
|
|
2391
|
+
invokePrivateVoidMethod(plugin, "installNext");
|
|
2392
|
+
|
|
2393
|
+
assertTrue(plugin.startNewThreadCalled);
|
|
2394
|
+
assertTrue(plugin.reloadCalled);
|
|
2395
|
+
assertEquals(1, updater.setCalls);
|
|
2396
|
+
assertNull(updater.lastSetNextBundleId);
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
@Test
|
|
2401
|
+
public void testOnLaunchCompletionConsumesWindowWithoutClearingInFlightDirectUpdate() {
|
|
2402
|
+
try (
|
|
2403
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2404
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2405
|
+
) {
|
|
2406
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2407
|
+
|
|
2408
|
+
TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
2409
|
+
plugin.implementation = new CapgoUpdater(null);
|
|
2410
|
+
plugin.implementation.directUpdate = true;
|
|
2411
|
+
plugin.configureDirectUpdateModeForTesting("onLaunch", false);
|
|
2412
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2413
|
+
|
|
2414
|
+
BundleInfo current = new BundleInfo("test-id", "1.0.0", BundleStatus.SUCCESS, new Date(), "abc123");
|
|
2415
|
+
|
|
2416
|
+
assertTrue(plugin.shouldUseDirectUpdateForTesting());
|
|
2417
|
+
assertFalse(plugin.hasConsumedOnLaunchDirectUpdateForTesting());
|
|
2418
|
+
|
|
2419
|
+
plugin.completeBackgroundTaskForTesting(current, true);
|
|
2420
|
+
|
|
2421
|
+
assertTrue(plugin.hasConsumedOnLaunchDirectUpdateForTesting());
|
|
2422
|
+
assertFalse(plugin.shouldUseDirectUpdateForTesting());
|
|
2423
|
+
assertTrue(plugin.implementation.directUpdate);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
|
|
2427
|
+
@Test
|
|
2428
|
+
public void testOnLaunchFreshDownloadConsumesWindowBeforeDownloadStarts() throws Exception {
|
|
2429
|
+
try (
|
|
2430
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2431
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2432
|
+
) {
|
|
2433
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2434
|
+
|
|
2435
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2436
|
+
FreshDownloadCapgoUpdater updater = new FreshDownloadCapgoUpdater();
|
|
2437
|
+
|
|
2438
|
+
plugin.implementation = updater;
|
|
2439
|
+
plugin.configureDirectUpdateModeForTesting("onLaunch", false);
|
|
2440
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2441
|
+
updater.consumedStateSupplier = plugin::hasConsumedOnLaunchDirectUpdateForTesting;
|
|
2442
|
+
updater.directUpdateStateSupplier = () -> Boolean.TRUE.equals(plugin.implementation.directUpdate);
|
|
2443
|
+
|
|
2444
|
+
assertTrue(plugin.shouldUseDirectUpdateForTesting());
|
|
2445
|
+
assertFalse(plugin.hasConsumedOnLaunchDirectUpdateForTesting());
|
|
2446
|
+
|
|
2447
|
+
invokeBackgroundDownload(plugin);
|
|
2448
|
+
|
|
2449
|
+
assertTrue(updater.downloadBackgroundCalled);
|
|
2450
|
+
assertTrue(updater.consumedWhenDownloadStarted);
|
|
2451
|
+
assertTrue(updater.directUpdateWhenDownloadStarted);
|
|
2452
|
+
assertTrue(plugin.hasConsumedOnLaunchDirectUpdateForTesting());
|
|
2453
|
+
assertFalse(plugin.shouldUseDirectUpdateForTesting());
|
|
2454
|
+
assertTrue(plugin.implementation.directUpdate);
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
@Test
|
|
2459
|
+
public void testOnlyDownloadModeDownloadsWithoutSchedulingOrDirectUpdate() throws Exception {
|
|
2460
|
+
try (
|
|
2461
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2462
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2463
|
+
) {
|
|
2464
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2465
|
+
|
|
2466
|
+
final ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2467
|
+
final FreshDownloadCapgoUpdater updater = configureOnlyDownloadBackgroundDownload(plugin);
|
|
2468
|
+
|
|
2469
|
+
plugin.implementation.directUpdate = true;
|
|
2470
|
+
updater.directUpdateStateSupplier = () -> Boolean.TRUE.equals(plugin.implementation.directUpdate);
|
|
2471
|
+
|
|
2472
|
+
assertFalse(plugin.shouldUseDirectUpdateForTesting());
|
|
2473
|
+
|
|
2474
|
+
invokeBackgroundDownload(plugin);
|
|
2475
|
+
|
|
2476
|
+
assertTrue(updater.downloadBackgroundCalled);
|
|
2477
|
+
assertFalse(updater.downloadBackgroundSetNext);
|
|
2478
|
+
assertFalse(updater.setNextBundleCalled);
|
|
2479
|
+
assertNull(updater.lastSetNextBundleId);
|
|
2480
|
+
assertFalse(updater.directUpdateWhenDownloadStarted);
|
|
2481
|
+
assertFalse(plugin.implementation.directUpdate);
|
|
2482
|
+
assertTrue(plugin.hasNotifiedEvent("updateAvailable"));
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
@Test
|
|
2487
|
+
public void testOnlyDownloadModeDoesNotScheduleExistingDownloadedBundle() throws Exception {
|
|
2488
|
+
try (
|
|
2489
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2490
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2491
|
+
) {
|
|
2492
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2493
|
+
|
|
2494
|
+
final ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2495
|
+
final FreshDownloadCapgoUpdater updater = configureOnlyDownloadBackgroundDownload(plugin);
|
|
2496
|
+
updater.existingLatestBundle = new BundleInfo("downloaded-id", "2.0.0", BundleStatus.PENDING, new Date(), "checksum");
|
|
2497
|
+
|
|
2498
|
+
invokeBackgroundDownload(plugin);
|
|
2499
|
+
|
|
2500
|
+
assertFalse(updater.downloadBackgroundCalled);
|
|
2501
|
+
assertFalse(updater.setNextBundleCalled);
|
|
2502
|
+
assertNull(updater.lastSetNextBundleId);
|
|
2503
|
+
assertTrue(plugin.hasNotifiedEvent("updateAvailable"));
|
|
2504
|
+
assertFalse(plugin.hasNotifiedEvent("noNeedUpdate"));
|
|
2505
|
+
|
|
2506
|
+
final JSObject completionPayload = plugin.getNotifiedEventPayload("appReady");
|
|
2507
|
+
final JSONObject completionBundle = completionPayload.getJSONObject("bundle");
|
|
2508
|
+
assertEquals(updater.currentBundle.getId(), completionBundle.getString("id"));
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
|
|
2512
|
+
@Test
|
|
2513
|
+
public void testOnlyDownloadModeBuiltinNotifiesUpdateAvailableWithoutScheduling() throws Exception {
|
|
2514
|
+
try (
|
|
2515
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2516
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2517
|
+
) {
|
|
2518
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2519
|
+
|
|
2520
|
+
final ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2521
|
+
final BuiltinLatestCapgoUpdater updater = new BuiltinLatestCapgoUpdater();
|
|
2522
|
+
|
|
2523
|
+
plugin.implementation = updater;
|
|
2524
|
+
plugin.setAutoUpdateModeForTesting("onlyDownload");
|
|
2525
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2526
|
+
|
|
2527
|
+
assertFalse(plugin.shouldUseDirectUpdateForTesting());
|
|
2528
|
+
|
|
2529
|
+
invokeBackgroundDownload(plugin);
|
|
2530
|
+
|
|
2531
|
+
assertFalse(updater.setNextBundleCalled);
|
|
2532
|
+
assertNull(updater.lastSetNextBundleId);
|
|
2533
|
+
assertTrue(plugin.hasNotifiedEvent("updateAvailable"));
|
|
2534
|
+
|
|
2535
|
+
final JSObject updatePayload = plugin.getNotifiedEventPayload("updateAvailable");
|
|
2536
|
+
final JSONObject updateBundle = updatePayload.getJSONObject("bundle");
|
|
2537
|
+
assertEquals(BundleInfo.ID_BUILTIN, updateBundle.getString("id"));
|
|
2538
|
+
assertFalse(plugin.hasNotifiedEvent("noNeedUpdate"));
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
@Test
|
|
2543
|
+
public void testOnlyDownloadModeBuiltinDoesNotNotifyWhenBuiltinIsCurrent() throws Exception {
|
|
2544
|
+
try (
|
|
2545
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2546
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2547
|
+
) {
|
|
2548
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2549
|
+
|
|
2550
|
+
final ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2551
|
+
final BundleInfo current = new BundleInfo(
|
|
2552
|
+
BundleInfo.ID_BUILTIN,
|
|
2553
|
+
"builtin",
|
|
2554
|
+
BundleStatus.SUCCESS,
|
|
2555
|
+
BundleInfo.DOWNLOADED_BUILTIN,
|
|
2556
|
+
"builtin"
|
|
2557
|
+
);
|
|
2558
|
+
final BuiltinLatestCapgoUpdater updater = new BuiltinLatestCapgoUpdater(current);
|
|
2559
|
+
|
|
2560
|
+
plugin.implementation = updater;
|
|
2561
|
+
plugin.setAutoUpdateModeForTesting("onlyDownload");
|
|
2562
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2563
|
+
|
|
2564
|
+
invokeBackgroundDownload(plugin);
|
|
2565
|
+
|
|
2566
|
+
assertFalse(updater.setNextBundleCalled);
|
|
2567
|
+
assertNull(updater.lastSetNextBundleId);
|
|
2568
|
+
assertFalse(plugin.hasNotifiedEvent("updateAvailable"));
|
|
2569
|
+
assertTrue(plugin.hasNotifiedEvent("noNeedUpdate"));
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
|
|
2573
|
+
@Test
|
|
2574
|
+
public void testNoNewVersionAvailableDoesNotNotifyDownloadFailed() throws Exception {
|
|
2575
|
+
try (
|
|
2576
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2577
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2578
|
+
) {
|
|
2579
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2580
|
+
|
|
2581
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2582
|
+
NoNewVersionCapgoUpdater updater = new NoNewVersionCapgoUpdater();
|
|
2583
|
+
|
|
2584
|
+
plugin.implementation = updater;
|
|
2585
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2586
|
+
|
|
2587
|
+
invokeBackgroundDownload(plugin);
|
|
2588
|
+
|
|
2589
|
+
assertTrue(plugin.hasNotifiedEvent("noNeedUpdate"));
|
|
2590
|
+
assertTrue(plugin.hasNotifiedEvent("updateCheckResult"));
|
|
2591
|
+
JSObject payload = plugin.getNotifiedEventPayload("updateCheckResult");
|
|
2592
|
+
assertNotNull(payload);
|
|
2593
|
+
assertEquals("up_to_date", payload.getString("kind"));
|
|
2594
|
+
assertEquals(200, payload.getInt("statusCode"));
|
|
2595
|
+
assertEquals("1.0.0", payload.getString("version"));
|
|
2596
|
+
assertFalse(plugin.hasNotifiedEvent("downloadFailed"));
|
|
2597
|
+
assertFalse(updater.sendStatsCalled);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
@Test
|
|
2602
|
+
public void testGetLatestRejectsLegacyErrorWithoutBackendKind() {
|
|
2603
|
+
try (
|
|
2604
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2605
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2606
|
+
) {
|
|
2607
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2608
|
+
|
|
2609
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2610
|
+
PluginCall call = mock(PluginCall.class);
|
|
2611
|
+
|
|
2612
|
+
plugin.implementation = new NoNewVersionCapgoUpdater(false);
|
|
2613
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2614
|
+
|
|
2615
|
+
plugin.getLatest(call);
|
|
2616
|
+
|
|
2617
|
+
verify(call, never()).resolve(any(JSObject.class));
|
|
2618
|
+
verify(call).reject("no_new_version_available");
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
@Test
|
|
2623
|
+
public void testBlockedUpdateCheckDoesNotNotifyDownloadFailed() throws Exception {
|
|
2624
|
+
try (
|
|
2625
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2626
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2627
|
+
) {
|
|
2628
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2629
|
+
|
|
2630
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2631
|
+
BlockedUpdateCapgoUpdater updater = new BlockedUpdateCapgoUpdater();
|
|
2632
|
+
|
|
2633
|
+
plugin.implementation = updater;
|
|
2634
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2635
|
+
|
|
2636
|
+
invokeBackgroundDownload(plugin);
|
|
2637
|
+
|
|
2638
|
+
assertTrue(plugin.hasNotifiedEvent("noNeedUpdate"));
|
|
2639
|
+
assertTrue(plugin.hasNotifiedEvent("updateCheckResult"));
|
|
2640
|
+
JSObject payload = plugin.getNotifiedEventPayload("updateCheckResult");
|
|
2641
|
+
assertNotNull(payload);
|
|
2642
|
+
assertEquals("blocked", payload.getString("kind"));
|
|
2643
|
+
assertEquals(200, payload.getInt("statusCode"));
|
|
2644
|
+
assertEquals("1.0.0", payload.getString("version"));
|
|
2645
|
+
assertFalse(plugin.hasNotifiedEvent("downloadFailed"));
|
|
2646
|
+
assertFalse(updater.sendStatsCalled);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
@Test
|
|
2651
|
+
public void testBreakingNoUrlUpdateCheckNotifiesBreakingListeners() throws Exception {
|
|
2652
|
+
try (
|
|
2653
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2654
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2655
|
+
) {
|
|
2656
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2657
|
+
|
|
2658
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2659
|
+
BreakingNoUrlCapgoUpdater updater = new BreakingNoUrlCapgoUpdater();
|
|
2660
|
+
|
|
2661
|
+
plugin.implementation = updater;
|
|
2662
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2663
|
+
|
|
2664
|
+
invokeBackgroundDownload(plugin);
|
|
2665
|
+
|
|
2666
|
+
assertTrue(plugin.hasNotifiedEvent("breakingAvailable"));
|
|
2667
|
+
assertTrue(plugin.hasNotifiedEvent("majorAvailable"));
|
|
2668
|
+
assertEquals("1.0.17", plugin.getNotifiedEventPayload("breakingAvailable").getString("version"));
|
|
2669
|
+
assertEquals("1.0.17", plugin.getNotifiedEventPayload("majorAvailable").getString("version"));
|
|
2670
|
+
assertTrue(plugin.hasNotifiedEvent("downloadFailed"));
|
|
2671
|
+
assertTrue(updater.downloadFailStatsCalled);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
@Test
|
|
2676
|
+
public void testGetLatestBreakingResponseNotifiesBreakingListeners() {
|
|
2677
|
+
try (
|
|
2678
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2679
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2680
|
+
) {
|
|
2681
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2682
|
+
|
|
2683
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2684
|
+
PluginCall call = mock(PluginCall.class);
|
|
2685
|
+
|
|
2686
|
+
plugin.implementation = new BreakingNoUrlCapgoUpdater();
|
|
2687
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2688
|
+
|
|
2689
|
+
plugin.getLatest(call);
|
|
2690
|
+
|
|
2691
|
+
assertTrue(plugin.hasNotifiedEvent("breakingAvailable"));
|
|
2692
|
+
assertTrue(plugin.hasNotifiedEvent("majorAvailable"));
|
|
2693
|
+
assertEquals("1.0.17", plugin.getNotifiedEventPayload("breakingAvailable").getString("version"));
|
|
2694
|
+
assertEquals("1.0.17", plugin.getNotifiedEventPayload("majorAvailable").getString("version"));
|
|
2695
|
+
verify(call).reject("store_update_required");
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
|
|
2699
|
+
@Test
|
|
2700
|
+
public void testFailedUpdateCheckNotifiesDownloadFailed() throws Exception {
|
|
2701
|
+
try (
|
|
2702
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2703
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2704
|
+
) {
|
|
2705
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2706
|
+
|
|
2707
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2708
|
+
FailedUpdateCapgoUpdater updater = new FailedUpdateCapgoUpdater();
|
|
2709
|
+
|
|
2710
|
+
plugin.implementation = updater;
|
|
2711
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2712
|
+
|
|
2713
|
+
invokeBackgroundDownload(plugin);
|
|
2714
|
+
|
|
2715
|
+
assertTrue(plugin.hasNotifiedEvent("updateCheckResult"));
|
|
2716
|
+
JSObject payload = plugin.getNotifiedEventPayload("updateCheckResult");
|
|
2717
|
+
assertNotNull(payload);
|
|
2718
|
+
assertEquals("failed", payload.getString("kind"));
|
|
2719
|
+
assertEquals(500, payload.getInt("statusCode"));
|
|
2720
|
+
assertEquals("1.0.0", payload.getString("version"));
|
|
2721
|
+
assertTrue(plugin.hasNotifiedEvent("downloadFailed"));
|
|
2722
|
+
assertTrue(updater.downloadFailStatsCalled);
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
@Test
|
|
2727
|
+
public void testInFlightDirectUpdateIsNotClearedByFollowUpForegroundCheck() throws Exception {
|
|
2728
|
+
try (
|
|
2729
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2730
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2731
|
+
) {
|
|
2732
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2733
|
+
|
|
2734
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2735
|
+
FreshDownloadCapgoUpdater updater = new FreshDownloadCapgoUpdater();
|
|
2736
|
+
updater.existingLatestBundle = new BundleInfo("download-id", "2.0.0", BundleStatus.DOWNLOADING, new Date(), "next-checksum");
|
|
2737
|
+
|
|
2738
|
+
plugin.implementation = updater;
|
|
2739
|
+
plugin.implementation.directUpdate = true;
|
|
2740
|
+
plugin.configureDirectUpdateModeForTesting("onLaunch", true);
|
|
2741
|
+
plugin.versionDownloadInProgress = true;
|
|
2742
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2743
|
+
updater.directUpdateStateSupplier = () -> Boolean.TRUE.equals(plugin.implementation.directUpdate);
|
|
2744
|
+
|
|
2745
|
+
invokeBackgroundDownload(plugin);
|
|
2746
|
+
|
|
2747
|
+
assertTrue(updater.downloadBackgroundCalled);
|
|
2748
|
+
assertTrue(updater.directUpdateWhenDownloadStarted);
|
|
2749
|
+
assertTrue(plugin.implementation.directUpdate);
|
|
2750
|
+
assertFalse(plugin.shouldUseDirectUpdateForTesting());
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
@Test
|
|
2755
|
+
public void testStaleDirectUpdateFlagIsClearedBeforeRetryingStaleDownloadingBundle() throws Exception {
|
|
2756
|
+
try (
|
|
2757
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2758
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2759
|
+
) {
|
|
2760
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2761
|
+
|
|
2762
|
+
ImmediateThreadCordovaUpdaterPlugin plugin = new ImmediateThreadCordovaUpdaterPlugin();
|
|
2763
|
+
FreshDownloadCapgoUpdater updater = new FreshDownloadCapgoUpdater();
|
|
2764
|
+
|
|
2765
|
+
plugin.implementation = updater;
|
|
2766
|
+
plugin.implementation.directUpdate = true;
|
|
2767
|
+
plugin.configureDirectUpdateModeForTesting("onLaunch", true);
|
|
2768
|
+
updater.existingLatestBundle = new BundleInfo("stale-download-id", "2.0.0", BundleStatus.DOWNLOADING, new Date(), "checksum");
|
|
2769
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2770
|
+
updater.directUpdateStateSupplier = () -> Boolean.TRUE.equals(plugin.implementation.directUpdate);
|
|
2771
|
+
|
|
2772
|
+
assertFalse(plugin.shouldUseDirectUpdateForTesting());
|
|
2773
|
+
|
|
2774
|
+
invokeBackgroundDownload(plugin);
|
|
2775
|
+
|
|
2776
|
+
assertTrue(updater.downloadBackgroundCalled);
|
|
2777
|
+
assertFalse(updater.directUpdateWhenDownloadStarted);
|
|
2778
|
+
assertFalse(plugin.implementation.directUpdate);
|
|
2779
|
+
assertFalse(plugin.shouldUseDirectUpdateForTesting());
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
@Test
|
|
2784
|
+
public void testHideSplashscreenInvokesSplashPluginWithoutMessageHandler() throws Exception {
|
|
2785
|
+
try (
|
|
2786
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2787
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2788
|
+
) {
|
|
2789
|
+
final Looper mainLooper = mock(Looper.class);
|
|
2790
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mainLooper);
|
|
2791
|
+
looperMock.when(Looper::myLooper).thenReturn(mainLooper);
|
|
2792
|
+
|
|
2793
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
2794
|
+
final Bridge bridge = mock(Bridge.class);
|
|
2795
|
+
final PluginHandle splashScreenPlugin = mock(PluginHandle.class);
|
|
2796
|
+
|
|
2797
|
+
when(bridge.getPlugin("SplashScreen")).thenReturn(splashScreenPlugin);
|
|
2798
|
+
|
|
2799
|
+
plugin.setBridge(bridge);
|
|
2800
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2801
|
+
|
|
2802
|
+
invokePrivateVoidMethod(plugin, "hideSplashscreenInternal");
|
|
2803
|
+
|
|
2804
|
+
final ArgumentCaptor<PluginCall> callCaptor = ArgumentCaptor.forClass(PluginCall.class);
|
|
2805
|
+
verify(splashScreenPlugin).invoke(eq("hide"), callCaptor.capture());
|
|
2806
|
+
callCaptor.getValue().resolve();
|
|
2807
|
+
assertEquals(PluginCall.CALLBACK_ID_DANGLING, callCaptor.getValue().getCallbackId());
|
|
2808
|
+
assertEquals("hide", callCaptor.getValue().getMethodName());
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
@Test
|
|
2813
|
+
public void testScheduleDirectUpdateFinishUsesBackgroundDispatchPath() {
|
|
2814
|
+
try (
|
|
2815
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2816
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2817
|
+
) {
|
|
2818
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2819
|
+
|
|
2820
|
+
final DirectUpdateDispatchPlugin plugin = new DirectUpdateDispatchPlugin();
|
|
2821
|
+
final ResetTrackingCapgoUpdater updater = new ResetTrackingCapgoUpdater();
|
|
2822
|
+
final BundleInfo latest = new BundleInfo("download-id", "2.0.0", BundleStatus.SUCCESS, new Date(), "checksum");
|
|
2823
|
+
|
|
2824
|
+
plugin.implementation = updater;
|
|
2825
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2826
|
+
|
|
2827
|
+
plugin.scheduleDirectUpdateFinish(latest);
|
|
2828
|
+
|
|
2829
|
+
assertTrue(plugin.startNewThreadCalled);
|
|
2830
|
+
assertTrue(plugin.reloadCalled);
|
|
2831
|
+
assertEquals(0, updater.setCalls);
|
|
2832
|
+
assertEquals(1, updater.stagePendingReloadCalls);
|
|
2833
|
+
assertEquals(1, updater.finalizePendingReloadCalls);
|
|
2834
|
+
assertSame(latest, updater.finalizedPendingReloadBundle);
|
|
2835
|
+
assertSame(plugin.activity, updater.activity);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
|
|
2839
|
+
@Test
|
|
2840
|
+
public void testReloadUsesBackgroundDispatchPath() {
|
|
2841
|
+
try (
|
|
2842
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2843
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2844
|
+
) {
|
|
2845
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
2846
|
+
|
|
2847
|
+
final ReloadDispatchPlugin plugin = new ReloadDispatchPlugin();
|
|
2848
|
+
final ReloadCapgoUpdater updater = new ReloadCapgoUpdater();
|
|
2849
|
+
final PluginCall call = mock(PluginCall.class);
|
|
2850
|
+
|
|
2851
|
+
plugin.implementation = updater;
|
|
2852
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2853
|
+
|
|
2854
|
+
plugin.reload(call);
|
|
2855
|
+
|
|
2856
|
+
assertTrue(plugin.startNewThreadCalled);
|
|
2857
|
+
assertTrue(plugin.reloadCalled);
|
|
2858
|
+
verify(call).resolve();
|
|
2859
|
+
}
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
@Test
|
|
2863
|
+
public void testShowSplashscreenDisablesPluginAutoHide() throws Exception {
|
|
2864
|
+
try (
|
|
2865
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2866
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2867
|
+
) {
|
|
2868
|
+
final Looper mainLooper = mock(Looper.class);
|
|
2869
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mainLooper);
|
|
2870
|
+
looperMock.when(Looper::myLooper).thenReturn(mainLooper);
|
|
2871
|
+
|
|
2872
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
2873
|
+
final Bridge bridge = mock(Bridge.class);
|
|
2874
|
+
final PluginHandle splashScreenPlugin = mock(PluginHandle.class);
|
|
2875
|
+
|
|
2876
|
+
when(bridge.getPlugin("SplashScreen")).thenReturn(splashScreenPlugin);
|
|
2877
|
+
|
|
2878
|
+
plugin.setBridge(bridge);
|
|
2879
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2880
|
+
|
|
2881
|
+
invokePrivateVoidMethod(plugin, "showSplashscreenNow");
|
|
2882
|
+
|
|
2883
|
+
final ArgumentCaptor<PluginCall> callCaptor = ArgumentCaptor.forClass(PluginCall.class);
|
|
2884
|
+
verify(splashScreenPlugin).invoke(eq("show"), callCaptor.capture());
|
|
2885
|
+
callCaptor.getValue().resolve();
|
|
2886
|
+
assertEquals(Boolean.FALSE, callCaptor.getValue().getBoolean("autoHide"));
|
|
2887
|
+
assertEquals("show", callCaptor.getValue().getMethodName());
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
@Test
|
|
2892
|
+
public void testStaleSplashscreenRetryTokenSkipsInvocation() throws Exception {
|
|
2893
|
+
try (
|
|
2894
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
2895
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
2896
|
+
) {
|
|
2897
|
+
final Looper mainLooper = mock(Looper.class);
|
|
2898
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mainLooper);
|
|
2899
|
+
looperMock.when(Looper::myLooper).thenReturn(mainLooper);
|
|
2900
|
+
|
|
2901
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
2902
|
+
final Bridge bridge = mock(Bridge.class);
|
|
2903
|
+
final PluginHandle splashScreenPlugin = mock(PluginHandle.class);
|
|
2904
|
+
|
|
2905
|
+
when(bridge.getPlugin("SplashScreen")).thenReturn(splashScreenPlugin);
|
|
2906
|
+
|
|
2907
|
+
plugin.setBridge(bridge);
|
|
2908
|
+
plugin.setLoggerForTesting(mock(Logger.class));
|
|
2909
|
+
|
|
2910
|
+
invokePrivateVoidMethod(plugin, "showSplashscreenNow");
|
|
2911
|
+
assertFalse(plugin.isCurrentSplashscreenInvocationTokenForTesting(0));
|
|
2912
|
+
|
|
2913
|
+
invokePrivateSplashMethod(plugin, "hide", new JSObject(), 1, 0);
|
|
2914
|
+
|
|
2915
|
+
verify(splashScreenPlugin, never()).invoke(eq("hide"), any(PluginCall.class));
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
@Test
|
|
2920
|
+
public void testZipEntryRejectsSiblingPrefixPathTraversal() throws Exception {
|
|
2921
|
+
final Path documentsDir = Files.createTempDirectory("capgo-zip-path");
|
|
2922
|
+
documentsDir.toFile().deleteOnExit();
|
|
2923
|
+
final Path zipPath = createZipWithEntry("../bundle-evil/pwned.txt");
|
|
2924
|
+
final Path escapedPath = documentsDir.resolve("bundle-evil").resolve("pwned.txt");
|
|
2925
|
+
|
|
2926
|
+
final CapgoUpdater updater = new StatsIgnoringCapgoUpdater();
|
|
2927
|
+
updater.documentsDir = documentsDir.toFile();
|
|
2928
|
+
|
|
2929
|
+
assertThrows(IOException.class, () -> invokeUnzip(updater, "bundle-id", zipPath, "bundle"));
|
|
2930
|
+
assertFalse(Files.exists(escapedPath));
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
@Test
|
|
2934
|
+
public void testManifestTargetRejectsPathTraversalAfterBrotliSuffixIsRemoved() throws Exception {
|
|
2935
|
+
final Path documentsDir = Files.createTempDirectory("capgo-manifest-path");
|
|
2936
|
+
documentsDir.toFile().deleteOnExit();
|
|
2937
|
+
final Path destFolder = documentsDir.resolve("bundle");
|
|
2938
|
+
Files.createDirectories(destFolder);
|
|
2939
|
+
|
|
2940
|
+
assertThrows(IOException.class, () -> DownloadService.resolveManifestTargetFile(destFolder.toFile(), "../bundle-evil/app.js.br"));
|
|
2941
|
+
assertFalse(Files.exists(documentsDir.resolve("bundle-evil")));
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
@Test
|
|
2945
|
+
public void testManifestTargetAllowsNestedBrotliFileInsideBundle() throws Exception {
|
|
2946
|
+
final Path documentsDir = Files.createTempDirectory("capgo-manifest-valid");
|
|
2947
|
+
documentsDir.toFile().deleteOnExit();
|
|
2948
|
+
final Path destFolder = documentsDir.resolve("bundle");
|
|
2949
|
+
Files.createDirectories(destFolder);
|
|
2950
|
+
|
|
2951
|
+
final File resolved = DownloadService.resolveManifestTargetFile(destFolder.toFile(), "assets/app.js.br");
|
|
2952
|
+
|
|
2953
|
+
assertEquals(destFolder.resolve("assets").resolve("app.js").toFile().getCanonicalFile(), resolved);
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
@Test
|
|
2957
|
+
public void buildUserAgentStripsNonIsoCharacters() {
|
|
2958
|
+
String ua = DownloadService.buildUserAgent("com.example.ัะตัั", "1.2.3๐ฅ", "Android 14 ๐");
|
|
2959
|
+
assertEquals("CapacitorUpdater/1.2.3 (com.example.) android/Android 14", ua);
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
@Test
|
|
2963
|
+
public void buildUserAgentFallsBackToUnknown() {
|
|
2964
|
+
String ua = DownloadService.buildUserAgent("", "", "");
|
|
2965
|
+
assertEquals("CapacitorUpdater/unknown (unknown) android/unknown", ua);
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
@Test
|
|
2969
|
+
public void persistDefaultChannelFromResponseStoresServerChannel() {
|
|
2970
|
+
final CapgoUpdater updater = new CapgoUpdater(mock(Logger.class));
|
|
2971
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
2972
|
+
when(editor.putString("CapacitorUpdater.defaultChannel", "company-a")).thenReturn(editor);
|
|
2973
|
+
|
|
2974
|
+
updater.persistDefaultChannelFromResponse(" company-a ", editor, "CapacitorUpdater.defaultChannel");
|
|
2975
|
+
|
|
2976
|
+
assertEquals("company-a", updater.defaultChannel);
|
|
2977
|
+
verify(editor).putString("CapacitorUpdater.defaultChannel", "company-a");
|
|
2978
|
+
verify(editor).apply();
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
@Test
|
|
2982
|
+
public void persistDefaultChannelFromResponseIgnoresBuiltinVersionName() {
|
|
2983
|
+
final CapgoUpdater updater = new CapgoUpdater(mock(Logger.class));
|
|
2984
|
+
final SharedPreferences.Editor editor = mock(SharedPreferences.Editor.class);
|
|
2985
|
+
updater.defaultChannel = "stable";
|
|
2986
|
+
|
|
2987
|
+
updater.persistDefaultChannelFromResponse("builtin", editor, "CapacitorUpdater.defaultChannel");
|
|
2988
|
+
|
|
2989
|
+
assertEquals("stable", updater.defaultChannel);
|
|
2990
|
+
verify(editor, never()).putString(anyString(), anyString());
|
|
2991
|
+
verify(editor, never()).apply();
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
/**
|
|
2995
|
+
* Regression test for: NoSuchMethodError crash on Android 8.0/8.1 (API 26/27).
|
|
2996
|
+
* getLongVersionCode() was introduced in API 28; the plugin must use
|
|
2997
|
+
* PackageInfoCompat.getLongVersionCode() to support API 24-27.
|
|
2998
|
+
*
|
|
2999
|
+
* <p>The @SuppressWarnings("deprecation") is intentional: we set PackageInfo.versionCode
|
|
3000
|
+
* (deprecated since API 28) directly to simulate a pre-API-28 device and verify
|
|
3001
|
+
* that PackageInfoCompat falls back to it correctly on older Android versions.
|
|
3002
|
+
*/
|
|
3003
|
+
@Test
|
|
3004
|
+
@SuppressWarnings("deprecation")
|
|
3005
|
+
public void getVersionCodeReturnsStringVersionCodeViaPackageInfoCompat() throws Exception {
|
|
3006
|
+
try (
|
|
3007
|
+
MockedStatic<Looper> looperMock = mockStatic(Looper.class);
|
|
3008
|
+
MockedConstruction<Handler> ignored = mockConstruction(Handler.class)
|
|
3009
|
+
) {
|
|
3010
|
+
looperMock.when(Looper::getMainLooper).thenReturn(mock(Looper.class));
|
|
3011
|
+
looperMock.when(Looper::myLooper).thenReturn(mock(Looper.class));
|
|
3012
|
+
|
|
3013
|
+
final TestableCordovaUpdaterPlugin plugin = new TestableCordovaUpdaterPlugin();
|
|
3014
|
+
final PackageInfo packageInfo = new PackageInfo();
|
|
3015
|
+
packageInfo.versionCode = 42;
|
|
3016
|
+
|
|
3017
|
+
final Method getVersionCode = CordovaUpdaterPlugin.class.getDeclaredMethod("getVersionCode", PackageInfo.class);
|
|
3018
|
+
getVersionCode.setAccessible(true);
|
|
3019
|
+
final String result = (String) getVersionCode.invoke(plugin, packageInfo);
|
|
3020
|
+
|
|
3021
|
+
assertEquals("42", result);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
@Test
|
|
3026
|
+
public void normalizeShakeMenuGestureSupportsThreeFingerPinch() {
|
|
3027
|
+
assertEquals(CordovaUpdaterPlugin.SHAKE_MENU_GESTURE_SHAKE, CordovaUpdaterPlugin.normalizedShakeMenuGesture(null));
|
|
3028
|
+
assertEquals(CordovaUpdaterPlugin.SHAKE_MENU_GESTURE_SHAKE, CordovaUpdaterPlugin.normalizedShakeMenuGesture("shake"));
|
|
3029
|
+
assertEquals(CordovaUpdaterPlugin.SHAKE_MENU_GESTURE_SHAKE, CordovaUpdaterPlugin.normalizedShakeMenuGesture("unknown"));
|
|
3030
|
+
assertEquals(
|
|
3031
|
+
CordovaUpdaterPlugin.SHAKE_MENU_GESTURE_THREE_FINGER_PINCH,
|
|
3032
|
+
CordovaUpdaterPlugin.normalizedShakeMenuGesture("threeFingerPinch")
|
|
3033
|
+
);
|
|
3034
|
+
assertTrue(CordovaUpdaterPlugin.isSupportedShakeMenuGesture("shake"));
|
|
3035
|
+
assertTrue(CordovaUpdaterPlugin.isSupportedShakeMenuGesture("threeFingerPinch"));
|
|
3036
|
+
assertFalse(CordovaUpdaterPlugin.isSupportedShakeMenuGesture(" "));
|
|
3037
|
+
assertFalse(CordovaUpdaterPlugin.isSupportedShakeMenuGesture("pinch"));
|
|
3038
|
+
}
|
|
3039
|
+
}
|