@axinom/mosaic-db-common 0.55.0-rc.13 → 0.55.0-rc.16
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/dist/migrations/utils.d.ts +1 -1
- package/dist/migrations/utils.d.ts.map +1 -1
- package/dist/migrations/utils.js +5 -3
- package/dist/migrations/utils.js.map +1 -1
- package/package.json +12 -10
- package/src/auth/helpers.spec.ts +1 -0
- package/src/migrations/compare-migration-hashes.spec.ts +82 -58
- package/src/migrations/utils.ts +7 -3
- package/src/replication/create-logical-replication-service.spec.ts +82 -73
- package/src/replication/ensure-replication-slot-and-publication-exist.spec.ts +64 -40
- package/src/zapatos/transform-custom-type.spec.ts +1 -0
- package/src/zapatos/update-helpers.spec.ts +1 -0
|
@@ -28,7 +28,7 @@ export declare const runCurrentSql: (settings: Settings, logger: DbLogger) => Pr
|
|
|
28
28
|
]
|
|
29
29
|
```
|
|
30
30
|
*/
|
|
31
|
-
export declare const getBeforeMigrationScripts: () => Promise<string[]>;
|
|
31
|
+
export declare const getBeforeMigrationScripts: (migrationsFolder?: string) => Promise<string[]>;
|
|
32
32
|
/**
|
|
33
33
|
* Retrieve a relative path to watch-fixtures.sql of `graphile-build-pg` package. The path is relative to the 'migrations' folder of a specific project.
|
|
34
34
|
* Should only be used in development mode. Useful when there is a need to explicitly install postgraphile watch fixtures, so that database owner user can be created without a superuser privilege.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/migrations/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAO,MAAM,kBAAkB,CAAC;AAGjD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,QAAQ,EAClB,SAAS,MAAM,EACf,QAAQ,QAAQ,KACf,OAAO,CAAC,IAAI,CAYd,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,QAAQ,EAClB,QAAQ,QAAQ,KACf,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/migrations/utils.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAO,MAAM,kBAAkB,CAAC;AAGjD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,QAAQ,EAClB,SAAS,MAAM,EACf,QAAQ,QAAQ,KACf,OAAO,CAAC,IAAI,CAYd,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,UAAU,QAAQ,EAClB,QAAQ,QAAQ,KACf,OAAO,CAAC,IAAI,CAOd,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,yBAAyB,GACpC,mBAAmB,MAAM,KACxB,OAAO,CAAC,MAAM,EAAE,CAgBlB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mCAAmC,QAAO,MAyBtD,CAAC"}
|
package/dist/migrations/utils.js
CHANGED
|
@@ -50,7 +50,9 @@ exports.runSqlScripts = runSqlScripts;
|
|
|
50
50
|
* @param settings - graphile-migrate settings object
|
|
51
51
|
*/
|
|
52
52
|
const runCurrentSql = async (settings, logger) => {
|
|
53
|
-
|
|
53
|
+
var _a;
|
|
54
|
+
const migrationsFolder = (_a = settings.migrationsFolder) !== null && _a !== void 0 ? _a : (0, path_1.join)(process.cwd(), 'migrations');
|
|
55
|
+
const path = (0, path_1.join)(migrationsFolder, 'current.sql');
|
|
54
56
|
const content = await fs_1.promises.readFile(path, 'utf8');
|
|
55
57
|
logger.debug(`Running Script: '${path}'`);
|
|
56
58
|
await (0, graphile_migrate_1.run)(settings, content, path);
|
|
@@ -71,8 +73,8 @@ exports.runCurrentSql = runCurrentSql;
|
|
|
71
73
|
]
|
|
72
74
|
```
|
|
73
75
|
*/
|
|
74
|
-
const getBeforeMigrationScripts = async () => {
|
|
75
|
-
const executionPath = (0, path_1.join)(process.cwd(), 'migrations');
|
|
76
|
+
const getBeforeMigrationScripts = async (migrationsFolder) => {
|
|
77
|
+
const executionPath = migrationsFolder !== null && migrationsFolder !== void 0 ? migrationsFolder : (0, path_1.join)(process.cwd(), 'migrations');
|
|
76
78
|
const dirPath = (0, path_1.resolve)(__dirname, '..', '..', 'migrations', 'before-migration');
|
|
77
79
|
return (await (0, readdirp_1.readdirpPromise)(dirPath, {
|
|
78
80
|
fileFilter: (entry) => entry.basename.endsWith('.sql'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/migrations/utils.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,+BAA+B;AAC/B,2BAAiD;AACjD,uDAAiD;AACjD,+BAA0D;AAC1D,uCAA2C;AAG3C;;;;;GAKG;AACI,MAAM,aAAa,GAAG,KAAK,EAChC,QAAkB,EAClB,OAAe,EACf,MAAgB,EACD,EAAE;;IACjB,MAAM,UAAU,GAAG,CACjB,MAAM,IAAA,0BAAe,EAAC,OAAO,EAAE;QAC7B,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC;KACvD,CAAC,CACH,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;;QAC/C,KAA6B,eAAA,eAAA,cAAA,UAAU,CAAA,gBAAA,wFAAE,CAAC;YAAb,0BAAU;YAAV,WAAU;YAA5B,MAAM,EAAE,IAAI,EAAE,KAAA,CAAA;YACvB,MAAM,UAAU,GAAG,IAAA,WAAI,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACvC,MAAM,OAAO,GAAG,MAAM,aAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YACvD,MAAM,CAAC,KAAK,CAAC,oBAAoB,UAAU,GAAG,CAAC,CAAC;YAChD,MAAM,IAAA,sBAAG,EAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC;;;;;;;;;AACH,CAAC,CAAC;AAhBW,QAAA,aAAa,iBAgBxB;AAEF;;;;GAIG;AACI,MAAM,aAAa,GAAG,KAAK,EAChC,QAAkB,EAClB,MAAgB,EACD,EAAE
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/migrations/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axinom/mosaic-db-common",
|
|
3
|
-
"version": "0.55.0-rc.
|
|
3
|
+
"version": "0.55.0-rc.16",
|
|
4
4
|
"description": "This library encapsulates database-related functionality to develop Mosaic based services.",
|
|
5
5
|
"author": "Axinom",
|
|
6
6
|
"license": "PROPRIETARY",
|
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
"ts:validate": "tsc",
|
|
23
23
|
"build:ci": "yarn workspaces focus && yarn build",
|
|
24
24
|
"dev": "tsc -w --project tsconfig.build.json",
|
|
25
|
-
"test": "
|
|
26
|
-
"test:watch": "
|
|
27
|
-
"test:cov": "
|
|
25
|
+
"test": "vitest run --silent",
|
|
26
|
+
"test:watch": "vitest watch",
|
|
27
|
+
"test:cov": "vitest run --coverage --silent && yarn posttest:cov",
|
|
28
28
|
"posttest:cov": "ts-node ../../scripts/open-test-coverage.ts -- libs/db-common",
|
|
29
|
-
"test:debug": "
|
|
30
|
-
"test:ci": "
|
|
29
|
+
"test:debug": "vitest run --inspect-brk --no-file-parallelism",
|
|
30
|
+
"test:ci": "vitest run --reporter=default --reporter=junit --coverage --coverage.reporter=cobertura --coverage.reporter=html",
|
|
31
|
+
"test:ui": "vitest --ui",
|
|
31
32
|
"lint": "eslint . --ext .ts,.tsx,.js --color --cache"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
@@ -41,19 +42,20 @@
|
|
|
41
42
|
"zapatos": "3.6.0"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
|
-
"@axinom/mosaic-dev-be-common": "^0.
|
|
45
|
+
"@axinom/mosaic-dev-be-common": "^0.16.0-rc.1",
|
|
45
46
|
"@types/express": "^4.17.17",
|
|
46
47
|
"@types/node": "^22.0.0",
|
|
47
48
|
"@types/pg": "^8.6.1",
|
|
48
49
|
"@types/yargs": "^16",
|
|
50
|
+
"@vitest/ui": "^4.0.18",
|
|
49
51
|
"eslint": "^8.35.0",
|
|
50
|
-
"jest": "^29",
|
|
51
52
|
"rimraf": "^3.0.2",
|
|
52
53
|
"ts-node": "^10.9.1",
|
|
53
|
-
"typescript": "^5.9.3"
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"vitest": "^4.0.18"
|
|
54
56
|
},
|
|
55
57
|
"publishConfig": {
|
|
56
58
|
"access": "public"
|
|
57
59
|
},
|
|
58
|
-
"gitHead": "
|
|
60
|
+
"gitHead": "61cc0e626bb816beee81d16b31c46f1d2a13844d"
|
|
59
61
|
}
|
package/src/auth/helpers.spec.ts
CHANGED
|
@@ -1,54 +1,79 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import * as compareHelpers from './compare-migration-hashes-helpers';
|
|
3
|
-
import { CompareMigrationHashesErrorCallback, MigrationRecord } from './types';
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
interface MockPgClient {
|
|
4
|
+
query: (queryString: string) => unknown;
|
|
5
|
+
release: () => void;
|
|
6
|
+
}
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
8
|
+
interface MockPgState {
|
|
9
|
+
migrationsTableExistsCall: () => {
|
|
10
|
+
rows: { migrationsTableExists: boolean }[];
|
|
11
|
+
};
|
|
12
|
+
migrationsTableCall: () => { rows: unknown[] };
|
|
13
|
+
connectCallback: (() => Promise<MockPgClient>) | undefined;
|
|
14
|
+
}
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return migrationsTableExistsCall();
|
|
20
|
-
} else {
|
|
21
|
-
return migrationsTableCall();
|
|
22
|
-
}
|
|
16
|
+
const mockState = vi.hoisted(() => {
|
|
17
|
+
const state: MockPgState = {
|
|
18
|
+
migrationsTableExistsCall: () => {
|
|
19
|
+
return { rows: [{ migrationsTableExists: true }] };
|
|
23
20
|
},
|
|
24
|
-
|
|
25
|
-
return;
|
|
21
|
+
migrationsTableCall: () => {
|
|
22
|
+
return { rows: [] };
|
|
26
23
|
},
|
|
24
|
+
connectCallback: undefined,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const defaultConnectCallback = async () => {
|
|
28
|
+
return {
|
|
29
|
+
query: (queryString: string) => {
|
|
30
|
+
if (queryString.startsWith('SELECT EXISTS')) {
|
|
31
|
+
return state.migrationsTableExistsCall();
|
|
32
|
+
} else {
|
|
33
|
+
return state.migrationsTableCall();
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
release: () => {
|
|
37
|
+
return;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
27
40
|
};
|
|
28
|
-
};
|
|
29
|
-
let connectCallback = defaultConnectCallback;
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
state.connectCallback = defaultConnectCallback;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
state,
|
|
46
|
+
defaultConnectCallback,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
vi.mock('pg', async () => {
|
|
51
|
+
const original = await vi.importActual<typeof import('pg')>('pg');
|
|
52
|
+
|
|
53
|
+
class MockPool {
|
|
54
|
+
connect = async () => {
|
|
55
|
+
return mockState.state.connectCallback!();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
end = () => {
|
|
59
|
+
return;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
33
63
|
return {
|
|
34
64
|
...original,
|
|
35
|
-
Pool:
|
|
36
|
-
return {
|
|
37
|
-
connect: async () => {
|
|
38
|
-
return connectCallback();
|
|
39
|
-
},
|
|
40
|
-
end: () => {
|
|
41
|
-
return;
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}),
|
|
65
|
+
Pool: MockPool,
|
|
45
66
|
};
|
|
46
67
|
});
|
|
47
68
|
|
|
69
|
+
import { compareMigrationHashes } from './compare-migration-hashes';
|
|
70
|
+
import * as compareHelpers from './compare-migration-hashes-helpers';
|
|
71
|
+
import { CompareMigrationHashesErrorCallback, MigrationRecord } from './types';
|
|
72
|
+
|
|
48
73
|
describe('compareMigrationHashes', () => {
|
|
49
74
|
let errorMessage: string;
|
|
50
75
|
let errorMigrationRecords: MigrationRecord[] | undefined;
|
|
51
|
-
let mockGetFileMigrationRecords:
|
|
76
|
+
let mockGetFileMigrationRecords: ReturnType<typeof vi.spyOn>;
|
|
52
77
|
const mockErrorCallback: CompareMigrationHashesErrorCallback = (
|
|
53
78
|
message: string,
|
|
54
79
|
mismatchedRecords?: MigrationRecord[],
|
|
@@ -58,23 +83,22 @@ describe('compareMigrationHashes', () => {
|
|
|
58
83
|
};
|
|
59
84
|
|
|
60
85
|
beforeEach(() => {
|
|
61
|
-
mockGetFileMigrationRecords =
|
|
86
|
+
mockGetFileMigrationRecords = vi.spyOn(
|
|
62
87
|
compareHelpers,
|
|
63
88
|
'getFileMigrationRecords',
|
|
64
89
|
);
|
|
65
90
|
});
|
|
66
91
|
|
|
67
92
|
afterEach(() => {
|
|
68
|
-
|
|
69
|
-
connectCallback = defaultConnectCallback;
|
|
70
|
-
migrationsTableExistsCall = () => {
|
|
93
|
+
vi.restoreAllMocks();
|
|
94
|
+
mockState.state.connectCallback = mockState.defaultConnectCallback;
|
|
95
|
+
mockState.state.migrationsTableExistsCall = () => {
|
|
71
96
|
return { rows: [{ migrationsTableExists: true }] };
|
|
72
97
|
};
|
|
73
98
|
|
|
74
|
-
migrationsTableCall = () => {
|
|
99
|
+
mockState.state.migrationsTableCall = () => {
|
|
75
100
|
return { rows: [] };
|
|
76
101
|
};
|
|
77
|
-
mockGetFileMigrationRecords.mockClear();
|
|
78
102
|
});
|
|
79
103
|
|
|
80
104
|
const migration000001: MigrationRecord = {
|
|
@@ -121,20 +145,20 @@ describe('compareMigrationHashes', () => {
|
|
|
121
145
|
mockGetFileMigrationRecords.mockImplementation(() => Promise.resolve([]));
|
|
122
146
|
|
|
123
147
|
// Act & Assert
|
|
124
|
-
await expect(
|
|
148
|
+
await expect(
|
|
125
149
|
compareMigrationHashes({}, mockErrorCallback),
|
|
126
150
|
).rejects.toThrow('Database connection string is not set up.');
|
|
127
151
|
});
|
|
128
152
|
|
|
129
153
|
it('Connection to database fails - original error is thrown as is', async () => {
|
|
130
154
|
// Arrange
|
|
131
|
-
connectCallback = async () => {
|
|
155
|
+
mockState.state.connectCallback = async () => {
|
|
132
156
|
throw new Error('database "test_database" does not exist');
|
|
133
157
|
};
|
|
134
158
|
mockGetFileMigrationRecords.mockImplementation(() => Promise.resolve([]));
|
|
135
159
|
|
|
136
160
|
// Act & Assert
|
|
137
|
-
await expect(
|
|
161
|
+
await expect(
|
|
138
162
|
compareMigrationHashes(
|
|
139
163
|
{ connectionString: 'some connection string' },
|
|
140
164
|
mockErrorCallback,
|
|
@@ -144,13 +168,13 @@ describe('compareMigrationHashes', () => {
|
|
|
144
168
|
|
|
145
169
|
it('Migrations table exists check fails - original error is thrown', async () => {
|
|
146
170
|
// Arrange
|
|
147
|
-
migrationsTableExistsCall = () => {
|
|
171
|
+
mockState.state.migrationsTableExistsCall = () => {
|
|
148
172
|
throw new Error('Query failed for whatever reason.');
|
|
149
173
|
};
|
|
150
174
|
mockGetFileMigrationRecords.mockImplementation(() => Promise.resolve([]));
|
|
151
175
|
|
|
152
176
|
// Act & Assert
|
|
153
|
-
await expect(
|
|
177
|
+
await expect(
|
|
154
178
|
compareMigrationHashes(
|
|
155
179
|
{ connectionString: 'valid connection string' },
|
|
156
180
|
mockErrorCallback,
|
|
@@ -160,13 +184,13 @@ describe('compareMigrationHashes', () => {
|
|
|
160
184
|
|
|
161
185
|
it('Migrations history request to database fails - original error is thrown', async () => {
|
|
162
186
|
// Arrange
|
|
163
|
-
migrationsTableCall = () => {
|
|
187
|
+
mockState.state.migrationsTableCall = () => {
|
|
164
188
|
throw new Error('Query failed for whatever reason.');
|
|
165
189
|
};
|
|
166
190
|
mockGetFileMigrationRecords.mockImplementation(() => Promise.resolve([]));
|
|
167
191
|
|
|
168
192
|
// Act & Assert
|
|
169
|
-
await expect(
|
|
193
|
+
await expect(
|
|
170
194
|
compareMigrationHashes(
|
|
171
195
|
{ connectionString: 'valid connection string' },
|
|
172
196
|
mockErrorCallback,
|
|
@@ -178,7 +202,7 @@ describe('compareMigrationHashes', () => {
|
|
|
178
202
|
describe('migration hashes match', () => {
|
|
179
203
|
it('if migration history table does not exist', async () => {
|
|
180
204
|
// Arrange
|
|
181
|
-
migrationsTableExistsCall = () => {
|
|
205
|
+
mockState.state.migrationsTableExistsCall = () => {
|
|
182
206
|
return { rows: [{ migrationsTableExists: false }] };
|
|
183
207
|
};
|
|
184
208
|
mockGetFileMigrationRecords.mockImplementation(() => Promise.resolve([]));
|
|
@@ -197,7 +221,7 @@ describe('compareMigrationHashes', () => {
|
|
|
197
221
|
it('if migration history is empty and no committed migrations', async () => {
|
|
198
222
|
// Arrange
|
|
199
223
|
mockGetFileMigrationRecords.mockImplementation(() => Promise.resolve([]));
|
|
200
|
-
migrationsTableCall = () => {
|
|
224
|
+
mockState.state.migrationsTableCall = () => {
|
|
201
225
|
return { rows: [] };
|
|
202
226
|
};
|
|
203
227
|
|
|
@@ -217,7 +241,7 @@ describe('compareMigrationHashes', () => {
|
|
|
217
241
|
mockGetFileMigrationRecords.mockImplementation(() =>
|
|
218
242
|
Promise.resolve([migration000001, migration000002]),
|
|
219
243
|
);
|
|
220
|
-
migrationsTableCall = () => {
|
|
244
|
+
mockState.state.migrationsTableCall = () => {
|
|
221
245
|
return { rows: [] };
|
|
222
246
|
};
|
|
223
247
|
|
|
@@ -237,7 +261,7 @@ describe('compareMigrationHashes', () => {
|
|
|
237
261
|
mockGetFileMigrationRecords.mockImplementation(() =>
|
|
238
262
|
Promise.resolve([migration000001, migration000002, migration000003]),
|
|
239
263
|
);
|
|
240
|
-
migrationsTableCall = () => {
|
|
264
|
+
mockState.state.migrationsTableCall = () => {
|
|
241
265
|
return { rows: [dbMigration000001] };
|
|
242
266
|
};
|
|
243
267
|
|
|
@@ -257,7 +281,7 @@ describe('compareMigrationHashes', () => {
|
|
|
257
281
|
mockGetFileMigrationRecords.mockImplementation(() =>
|
|
258
282
|
Promise.resolve([migration000001, migration000002, migration000003]),
|
|
259
283
|
);
|
|
260
|
-
migrationsTableCall = () => {
|
|
284
|
+
mockState.state.migrationsTableCall = () => {
|
|
261
285
|
return {
|
|
262
286
|
rows: [dbMigration000001, dbMigration000002, dbMigration000003],
|
|
263
287
|
};
|
|
@@ -279,7 +303,7 @@ describe('compareMigrationHashes', () => {
|
|
|
279
303
|
it('if migration history has records and there is no committed migrations', async () => {
|
|
280
304
|
// Arrange
|
|
281
305
|
mockGetFileMigrationRecords.mockImplementation(() => Promise.resolve([]));
|
|
282
|
-
migrationsTableCall = () => {
|
|
306
|
+
mockState.state.migrationsTableCall = () => {
|
|
283
307
|
return {
|
|
284
308
|
rows: [dbMigration000001, dbMigration000002, dbMigration000003],
|
|
285
309
|
};
|
|
@@ -310,7 +334,7 @@ describe('compareMigrationHashes', () => {
|
|
|
310
334
|
mockGetFileMigrationRecords.mockImplementation(() =>
|
|
311
335
|
Promise.resolve([migration000001]),
|
|
312
336
|
);
|
|
313
|
-
migrationsTableCall = () => {
|
|
337
|
+
mockState.state.migrationsTableCall = () => {
|
|
314
338
|
return {
|
|
315
339
|
rows: [dbMigration000001, dbMigration000002, dbMigration000003],
|
|
316
340
|
};
|
|
@@ -345,7 +369,7 @@ describe('compareMigrationHashes', () => {
|
|
|
345
369
|
...dbMigration000003,
|
|
346
370
|
hash: 'sha1:a929171757965e9dc0bdd9678b11f5bc76fdcdd9',
|
|
347
371
|
};
|
|
348
|
-
migrationsTableCall = () => {
|
|
372
|
+
mockState.state.migrationsTableCall = () => {
|
|
349
373
|
return {
|
|
350
374
|
rows: [
|
|
351
375
|
dbMigration000001,
|
|
@@ -384,7 +408,7 @@ describe('compareMigrationHashes', () => {
|
|
|
384
408
|
...dbMigration000003,
|
|
385
409
|
previousHash: 'sha1:a929171757965e9dc0bdd9678b11f5bc76fdcdd9',
|
|
386
410
|
};
|
|
387
|
-
migrationsTableCall = () => {
|
|
411
|
+
mockState.state.migrationsTableCall = () => {
|
|
388
412
|
return {
|
|
389
413
|
rows: [
|
|
390
414
|
migration000001,
|
package/src/migrations/utils.ts
CHANGED
|
@@ -38,7 +38,9 @@ export const runCurrentSql = async (
|
|
|
38
38
|
settings: Settings,
|
|
39
39
|
logger: DbLogger,
|
|
40
40
|
): Promise<void> => {
|
|
41
|
-
const
|
|
41
|
+
const migrationsFolder =
|
|
42
|
+
settings.migrationsFolder ?? join(process.cwd(), 'migrations');
|
|
43
|
+
const path = join(migrationsFolder, 'current.sql');
|
|
42
44
|
const content = await fsp.readFile(path, 'utf8');
|
|
43
45
|
logger.debug(`Running Script: '${path}'`);
|
|
44
46
|
await run(settings, content, path);
|
|
@@ -59,8 +61,10 @@ export const runCurrentSql = async (
|
|
|
59
61
|
]
|
|
60
62
|
```
|
|
61
63
|
*/
|
|
62
|
-
export const getBeforeMigrationScripts = async (
|
|
63
|
-
|
|
64
|
+
export const getBeforeMigrationScripts = async (
|
|
65
|
+
migrationsFolder?: string,
|
|
66
|
+
): Promise<string[]> => {
|
|
67
|
+
const executionPath = migrationsFolder ?? join(process.cwd(), 'migrations');
|
|
64
68
|
const dirPath = resolve(
|
|
65
69
|
__dirname,
|
|
66
70
|
'..',
|
|
@@ -1,64 +1,71 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
LogicalReplicationMessageHandler,
|
|
4
|
-
createLogicalReplicationService,
|
|
5
|
-
} from './create-logical-replication-service';
|
|
6
|
-
|
|
7
|
-
const sleep = (ms: number): Promise<void> =>
|
|
8
|
-
new Promise((res) => setTimeout(res, ms));
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
9
2
|
|
|
10
|
-
const repService
|
|
3
|
+
const repService = vi.hoisted(() => ({
|
|
11
4
|
// We expose the callback methods that are normally called from the "on"
|
|
12
5
|
// "data/error/heartbeat" emitted events to be able to manually call them.
|
|
13
|
-
handleData
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
handleData: undefined as
|
|
7
|
+
| ((lsn: string, log: any) => Promise<void> | void)
|
|
8
|
+
| undefined,
|
|
9
|
+
handleError: undefined as ((err: Error) => void) | undefined,
|
|
10
|
+
handleHeartbeat: undefined as
|
|
11
|
+
| ((
|
|
12
|
+
lsn: string,
|
|
13
|
+
timestamp: number,
|
|
14
|
+
shouldRespond: boolean,
|
|
15
|
+
) => Promise<void> | void)
|
|
16
|
+
| undefined,
|
|
17
|
+
acknowledge: undefined as ReturnType<typeof vi.fn> | undefined,
|
|
18
|
+
stop: undefined as ReturnType<typeof vi.fn> | undefined,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock('pg-logical-replication', async () => {
|
|
22
|
+
const actual = await vi.importActual<typeof import('pg-logical-replication')>(
|
|
23
|
+
'pg-logical-replication',
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
class MockLogicalReplicationService {
|
|
27
|
+
constructor(_config: unknown, _ackConfig: unknown) {
|
|
28
|
+
repService.acknowledge = this.acknowledge;
|
|
29
|
+
repService.stop = this.stop;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
on = (event: 'data' | 'error' | 'heartbeat', listener: any) => {
|
|
33
|
+
switch (event) {
|
|
34
|
+
case 'data':
|
|
35
|
+
repService.handleData = listener;
|
|
36
|
+
break;
|
|
37
|
+
case 'error':
|
|
38
|
+
repService.handleError = listener;
|
|
39
|
+
break;
|
|
40
|
+
case 'heartbeat':
|
|
41
|
+
repService.handleHeartbeat = listener;
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
acknowledge = vi.fn();
|
|
47
|
+
removeAllListeners = vi.fn();
|
|
48
|
+
emit = vi.fn();
|
|
49
|
+
stop = vi.fn(() => Promise.resolve());
|
|
50
|
+
subscribe = () =>
|
|
51
|
+
new Promise(() => {
|
|
52
|
+
/** never return */
|
|
53
|
+
});
|
|
54
|
+
isStop = () => false;
|
|
55
|
+
}
|
|
23
56
|
|
|
24
|
-
jest.mock('pg-logical-replication', () => {
|
|
25
57
|
return {
|
|
26
|
-
...
|
|
27
|
-
LogicalReplicationService:
|
|
28
|
-
const lrs = {
|
|
29
|
-
handleData: undefined,
|
|
30
|
-
handleError: undefined,
|
|
31
|
-
handleHeartbeat: undefined,
|
|
32
|
-
on: (event: 'data' | 'error' | 'heartbeat', listener: any) => {
|
|
33
|
-
switch (event) {
|
|
34
|
-
case 'data':
|
|
35
|
-
repService.handleData = listener;
|
|
36
|
-
break;
|
|
37
|
-
case 'error':
|
|
38
|
-
repService.handleError = listener;
|
|
39
|
-
break;
|
|
40
|
-
case 'heartbeat':
|
|
41
|
-
repService.handleHeartbeat = listener;
|
|
42
|
-
break;
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
|
-
acknowledge: jest.fn(),
|
|
46
|
-
removeAllListeners: jest.fn(),
|
|
47
|
-
emit: jest.fn(),
|
|
48
|
-
stop: jest.fn(() => Promise.resolve()),
|
|
49
|
-
subscribe: () =>
|
|
50
|
-
new Promise(() => {
|
|
51
|
-
/** never return */
|
|
52
|
-
}),
|
|
53
|
-
isStop: () => false,
|
|
54
|
-
};
|
|
55
|
-
repService.acknowledge = lrs.acknowledge;
|
|
56
|
-
repService.stop = lrs.stop;
|
|
57
|
-
return lrs;
|
|
58
|
-
}),
|
|
58
|
+
...actual,
|
|
59
|
+
LogicalReplicationService: MockLogicalReplicationService,
|
|
59
60
|
};
|
|
60
61
|
});
|
|
61
62
|
|
|
63
|
+
import { Pgoutput } from 'pg-logical-replication';
|
|
64
|
+
import {
|
|
65
|
+
LogicalReplicationMessageHandler,
|
|
66
|
+
createLogicalReplicationService,
|
|
67
|
+
} from './create-logical-replication-service';
|
|
68
|
+
|
|
62
69
|
const relation: Pgoutput.MessageRelation = {
|
|
63
70
|
tag: 'relation',
|
|
64
71
|
relationOid: 1,
|
|
@@ -89,6 +96,10 @@ const relation: Pgoutput.MessageRelation = {
|
|
|
89
96
|
};
|
|
90
97
|
|
|
91
98
|
describe('createLogicalReplicationService', () => {
|
|
99
|
+
afterEach(() => {
|
|
100
|
+
vi.useRealTimers();
|
|
101
|
+
});
|
|
102
|
+
|
|
92
103
|
beforeEach(() => {
|
|
93
104
|
repService.handleData = undefined;
|
|
94
105
|
repService.handleError = undefined;
|
|
@@ -98,27 +109,23 @@ describe('createLogicalReplicationService', () => {
|
|
|
98
109
|
});
|
|
99
110
|
|
|
100
111
|
it('initialization throws an error if empty operations array is passed', async () => {
|
|
101
|
-
// Act
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
// Act & Assert
|
|
113
|
+
await expect(
|
|
114
|
+
createLogicalReplicationService({
|
|
104
115
|
connectionString: 'test-valid-connection-string',
|
|
105
116
|
publicationNames: ['test_publication'],
|
|
106
117
|
replicationSlotName: 'test_slot',
|
|
107
|
-
messageHandler:
|
|
118
|
+
messageHandler: vi.fn(),
|
|
108
119
|
operationsToWatch: [],
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
expect((error as Error).message).toBe(
|
|
114
|
-
'Unable to start the logical replication service when operationsToWatch is an empty array.',
|
|
115
|
-
);
|
|
116
|
-
}
|
|
120
|
+
}),
|
|
121
|
+
).rejects.toThrow(
|
|
122
|
+
'Unable to start the logical replication service when operationsToWatch is an empty array.',
|
|
123
|
+
);
|
|
117
124
|
});
|
|
118
125
|
|
|
119
126
|
it('should call messageHandler and acknowledge the message when no errors are thrown', async () => {
|
|
120
127
|
// Arrange
|
|
121
|
-
const messageHandler: LogicalReplicationMessageHandler =
|
|
128
|
+
const messageHandler: LogicalReplicationMessageHandler = vi.fn();
|
|
122
129
|
const cleanup = await createLogicalReplicationService({
|
|
123
130
|
connectionString: 'test-valid-connection-string',
|
|
124
131
|
publicationNames: ['test_publication'],
|
|
@@ -171,7 +178,7 @@ describe('createLogicalReplicationService', () => {
|
|
|
171
178
|
|
|
172
179
|
it('should call messageHandler with minimal scoped message and acknowledge the message when no errors are thrown', async () => {
|
|
173
180
|
// Arrange
|
|
174
|
-
const messageHandler: LogicalReplicationMessageHandler =
|
|
181
|
+
const messageHandler: LogicalReplicationMessageHandler = vi.fn();
|
|
175
182
|
const cleanup = await createLogicalReplicationService({
|
|
176
183
|
connectionString: 'test-valid-connection-string',
|
|
177
184
|
publicationNames: ['test_publication'],
|
|
@@ -249,11 +256,12 @@ describe('createLogicalReplicationService', () => {
|
|
|
249
256
|
|
|
250
257
|
it('A heartbeat should be acknowledged after 5 seconds', async () => {
|
|
251
258
|
// Arrange
|
|
259
|
+
vi.useFakeTimers();
|
|
252
260
|
const cleanup = await createLogicalReplicationService({
|
|
253
261
|
connectionString: 'test-valid-connection-string',
|
|
254
262
|
publicationNames: ['test_publication'],
|
|
255
263
|
replicationSlotName: 'test_slot',
|
|
256
|
-
messageHandler:
|
|
264
|
+
messageHandler: vi.fn(),
|
|
257
265
|
});
|
|
258
266
|
expect(repService.handleHeartbeat).toBeDefined();
|
|
259
267
|
|
|
@@ -264,9 +272,9 @@ describe('createLogicalReplicationService', () => {
|
|
|
264
272
|
expect(repService.handleData).toBeDefined();
|
|
265
273
|
expect(repService.handleError).toBeDefined();
|
|
266
274
|
expect(repService.acknowledge).not.toHaveBeenCalled();
|
|
267
|
-
await
|
|
275
|
+
await vi.advanceTimersByTimeAsync(4000);
|
|
268
276
|
expect(repService.acknowledge).not.toHaveBeenCalled();
|
|
269
|
-
await
|
|
277
|
+
await vi.advanceTimersByTimeAsync(1010);
|
|
270
278
|
expect(repService.acknowledge).toHaveBeenCalled();
|
|
271
279
|
expect(repService.stop).not.toHaveBeenCalled();
|
|
272
280
|
await cleanup();
|
|
@@ -275,18 +283,19 @@ describe('createLogicalReplicationService', () => {
|
|
|
275
283
|
|
|
276
284
|
it('A heartbeat should not be acknowledged after 5 seconds when a message acknowledgement comes in between', async () => {
|
|
277
285
|
// Arrange
|
|
286
|
+
vi.useFakeTimers();
|
|
278
287
|
const cleanup = await createLogicalReplicationService({
|
|
279
288
|
connectionString: 'test-valid-connection-string',
|
|
280
289
|
publicationNames: ['test_publication'],
|
|
281
290
|
replicationSlotName: 'test_slot',
|
|
282
|
-
messageHandler:
|
|
291
|
+
messageHandler: vi.fn(),
|
|
283
292
|
});
|
|
284
293
|
expect(repService.handleData).toBeDefined();
|
|
285
294
|
expect(repService.handleHeartbeat).toBeDefined();
|
|
286
295
|
|
|
287
296
|
// Act
|
|
288
297
|
await repService.handleHeartbeat!('0/00000001', 123, true);
|
|
289
|
-
await
|
|
298
|
+
await vi.advanceTimersByTimeAsync(1000);
|
|
290
299
|
await repService.handleData!('0/00000002', {
|
|
291
300
|
tag: 'insert',
|
|
292
301
|
relation,
|
|
@@ -303,7 +312,7 @@ describe('createLogicalReplicationService', () => {
|
|
|
303
312
|
// Assert
|
|
304
313
|
expect(repService.handleError).toBeDefined();
|
|
305
314
|
expect(repService.acknowledge).toHaveBeenCalledWith('0/00000002');
|
|
306
|
-
await
|
|
315
|
+
await vi.advanceTimersByTimeAsync(4010);
|
|
307
316
|
expect(repService.acknowledge).toHaveBeenCalledTimes(1);
|
|
308
317
|
expect(repService.stop).not.toHaveBeenCalled();
|
|
309
318
|
await cleanup();
|
|
@@ -1,46 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe,
|
|
5
|
+
expect,
|
|
6
|
+
it,
|
|
7
|
+
vi,
|
|
8
|
+
type MockInstance,
|
|
9
|
+
} from 'vitest';
|
|
10
|
+
|
|
11
|
+
const mockState = vi.hoisted(() => ({
|
|
12
|
+
slotName: 'test_slot_name',
|
|
13
|
+
pubName: 'test_pub_name',
|
|
14
|
+
existsResult: (() => undefined) as () => unknown,
|
|
15
|
+
tablesResult: (() => undefined) as () => unknown,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock('zapatos/db', async () => {
|
|
19
|
+
const actual = await vi.importActual<typeof import('zapatos/db')>(
|
|
20
|
+
'zapatos/db',
|
|
21
|
+
);
|
|
11
22
|
return {
|
|
12
|
-
...
|
|
13
|
-
sql:
|
|
23
|
+
...actual,
|
|
24
|
+
sql: vi.fn().mockImplementation((_query, firstParam) => {
|
|
14
25
|
return stub<SQLFragment>({
|
|
15
|
-
run:
|
|
16
|
-
if (firstParam.value === slotName) {
|
|
17
|
-
return existsResult();
|
|
26
|
+
run: vi.fn().mockImplementation(() => {
|
|
27
|
+
if (firstParam.value === mockState.slotName) {
|
|
28
|
+
return mockState.existsResult();
|
|
18
29
|
}
|
|
19
|
-
if (firstParam.value === pubName) {
|
|
20
|
-
return tablesResult();
|
|
30
|
+
if (firstParam.value === mockState.pubName) {
|
|
31
|
+
return mockState.tablesResult();
|
|
21
32
|
}
|
|
22
33
|
return undefined;
|
|
23
34
|
}),
|
|
24
35
|
});
|
|
25
36
|
}),
|
|
26
|
-
transaction:
|
|
37
|
+
transaction: vi.fn().mockImplementation(() => {
|
|
27
38
|
return;
|
|
28
39
|
}),
|
|
29
40
|
};
|
|
30
41
|
});
|
|
31
42
|
|
|
43
|
+
import { stub } from '@axinom/mosaic-dev-be-common/vitest';
|
|
44
|
+
import { Pool } from 'pg';
|
|
45
|
+
import { SQLFragment } from 'zapatos/db';
|
|
46
|
+
import { ensureReplicationSlotAndPublicationExist } from './ensure-replication-slot-and-publication-exist';
|
|
47
|
+
|
|
48
|
+
const slotName = mockState.slotName;
|
|
49
|
+
const pubName = mockState.pubName;
|
|
50
|
+
|
|
32
51
|
describe('ensureReplicationSlotAndPublicationExist', () => {
|
|
33
|
-
let
|
|
52
|
+
let consoleLogSpy: MockInstance;
|
|
53
|
+
const expectSingleLog = (message: string): void => {
|
|
54
|
+
expect(consoleLogSpy).toHaveBeenCalledTimes(1);
|
|
55
|
+
expect(consoleLogSpy).toHaveBeenCalledWith(message);
|
|
56
|
+
};
|
|
57
|
+
|
|
34
58
|
beforeEach(() => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
59
|
+
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {
|
|
60
|
+
return;
|
|
61
|
+
});
|
|
38
62
|
});
|
|
39
63
|
|
|
40
64
|
afterEach(() => {
|
|
41
|
-
existsResult = () => undefined;
|
|
42
|
-
tablesResult = () => undefined;
|
|
43
|
-
|
|
65
|
+
mockState.existsResult = () => undefined;
|
|
66
|
+
mockState.tablesResult = () => undefined;
|
|
67
|
+
vi.restoreAllMocks();
|
|
44
68
|
});
|
|
45
69
|
|
|
46
70
|
it.each([
|
|
@@ -51,8 +75,8 @@ describe('ensureReplicationSlotAndPublicationExist', () => {
|
|
|
51
75
|
'call when slot and/or publication does not exist -> pass, %p, %p',
|
|
52
76
|
async (slotExists, pubExists) => {
|
|
53
77
|
// Arrange
|
|
54
|
-
existsResult = () => [{ slotExists, pubExists }];
|
|
55
|
-
tablesResult = () =>
|
|
78
|
+
mockState.existsResult = () => [{ slotExists, pubExists }];
|
|
79
|
+
mockState.tablesResult = () =>
|
|
56
80
|
pubExists
|
|
57
81
|
? [
|
|
58
82
|
{ schemaname: 'app_public', tablename: 'table_one' },
|
|
@@ -71,7 +95,7 @@ describe('ensureReplicationSlotAndPublicationExist', () => {
|
|
|
71
95
|
});
|
|
72
96
|
|
|
73
97
|
// Assert
|
|
74
|
-
|
|
98
|
+
expectSingleLog(
|
|
75
99
|
'The replication slot "test_slot_name" and publication "test_pub_name" successfully (re)created.',
|
|
76
100
|
);
|
|
77
101
|
},
|
|
@@ -79,8 +103,8 @@ describe('ensureReplicationSlotAndPublicationExist', () => {
|
|
|
79
103
|
|
|
80
104
|
it('call when slot and publication exist and new table is added -> pass', async () => {
|
|
81
105
|
// Arrange
|
|
82
|
-
existsResult = () => [{ slotExists: true, pubExists: true }];
|
|
83
|
-
tablesResult = () => [
|
|
106
|
+
mockState.existsResult = () => [{ slotExists: true, pubExists: true }];
|
|
107
|
+
mockState.tablesResult = () => [
|
|
84
108
|
{ schemaname: 'app_public', tablename: 'table_one' },
|
|
85
109
|
{ schemaname: 'app_public', tablename: 'table_two' },
|
|
86
110
|
];
|
|
@@ -95,15 +119,15 @@ describe('ensureReplicationSlotAndPublicationExist', () => {
|
|
|
95
119
|
});
|
|
96
120
|
|
|
97
121
|
// Assert
|
|
98
|
-
|
|
122
|
+
expectSingleLog(
|
|
99
123
|
'The replication slot "test_slot_name" and publication "test_pub_name" successfully (re)created.',
|
|
100
124
|
);
|
|
101
125
|
});
|
|
102
126
|
|
|
103
127
|
it('call when slot and publication exist and old table is removed -> pass', async () => {
|
|
104
128
|
// Arrange
|
|
105
|
-
existsResult = () => [{ slotExists: true, pubExists: true }];
|
|
106
|
-
tablesResult = () => [
|
|
129
|
+
mockState.existsResult = () => [{ slotExists: true, pubExists: true }];
|
|
130
|
+
mockState.tablesResult = () => [
|
|
107
131
|
{ schemaname: 'app_public', tablename: 'table_one' },
|
|
108
132
|
{ schemaname: 'app_public', tablename: 'table_two' },
|
|
109
133
|
{ schemaname: 'app_public', tablename: 'table_three' },
|
|
@@ -119,15 +143,15 @@ describe('ensureReplicationSlotAndPublicationExist', () => {
|
|
|
119
143
|
});
|
|
120
144
|
|
|
121
145
|
// Assert
|
|
122
|
-
|
|
146
|
+
expectSingleLog(
|
|
123
147
|
'The replication slot "test_slot_name" and publication "test_pub_name" successfully (re)created.',
|
|
124
148
|
);
|
|
125
149
|
});
|
|
126
150
|
|
|
127
151
|
it('call when slot and publication exist and one table is replaced by another -> pass', async () => {
|
|
128
152
|
// Arrange
|
|
129
|
-
existsResult = () => [{ slotExists: true, pubExists: true }];
|
|
130
|
-
tablesResult = () => [
|
|
153
|
+
mockState.existsResult = () => [{ slotExists: true, pubExists: true }];
|
|
154
|
+
mockState.tablesResult = () => [
|
|
131
155
|
{ schemaname: 'app_public', tablename: 'table_one' },
|
|
132
156
|
{ schemaname: 'app_public', tablename: 'table_two' },
|
|
133
157
|
{ schemaname: 'app_public', tablename: 'table_three' },
|
|
@@ -143,15 +167,15 @@ describe('ensureReplicationSlotAndPublicationExist', () => {
|
|
|
143
167
|
});
|
|
144
168
|
|
|
145
169
|
// Assert
|
|
146
|
-
|
|
170
|
+
expectSingleLog(
|
|
147
171
|
'The replication slot "test_slot_name" and publication "test_pub_name" successfully (re)created.',
|
|
148
172
|
);
|
|
149
173
|
});
|
|
150
174
|
|
|
151
175
|
it('call when slot and publication exist and tables are in sync -> skip', async () => {
|
|
152
176
|
// Arrange
|
|
153
|
-
existsResult = () => [{ slotExists: true, pubExists: true }];
|
|
154
|
-
tablesResult = () => [
|
|
177
|
+
mockState.existsResult = () => [{ slotExists: true, pubExists: true }];
|
|
178
|
+
mockState.tablesResult = () => [
|
|
155
179
|
{ schemaname: 'app_public', tablename: 'table_one' },
|
|
156
180
|
{ schemaname: 'app_public', tablename: 'table_two' },
|
|
157
181
|
{ schemaname: 'app_public', tablename: 'table_three' },
|
|
@@ -167,7 +191,7 @@ describe('ensureReplicationSlotAndPublicationExist', () => {
|
|
|
167
191
|
});
|
|
168
192
|
|
|
169
193
|
// Assert
|
|
170
|
-
|
|
194
|
+
expectSingleLog(
|
|
171
195
|
'The replication slot "test_slot_name" and publication "test_pub_name" already exist.',
|
|
172
196
|
);
|
|
173
197
|
});
|