Skip to content

Commit 5dbe832

Browse files
authored
chore: adding more FDv2 tests (#768)
More tests for FDv2 branch
1 parent c2debf7 commit 5dbe832

File tree

7 files changed

+280
-4
lines changed

7 files changed

+280
-4
lines changed

packages/shared/sdk-server/__tests__/store/PersistentStoreWrapper.test.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,5 +499,143 @@ describe.each(['caching', 'non-caching'])(
499499
const allValues = await asyncWrapper.all(VersionedDataKinds.Features);
500500
expect(allValues).toEqual({});
501501
});
502+
503+
it('applyChanges with basis results in initialization', async () => {
504+
await asyncWrapper.applyChanges(
505+
true,
506+
{
507+
features: {
508+
key1: {
509+
version: 1,
510+
},
511+
},
512+
},
513+
'selector1',
514+
);
515+
516+
expect(await asyncWrapper.initialized()).toBeTruthy();
517+
expect(await asyncWrapper.all(VersionedDataKinds.Features)).toEqual({
518+
key1: {
519+
version: 1,
520+
},
521+
});
522+
});
523+
524+
it('applyChanges with basis overwrites existing data', async () => {
525+
await asyncWrapper.applyChanges(
526+
true,
527+
{
528+
features: {
529+
oldFeature: {
530+
version: 1,
531+
},
532+
},
533+
},
534+
'selector1',
535+
);
536+
537+
expect(await asyncWrapper.all(VersionedDataKinds.Features)).toEqual({
538+
oldFeature: {
539+
version: 1,
540+
},
541+
});
542+
543+
await asyncWrapper.applyChanges(
544+
true,
545+
{
546+
features: {
547+
newFeature: {
548+
version: 1,
549+
},
550+
},
551+
},
552+
'selector1',
553+
);
554+
555+
expect(await asyncWrapper.all(VersionedDataKinds.Features)).toEqual({
556+
newFeature: {
557+
version: 1,
558+
},
559+
});
560+
});
561+
562+
it('applyChanges callback fires after all upserts complete', async () => {
563+
let callbackCount = 0;
564+
jest
565+
.spyOn(mockPersistentStore, 'upsert')
566+
.mockImplementation(async (_kind, _key, _data, cb) => {
567+
callbackCount += 1;
568+
// this await gives chance for execution to continue elsewhere. If there is a bug, this will lead to a failure
569+
await new Promise((f) => {
570+
setTimeout(f, 1);
571+
});
572+
cb();
573+
});
574+
575+
await asyncWrapper.applyChanges(
576+
false,
577+
{
578+
features: {
579+
key1: {
580+
version: 1,
581+
},
582+
key2: {
583+
version: 1,
584+
},
585+
key3: {
586+
version: 1,
587+
},
588+
},
589+
},
590+
'selector',
591+
);
592+
expect(callbackCount).toEqual(3);
593+
});
594+
595+
it('applyChanges with basis=false merges correctly', async () => {
596+
await asyncWrapper.applyChanges(
597+
true,
598+
{
599+
features: {
600+
key1: {
601+
version: 1,
602+
},
603+
key2: {
604+
version: 1,
605+
},
606+
},
607+
},
608+
'selector',
609+
);
610+
611+
await asyncWrapper.applyChanges(
612+
false,
613+
{
614+
features: {
615+
key1: {
616+
version: 2,
617+
},
618+
key3: {
619+
version: 1,
620+
},
621+
},
622+
},
623+
'selector',
624+
);
625+
626+
expect(await asyncWrapper.all(VersionedDataKinds.Features)).toEqual({
627+
key1: {
628+
key: 'key1',
629+
version: 2,
630+
},
631+
key2: {
632+
version: 1,
633+
},
634+
key3: {
635+
key: 'key3',
636+
version: 1,
637+
},
638+
});
639+
});
502640
},
503641
);
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { LDFeatureStore } from '../../src/api/subsystems';
2+
import AsyncStoreFacade from '../../src/store/AsyncStoreFacade';
3+
import InMemoryFeatureStore from '../../src/store/InMemoryFeatureStore';
4+
import TransactionalPersistentStore from '../../src/store/TransactionalPersistentStore';
5+
import VersionedDataKinds from '../../src/store/VersionedDataKinds';
6+
7+
describe('given a non transactional store', () => {
8+
let mockNontransactionalStore: LDFeatureStore;
9+
let transactionalStore: TransactionalPersistentStore;
10+
11+
let nonTransactionalFacade: AsyncStoreFacade;
12+
let transactionalFacade: AsyncStoreFacade;
13+
14+
beforeEach(() => {
15+
mockNontransactionalStore = new InMemoryFeatureStore();
16+
transactionalStore = new TransactionalPersistentStore(mockNontransactionalStore);
17+
18+
// these two facades are used to make test writing easier
19+
nonTransactionalFacade = new AsyncStoreFacade(mockNontransactionalStore);
20+
transactionalFacade = new AsyncStoreFacade(transactionalStore);
21+
});
22+
23+
afterEach(() => {
24+
transactionalFacade.close();
25+
jest.restoreAllMocks();
26+
});
27+
28+
it('applies changes to non transactional store', async () => {
29+
await transactionalFacade.applyChanges(
30+
false,
31+
{
32+
features: {
33+
key1: {
34+
version: 2,
35+
},
36+
},
37+
},
38+
'selector1',
39+
);
40+
expect(await nonTransactionalFacade.all(VersionedDataKinds.Features)).toEqual({
41+
key1: {
42+
key: 'key1',
43+
version: 2,
44+
},
45+
});
46+
expect(await transactionalFacade.all(VersionedDataKinds.Features)).toEqual({
47+
key1: {
48+
key: 'key1',
49+
version: 2,
50+
},
51+
});
52+
});
53+
54+
it('it reads through to non transactional store before basis is provided', async () => {
55+
await nonTransactionalFacade.init({
56+
features: {
57+
key1: {
58+
version: 1,
59+
},
60+
},
61+
});
62+
expect(await transactionalFacade.all(VersionedDataKinds.Features)).toEqual({
63+
key1: {
64+
version: 1,
65+
},
66+
});
67+
});
68+
69+
it('it switches to memory store when basis is provided', async () => {
70+
// situate some mock data in non transactional store
71+
await nonTransactionalFacade.init({
72+
features: {
73+
nontransactionalFeature: {
74+
version: 1,
75+
},
76+
},
77+
});
78+
79+
await transactionalFacade.applyChanges(
80+
true,
81+
{
82+
features: {
83+
key1: {
84+
version: 1,
85+
},
86+
},
87+
},
88+
'selector1',
89+
);
90+
91+
expect(await nonTransactionalFacade.all(VersionedDataKinds.Features)).toEqual({
92+
key1: {
93+
version: 1,
94+
},
95+
});
96+
97+
expect(await transactionalFacade.all(VersionedDataKinds.Features)).toEqual({
98+
key1: {
99+
version: 1,
100+
},
101+
});
102+
103+
// corrupt non transactional store and then read from transactional store to prove it is not
104+
// using underlying non transactional store for reads
105+
await nonTransactionalFacade.init({
106+
features: {
107+
nontransactionalFeature: {
108+
version: 1,
109+
},
110+
},
111+
});
112+
113+
// still should read from memory
114+
expect(await transactionalFacade.all(VersionedDataKinds.Features)).toEqual({
115+
key1: {
116+
version: 1,
117+
},
118+
});
119+
});
120+
});

