@luvio/adapter-test-library 0.105.0 → 0.108.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/babel.config.js +1 -0
- package/dist/es/es2018/{durableStore.d.ts → MockDurableStore.d.ts} +5 -3
- package/dist/es/es2018/durableStorePersistence.d.ts +2 -0
- package/dist/es/es2018/main.d.ts +2 -2
- package/dist/es/es2018/network.d.ts +7 -0
- package/dist/es/es2018/test-library.js +82 -34
- package/dist/es/es2018/utils.d.ts +1 -0
- package/dist/umd/es2018/{durableStore.d.ts → MockDurableStore.d.ts} +5 -3
- package/dist/umd/es2018/durableStorePersistence.d.ts +2 -0
- package/dist/umd/es2018/main.d.ts +2 -2
- package/dist/umd/es2018/network.d.ts +7 -0
- package/dist/umd/es2018/test-library.js +86 -36
- package/dist/umd/es2018/utils.d.ts +1 -0
- package/dist/umd/es5/{durableStore.d.ts → MockDurableStore.d.ts} +5 -3
- package/dist/umd/es5/durableStorePersistence.d.ts +2 -0
- package/dist/umd/es5/main.d.ts +2 -2
- package/dist/umd/es5/network.d.ts +7 -0
- package/dist/umd/es5/test-library.js +234 -101
- package/dist/umd/es5/utils.d.ts +1 -0
- package/jest.config.js +16 -0
- package/package.json +4 -4
- package/rollup.config.js +3 -0
- package/src/MockDurableStore.ts +142 -0
- package/src/__tests__/MockDurableStore.spec.ts +53 -0
- package/src/durableStorePersistence.ts +13 -0
- package/src/main.ts +2 -1
- package/src/network.ts +16 -1
- package/src/utils.ts +4 -0
- package/src/durableStore.ts +0 -108
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { DurableStorePersistence } from '../durableStorePersistence';
|
|
2
|
+
import { MockDurableStore } from '../MockDurableStore';
|
|
3
|
+
|
|
4
|
+
describe('MockDurableStore', () => {
|
|
5
|
+
describe('read/write synchronization', () => {
|
|
6
|
+
it('allows parallel reads', async () => {
|
|
7
|
+
const persistence: DurableStorePersistence = {
|
|
8
|
+
get: jest.fn().mockResolvedValue(undefined),
|
|
9
|
+
set: jest.fn().mockResolvedValue(undefined),
|
|
10
|
+
delete: jest.fn().mockResolvedValue(undefined),
|
|
11
|
+
};
|
|
12
|
+
const ds = new MockDurableStore(persistence);
|
|
13
|
+
const segment = 'foo';
|
|
14
|
+
|
|
15
|
+
// kick off reads without awaiting, all should start
|
|
16
|
+
const promise1 = ds.getAllEntries(segment);
|
|
17
|
+
const promise2 = ds.getAllEntries(segment);
|
|
18
|
+
const promise3 = ds.getEntries(['123'], segment);
|
|
19
|
+
|
|
20
|
+
expect(persistence.get).toBeCalledTimes(3);
|
|
21
|
+
|
|
22
|
+
const result1 = await promise1;
|
|
23
|
+
const result2 = await promise2;
|
|
24
|
+
const result3 = await promise3;
|
|
25
|
+
|
|
26
|
+
expect(result1).toBe(undefined);
|
|
27
|
+
expect(result2).toBe(undefined);
|
|
28
|
+
expect(result3).toBe(undefined);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('read then write then read will respect ordering', async () => {
|
|
32
|
+
const ds = new MockDurableStore();
|
|
33
|
+
const segment = 'foo';
|
|
34
|
+
const key = '123';
|
|
35
|
+
const value = {};
|
|
36
|
+
|
|
37
|
+
const read1Promise = ds.getEntries([key], segment);
|
|
38
|
+
const write1Promise = ds.setEntries({ [key]: { data: value } }, segment);
|
|
39
|
+
const read2Promise = ds.getEntries([key], segment);
|
|
40
|
+
|
|
41
|
+
// first read should be empty
|
|
42
|
+
const read1Result = await read1Promise;
|
|
43
|
+
expect(read1Result).toBe(undefined);
|
|
44
|
+
|
|
45
|
+
// wait for the write to finish
|
|
46
|
+
await write1Promise;
|
|
47
|
+
|
|
48
|
+
// second read should include data from the write
|
|
49
|
+
const read2Result = await read2Promise;
|
|
50
|
+
expect(read2Result).toEqual({ [key]: { data: value } });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { flushPromises } from './utils';
|
|
2
|
+
|
|
1
3
|
export interface DurableStorePersistence {
|
|
2
4
|
get<T>(key: string): Promise<T | undefined>;
|
|
3
5
|
set<T>(key: string, value: T): Promise<void>;
|
|
4
6
|
delete(key: string): Promise<void>;
|
|
7
|
+
flushPendingWork(): Promise<void>;
|
|
5
8
|
}
|
|
6
9
|
|
|
7
10
|
export class MemoryDurableStorePersistence implements DurableStorePersistence {
|
|
@@ -12,10 +15,20 @@ export class MemoryDurableStorePersistence implements DurableStorePersistence {
|
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
async set<T>(key: string, value: T): Promise<void> {
|
|
18
|
+
// simulate a more realistic durable store by making the write wait a
|
|
19
|
+
// tick before actually setting the value
|
|
20
|
+
await flushPromises();
|
|
15
21
|
this.store[key] = value;
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
async delete(key: string): Promise<void> {
|
|
19
25
|
delete this.store[key];
|
|
20
26
|
}
|
|
27
|
+
|
|
28
|
+
async flushPendingWork(): Promise<void> {
|
|
29
|
+
// since this implementation does actual "IO" synchronously the only
|
|
30
|
+
// thing necessary to await any pending IO is to flush the current
|
|
31
|
+
// microtask queue
|
|
32
|
+
await flushPromises();
|
|
33
|
+
}
|
|
21
34
|
}
|
package/src/main.ts
CHANGED
|
@@ -10,9 +10,10 @@ export {
|
|
|
10
10
|
setNetworkConnectivity,
|
|
11
11
|
buildFetchResponse,
|
|
12
12
|
overrideMockNetworkResponses,
|
|
13
|
+
flushPendingNetworkRequests,
|
|
13
14
|
} from './network';
|
|
14
15
|
export { verifyImmutable, isImmutable } from './verification';
|
|
15
16
|
export { getMockLuvioWithFulfilledSnapshot, getMockFulfilledSnapshot } from './mocks';
|
|
16
17
|
export { stripProperties } from './utils';
|
|
17
|
-
export { MockDurableStore } from './
|
|
18
|
+
export { MockDurableStore } from './MockDurableStore';
|
|
18
19
|
export { MemoryDurableStorePersistence, DurableStorePersistence } from './durableStorePersistence';
|
package/src/network.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NetworkAdapter, ResourceRequest, FetchResponse, Headers } from '@luvio/engine';
|
|
2
2
|
import sinon from 'sinon';
|
|
3
3
|
|
|
4
|
-
import { clone } from './utils';
|
|
4
|
+
import { clone, flushPromises } from './utils';
|
|
5
5
|
|
|
6
6
|
const networkConnectivityStateMap = new WeakMap<sinon.SinonStub, ConnectivityState>();
|
|
7
7
|
|
|
@@ -68,6 +68,21 @@ function isOkResponse(status: number) {
|
|
|
68
68
|
return status >= 200 && status <= 299;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Flushes any pending network requests. Useful for tests that need to ensure all
|
|
73
|
+
* un-awaited background refreshes are complete
|
|
74
|
+
*
|
|
75
|
+
* @param _mockNetworkAdapter {NetworkAdapter} The network adapter instance to flush
|
|
76
|
+
*/
|
|
77
|
+
export async function flushPendingNetworkRequests(
|
|
78
|
+
_mockNetworkAdapter: NetworkAdapter
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
// since the network mock is actually synchronous (just returns things wrapped
|
|
81
|
+
// in Promise.resolve/reject) the only thing necessary to flush any pending
|
|
82
|
+
// network activity is to flush the pending microtask queue
|
|
83
|
+
await flushPromises();
|
|
84
|
+
}
|
|
85
|
+
|
|
71
86
|
export function buildMockNetworkAdapter(mockPayloads: MockPayload[]) {
|
|
72
87
|
// any endpoints not setup with a fake will return a rejected promise
|
|
73
88
|
const networkAdapter = sinon.stub().rejects(buildMockSetupError());
|
package/src/utils.ts
CHANGED
package/src/durableStore.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DurableStore,
|
|
3
|
-
DurableStoreEntries,
|
|
4
|
-
OnDurableStoreChangedListener,
|
|
5
|
-
DurableStoreOperation,
|
|
6
|
-
DurableStoreChange,
|
|
7
|
-
} from '@luvio/environments';
|
|
8
|
-
import { DurableStoreOperationType } from '@luvio/environments';
|
|
9
|
-
import { clone } from './utils';
|
|
10
|
-
import type { DurableStorePersistence } from './durableStorePersistence';
|
|
11
|
-
import { MemoryDurableStorePersistence } from './durableStorePersistence';
|
|
12
|
-
export class MockDurableStore implements DurableStore {
|
|
13
|
-
// NOTE: This mock class doesn't enforce read/write synchronization
|
|
14
|
-
|
|
15
|
-
listeners = new Set<OnDurableStoreChangedListener>();
|
|
16
|
-
persistence: DurableStorePersistence;
|
|
17
|
-
|
|
18
|
-
constructor(persistence?: DurableStorePersistence) {
|
|
19
|
-
this.persistence = persistence || new MemoryDurableStorePersistence();
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
getEntries<T>(
|
|
23
|
-
entryIds: string[],
|
|
24
|
-
segment: string
|
|
25
|
-
): Promise<DurableStoreEntries<T> | undefined> {
|
|
26
|
-
const returnSource = Object.create(null);
|
|
27
|
-
return this.persistence.get<DurableStoreEntries<T>>(segment).then((entries) => {
|
|
28
|
-
if (entries === undefined) {
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
for (const entryId of entryIds) {
|
|
33
|
-
const entry = entries[entryId];
|
|
34
|
-
if (entry !== undefined) {
|
|
35
|
-
returnSource[entryId] = clone(entry);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return returnSource;
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
getAllEntries<T>(segment: string): Promise<DurableStoreEntries<T>> {
|
|
43
|
-
const returnSource = Object.create(null);
|
|
44
|
-
|
|
45
|
-
return this.persistence.get<DurableStoreEntries<T>>(segment).then((rawEntries) => {
|
|
46
|
-
const entries = rawEntries === undefined ? {} : rawEntries;
|
|
47
|
-
|
|
48
|
-
for (const key of Object.keys(entries)) {
|
|
49
|
-
returnSource[key] = clone(entries[key]);
|
|
50
|
-
}
|
|
51
|
-
return returnSource;
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
setEntries<T>(entries: DurableStoreEntries<T>, segment: string): Promise<void> {
|
|
56
|
-
return this.batchOperations([
|
|
57
|
-
{ entries, segment, type: DurableStoreOperationType.SetEntries },
|
|
58
|
-
]);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
evictEntries(ids: string[], segment: string): Promise<void> {
|
|
62
|
-
return this.batchOperations([
|
|
63
|
-
{ ids, segment, type: DurableStoreOperationType.EvictEntries },
|
|
64
|
-
]);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
registerOnChangedListener(listener: OnDurableStoreChangedListener): () => Promise<void> {
|
|
68
|
-
this.listeners.add(listener);
|
|
69
|
-
return () => {
|
|
70
|
-
this.listeners.delete(listener);
|
|
71
|
-
return Promise.resolve();
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async batchOperations<T>(operations: DurableStoreOperation<T>[]): Promise<void> {
|
|
76
|
-
const changes: DurableStoreChange[] = [];
|
|
77
|
-
for (let i = 0; i < operations.length; i++) {
|
|
78
|
-
changes.push(await this.performOperation(operations[i]));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
this.listeners.forEach((listener) => {
|
|
82
|
-
listener(changes);
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async performOperation<T>(operation: DurableStoreOperation<T>): Promise<DurableStoreChange> {
|
|
87
|
-
const segment = operation.segment;
|
|
88
|
-
const rawEntries = await this.persistence.get<DurableStoreEntries<T>>(segment);
|
|
89
|
-
const entries = rawEntries === undefined ? {} : rawEntries;
|
|
90
|
-
let ids: string[] = [];
|
|
91
|
-
switch (operation.type) {
|
|
92
|
-
case DurableStoreOperationType.SetEntries:
|
|
93
|
-
ids = Object.keys(operation.entries);
|
|
94
|
-
ids.forEach((id) => {
|
|
95
|
-
entries[id] = clone(operation.entries[id]);
|
|
96
|
-
});
|
|
97
|
-
break;
|
|
98
|
-
case DurableStoreOperationType.EvictEntries:
|
|
99
|
-
ids = operation.ids;
|
|
100
|
-
ids.forEach((id) => {
|
|
101
|
-
delete entries[id];
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
await this.persistence.set(operation.segment, entries);
|
|
106
|
-
return { ids, segment, type: operation.type };
|
|
107
|
-
}
|
|
108
|
-
}
|