react-native-notify-kit 9.4.0 → 9.5.0
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/README.md +10 -4
- package/android/build.gradle +1 -0
- package/android/src/androidTest/java/app/notifee/core/DoScheduledWorkOrderingTest.java +234 -0
- package/android/src/androidTest/java/app/notifee/core/RebootRecoveryTest.java +204 -0
- package/android/src/androidTest/java/app/notifee/core/database/TimingWorkDataRepository.java +56 -0
- package/android/src/androidTest/java/app/notifee/core/database/WorkDataRepositoryRaceTest.java +221 -0
- package/android/src/androidTest/res/drawable/test_icon.xml +14 -0
- package/android/src/main/java/app/notifee/core/NotifeeAlarmManager.java +160 -79
- package/android/src/main/java/app/notifee/core/NotificationManager.java +153 -96
- package/android/src/main/java/app/notifee/core/database/WorkDataRepository.java +38 -34
- package/android/src/test/java/app/notifee/core/database/WorkDataRepositoryFutureContractTest.java +279 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/version.ts +1 -1
package/android/src/test/java/app/notifee/core/database/WorkDataRepositoryFutureContractTest.java
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
package app.notifee.core.database;
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Copyright (c) 2016-present Invertase Limited & Contributors
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this library except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import static org.junit.Assert.assertFalse;
|
|
14
|
+
import static org.junit.Assert.assertNotNull;
|
|
15
|
+
import static org.junit.Assert.assertSame;
|
|
16
|
+
import static org.junit.Assert.assertTrue;
|
|
17
|
+
import static org.junit.Assert.fail;
|
|
18
|
+
import static org.mockito.Mockito.doAnswer;
|
|
19
|
+
import static org.mockito.Mockito.doThrow;
|
|
20
|
+
import static org.mockito.Mockito.mock;
|
|
21
|
+
import static org.mockito.Mockito.verify;
|
|
22
|
+
|
|
23
|
+
import com.google.common.util.concurrent.ListenableFuture;
|
|
24
|
+
import com.google.common.util.concurrent.ListeningExecutorService;
|
|
25
|
+
import com.google.common.util.concurrent.MoreExecutors;
|
|
26
|
+
import java.util.Collections;
|
|
27
|
+
import java.util.concurrent.CountDownLatch;
|
|
28
|
+
import java.util.concurrent.ExecutionException;
|
|
29
|
+
import java.util.concurrent.Executors;
|
|
30
|
+
import java.util.concurrent.TimeUnit;
|
|
31
|
+
import org.junit.After;
|
|
32
|
+
import org.junit.Before;
|
|
33
|
+
import org.junit.Test;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Locks in the post-#549 contract that every {@link WorkDataRepository} mutation method returns a
|
|
37
|
+
* non-null {@link ListenableFuture} that only transitions to done after the underlying DAO call has
|
|
38
|
+
* actually returned — and that DAO exceptions propagate through {@code ExecutionException}.
|
|
39
|
+
*
|
|
40
|
+
* <p>Before the #549 fix, these methods were {@code void} and enqueued their work fire-and-forget
|
|
41
|
+
* on a cached thread pool. Any future refactor that drops the future return type or silently
|
|
42
|
+
* swallows DAO exceptions would be a regression and must fail these tests.
|
|
43
|
+
*/
|
|
44
|
+
public class WorkDataRepositoryFutureContractTest {
|
|
45
|
+
|
|
46
|
+
private WorkDataDao mockDao;
|
|
47
|
+
private ListeningExecutorService executor;
|
|
48
|
+
private WorkDataRepository repo;
|
|
49
|
+
|
|
50
|
+
@Before
|
|
51
|
+
public void setUp() {
|
|
52
|
+
mockDao = mock(WorkDataDao.class);
|
|
53
|
+
// Single-threaded so we can deterministically gate the DAO call with a latch
|
|
54
|
+
// and assert future.isDone() before the DAO has returned.
|
|
55
|
+
executor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
|
|
56
|
+
repo = new WorkDataRepository(mockDao, executor);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@After
|
|
60
|
+
public void tearDown() {
|
|
61
|
+
executor.shutdownNow();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// -------- insert --------
|
|
65
|
+
|
|
66
|
+
@Test
|
|
67
|
+
public void insert_returnsNonNullFuture() {
|
|
68
|
+
assertNotNull(repo.insert(newEntity("a")));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@Test
|
|
72
|
+
public void insert_futureNotDoneUntilDaoReturns() throws Exception {
|
|
73
|
+
CountDownLatch daoEntered = new CountDownLatch(1);
|
|
74
|
+
CountDownLatch daoGate = new CountDownLatch(1);
|
|
75
|
+
WorkDataEntity entity = newEntity("a");
|
|
76
|
+
doAnswer(
|
|
77
|
+
inv -> {
|
|
78
|
+
daoEntered.countDown();
|
|
79
|
+
daoGate.await();
|
|
80
|
+
return null;
|
|
81
|
+
})
|
|
82
|
+
.when(mockDao)
|
|
83
|
+
.insert(entity);
|
|
84
|
+
|
|
85
|
+
ListenableFuture<Void> f = repo.insert(entity);
|
|
86
|
+
assertTrue("DAO should be entered within 1s", daoEntered.await(1, TimeUnit.SECONDS));
|
|
87
|
+
assertFalse("insert future must not be done while DAO is blocked", f.isDone());
|
|
88
|
+
|
|
89
|
+
daoGate.countDown();
|
|
90
|
+
f.get(1, TimeUnit.SECONDS);
|
|
91
|
+
assertTrue(f.isDone());
|
|
92
|
+
verify(mockDao).insert(entity);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
@Test
|
|
96
|
+
public void insert_propagatesDaoException() throws Exception {
|
|
97
|
+
RuntimeException boom = new RuntimeException("disk full");
|
|
98
|
+
WorkDataEntity entity = newEntity("a");
|
|
99
|
+
doThrow(boom).when(mockDao).insert(entity);
|
|
100
|
+
|
|
101
|
+
ListenableFuture<Void> f = repo.insert(entity);
|
|
102
|
+
try {
|
|
103
|
+
f.get(1, TimeUnit.SECONDS);
|
|
104
|
+
fail("Expected ExecutionException wrapping the DAO failure");
|
|
105
|
+
} catch (ExecutionException e) {
|
|
106
|
+
assertSame(boom, e.getCause());
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// -------- deleteById --------
|
|
111
|
+
|
|
112
|
+
@Test
|
|
113
|
+
public void deleteById_returnsNonNullFuture() {
|
|
114
|
+
assertNotNull(repo.deleteById("a"));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@Test
|
|
118
|
+
public void deleteById_futureNotDoneUntilDaoReturns() throws Exception {
|
|
119
|
+
CountDownLatch daoEntered = new CountDownLatch(1);
|
|
120
|
+
CountDownLatch daoGate = new CountDownLatch(1);
|
|
121
|
+
doAnswer(
|
|
122
|
+
inv -> {
|
|
123
|
+
daoEntered.countDown();
|
|
124
|
+
daoGate.await();
|
|
125
|
+
return null;
|
|
126
|
+
})
|
|
127
|
+
.when(mockDao)
|
|
128
|
+
.deleteById("a");
|
|
129
|
+
|
|
130
|
+
ListenableFuture<Void> f = repo.deleteById("a");
|
|
131
|
+
assertTrue(daoEntered.await(1, TimeUnit.SECONDS));
|
|
132
|
+
assertFalse(f.isDone());
|
|
133
|
+
|
|
134
|
+
daoGate.countDown();
|
|
135
|
+
f.get(1, TimeUnit.SECONDS);
|
|
136
|
+
verify(mockDao).deleteById("a");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@Test
|
|
140
|
+
public void deleteById_propagatesDaoException() throws Exception {
|
|
141
|
+
RuntimeException boom = new RuntimeException("row missing");
|
|
142
|
+
doThrow(boom).when(mockDao).deleteById("a");
|
|
143
|
+
|
|
144
|
+
ListenableFuture<Void> f = repo.deleteById("a");
|
|
145
|
+
try {
|
|
146
|
+
f.get(1, TimeUnit.SECONDS);
|
|
147
|
+
fail("Expected ExecutionException wrapping the DAO failure");
|
|
148
|
+
} catch (ExecutionException e) {
|
|
149
|
+
assertSame(boom, e.getCause());
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// -------- deleteByIds --------
|
|
154
|
+
|
|
155
|
+
@Test
|
|
156
|
+
public void deleteByIds_futureNotDoneUntilDaoReturns() throws Exception {
|
|
157
|
+
CountDownLatch daoEntered = new CountDownLatch(1);
|
|
158
|
+
CountDownLatch daoGate = new CountDownLatch(1);
|
|
159
|
+
doAnswer(
|
|
160
|
+
inv -> {
|
|
161
|
+
daoEntered.countDown();
|
|
162
|
+
daoGate.await();
|
|
163
|
+
return null;
|
|
164
|
+
})
|
|
165
|
+
.when(mockDao)
|
|
166
|
+
.deleteByIds(Collections.singletonList("a"));
|
|
167
|
+
|
|
168
|
+
ListenableFuture<Void> f = repo.deleteByIds(Collections.singletonList("a"));
|
|
169
|
+
assertTrue(daoEntered.await(1, TimeUnit.SECONDS));
|
|
170
|
+
assertFalse(f.isDone());
|
|
171
|
+
|
|
172
|
+
daoGate.countDown();
|
|
173
|
+
f.get(1, TimeUnit.SECONDS);
|
|
174
|
+
verify(mockDao).deleteByIds(Collections.singletonList("a"));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@Test
|
|
178
|
+
public void deleteByIds_propagatesDaoException() throws Exception {
|
|
179
|
+
RuntimeException boom = new RuntimeException("constraint violation");
|
|
180
|
+
doThrow(boom).when(mockDao).deleteByIds(Collections.singletonList("a"));
|
|
181
|
+
|
|
182
|
+
ListenableFuture<Void> f = repo.deleteByIds(Collections.singletonList("a"));
|
|
183
|
+
try {
|
|
184
|
+
f.get(1, TimeUnit.SECONDS);
|
|
185
|
+
fail("Expected ExecutionException");
|
|
186
|
+
} catch (ExecutionException e) {
|
|
187
|
+
assertSame(boom, e.getCause());
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// -------- deleteAll --------
|
|
192
|
+
|
|
193
|
+
@Test
|
|
194
|
+
public void deleteAll_returnsNonNullFuture() {
|
|
195
|
+
assertNotNull(repo.deleteAll());
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@Test
|
|
199
|
+
public void deleteAll_futureNotDoneUntilDaoReturns() throws Exception {
|
|
200
|
+
CountDownLatch daoEntered = new CountDownLatch(1);
|
|
201
|
+
CountDownLatch daoGate = new CountDownLatch(1);
|
|
202
|
+
doAnswer(
|
|
203
|
+
inv -> {
|
|
204
|
+
daoEntered.countDown();
|
|
205
|
+
daoGate.await();
|
|
206
|
+
return null;
|
|
207
|
+
})
|
|
208
|
+
.when(mockDao)
|
|
209
|
+
.deleteAll();
|
|
210
|
+
|
|
211
|
+
ListenableFuture<Void> f = repo.deleteAll();
|
|
212
|
+
assertTrue(daoEntered.await(1, TimeUnit.SECONDS));
|
|
213
|
+
assertFalse(f.isDone());
|
|
214
|
+
|
|
215
|
+
daoGate.countDown();
|
|
216
|
+
f.get(1, TimeUnit.SECONDS);
|
|
217
|
+
verify(mockDao).deleteAll();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@Test
|
|
221
|
+
public void deleteAll_propagatesDaoException() throws Exception {
|
|
222
|
+
RuntimeException boom = new RuntimeException("db locked");
|
|
223
|
+
doThrow(boom).when(mockDao).deleteAll();
|
|
224
|
+
|
|
225
|
+
ListenableFuture<Void> f = repo.deleteAll();
|
|
226
|
+
try {
|
|
227
|
+
f.get(1, TimeUnit.SECONDS);
|
|
228
|
+
fail("Expected ExecutionException");
|
|
229
|
+
} catch (ExecutionException e) {
|
|
230
|
+
assertSame(boom, e.getCause());
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// -------- update --------
|
|
235
|
+
|
|
236
|
+
@Test
|
|
237
|
+
public void update_futureNotDoneUntilDaoReturns() throws Exception {
|
|
238
|
+
CountDownLatch daoEntered = new CountDownLatch(1);
|
|
239
|
+
CountDownLatch daoGate = new CountDownLatch(1);
|
|
240
|
+
WorkDataEntity entity = newEntity("a");
|
|
241
|
+
doAnswer(
|
|
242
|
+
inv -> {
|
|
243
|
+
daoEntered.countDown();
|
|
244
|
+
daoGate.await();
|
|
245
|
+
return null;
|
|
246
|
+
})
|
|
247
|
+
.when(mockDao)
|
|
248
|
+
.update(entity);
|
|
249
|
+
|
|
250
|
+
ListenableFuture<Void> f = repo.update(entity);
|
|
251
|
+
assertTrue(daoEntered.await(1, TimeUnit.SECONDS));
|
|
252
|
+
assertFalse(f.isDone());
|
|
253
|
+
|
|
254
|
+
daoGate.countDown();
|
|
255
|
+
f.get(1, TimeUnit.SECONDS);
|
|
256
|
+
verify(mockDao).update(entity);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
@Test
|
|
260
|
+
public void update_propagatesDaoException() throws Exception {
|
|
261
|
+
RuntimeException boom = new RuntimeException("schema mismatch");
|
|
262
|
+
WorkDataEntity entity = newEntity("a");
|
|
263
|
+
doThrow(boom).when(mockDao).update(entity);
|
|
264
|
+
|
|
265
|
+
ListenableFuture<Void> f = repo.update(entity);
|
|
266
|
+
try {
|
|
267
|
+
f.get(1, TimeUnit.SECONDS);
|
|
268
|
+
fail("Expected ExecutionException");
|
|
269
|
+
} catch (ExecutionException e) {
|
|
270
|
+
assertSame(boom, e.getCause());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// -------- helpers --------
|
|
275
|
+
|
|
276
|
+
private static WorkDataEntity newEntity(String id) {
|
|
277
|
+
return new WorkDataEntity(id, new byte[0], new byte[0], false);
|
|
278
|
+
}
|
|
279
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "9.
|
|
1
|
+
export declare const version = "9.5.0";
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by genversion.
|
|
2
|
-
export const version = '9.
|
|
2
|
+
export const version = '9.5.0';
|