packages/shared/sdk-server/src/api/subsystems/LDFeatureStore.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ export interface LDFeatureStore {
139139

140140
/**
141141
* Applies the provided data onto the existing data, replacing all data or upserting depending
142-
* on the basis parameter.
142+
* on the basis parameter. Must call {@link applyChanges} providing basis before calling {@link applyChanges}
143+
* that is not a basis.
143144
*
144145
* @param basis If true, completely overwrites the current contents of the data store
145146
* with the provided data. If false, upserts the items in the provided data. Upserts

packages/shared/sdk-server/src/store/AsyncStoreFacade.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ export default class AsyncStoreFacade {
5757
});
5858
}
5959

60+
async applyChanges(
61+
basis: boolean,
62+
data: LDFeatureStoreDataStorage,
63+
selector: String | undefined,
64+
): Promise<void> {
65+
return promisify((cb) => {
66+
this._store.applyChanges(basis, data, selector, cb);
67+
});
68+
}
69+
6070
close(): void {
6171
this._store.close();
6272
}

packages/shared/sdk-server/src/store/InMemoryFeatureStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ export default class InMemoryFeatureStore implements LDFeatureStore {
9595
const old = existingItems[key];
9696
// TODO: SDK-1046 - Determine if version check should be removed
9797
if (!old || old.version < item.version) {
98-
existingItems[key] = item;
98+
existingItems[key] = { key, ...item };
9999
}
100100
} else {
101-
existingItems[key] = item;
101+
existingItems[key] = { key, ...item };
102102
}
103103
});
104104
});

packages/shared/sdk-server/src/store/TransactionalPersistentStore.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default class TransactionalPersistentStore implements LDFeatureStore {
1818
private _activeStore: LDFeatureStore;
1919

2020
constructor(private readonly _nonTransPersistenceStore: LDFeatureStore) {
21+
// persistence store is inital active store
2122
this._activeStore = this._nonTransPersistenceStore;
2223
this._memoryStore = new InMemoryFeatureStore();
2324
}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import AsyncStoreFacade from './AsyncStoreFacade';
22
import PersistentDataStoreWrapper from './PersistentDataStoreWrapper';
33
import { deserializePoll } from './serialization';
4+
import TransactionalPersistentStore from './TransactionalPersistentStore';
45

5-
export { AsyncStoreFacade, PersistentDataStoreWrapper, deserializePoll };
6+
export {
7+
AsyncStoreFacade,
8+
PersistentDataStoreWrapper,
9+
TransactionalPersistentStore,
10+
deserializePoll,
11+
};

0 commit comments

Comments
 (0)