@powersync/service-core 1.22.0 → 1.23.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/CHANGELOG.md +23 -0
- package/dist/api/RouteAPI.d.ts +2 -2
- package/dist/api/diagnostics.d.ts +12 -1
- package/dist/api/diagnostics.js +34 -22
- package/dist/api/diagnostics.js.map +1 -1
- package/dist/entry/commands/compact-action.js +1 -1
- package/dist/entry/commands/compact-action.js.map +1 -1
- package/dist/modules/AbstractModule.d.ts +6 -2
- package/dist/modules/AbstractModule.js.map +1 -1
- package/dist/replication/AbstractReplicator.d.ts +14 -3
- package/dist/replication/AbstractReplicator.js +122 -62
- package/dist/replication/AbstractReplicator.js.map +1 -1
- package/dist/replication/ReplicationModule.d.ts +3 -3
- package/dist/replication/ReplicationModule.js.map +1 -1
- package/dist/routes/configure-fastify.d.ts +12 -12
- package/dist/routes/endpoints/admin.d.ts +24 -24
- package/dist/routes/endpoints/admin.js +46 -35
- package/dist/routes/endpoints/admin.js.map +1 -1
- package/dist/routes/endpoints/checkpointing.js +1 -1
- package/dist/routes/endpoints/checkpointing.js.map +1 -1
- package/dist/routes/endpoints/socket-route.js +1 -1
- package/dist/routes/endpoints/socket-route.js.map +1 -1
- package/dist/routes/endpoints/sync-rules.js +22 -13
- package/dist/routes/endpoints/sync-rules.js.map +1 -1
- package/dist/routes/endpoints/sync-stream.js +1 -1
- package/dist/routes/endpoints/sync-stream.js.map +1 -1
- package/dist/runner/teardown.js +8 -8
- package/dist/runner/teardown.js.map +1 -1
- package/dist/storage/BucketStorageBatch.d.ts +0 -6
- package/dist/storage/BucketStorageBatch.js.map +1 -1
- package/dist/storage/BucketStorageFactory.d.ts +22 -27
- package/dist/storage/BucketStorageFactory.js +4 -18
- package/dist/storage/BucketStorageFactory.js.map +1 -1
- package/dist/storage/ParsedSyncConfigSet.d.ts +23 -0
- package/dist/storage/ParsedSyncConfigSet.js +2 -0
- package/dist/storage/ParsedSyncConfigSet.js.map +1 -0
- package/dist/storage/PersistedReplicationStream.d.ts +32 -0
- package/dist/storage/PersistedReplicationStream.js +26 -0
- package/dist/storage/PersistedReplicationStream.js.map +1 -0
- package/dist/storage/PersistedSyncConfigContent.d.ts +63 -0
- package/dist/storage/PersistedSyncConfigContent.js +125 -0
- package/dist/storage/PersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/PersistedSyncConfigStatus.d.ts +13 -0
- package/dist/storage/PersistedSyncConfigStatus.js +2 -0
- package/dist/storage/PersistedSyncConfigStatus.js.map +1 -0
- package/dist/storage/SourceTable.d.ts +9 -3
- package/dist/storage/SourceTable.js +13 -3
- package/dist/storage/SourceTable.js.map +1 -1
- package/dist/storage/SyncRulesBucketStorage.d.ts +59 -16
- package/dist/storage/SyncRulesBucketStorage.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +117 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js +277 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.d.ts +6 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.js +31 -0
- package/dist/storage/implementation/IncrementalReprocessingCompatibility.js.map +1 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.d.ts +17 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js +62 -0
- package/dist/storage/implementation/IncrementalReprocessingSyncConfigLog.js.map +1 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.d.ts +15 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.js +20 -0
- package/dist/storage/implementation/ReplicationStreamStorageIds.js.map +1 -0
- package/dist/storage/storage-index.d.ts +8 -1
- package/dist/storage/storage-index.js +8 -1
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/sync/sync.js +5 -0
- package/dist/sync/sync.js.map +1 -1
- package/dist/util/checkpointing.js +1 -1
- package/dist/util/checkpointing.js.map +1 -1
- package/dist/util/config/types.d.ts +1 -1
- package/dist/util/lsn.d.ts +4 -0
- package/dist/util/lsn.js +10 -0
- package/dist/util/lsn.js.map +1 -1
- package/package.json +6 -7
- package/src/api/RouteAPI.ts +2 -2
- package/src/api/diagnostics.ts +43 -26
- package/src/entry/commands/compact-action.ts +1 -1
- package/src/modules/AbstractModule.ts +6 -2
- package/src/replication/AbstractReplicator.ts +145 -65
- package/src/replication/ReplicationModule.ts +2 -3
- package/src/routes/endpoints/admin.ts +72 -50
- package/src/routes/endpoints/checkpointing.ts +1 -1
- package/src/routes/endpoints/socket-route.ts +1 -1
- package/src/routes/endpoints/sync-rules.ts +23 -13
- package/src/routes/endpoints/sync-stream.ts +1 -1
- package/src/runner/teardown.ts +8 -8
- package/src/storage/BucketStorageBatch.ts +0 -7
- package/src/storage/BucketStorageFactory.ts +30 -39
- package/src/storage/ParsedSyncConfigSet.ts +25 -0
- package/src/storage/PersistedReplicationStream.ts +55 -0
- package/src/storage/PersistedSyncConfigContent.ts +192 -0
- package/src/storage/PersistedSyncConfigStatus.ts +14 -0
- package/src/storage/SourceTable.ts +20 -3
- package/src/storage/SyncRulesBucketStorage.ts +64 -16
- package/src/storage/implementation/BucketDefinitionMapping.ts +436 -0
- package/src/storage/implementation/IncrementalReprocessingCompatibility.ts +41 -0
- package/src/storage/implementation/IncrementalReprocessingSyncConfigLog.ts +109 -0
- package/src/storage/implementation/ReplicationStreamStorageIds.ts +21 -0
- package/src/storage/storage-index.ts +9 -1
- package/src/sync/sync.ts +5 -0
- package/src/util/checkpointing.ts +1 -1
- package/src/util/config/types.ts +1 -1
- package/src/util/lsn.ts +9 -0
- package/test/src/diagnostics.test.ts +62 -34
- package/test/src/module-loader.test.ts +33 -1
- package/test/src/routes/admin.test.ts +182 -18
- package/test/src/routes/mocks.ts +6 -2
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/PersistedSyncRulesContent.d.ts +0 -59
- package/dist/storage/PersistedSyncRulesContent.js +0 -100
- package/dist/storage/PersistedSyncRulesContent.js.map +0 -1
- package/src/storage/PersistedSyncRulesContent.ts +0 -174
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DiagnosticsOptions, getSyncRulesStatus } from '@/api/diagnostics.js';
|
|
2
2
|
import { RouteAPI, SlotWalBudgetInfo } from '@/api/RouteAPI.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ParsedSyncConfigSet, storage } from '@/index.js';
|
|
4
4
|
import { SqlSyncRules } from '@powersync/service-sync-rules';
|
|
5
5
|
import { describe, expect, test } from 'vitest';
|
|
6
6
|
|
|
@@ -13,53 +13,57 @@ bucket_definitions:
|
|
|
13
13
|
- SELECT id FROM test_table
|
|
14
14
|
`;
|
|
15
15
|
|
|
16
|
-
function makeSyncRulesContent(overrides?: {
|
|
16
|
+
function makeSyncRulesContent(overrides?: {
|
|
17
|
+
slot_name?: string;
|
|
18
|
+
status?: storage.PersistedSyncConfigStatus;
|
|
19
|
+
}): storage.PersistedSyncConfigContent {
|
|
17
20
|
// We don't implement the entire interface correctly here - just enough to test the diagnostics logic.
|
|
18
|
-
|
|
19
|
-
id: 1,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
compiled_plan: null,
|
|
23
|
-
active: true,
|
|
24
|
-
storageVersion: 1,
|
|
21
|
+
const status = overrides?.status ?? {
|
|
22
|
+
id: '1',
|
|
23
|
+
replicationStreamId: 1,
|
|
24
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
25
25
|
last_checkpoint_lsn: 'some_lsn',
|
|
26
26
|
last_fatal_error: null,
|
|
27
27
|
last_fatal_error_ts: null,
|
|
28
28
|
last_keepalive_ts: new Date(),
|
|
29
|
-
last_checkpoint_ts: new Date()
|
|
29
|
+
last_checkpoint_ts: new Date()
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
replicationStreamId: 1,
|
|
34
|
+
syncConfigId: null,
|
|
35
|
+
replicationStreamName: overrides?.slot_name ?? 'test_slot',
|
|
36
|
+
sync_rules_content: MINIMAL_SYNC_RULES,
|
|
37
|
+
compiled_plan: null,
|
|
38
|
+
storageVersion: 1,
|
|
39
|
+
syncConfigState: status.state,
|
|
30
40
|
parsed(options?: any) {
|
|
31
41
|
const syncRules = SqlSyncRules.fromYaml(MINIMAL_SYNC_RULES, {
|
|
32
42
|
...options,
|
|
33
43
|
defaultSchema: 'public'
|
|
34
44
|
});
|
|
35
45
|
return {
|
|
36
|
-
|
|
37
|
-
} as
|
|
38
|
-
},
|
|
39
|
-
lock() {
|
|
40
|
-
throw new Error('Not implemented in mock');
|
|
46
|
+
syncConfigs: [syncRules]
|
|
47
|
+
} as ParsedSyncConfigSet;
|
|
41
48
|
},
|
|
42
|
-
current_lock: null,
|
|
43
49
|
logger: null as any,
|
|
44
50
|
asUpdateOptions: null as any,
|
|
45
|
-
getStorageConfig: null as any
|
|
46
|
-
|
|
51
|
+
getStorageConfig: null as any,
|
|
52
|
+
async getSyncConfigStatus() {
|
|
53
|
+
return status;
|
|
54
|
+
}
|
|
55
|
+
} as storage.PersistedSyncConfigContent;
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
function
|
|
58
|
+
function makeSystemStorage() {
|
|
50
59
|
return {
|
|
51
|
-
|
|
60
|
+
async getStatus() {
|
|
52
61
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
snapshot_done: true,
|
|
56
|
-
checkpoint_lsn: 'some_lsn',
|
|
57
|
-
active: true
|
|
58
|
-
};
|
|
59
|
-
}
|
|
62
|
+
snapshotDone: true,
|
|
63
|
+
resumeLsn: 'some_lsn'
|
|
60
64
|
};
|
|
61
65
|
}
|
|
62
|
-
} as
|
|
66
|
+
} as storage.SyncRulesBucketStorage;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
function makeRouteAPI(walBudget?: SlotWalBudgetInfo | undefined): RouteAPI {
|
|
@@ -92,7 +96,8 @@ function makeRouteAPI(walBudget?: SlotWalBudgetInfo | undefined): RouteAPI {
|
|
|
92
96
|
const OPTIONS: DiagnosticsOptions = {
|
|
93
97
|
live_status: true,
|
|
94
98
|
check_connection: true,
|
|
95
|
-
include_content: false
|
|
99
|
+
include_content: false,
|
|
100
|
+
active: true
|
|
96
101
|
};
|
|
97
102
|
|
|
98
103
|
describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
@@ -102,7 +107,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
102
107
|
safe_wal_size: 4 * GB,
|
|
103
108
|
max_slot_wal_keep_size: 10 * GB
|
|
104
109
|
});
|
|
105
|
-
const result = await getSyncRulesStatus(
|
|
110
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
106
111
|
const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
|
|
107
112
|
expect(walWarnings).toHaveLength(1);
|
|
108
113
|
expect(walWarnings[0].level).toBe('warning');
|
|
@@ -115,7 +120,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
115
120
|
safe_wal_size: 8 * GB,
|
|
116
121
|
max_slot_wal_keep_size: 10 * GB
|
|
117
122
|
});
|
|
118
|
-
const result = await getSyncRulesStatus(
|
|
123
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
119
124
|
const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
|
|
120
125
|
expect(walWarnings).toHaveLength(0);
|
|
121
126
|
});
|
|
@@ -126,7 +131,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
126
131
|
safe_wal_size: -2.4 * GB,
|
|
127
132
|
max_slot_wal_keep_size: 1 * 1024 * 1024 // 1MB
|
|
128
133
|
});
|
|
129
|
-
const result = await getSyncRulesStatus(
|
|
134
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
130
135
|
const walWarnings = result!.errors.filter((e) => e.message.includes('WAL budget'));
|
|
131
136
|
expect(walWarnings).toHaveLength(1);
|
|
132
137
|
expect(walWarnings[0].message).toContain('0%');
|
|
@@ -137,7 +142,7 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
137
142
|
const api = makeRouteAPI({
|
|
138
143
|
wal_status: 'lost'
|
|
139
144
|
});
|
|
140
|
-
const result = await getSyncRulesStatus(
|
|
145
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
141
146
|
const walErrors = result!.errors.filter(
|
|
142
147
|
(e) => e.message.includes('WAL budget') || e.message.includes('PSYNC_S1146')
|
|
143
148
|
);
|
|
@@ -146,10 +151,33 @@ describe('getSyncRulesStatus WAL budget warnings', () => {
|
|
|
146
151
|
|
|
147
152
|
test('no WAL error when getSlotWalBudget is not defined', async () => {
|
|
148
153
|
const api = makeRouteAPI();
|
|
149
|
-
const result = await getSyncRulesStatus(
|
|
154
|
+
const result = await getSyncRulesStatus(api, makeSyncRulesContent(), OPTIONS, makeSystemStorage());
|
|
150
155
|
const walErrors = result!.errors.filter(
|
|
151
156
|
(e) => e.message.includes('WAL budget') || e.message.includes('PSYNC_S1146')
|
|
152
157
|
);
|
|
153
158
|
expect(walErrors).toHaveLength(0);
|
|
154
159
|
});
|
|
160
|
+
|
|
161
|
+
test('uses sync config status for status-derived diagnostics fields', async () => {
|
|
162
|
+
const configStatus: storage.PersistedSyncConfigStatus = {
|
|
163
|
+
id: 'config-a',
|
|
164
|
+
replicationStreamId: 1,
|
|
165
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
166
|
+
last_checkpoint_lsn: 'config_lsn',
|
|
167
|
+
last_fatal_error: 'config failed',
|
|
168
|
+
last_fatal_error_ts: new Date('2026-01-01T00:00:00.000Z'),
|
|
169
|
+
last_keepalive_ts: new Date('2026-01-01T00:01:00.000Z'),
|
|
170
|
+
last_checkpoint_ts: new Date('2026-01-01T00:02:00.000Z')
|
|
171
|
+
};
|
|
172
|
+
const result = await getSyncRulesStatus(
|
|
173
|
+
makeRouteAPI(),
|
|
174
|
+
makeSyncRulesContent({ status: configStatus }),
|
|
175
|
+
OPTIONS,
|
|
176
|
+
makeSystemStorage()
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
expect(result!.connections[0].last_checkpoint_ts).toBe('2026-01-01T00:02:00.000Z');
|
|
180
|
+
expect(result!.connections[0].last_keepalive_ts).toBe('2026-01-01T00:01:00.000Z');
|
|
181
|
+
expect(result!.errors.some((error) => error.message == 'config failed')).toBe(true);
|
|
182
|
+
});
|
|
155
183
|
});
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
AbstractModule,
|
|
3
|
+
loadModules,
|
|
4
|
+
ModuleManager,
|
|
5
|
+
ServiceContextContainer,
|
|
6
|
+
storage,
|
|
7
|
+
TearDownOptions
|
|
8
|
+
} from '@/index.js';
|
|
2
9
|
import { describe, expect, it } from 'vitest';
|
|
3
10
|
|
|
4
11
|
interface MockConfig {
|
|
@@ -27,6 +34,19 @@ class MockPostgresStorageModule extends AbstractModule {
|
|
|
27
34
|
async initialize(context: ServiceContextContainer): Promise<void> {}
|
|
28
35
|
async teardown(options: TearDownOptions): Promise<void> {}
|
|
29
36
|
}
|
|
37
|
+
class RecordingModule extends AbstractModule {
|
|
38
|
+
teardownOptions: TearDownOptions | undefined;
|
|
39
|
+
|
|
40
|
+
constructor(name: string) {
|
|
41
|
+
super({ name });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async initialize(context: ServiceContextContainer): Promise<void> {}
|
|
45
|
+
|
|
46
|
+
async teardown(options: TearDownOptions): Promise<void> {
|
|
47
|
+
this.teardownOptions = options;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
30
50
|
const mockLoaders = {
|
|
31
51
|
connection: {
|
|
32
52
|
mysql: async () => {
|
|
@@ -99,4 +119,16 @@ describe('module loader', () => {
|
|
|
99
119
|
|
|
100
120
|
await expect(loadModules(config as any, loaders)).rejects.toThrowError('Failed to load MySQL module');
|
|
101
121
|
});
|
|
122
|
+
|
|
123
|
+
it('passes replication streams through teardown options', async () => {
|
|
124
|
+
const module = new RecordingModule('RecordingModule');
|
|
125
|
+
const moduleManager = new ModuleManager();
|
|
126
|
+
const replicationStreams = [{ replicationStreamId: 1 }] as storage.PersistedReplicationStream[];
|
|
127
|
+
|
|
128
|
+
moduleManager.register([module]);
|
|
129
|
+
await moduleManager.tearDown({ replicationStreams, syncRules: replicationStreams });
|
|
130
|
+
|
|
131
|
+
expect(module.teardownOptions?.replicationStreams).toBe(replicationStreams);
|
|
132
|
+
expect(module.teardownOptions?.syncRules).toBe(replicationStreams);
|
|
133
|
+
});
|
|
102
134
|
});
|
|
@@ -1,27 +1,92 @@
|
|
|
1
|
-
import { BasicRouterRequest, Context, JwtPayload } from '@/index.js';
|
|
1
|
+
import { BasicRouterRequest, Context, JwtPayload, ParsedSyncConfigSet, storage } from '@/index.js';
|
|
2
2
|
import { logger } from '@powersync/lib-services-framework';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { SqlSyncRules } from '@powersync/service-sync-rules';
|
|
4
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { diagnostics, reprocess, validate } from '../../../src/routes/endpoints/admin.js';
|
|
5
6
|
import { mockServiceContext } from './mocks.js';
|
|
6
7
|
|
|
7
8
|
describe('admin routes', () => {
|
|
9
|
+
const request: BasicRouterRequest = {
|
|
10
|
+
headers: {},
|
|
11
|
+
hostname: '',
|
|
12
|
+
protocol: 'http'
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function makeContext(activeBucketStorage?: any): Context {
|
|
16
|
+
const service_context = mockServiceContext(null);
|
|
17
|
+
if (activeBucketStorage != null) {
|
|
18
|
+
(service_context.storageEngine as any).activeBucketStorage = activeBucketStorage;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
logger: logger,
|
|
23
|
+
service_context,
|
|
24
|
+
token_payload: new JwtPayload({
|
|
25
|
+
sub: '',
|
|
26
|
+
exp: 0,
|
|
27
|
+
iat: 0
|
|
28
|
+
})
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function makeSyncConfigContent(options: {
|
|
33
|
+
id?: number;
|
|
34
|
+
syncConfigId?: string;
|
|
35
|
+
active?: boolean;
|
|
36
|
+
content?: string;
|
|
37
|
+
}): storage.PersistedSyncConfigContent {
|
|
38
|
+
const id = options.id ?? 1;
|
|
39
|
+
const syncConfigId = options.syncConfigId ?? String(id);
|
|
40
|
+
const active = options.active ?? true;
|
|
41
|
+
const state = active ? storage.SyncRuleState.ACTIVE : storage.SyncRuleState.PROCESSING;
|
|
42
|
+
const lastKeepaliveTs = new Date('2026-01-01T00:00:00.000Z');
|
|
43
|
+
const lastCheckpointTs = new Date('2026-01-01T00:00:00.000Z');
|
|
44
|
+
const syncConfigStatus = {
|
|
45
|
+
id: syncConfigId,
|
|
46
|
+
replicationStreamId: id,
|
|
47
|
+
state,
|
|
48
|
+
last_checkpoint_lsn: null,
|
|
49
|
+
last_fatal_error: null,
|
|
50
|
+
last_fatal_error_ts: null,
|
|
51
|
+
last_keepalive_ts: lastKeepaliveTs,
|
|
52
|
+
last_checkpoint_ts: lastCheckpointTs
|
|
53
|
+
};
|
|
54
|
+
const content = {
|
|
55
|
+
replicationStreamId: id,
|
|
56
|
+
syncConfigId,
|
|
57
|
+
replicationStreamName: `slot_${id}`,
|
|
58
|
+
sync_rules_content:
|
|
59
|
+
options.content ??
|
|
60
|
+
`
|
|
61
|
+
bucket_definitions:
|
|
62
|
+
global:
|
|
63
|
+
data:
|
|
64
|
+
- SELECT id FROM test
|
|
65
|
+
`,
|
|
66
|
+
compiled_plan: null,
|
|
67
|
+
storageVersion: storage.LEGACY_STORAGE_VERSION,
|
|
68
|
+
syncConfigState: syncConfigStatus.state,
|
|
69
|
+
parsed(options?: any) {
|
|
70
|
+
const syncRules = SqlSyncRules.fromYaml(content.sync_rules_content, {
|
|
71
|
+
...options,
|
|
72
|
+
defaultSchema: 'public'
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
syncConfigs: [syncRules]
|
|
76
|
+
} as ParsedSyncConfigSet;
|
|
77
|
+
},
|
|
78
|
+
asUpdateOptions: vi.fn(),
|
|
79
|
+
getStorageConfig: vi.fn(),
|
|
80
|
+
async getSyncConfigStatus() {
|
|
81
|
+
return syncConfigStatus;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return content as unknown as storage.PersistedSyncConfigContent;
|
|
85
|
+
}
|
|
86
|
+
|
|
8
87
|
describe('validate', () => {
|
|
9
88
|
it('reports errors with source location', async () => {
|
|
10
|
-
const context
|
|
11
|
-
logger: logger,
|
|
12
|
-
service_context: mockServiceContext(null),
|
|
13
|
-
token_payload: new JwtPayload({
|
|
14
|
-
sub: '',
|
|
15
|
-
exp: 0,
|
|
16
|
-
iat: 0
|
|
17
|
-
})
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const request: BasicRouterRequest = {
|
|
21
|
-
headers: {},
|
|
22
|
-
hostname: '',
|
|
23
|
-
protocol: 'http'
|
|
24
|
-
};
|
|
89
|
+
const context = makeContext();
|
|
25
90
|
|
|
26
91
|
const response = await validate.handler({
|
|
27
92
|
context,
|
|
@@ -45,4 +110,103 @@ bucket_definitions:
|
|
|
45
110
|
]);
|
|
46
111
|
});
|
|
47
112
|
});
|
|
113
|
+
|
|
114
|
+
describe('diagnostics', () => {
|
|
115
|
+
it('returns deploying config status', async () => {
|
|
116
|
+
const active = makeSyncConfigContent({ id: 1, syncConfigId: 'active-config' });
|
|
117
|
+
const deploying = makeSyncConfigContent({ id: 2, syncConfigId: 'deploying-config', active: false });
|
|
118
|
+
const getInstance = vi.fn(() => ({
|
|
119
|
+
async getStatus() {
|
|
120
|
+
return {
|
|
121
|
+
snapshotDone: false,
|
|
122
|
+
resumeLsn: null
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}));
|
|
126
|
+
const activeBucketStorage = {
|
|
127
|
+
getActiveSyncConfig: vi.fn(async () => ({
|
|
128
|
+
content: active,
|
|
129
|
+
replicationStream: {},
|
|
130
|
+
storage: getInstance()
|
|
131
|
+
})),
|
|
132
|
+
getDeployingSyncConfig: vi.fn(async () => ({
|
|
133
|
+
content: deploying,
|
|
134
|
+
replicationStream: {},
|
|
135
|
+
storage: getInstance()
|
|
136
|
+
}))
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const response = await diagnostics.handler({
|
|
140
|
+
context: makeContext(activeBucketStorage),
|
|
141
|
+
params: {},
|
|
142
|
+
request
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
expect(response.deploying_sync_rules?.connections[0].slot_name).toBe('slot_2');
|
|
146
|
+
expect(response.active_sync_rules?.connections[0].slot_name).toBe('slot_1');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('reprocess', () => {
|
|
151
|
+
it('reprocesses the active sync config', async () => {
|
|
152
|
+
const active = makeSyncConfigContent({ id: 7, syncConfigId: 'active-config' });
|
|
153
|
+
const updateSyncRules = vi.fn(async () => ({
|
|
154
|
+
replicationStreamId: 8,
|
|
155
|
+
replicationStreamName: 'new_slot',
|
|
156
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
157
|
+
storageVersion: storage.LEGACY_STORAGE_VERSION,
|
|
158
|
+
replicationJobId: '8',
|
|
159
|
+
current_lock: null
|
|
160
|
+
}));
|
|
161
|
+
const activeBucketStorage = {
|
|
162
|
+
getDeployingSyncConfig: vi.fn(async () => null),
|
|
163
|
+
getActiveSyncConfig: vi.fn(async () => ({
|
|
164
|
+
content: active,
|
|
165
|
+
replicationStream: {},
|
|
166
|
+
storage: {}
|
|
167
|
+
})),
|
|
168
|
+
getSyncConfigContent: vi.fn(),
|
|
169
|
+
updateSyncRules
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const response = await reprocess.handler({
|
|
173
|
+
context: makeContext(activeBucketStorage),
|
|
174
|
+
params: {},
|
|
175
|
+
request
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(activeBucketStorage.getActiveSyncConfig).toHaveBeenCalledTimes(1);
|
|
179
|
+
expect(activeBucketStorage.getSyncConfigContent).not.toHaveBeenCalled();
|
|
180
|
+
expect(updateSyncRules).toHaveBeenCalledTimes(1);
|
|
181
|
+
expect(response.connections[0].slot_name).toBe('new_slot');
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('rejects reprocess while a sync config is deploying', async () => {
|
|
185
|
+
const activeBucketStorage = {
|
|
186
|
+
getDeployingSyncConfig: vi.fn(async () => ({
|
|
187
|
+
content: makeSyncConfigContent({ id: 2, active: false }),
|
|
188
|
+
replicationStream: {},
|
|
189
|
+
storage: {}
|
|
190
|
+
})),
|
|
191
|
+
getActiveSyncConfig: vi.fn(),
|
|
192
|
+
updateSyncRules: vi.fn()
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
await expect(
|
|
196
|
+
reprocess.handler({
|
|
197
|
+
context: makeContext(activeBucketStorage),
|
|
198
|
+
params: {},
|
|
199
|
+
request
|
|
200
|
+
})
|
|
201
|
+
).rejects.toMatchObject({
|
|
202
|
+
errorData: {
|
|
203
|
+
status: 409,
|
|
204
|
+
code: 'PSYNC_S4106',
|
|
205
|
+
description: 'Busy processing sync config - cannot reprocess'
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
expect(activeBucketStorage.getActiveSyncConfig).not.toHaveBeenCalled();
|
|
209
|
+
expect(activeBucketStorage.updateSyncRules).not.toHaveBeenCalled();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
48
212
|
});
|
package/test/src/routes/mocks.ts
CHANGED
|
@@ -18,8 +18,12 @@ export function mockServiceContext(storage: Partial<SyncRulesBucketStorage> | nu
|
|
|
18
18
|
|
|
19
19
|
const storageEngine: StorageEngine = {
|
|
20
20
|
activeBucketStorage: {
|
|
21
|
-
async
|
|
22
|
-
return storage
|
|
21
|
+
async getActiveSyncConfig() {
|
|
22
|
+
return storage == null
|
|
23
|
+
? null
|
|
24
|
+
: {
|
|
25
|
+
storage
|
|
26
|
+
};
|
|
23
27
|
}
|
|
24
28
|
} as Partial<BucketStorageFactory>
|
|
25
29
|
} as any;
|
package/tsconfig.json
CHANGED