@powersync/service-core-tests 0.15.5 → 0.17.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/CHANGELOG.md +44 -0
- package/dist/test-utils/AbstractStreamTestContext.d.ts +52 -0
- package/dist/test-utils/AbstractStreamTestContext.js +147 -0
- package/dist/test-utils/AbstractStreamTestContext.js.map +1 -0
- package/dist/test-utils/StorageDataHelpers.d.ts +3 -3
- package/dist/test-utils/StorageDataHelpers.js.map +1 -1
- package/dist/test-utils/general-utils.d.ts +13 -8
- package/dist/test-utils/general-utils.js +39 -25
- package/dist/test-utils/general-utils.js.map +1 -1
- package/dist/test-utils/test-utils-index.d.ts +1 -0
- package/dist/test-utils/test-utils-index.js +1 -0
- package/dist/test-utils/test-utils-index.js.map +1 -1
- package/dist/tests/register-compacting-tests.js +15 -8
- package/dist/tests/register-compacting-tests.js.map +1 -1
- package/dist/tests/register-data-storage-data-tests.js +104 -75
- package/dist/tests/register-data-storage-data-tests.js.map +1 -1
- package/dist/tests/register-data-storage-parameter-tests.js +48 -47
- package/dist/tests/register-data-storage-parameter-tests.js.map +1 -1
- package/dist/tests/register-sync-tests.js +6 -3
- package/dist/tests/register-sync-tests.js.map +1 -1
- package/dist/tests/util.d.ts +3 -4
- package/dist/tests/util.js +8 -5
- package/dist/tests/util.js.map +1 -1
- package/package.json +5 -5
- package/src/test-utils/AbstractStreamTestContext.ts +183 -0
- package/src/test-utils/StorageDataHelpers.ts +4 -4
- package/src/test-utils/general-utils.ts +52 -34
- package/src/test-utils/test-utils-index.ts +1 -0
- package/src/tests/register-compacting-tests.ts +15 -8
- package/src/tests/register-data-storage-data-tests.ts +130 -77
- package/src/tests/register-data-storage-parameter-tests.ts +62 -47
- package/src/tests/register-sync-tests.ts +6 -3
- package/src/tests/util.ts +14 -11
- package/tsconfig.json +4 -4
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
InternalOpId,
|
|
3
3
|
OplogEntry,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
ParsedSyncConfigSet,
|
|
5
|
+
PersistedSyncConfigContent,
|
|
6
6
|
SyncRulesBucketStorage
|
|
7
7
|
} from '@powersync/service-core';
|
|
8
8
|
import { bucketRequest } from './general-utils.js';
|
|
@@ -10,9 +10,9 @@ import { fromAsync } from './stream_utils.js';
|
|
|
10
10
|
|
|
11
11
|
export class StorageDataHelpers {
|
|
12
12
|
storage: SyncRulesBucketStorage;
|
|
13
|
-
syncRules:
|
|
13
|
+
syncRules: PersistedSyncConfigContent | ParsedSyncConfigSet;
|
|
14
14
|
|
|
15
|
-
constructor(storage: SyncRulesBucketStorage, syncRules:
|
|
15
|
+
constructor(storage: SyncRulesBucketStorage, syncRules: PersistedSyncConfigContent | ParsedSyncConfigSet) {
|
|
16
16
|
this.storage = storage;
|
|
17
17
|
this.syncRules = syncRules;
|
|
18
18
|
}
|
|
@@ -4,7 +4,7 @@ import * as bson from 'bson';
|
|
|
4
4
|
|
|
5
5
|
export const ZERO_LSN = '0/0';
|
|
6
6
|
|
|
7
|
-
export const PARSE_OPTIONS: storage.
|
|
7
|
+
export const PARSE_OPTIONS: storage.ParseSyncConfigOptions = {
|
|
8
8
|
defaultSchema: 'public'
|
|
9
9
|
};
|
|
10
10
|
|
|
@@ -14,50 +14,65 @@ export const BATCH_OPTIONS: storage.CreateWriterOptions = {
|
|
|
14
14
|
storeCurrentData: true
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export function makeTestTable(
|
|
18
|
-
name: string,
|
|
19
|
-
replicaIdColumns?: string[] | undefined,
|
|
20
|
-
options?: { tableIdStrings: boolean }
|
|
21
|
-
) {
|
|
22
|
-
const relId = utils.hashData('table', name, (replicaIdColumns ?? ['id']).join(','));
|
|
23
|
-
const id =
|
|
24
|
-
options?.tableIdStrings == false ? new bson.ObjectId('6544e3899293153fa7b38331') : '6544e3899293153fa7b38331';
|
|
25
|
-
return new storage.SourceTable({
|
|
26
|
-
id: id,
|
|
27
|
-
connectionTag: storage.SourceTable.DEFAULT_TAG,
|
|
28
|
-
objectId: relId,
|
|
29
|
-
schema: 'public',
|
|
30
|
-
name: name,
|
|
31
|
-
replicaIdColumns: (replicaIdColumns ?? ['id']).map((column) => ({ name: column, type: 'VARCHAR', typeId: 25 })),
|
|
32
|
-
snapshotComplete: true
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
17
|
/**
|
|
36
|
-
*
|
|
18
|
+
* Deploy a sync config and return both the replication stream (for {@link storage.BucketStorageFactory.getInstance})
|
|
19
|
+
* and the deployed config content (for parsing / bucket requests).
|
|
37
20
|
*
|
|
38
|
-
*
|
|
21
|
+
* Replication streams and sync config content are separate concerns since one stream can hold multiple configs.
|
|
22
|
+
*/
|
|
23
|
+
export async function deploySyncRules(
|
|
24
|
+
factory: storage.BucketStorageFactory,
|
|
25
|
+
options: storage.UpdateSyncRulesOptions
|
|
26
|
+
): Promise<{ stream: storage.PersistedReplicationStream; content: storage.PersistedSyncConfigContent }> {
|
|
27
|
+
const stream = await factory.updateSyncRules(options);
|
|
28
|
+
const content = stream.syncConfigContent[0];
|
|
29
|
+
return { stream, content };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* With newer storage versions, we need actual test tables, resolved via the writer.
|
|
39
34
|
*/
|
|
40
35
|
export async function resolveTestTable(
|
|
41
|
-
|
|
36
|
+
writer: storage.BucketStorageBatch,
|
|
42
37
|
name: string,
|
|
43
38
|
replicaIdColumns: string[] | undefined,
|
|
44
39
|
options: { tableIdStrings: boolean },
|
|
45
40
|
idIndex: number = 1
|
|
46
41
|
) {
|
|
42
|
+
void idIndex;
|
|
43
|
+
void options;
|
|
44
|
+
|
|
47
45
|
const relId = utils.hashData('table', name, (replicaIdColumns ?? ['id']).join(','));
|
|
48
|
-
//
|
|
49
|
-
//
|
|
46
|
+
// Semi-hardcoded id for tests, to get consistent output.
|
|
47
|
+
// If the same test uses multiple tables, pass idIndex to get different ids.
|
|
50
48
|
const idString = '6544e3899293153fa7b383' + (30 + idIndex).toString().padStart(2, '0');
|
|
51
49
|
const id = options.tableIdStrings == false ? new bson.ObjectId(idString) : idString;
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
let didGenerateId = false;
|
|
51
|
+
|
|
52
|
+
const source: storage.SourceEntityDescriptor = {
|
|
54
53
|
connectionTag: storage.SourceTable.DEFAULT_TAG,
|
|
55
54
|
objectId: relId,
|
|
56
55
|
schema: 'public',
|
|
57
56
|
name: name,
|
|
58
|
-
replicaIdColumns: (replicaIdColumns ?? ['id']).map((column) => ({ name: column, type: 'VARCHAR', typeId: 25 }))
|
|
59
|
-
|
|
57
|
+
replicaIdColumns: (replicaIdColumns ?? ['id']).map((column) => ({ name: column, type: 'VARCHAR', typeId: 25 }))
|
|
58
|
+
};
|
|
59
|
+
const resolved = await writer.resolveTables({
|
|
60
|
+
connection_id: 1,
|
|
61
|
+
source,
|
|
62
|
+
idGenerator: () => {
|
|
63
|
+
if (didGenerateId) {
|
|
64
|
+
throw new Error('idGenerator called multiple times - not supported in tests');
|
|
65
|
+
}
|
|
66
|
+
didGenerateId = true;
|
|
67
|
+
return id;
|
|
68
|
+
}
|
|
60
69
|
});
|
|
70
|
+
|
|
71
|
+
const table = resolved.tables[0];
|
|
72
|
+
if (table == null) {
|
|
73
|
+
throw new Error(`Failed to resolve test table ${source.schema}.${source.name}`);
|
|
74
|
+
}
|
|
75
|
+
return table;
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
export function getBatchData(
|
|
@@ -78,9 +93,9 @@ export function getBatchData(
|
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
function isParsedSyncRules(
|
|
81
|
-
syncRules: storage.
|
|
82
|
-
): syncRules is storage.
|
|
83
|
-
return (syncRules as storage.
|
|
96
|
+
syncRules: storage.PersistedSyncConfigContent | storage.ParsedSyncConfigSet
|
|
97
|
+
): syncRules is storage.ParsedSyncConfigSet {
|
|
98
|
+
return (syncRules as storage.ParsedSyncConfigSet).syncConfigs !== undefined;
|
|
84
99
|
}
|
|
85
100
|
|
|
86
101
|
/**
|
|
@@ -88,7 +103,7 @@ function isParsedSyncRules(
|
|
|
88
103
|
* This converts a bucket name like "global[]" into the actual bucket name, for use in tests.
|
|
89
104
|
*/
|
|
90
105
|
export function bucketRequest(
|
|
91
|
-
syncRules: storage.
|
|
106
|
+
syncRules: storage.PersistedSyncConfigContent | storage.ParsedSyncConfigSet,
|
|
92
107
|
bucket: string,
|
|
93
108
|
start?: InternalOpId | string | number
|
|
94
109
|
): BucketDataRequest {
|
|
@@ -97,10 +112,13 @@ export function bucketRequest(
|
|
|
97
112
|
const parameterStart = bucket.indexOf('[');
|
|
98
113
|
const definitionName = bucket.substring(0, parameterStart);
|
|
99
114
|
const parameters = bucket.substring(parameterStart);
|
|
100
|
-
const
|
|
115
|
+
const availableSources = parsed.syncConfigs.flatMap((config) => config.config.bucketDataSources);
|
|
116
|
+
const source = availableSources.find((b) => b.uniqueName === definitionName);
|
|
101
117
|
|
|
102
118
|
if (source == null) {
|
|
103
|
-
throw new Error(
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Failed to find global bucket ${bucket}. Available: ${availableSources.map((s) => s.uniqueName).join(',')}`
|
|
121
|
+
);
|
|
104
122
|
}
|
|
105
123
|
const bucketName = hydrationState.getBucketSourceScope(source).bucketPrefix + parameters;
|
|
106
124
|
return {
|
|
@@ -17,6 +17,7 @@ bucket_definitions:
|
|
|
17
17
|
`)
|
|
18
18
|
);
|
|
19
19
|
const bucketStorage = factory.getInstance(syncRules);
|
|
20
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
20
21
|
|
|
21
22
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
22
23
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -53,7 +54,7 @@ bucket_definitions:
|
|
|
53
54
|
|
|
54
55
|
const checkpoint = writer.last_flushed_op!;
|
|
55
56
|
|
|
56
|
-
const request = bucketRequest(
|
|
57
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
57
58
|
|
|
58
59
|
const batchBefore = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
59
60
|
const dataBefore = batchBefore.chunkData.data;
|
|
@@ -124,6 +125,7 @@ bucket_definitions:
|
|
|
124
125
|
`)
|
|
125
126
|
);
|
|
126
127
|
const bucketStorage = factory.getInstance(syncRules);
|
|
128
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
127
129
|
|
|
128
130
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
129
131
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -168,7 +170,7 @@ bucket_definitions:
|
|
|
168
170
|
await writer.flush();
|
|
169
171
|
|
|
170
172
|
const checkpoint = writer.last_flushed_op!;
|
|
171
|
-
const request = bucketRequest(
|
|
173
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
172
174
|
|
|
173
175
|
const batchBefore = await test_utils.oneFromAsync(bucketStorage.getBucketDataBatch(checkpoint, [request]));
|
|
174
176
|
const dataBefore = batchBefore.chunkData.data;
|
|
@@ -240,6 +242,7 @@ bucket_definitions:
|
|
|
240
242
|
`)
|
|
241
243
|
);
|
|
242
244
|
const bucketStorage = factory.getInstance(syncRules);
|
|
245
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
243
246
|
|
|
244
247
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
245
248
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -275,7 +278,7 @@ bucket_definitions:
|
|
|
275
278
|
await writer.flush();
|
|
276
279
|
|
|
277
280
|
const checkpoint1 = writer.last_flushed_op!;
|
|
278
|
-
const request = bucketRequest(
|
|
281
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
279
282
|
await using writer2 = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
280
283
|
const testTable2 = await test_utils.resolveTestTable(writer2, 'test', ['id'], config);
|
|
281
284
|
await writer2.save({
|
|
@@ -328,6 +331,7 @@ bucket_definitions:
|
|
|
328
331
|
- select * from test where b = bucket.b`)
|
|
329
332
|
);
|
|
330
333
|
const bucketStorage = factory.getInstance(syncRules);
|
|
334
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
331
335
|
|
|
332
336
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
333
337
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -413,7 +417,7 @@ bucket_definitions:
|
|
|
413
417
|
const batchAfter = await test_utils.fromAsync(
|
|
414
418
|
bucketStorage.getBucketDataBatch(
|
|
415
419
|
checkpoint,
|
|
416
|
-
bucketRequestMap(
|
|
420
|
+
bucketRequestMap(syncRulesContent, [
|
|
417
421
|
['grouped["b1"]', 0n],
|
|
418
422
|
['grouped["b2"]', 0n]
|
|
419
423
|
])
|
|
@@ -458,6 +462,7 @@ bucket_definitions:
|
|
|
458
462
|
`)
|
|
459
463
|
);
|
|
460
464
|
const bucketStorage = factory.getInstance(syncRules);
|
|
465
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
461
466
|
|
|
462
467
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
463
468
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -513,7 +518,7 @@ bucket_definitions:
|
|
|
513
518
|
await writer2.commit('2/1');
|
|
514
519
|
await writer2.flush();
|
|
515
520
|
const checkpoint2 = writer2.last_flushed_op!;
|
|
516
|
-
const request = bucketRequest(
|
|
521
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
517
522
|
await bucketStorage.clearChecksumCache();
|
|
518
523
|
const checksumAfter = await bucketStorage.getChecksums(checkpoint2, [request]);
|
|
519
524
|
const globalChecksum = checksumAfter.get(request.bucket);
|
|
@@ -536,6 +541,7 @@ bucket_definitions:
|
|
|
536
541
|
`)
|
|
537
542
|
);
|
|
538
543
|
const bucketStorage = factory.getInstance(syncRules);
|
|
544
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
539
545
|
|
|
540
546
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
541
547
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -562,7 +568,7 @@ bucket_definitions:
|
|
|
562
568
|
await writer.flush();
|
|
563
569
|
|
|
564
570
|
// Get checksums here just to populate the cache
|
|
565
|
-
await bucketStorage.getChecksums(writer.last_flushed_op!, bucketRequests(
|
|
571
|
+
await bucketStorage.getChecksums(writer.last_flushed_op!, bucketRequests(syncRulesContent, ['global[]']));
|
|
566
572
|
await using writer2 = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
567
573
|
const testTable2 = await test_utils.resolveTestTable(writer2, 'test', ['id'], config);
|
|
568
574
|
await writer2.save({
|
|
@@ -585,7 +591,7 @@ bucket_definitions:
|
|
|
585
591
|
});
|
|
586
592
|
|
|
587
593
|
const checkpoint2 = writer2.last_flushed_op!;
|
|
588
|
-
const request = bucketRequest(
|
|
594
|
+
const request = bucketRequest(syncRulesContent, 'global[]');
|
|
589
595
|
// Check that the checksum was correctly updated with the clear operation after having a cached checksum
|
|
590
596
|
const checksumAfter = await bucketStorage.getChecksums(checkpoint2, [request]);
|
|
591
597
|
const globalChecksum = checksumAfter.get(request.bucket);
|
|
@@ -607,6 +613,7 @@ bucket_definitions:
|
|
|
607
613
|
`)
|
|
608
614
|
);
|
|
609
615
|
const bucketStorage = factory.getInstance(syncRules);
|
|
616
|
+
const syncRulesContent = syncRules.syncConfigContent[0];
|
|
610
617
|
|
|
611
618
|
await using writer = await bucketStorage.createWriter(test_utils.BATCH_OPTIONS);
|
|
612
619
|
const testTable = await test_utils.resolveTestTable(writer, 'test', ['id'], config);
|
|
@@ -646,7 +653,7 @@ bucket_definitions:
|
|
|
646
653
|
});
|
|
647
654
|
|
|
648
655
|
const batchAfterDefaultCompact = await test_utils.oneFromAsync(
|
|
649
|
-
bucketStorage.getBucketDataBatch(checkpoint2, bucketRequestMap(
|
|
656
|
+
bucketStorage.getBucketDataBatch(checkpoint2, bucketRequestMap(syncRulesContent, [['global[]', 0n]]))
|
|
650
657
|
);
|
|
651
658
|
|
|
652
659
|
// Operation 1 should remain a PUT because op_id=2 is above the default maxOpId checkpoint.
|