@vida-global/core 1.2.5 → 1.3.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/.github/workflows/npm-test.yml +24 -0
- package/index.js +1 -1
- package/lib/{active_record → activeRecord}/README.md +3 -3
- package/lib/{active_record → activeRecord}/baseRecord.js +11 -2
- package/lib/http/README.md +2 -2
- package/lib/server/README.md +181 -20
- package/lib/server/errors.js +28 -0
- package/lib/server/index.js +3 -3
- package/lib/server/server.js +7 -53
- package/lib/server/serverController.js +147 -20
- package/package.json +1 -1
- package/scripts/{active_record → activeRecord}/migrate.js +1 -1
- package/test/{active_record → activeRecord}/baseRecord.test.js +46 -90
- package/test/activeRecord/db/connection.test.js +149 -0
- package/test/activeRecord/db/connectionConfiguration.test.js +128 -0
- package/test/activeRecord/db/migrator.test.js +144 -0
- package/test/activeRecord/db/queryInterface.test.js +48 -0
- package/test/activeRecord/helpers/baseRecord.js +32 -0
- package/test/activeRecord/helpers/baseRecordMocks.js +59 -0
- package/test/activeRecord/helpers/connection.js +28 -0
- package/test/activeRecord/helpers/connectionConfiguration.js +32 -0
- package/test/activeRecord/helpers/fixtures.js +39 -0
- package/test/activeRecord/helpers/migrator.js +78 -0
- package/test/activeRecord/helpers/queryInterface.js +29 -0
- package/test/http/client.test.js +61 -239
- package/test/http/error.test.js +23 -47
- package/test/http/helpers/client.js +80 -0
- package/test/http/helpers/error.js +31 -0
- package/test/server/helpers/autoload/TmpWithHelpersController.js +17 -0
- package/test/server/helpers/serverController.js +13 -0
- package/test/server/serverController.test.js +319 -6
- package/test/active_record/db/connection.test.js +0 -221
- package/test/active_record/db/connectionConfiguration.test.js +0 -184
- package/test/active_record/db/migrator.test.js +0 -266
- package/test/active_record/db/queryInterface.test.js +0 -66
- /package/lib/{active_record → activeRecord}/db/connection.js +0 -0
- /package/lib/{active_record → activeRecord}/db/connectionConfiguration.js +0 -0
- /package/lib/{active_record → activeRecord}/db/importSchema.js +0 -0
- /package/lib/{active_record → activeRecord}/db/migration.js +0 -0
- /package/lib/{active_record → activeRecord}/db/migrationTemplate.js +0 -0
- /package/lib/{active_record → activeRecord}/db/migrationVersion.js +0 -0
- /package/lib/{active_record → activeRecord}/db/migrator.js +0 -0
- /package/lib/{active_record → activeRecord}/db/queryInterface.js +0 -0
- /package/lib/{active_record → activeRecord}/db/schema.js +0 -0
- /package/lib/{active_record → activeRecord}/index.js +0 -0
- /package/lib/{active_record → activeRecord}/utils.js +0 -0
package/package.json
CHANGED
|
@@ -1,80 +1,30 @@
|
|
|
1
|
-
const { BaseRecord }
|
|
2
|
-
const {
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const {
|
|
8
|
-
const
|
|
9
|
-
const { camelize } = require('inflection');
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const connectionConfig = {database: ' ', host: ' ', password: ' ', username: ' '};
|
|
13
|
-
const configSpy = jest.spyOn(ConnectionConfiguration, '_fetchAllConfigs');
|
|
14
|
-
configSpy.mockImplementation(() => ({default: {test: connectionConfig}}));
|
|
15
|
-
|
|
16
|
-
const mockSequelize = new (class MockSequelize {})();
|
|
17
|
-
const connectionSpy = jest.spyOn(Connection.prototype, '_sequelize', 'get');
|
|
18
|
-
connectionSpy.mockImplementation(() => mockSequelize);
|
|
1
|
+
const { BaseRecord } = require('../../lib/activeRecord/baseRecord');
|
|
2
|
+
const { getActiveRecordSchema } = require('../../lib/activeRecord/db/schema');
|
|
3
|
+
const importSchema = require('../../lib/activeRecord/db/importSchema');
|
|
4
|
+
const { Model, Sequelize } = require('sequelize');
|
|
5
|
+
const { redisClientFactory } = require('../../lib/redis');
|
|
6
|
+
const TestHelpers = require('@vida-global/test-helpers');
|
|
7
|
+
const { camelize } = require('inflection');
|
|
8
|
+
const Helpers = require('./helpers/baseRecord');
|
|
19
9
|
|
|
20
10
|
|
|
21
11
|
jest.mock('sequelize', () => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
constructor(data, opts={}) {
|
|
25
|
-
this.dataValues = data || {}
|
|
26
|
-
this.options = opts;
|
|
27
|
-
for (const [k,v] of Object.entries(this.dataValues)) {
|
|
28
|
-
this[k] = v;
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
static addHook = jest.fn()
|
|
32
|
-
static init = jest.fn()
|
|
33
|
-
static findByPk = jest.fn();
|
|
34
|
-
static findAll = jest.fn();
|
|
35
|
-
static primaryKeyAttribute = TestHelpers.Faker.Text.randomString();
|
|
36
|
-
toJSON() { return this.dataValues }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
MockModel.findAll.mockImplementation(() => []);
|
|
40
|
-
|
|
41
|
-
const dataTypeKey1 = TestHelpers.Faker.Text.randomString();
|
|
42
|
-
const dataTypeValue1 = TestHelpers.Faker.Text.randomString();
|
|
43
|
-
const dataTypeKey2 = TestHelpers.Faker.Text.randomString();
|
|
44
|
-
const dataTypeValue2 = TestHelpers.Faker.Text.randomString();
|
|
45
|
-
const Sequelize = {DataTypes: {
|
|
46
|
-
[dataTypeKey1]: {types: {postgres: [dataTypeValue1]}, key: dataTypeKey1},
|
|
47
|
-
[dataTypeKey2]: {types: {postgres: [dataTypeValue2]}, key: dataTypeKey2},
|
|
48
|
-
postgres: {}
|
|
49
|
-
}};
|
|
50
|
-
return { Model: MockModel, Sequelize } ;
|
|
12
|
+
const { MockModel, MockSequelize } = require('./helpers/baseRecordMocks');
|
|
13
|
+
return { Model: MockModel, Sequelize: MockSequelize } ;
|
|
51
14
|
});
|
|
52
15
|
|
|
53
|
-
const dataTypeKey1 = Object.keys(Sequelize.DataTypes)[0];
|
|
54
|
-
const dataTypeKey2 = Object.keys(Sequelize.DataTypes)[1];
|
|
55
|
-
const dataTypeValue1 = Sequelize.DataTypes[dataTypeKey1].types.postgres[0];
|
|
56
|
-
const dataTypeValue2 = Sequelize.DataTypes[dataTypeKey2].types.postgres[0];
|
|
57
|
-
const col1Name = `${TestHelpers.Faker.Text.randomString()}_${TestHelpers.Faker.Text.randomString()}_${TestHelpers.Faker.Text.randomString()}`;
|
|
58
|
-
|
|
59
|
-
|
|
60
16
|
jest.mock('../../lib/redis', () => {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
mGet: jest.fn(),
|
|
64
|
-
mSet: jest.fn(),
|
|
65
|
-
}
|
|
66
|
-
const redisClientFactory = () => client;
|
|
67
|
-
|
|
68
|
-
return { redisClientFactory };
|
|
17
|
+
const { mockRedisClientFactory } = require('./helpers/baseRecordMocks');
|
|
18
|
+
return { redisClientFactory: mockRedisClientFactory };
|
|
69
19
|
});
|
|
70
20
|
|
|
71
|
-
jest.mock('../../lib/
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
importSchema.mockImplementation(() => structuredClone(schema));
|
|
77
|
-
|
|
21
|
+
jest.mock('../../lib/activeRecord/db/importSchema', () => jest.fn());
|
|
22
|
+
importSchema.mockImplementation(() => structuredClone(Helpers.schema));
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
importSchema.mockImplementation(() => structuredClone(Helpers.schema));
|
|
27
|
+
})
|
|
78
28
|
|
|
79
29
|
|
|
80
30
|
afterEach(() => {
|
|
@@ -94,9 +44,9 @@ describe('BaseRecord', () => {
|
|
|
94
44
|
it ('calls `getBaseRecordSchema` and passes the response to init', () => {
|
|
95
45
|
class User extends BaseRecord {}
|
|
96
46
|
User.initialize();
|
|
97
|
-
const tableDetails = {created_at: {type: Sequelize.DataTypes[dataTypeKey1]},
|
|
98
|
-
updated_at: {type: Sequelize.DataTypes[dataTypeKey2]},
|
|
99
|
-
[
|
|
47
|
+
const tableDetails = {created_at: {type: Sequelize.DataTypes[Helpers.dataTypeKey1]},
|
|
48
|
+
updated_at: {type: Sequelize.DataTypes[Helpers.dataTypeKey2]},
|
|
49
|
+
[Helpers.colName]: {type: Sequelize.DataTypes[Helpers.dataTypeKey2]}
|
|
100
50
|
};
|
|
101
51
|
expect(importSchema).toHaveBeenCalledTimes(1);
|
|
102
52
|
expect(Model.init).toHaveBeenCalledTimes(1);
|
|
@@ -105,7 +55,7 @@ describe('BaseRecord', () => {
|
|
|
105
55
|
createdAt: 'created_at',
|
|
106
56
|
deletedAt: 'deleted_at',
|
|
107
57
|
modelName: 'User',
|
|
108
|
-
sequelize:
|
|
58
|
+
sequelize: new Helpers.MockSequelize(),
|
|
109
59
|
tableName: 'users',
|
|
110
60
|
updatedAt: 'updated_at'
|
|
111
61
|
});
|
|
@@ -120,7 +70,7 @@ describe('BaseRecord', () => {
|
|
|
120
70
|
createdAt: 'created_at',
|
|
121
71
|
deletedAt: 'deleted_at',
|
|
122
72
|
modelName: 'User',
|
|
123
|
-
sequelize:
|
|
73
|
+
sequelize: new Helpers.MockSequelize(),
|
|
124
74
|
tableName: 'users',
|
|
125
75
|
timestamps: false,
|
|
126
76
|
updatedAt: 'updated_at'
|
|
@@ -148,17 +98,17 @@ describe('BaseRecord', () => {
|
|
|
148
98
|
|
|
149
99
|
it ('allows for overriding getters and setters', () => {
|
|
150
100
|
class User extends BaseRecord {}
|
|
151
|
-
const getterMethodName = `_get${camelize(
|
|
152
|
-
const setterMethodName = `_set${camelize(
|
|
101
|
+
const getterMethodName = `_get${camelize(Helpers.colName)}`;
|
|
102
|
+
const setterMethodName = `_set${camelize(Helpers.colName)}`;
|
|
153
103
|
const getter = jest.fn();
|
|
154
104
|
const setter = jest.fn();
|
|
155
105
|
User.prototype[getterMethodName] = getter;
|
|
156
106
|
User.prototype[setterMethodName] = setter;
|
|
157
107
|
User.initialize();
|
|
158
|
-
const tableDetails = {created_at: {type: Sequelize.DataTypes[dataTypeKey1]},
|
|
159
|
-
updated_at: {type: Sequelize.DataTypes[dataTypeKey2]},
|
|
160
|
-
[
|
|
161
|
-
type: Sequelize.DataTypes[dataTypeKey2],
|
|
108
|
+
const tableDetails = {created_at: {type: Sequelize.DataTypes[Helpers.dataTypeKey1]},
|
|
109
|
+
updated_at: {type: Sequelize.DataTypes[Helpers.dataTypeKey2]},
|
|
110
|
+
[Helpers.colName]: {
|
|
111
|
+
type: Sequelize.DataTypes[Helpers.dataTypeKey2],
|
|
162
112
|
get: getter,
|
|
163
113
|
set: setter,
|
|
164
114
|
}};
|
|
@@ -167,7 +117,7 @@ describe('BaseRecord', () => {
|
|
|
167
117
|
createdAt: 'created_at',
|
|
168
118
|
deletedAt: 'deleted_at',
|
|
169
119
|
modelName: 'User',
|
|
170
|
-
sequelize:
|
|
120
|
+
sequelize: new Helpers.MockSequelize(),
|
|
171
121
|
tableName: 'users',
|
|
172
122
|
updatedAt: 'updated_at'
|
|
173
123
|
});
|
|
@@ -535,27 +485,33 @@ describe('BaseRecord', () => {
|
|
|
535
485
|
});
|
|
536
486
|
|
|
537
487
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
488
|
+
const hooks = ['Save', 'Destroy'];
|
|
489
|
+
describe.each(hooks)('BaseRecord._after%sCacheHook', (hook) => {
|
|
490
|
+
class Cacheable extends BaseRecord {
|
|
491
|
+
static get isCacheable() { return true; }
|
|
492
|
+
}
|
|
493
|
+
const hookMethod = `_after${hook}CacheHook`;
|
|
494
|
+
|
|
495
|
+
it (`is called as an after ${hook.toLowerCase()} hook for cached models`, () => {
|
|
496
|
+
expect(Cacheable.addHook).toHaveBeenCalledTimes(0);
|
|
497
|
+
Cacheable.initialize();
|
|
498
|
+
expect(Cacheable.addHook).toHaveBeenCalledTimes(2);
|
|
499
|
+
expect(Cacheable.addHook).toHaveBeenCalledWith(`after${hook}`, Cacheable[hookMethod]);
|
|
544
500
|
});
|
|
545
501
|
|
|
546
502
|
it ('is not called as an after save hook for non cached models', () => {
|
|
547
503
|
class Foo extends BaseRecord {}
|
|
548
504
|
Foo.initialize();
|
|
549
|
-
expect(
|
|
505
|
+
expect(Cacheable.addHook).toHaveBeenCalledTimes(0);
|
|
550
506
|
});
|
|
551
507
|
|
|
552
508
|
it ('calls BaseRecord#clearSelfCache and BaseRecord#updateCache', async () => {
|
|
553
|
-
const person = new
|
|
509
|
+
const person = new Cacheable();
|
|
554
510
|
const clearSpy = jest.spyOn(person, 'clearSelfCache');
|
|
555
511
|
const updateSpy = jest.spyOn(person, 'updateCache');
|
|
556
512
|
const options = {};
|
|
557
513
|
|
|
558
|
-
await
|
|
514
|
+
await Cacheable[hookMethod](person,options);
|
|
559
515
|
|
|
560
516
|
expect(clearSpy).toHaveBeenCalledTimes(1);
|
|
561
517
|
expect(updateSpy).toHaveBeenCalledTimes(1);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const { Connection } = require('../../../lib/activeRecord/db/connection');
|
|
2
|
+
const { ConnectionConfiguration } = require('../../../lib/activeRecord/db/connectionConfiguration');
|
|
3
|
+
const Helpers = require('../helpers/connection');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
let fetchConfigsSpy;
|
|
7
|
+
|
|
8
|
+
jest.mock('sequelize', () => {
|
|
9
|
+
const { MockSequelize } = require('../helpers/fixtures');
|
|
10
|
+
return { Sequelize: MockSequelize };
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
fetchConfigsSpy = jest.spyOn(ConnectionConfiguration, '_fetchAllConfigs');
|
|
15
|
+
fetchConfigsSpy.mockImplementation(Helpers.buildConfigs);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
fetchConfigsSpy.mockRestore();
|
|
20
|
+
Connection.clearConnectionsCache();
|
|
21
|
+
process.env.NODE_ENV = Helpers.originalNodeEnv;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
describe('Connection', () => {
|
|
26
|
+
describe('Connection#_sequelize', () => {
|
|
27
|
+
it ('caches connections per database id', () => {
|
|
28
|
+
const conn1 = new Connection();
|
|
29
|
+
const conn2 = new Connection();
|
|
30
|
+
const conn3 = new Connection(Helpers.databaseId1);
|
|
31
|
+
|
|
32
|
+
expect(conn1._sequelize).toBe(conn2._sequelize);
|
|
33
|
+
expect(conn1._sequelize).not.toBe(conn3._sequelize);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
describe('settings (postgres)', () => {
|
|
38
|
+
it.each([
|
|
39
|
+
['default', () => new Connection(), Helpers.defaultConfig],
|
|
40
|
+
['secondary', () => new Connection(Helpers.databaseId1), Helpers.secondaryConfig],
|
|
41
|
+
])('maps %s postgres config into sequelize options', (_label, connectionFactory, expectedConfig) => {
|
|
42
|
+
const connection = connectionFactory();
|
|
43
|
+
const options = connection._sequelize.options;
|
|
44
|
+
|
|
45
|
+
expect(options.database).toEqual(expectedConfig.database);
|
|
46
|
+
expect(options.dialect).toEqual('postgres');
|
|
47
|
+
expect(options.host).toEqual(expectedConfig.host);
|
|
48
|
+
expect(options.password).toEqual(expectedConfig.password);
|
|
49
|
+
expect(options.port).toEqual(expectedConfig.port);
|
|
50
|
+
expect(options.username).toEqual(expectedConfig.username);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it ('requires ssl when configured', () => {
|
|
54
|
+
const conn = new Connection(Helpers.databaseId2);
|
|
55
|
+
|
|
56
|
+
expect(conn._sequelize.options.dialectOptions).toEqual({ ssl: { require: true } });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it.each([
|
|
60
|
+
['development', undefined],
|
|
61
|
+
['production', false],
|
|
62
|
+
])('sets logging for %s', (environment, expectedLogging) => {
|
|
63
|
+
process.env.NODE_ENV = environment;
|
|
64
|
+
const conn = new Connection();
|
|
65
|
+
|
|
66
|
+
expect(conn._sequelize.options.logging).toBe(expectedLogging);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it ('sets the configured pool or defaults', () => {
|
|
70
|
+
const conn1 = new Connection();
|
|
71
|
+
const conn2 = new Connection(Helpers.databaseId1);
|
|
72
|
+
|
|
73
|
+
expect(conn1._sequelize.options.pool).toEqual(Helpers.defaultConfig.pool);
|
|
74
|
+
expect(conn2._sequelize.options.pool).toEqual({ min: 0, max: 5 });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it ('configures underscored naming', () => {
|
|
78
|
+
const conn = new Connection();
|
|
79
|
+
|
|
80
|
+
expect(conn._sequelize.options.define.underscored).toBeTruthy();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it ('configures replication when readers are present', () => {
|
|
84
|
+
const replicatedConfig = Helpers.buildPostgresConfig({
|
|
85
|
+
readers: [Helpers.buildPostgresConfig(), Helpers.buildPostgresConfig()],
|
|
86
|
+
});
|
|
87
|
+
fetchConfigsSpy.mockImplementation(() => ({ default: { test: replicatedConfig } }));
|
|
88
|
+
|
|
89
|
+
const conn = new Connection();
|
|
90
|
+
const options = conn._sequelize.options;
|
|
91
|
+
|
|
92
|
+
expect(options.database).toBe(undefined);
|
|
93
|
+
expect(options.host).toBe(undefined);
|
|
94
|
+
expect(options.port).toBe(undefined);
|
|
95
|
+
expect(options.username).toBe(undefined);
|
|
96
|
+
expect(options.password).toBe(undefined);
|
|
97
|
+
expect(options.replication).toEqual({
|
|
98
|
+
write: {
|
|
99
|
+
database: replicatedConfig.database,
|
|
100
|
+
host: replicatedConfig.host,
|
|
101
|
+
password: replicatedConfig.password,
|
|
102
|
+
port: replicatedConfig.port,
|
|
103
|
+
username: replicatedConfig.username,
|
|
104
|
+
},
|
|
105
|
+
read: replicatedConfig.readers,
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
describe('settings (sqlite)', () => {
|
|
112
|
+
it ('maps sqlite options', () => {
|
|
113
|
+
const conn = new Connection('sqlite');
|
|
114
|
+
|
|
115
|
+
expect(conn._sequelize.options.dialect).toEqual('sqlite');
|
|
116
|
+
expect(conn._sequelize.options.storage).toEqual(
|
|
117
|
+
`${process.cwd()}/config/db/database.test.sqlite`,
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
describe('Connection#close', () => {
|
|
125
|
+
it ('closes a single connection', () => {
|
|
126
|
+
const conn = new Connection();
|
|
127
|
+
conn._sequelize; // reference it to initiate the connection
|
|
128
|
+
|
|
129
|
+
conn.close();
|
|
130
|
+
|
|
131
|
+
expect(conn._sequelize.close).toHaveBeenCalledTimes(1);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
describe('Connection.closeAll', () => {
|
|
137
|
+
it ('closes all cached connections', () => {
|
|
138
|
+
const defaultConnection = new Connection();
|
|
139
|
+
const secondaryConnection = new Connection(Helpers.databaseId1);
|
|
140
|
+
defaultConnection._sequelize; // reference it to initiate the connection
|
|
141
|
+
secondaryConnection._sequelize; // reference it to initiate the connection
|
|
142
|
+
|
|
143
|
+
Connection.closeAll();
|
|
144
|
+
|
|
145
|
+
expect(defaultConnection._sequelize.close).toHaveBeenCalledTimes(1);
|
|
146
|
+
expect(secondaryConnection._sequelize.close).toHaveBeenCalledTimes(1);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const { ConnectionConfiguration } = require('../../../lib/activeRecord/db/connectionConfiguration');
|
|
2
|
+
const Helpers = require('../helpers/connectionConfiguration');
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
Helpers.fetchConfigsSpy.mockReset();
|
|
7
|
+
process.env.DB_POOL_MIN = Helpers.originalEnv.dbPoolMin;
|
|
8
|
+
process.env.DB_POOL_MAX = Helpers.originalEnv.dbPoolMax;
|
|
9
|
+
process.env.NODE_ENV = Helpers.originalEnv.nodeEnv;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
describe('ConnectionConfiguration', () => {
|
|
14
|
+
describe('ConnectionConfiguration.constructor', () => {
|
|
15
|
+
it.each([
|
|
16
|
+
['database id', () => ({ test: Helpers.testConfig })],
|
|
17
|
+
['environment', () => ({ [Helpers.databaseId1]: { production: Helpers.productionConfig } })],
|
|
18
|
+
['postgres fields', () => ({ [Helpers.databaseId1]: { test: { port: Math.random() } } })],
|
|
19
|
+
])('throws when required %s config is missing', (_label, configBuilder) => {
|
|
20
|
+
Helpers.mockConfigs(configBuilder());
|
|
21
|
+
expect(() => new ConnectionConfiguration(Helpers.databaseId1)).toThrow();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it ('does not require postgres details for sqlite', () => {
|
|
25
|
+
Helpers.mockConfigs({ [Helpers.databaseId1]: { test: Helpers.sqliteConfig } });
|
|
26
|
+
expect(() => new ConnectionConfiguration(Helpers.databaseId1)).not.toThrow();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
describe('resolved properties', () => {
|
|
32
|
+
it ('maps core fields from the environment config', () => {
|
|
33
|
+
Helpers.mockConfigs({
|
|
34
|
+
[Helpers.databaseId1]: { test: Helpers.testConfig, production: Helpers.productionConfig },
|
|
35
|
+
[Helpers.databaseId2]: { test: {}, production: {} },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const config = new ConnectionConfiguration(Helpers.databaseId1);
|
|
39
|
+
|
|
40
|
+
expect(config.database).toEqual(Helpers.testConfig.database);
|
|
41
|
+
expect(config.host).toEqual(Helpers.testConfig.host);
|
|
42
|
+
expect(config.password).toEqual(Helpers.testConfig.password);
|
|
43
|
+
expect(config.username).toEqual(Helpers.testConfig.username);
|
|
44
|
+
expect(config.ssl).toBeFalsy();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it ('sets ssl when configured', () => {
|
|
48
|
+
Helpers.mockConfigs({ [Helpers.databaseId1]: { test: Helpers.buildPostgresConfig({ ssl: true }) } });
|
|
49
|
+
const config = new ConnectionConfiguration(Helpers.databaseId1);
|
|
50
|
+
expect(config.ssl).toBeTruthy();
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
describe('ConnectionConfiguration#loggingEnabled', () => {
|
|
56
|
+
it.each([
|
|
57
|
+
['development', true],
|
|
58
|
+
['production', false],
|
|
59
|
+
])('returns %s logging setting', (environment, expected) => {
|
|
60
|
+
process.env.NODE_ENV = environment;
|
|
61
|
+
Helpers.mockConfigs({ [Helpers.databaseId1]: { [environment]: Helpers.testConfig } });
|
|
62
|
+
const config = new ConnectionConfiguration(Helpers.databaseId1);
|
|
63
|
+
expect(config.loggingEnabled).toBe(expected);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
describe('ConnectionConfiguration#pool', () => {
|
|
69
|
+
it ('uses default pool values when unset', () => {
|
|
70
|
+
Helpers.mockConfigs({ [Helpers.databaseId1]: { test: Helpers.testConfig } });
|
|
71
|
+
const config = new ConnectionConfiguration(Helpers.databaseId1);
|
|
72
|
+
expect(config.pool).toEqual({ min: 0, max: 5 });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it ('uses configured pool values', () => {
|
|
76
|
+
const pool = { min: Math.random(), max: Math.random() };
|
|
77
|
+
process.env.DB_POOL_MIN = String(Math.random());
|
|
78
|
+
process.env.DB_POOL_MAX = String(Math.random());
|
|
79
|
+
Helpers.mockConfigs({ [Helpers.databaseId1]: { test: Helpers.buildPostgresConfig({ pool }) } });
|
|
80
|
+
|
|
81
|
+
const config = new ConnectionConfiguration(Helpers.databaseId1);
|
|
82
|
+
|
|
83
|
+
expect(config.pool).toEqual(pool);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
describe('ConnectionConfiguration.configuredDatabaseIds', () => {
|
|
89
|
+
it ('returns all database ids configured for the current environment', () => {
|
|
90
|
+
Helpers.mockConfigs({
|
|
91
|
+
[Helpers.databaseId1]: { production: Helpers.productionConfig, test: Helpers.testConfig },
|
|
92
|
+
[Helpers.databaseId2]: { production: Helpers.productionConfig, test: Helpers.testConfig },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(ConnectionConfiguration.configuredDatabaseIds()).toEqual([Helpers.databaseId1, Helpers.databaseId2]);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it ('filters out ids missing the current environment', () => {
|
|
99
|
+
Helpers.mockConfigs({
|
|
100
|
+
[Helpers.databaseId1]: { production: Helpers.productionConfig },
|
|
101
|
+
[Helpers.databaseId2]: { production: Helpers.productionConfig, test: Helpers.testConfig },
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
expect(ConnectionConfiguration.configuredDatabaseIds()).toEqual([Helpers.databaseId2]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
describe('ConnectionConfiguration#dialect', () => {
|
|
110
|
+
it.each([
|
|
111
|
+
['postgres', { test: Helpers.testConfig }, 'postgres'],
|
|
112
|
+
['sqlite', { test: Helpers.sqliteConfig }, 'sqlite'],
|
|
113
|
+
])('returns %s dialect', (_label, configByEnv, expectedDialect) => {
|
|
114
|
+
Helpers.mockConfigs({ [Helpers.databaseId1]: configByEnv });
|
|
115
|
+
const config = new ConnectionConfiguration(Helpers.databaseId1);
|
|
116
|
+
expect(config.dialect).toEqual(expectedDialect);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
describe('ConnectionConfiguration#storage', () => {
|
|
122
|
+
it ('returns sqlite storage path', () => {
|
|
123
|
+
Helpers.mockConfigs({ [Helpers.databaseId1]: { test: Helpers.sqliteConfig } });
|
|
124
|
+
const config = new ConnectionConfiguration(Helpers.databaseId1);
|
|
125
|
+
expect(config.storage).toEqual(`${process.cwd()}/config/db/database.test.sqlite`);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const Helpers = require('../helpers/migrator');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
jest.clearAllMocks();
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
describe('Migrator', () => {
|
|
10
|
+
describe('Migrator#run', () => {
|
|
11
|
+
it ('runs `up` in a transaction', async () => {
|
|
12
|
+
await Helpers.migrator.run();
|
|
13
|
+
|
|
14
|
+
expect(Helpers.transactionSpy).toHaveBeenCalledTimes(1);
|
|
15
|
+
expect(Helpers.migrationModule.up).toHaveBeenCalledTimes(1);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it ('throws when `up` is missing', async () => {
|
|
19
|
+
const originalUp = Helpers.migrationModule.up;
|
|
20
|
+
delete Helpers.migrationModule.up;
|
|
21
|
+
|
|
22
|
+
await expect(Helpers.migrator.run()).rejects.toThrow();
|
|
23
|
+
|
|
24
|
+
Helpers.migrationModule.up = originalUp;
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
describe('Migrator#down', () => {
|
|
30
|
+
it ('runs `down` in a transaction', async () => {
|
|
31
|
+
await Helpers.migrator.rollback();
|
|
32
|
+
|
|
33
|
+
expect(Helpers.transactionSpy).toHaveBeenCalledTimes(1);
|
|
34
|
+
expect(Helpers.migrationModule.down).toHaveBeenCalledTimes(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it ('does not throw when `down` is missing', async () => {
|
|
38
|
+
const originalDown = Helpers.migrationModule.down;
|
|
39
|
+
delete Helpers.migrationModule.down;
|
|
40
|
+
|
|
41
|
+
await expect(Helpers.migrator.rollback()).resolves.toBe(undefined);
|
|
42
|
+
|
|
43
|
+
Helpers.migrationModule.down = originalDown;
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
describe('Migrator#createTable', () => {
|
|
49
|
+
const defaultColumns = {
|
|
50
|
+
id: { allowNull: false, autoIncrement: true, primaryKey: true, type: 'INTEGER' },
|
|
51
|
+
created_at: { allowNull: false, type: 'DATE' },
|
|
52
|
+
updated_at: { allowNull: false, type: 'DATE' },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
it ('creates tables with provided details', async () => {
|
|
56
|
+
const tableName = Helpers.randomString();
|
|
57
|
+
const details = { ...defaultColumns, [Helpers.randomString().toLowerCase()]: Helpers.randomNumber() };
|
|
58
|
+
|
|
59
|
+
await Helpers.migrator.createTable(tableName, details);
|
|
60
|
+
|
|
61
|
+
expect(Helpers.mockQueryInterface.createTable).toHaveBeenCalledWith(tableName, details);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it ('snake_cases created table columns', async () => {
|
|
65
|
+
const tableName = Helpers.randomString();
|
|
66
|
+
const details = { ...defaultColumns, myColumn: 1 };
|
|
67
|
+
|
|
68
|
+
await Helpers.migrator.createTable(tableName, details);
|
|
69
|
+
|
|
70
|
+
expect(Helpers.mockQueryInterface.createTable).toHaveBeenCalledWith(tableName, {
|
|
71
|
+
...defaultColumns,
|
|
72
|
+
my_column: 1,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
const tableOperations = [
|
|
79
|
+
['dropTable', [Helpers.randomString()]],
|
|
80
|
+
['addColumn', [Helpers.randomString(), Helpers.randomString(), Helpers.randomColumnDetails()]],
|
|
81
|
+
['removeColumn', [Helpers.randomString(), Helpers.randomString()]],
|
|
82
|
+
['changeColumn', [Helpers.randomString(), Helpers.randomString(), Helpers.randomColumnDetails()]],
|
|
83
|
+
];
|
|
84
|
+
describe.each(tableOperations)('Migrator#%s', (methodName, args) => {
|
|
85
|
+
it ('delegates to the query interface', async () => {
|
|
86
|
+
await Helpers.expectQueryInterfaceCall(methodName, args);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
describe('Migrator#renameColumn', () => {
|
|
92
|
+
it ('snake_cases renameColumn target', async () => {
|
|
93
|
+
const tableName = Helpers.randomString();
|
|
94
|
+
const oldName = Helpers.randomString();
|
|
95
|
+
|
|
96
|
+
await Helpers.migrator.renameColumn(tableName, oldName, 'myColumn');
|
|
97
|
+
|
|
98
|
+
expect(Helpers.mockQueryInterface.renameColumn).toHaveBeenCalledWith(tableName, oldName, 'my_column');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
describe('Migrator#addIndex', () => {
|
|
104
|
+
it.each([
|
|
105
|
+
['all supported options', { concurrently: true, unique: true, where: {}, name: 'foo' }],
|
|
106
|
+
['empty options', {}],
|
|
107
|
+
['falsey options', { concurrently: false, unique: false, where: null, name: null }],
|
|
108
|
+
])('builds addIndex options for %s', async (_label, options) => {
|
|
109
|
+
const tableName = Helpers.randomString();
|
|
110
|
+
const fields = [Helpers.randomString(), Helpers.randomString()];
|
|
111
|
+
const expectedOptions = { fields };
|
|
112
|
+
|
|
113
|
+
if (options.concurrently) expectedOptions.concurrently = true;
|
|
114
|
+
if (options.unique) expectedOptions.unique = true;
|
|
115
|
+
if (options.where) expectedOptions.where = options.where;
|
|
116
|
+
if (options.name) expectedOptions.name = options.name;
|
|
117
|
+
|
|
118
|
+
await Helpers.migrator.addIndex(tableName, fields, options);
|
|
119
|
+
|
|
120
|
+
expect(Helpers.mockQueryInterface.addIndex).toHaveBeenCalledTimes(1);
|
|
121
|
+
expect(Helpers.mockQueryInterface.addIndex).toHaveBeenCalledWith(tableName, expectedOptions);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
describe('Migrator#removeIndex', () => {
|
|
127
|
+
it.each([
|
|
128
|
+
['default options', false, {}],
|
|
129
|
+
['concurrently', true, { concurrently: true }],
|
|
130
|
+
])('passes removeIndex options for %s', async (_label, concurrently, expectedOptions) => {
|
|
131
|
+
const tableName = Helpers.randomString();
|
|
132
|
+
const fields = [Helpers.randomString(), Helpers.randomString()];
|
|
133
|
+
|
|
134
|
+
await Helpers.migrator.removeIndex(tableName, fields, concurrently);
|
|
135
|
+
|
|
136
|
+
expect(Helpers.mockQueryInterface.removeIndex).toHaveBeenCalledTimes(1);
|
|
137
|
+
expect(Helpers.mockQueryInterface.removeIndex).toHaveBeenCalledWith(
|
|
138
|
+
tableName,
|
|
139
|
+
fields,
|
|
140
|
+
expectedOptions,
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|