@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.
Files changed (46) hide show
  1. package/.github/workflows/npm-test.yml +24 -0
  2. package/index.js +1 -1
  3. package/lib/{active_record → activeRecord}/README.md +3 -3
  4. package/lib/{active_record → activeRecord}/baseRecord.js +11 -2
  5. package/lib/http/README.md +2 -2
  6. package/lib/server/README.md +181 -20
  7. package/lib/server/errors.js +28 -0
  8. package/lib/server/index.js +3 -3
  9. package/lib/server/server.js +7 -53
  10. package/lib/server/serverController.js +147 -20
  11. package/package.json +1 -1
  12. package/scripts/{active_record → activeRecord}/migrate.js +1 -1
  13. package/test/{active_record → activeRecord}/baseRecord.test.js +46 -90
  14. package/test/activeRecord/db/connection.test.js +149 -0
  15. package/test/activeRecord/db/connectionConfiguration.test.js +128 -0
  16. package/test/activeRecord/db/migrator.test.js +144 -0
  17. package/test/activeRecord/db/queryInterface.test.js +48 -0
  18. package/test/activeRecord/helpers/baseRecord.js +32 -0
  19. package/test/activeRecord/helpers/baseRecordMocks.js +59 -0
  20. package/test/activeRecord/helpers/connection.js +28 -0
  21. package/test/activeRecord/helpers/connectionConfiguration.js +32 -0
  22. package/test/activeRecord/helpers/fixtures.js +39 -0
  23. package/test/activeRecord/helpers/migrator.js +78 -0
  24. package/test/activeRecord/helpers/queryInterface.js +29 -0
  25. package/test/http/client.test.js +61 -239
  26. package/test/http/error.test.js +23 -47
  27. package/test/http/helpers/client.js +80 -0
  28. package/test/http/helpers/error.js +31 -0
  29. package/test/server/helpers/autoload/TmpWithHelpersController.js +17 -0
  30. package/test/server/helpers/serverController.js +13 -0
  31. package/test/server/serverController.test.js +319 -6
  32. package/test/active_record/db/connection.test.js +0 -221
  33. package/test/active_record/db/connectionConfiguration.test.js +0 -184
  34. package/test/active_record/db/migrator.test.js +0 -266
  35. package/test/active_record/db/queryInterface.test.js +0 -66
  36. /package/lib/{active_record → activeRecord}/db/connection.js +0 -0
  37. /package/lib/{active_record → activeRecord}/db/connectionConfiguration.js +0 -0
  38. /package/lib/{active_record → activeRecord}/db/importSchema.js +0 -0
  39. /package/lib/{active_record → activeRecord}/db/migration.js +0 -0
  40. /package/lib/{active_record → activeRecord}/db/migrationTemplate.js +0 -0
  41. /package/lib/{active_record → activeRecord}/db/migrationVersion.js +0 -0
  42. /package/lib/{active_record → activeRecord}/db/migrator.js +0 -0
  43. /package/lib/{active_record → activeRecord}/db/queryInterface.js +0 -0
  44. /package/lib/{active_record → activeRecord}/db/schema.js +0 -0
  45. /package/lib/{active_record → activeRecord}/index.js +0 -0
  46. /package/lib/{active_record → activeRecord}/utils.js +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vida-global/core",
3
- "version": "1.2.5",
3
+ "version": "1.3.0",
4
4
  "description": "Core libraries for supporting Vida development",
5
5
  "author": "",
6
6
  "license": "ISC",
@@ -1,4 +1,4 @@
1
- const migration = require('../../lib/active_record/db/migration');
1
+ const migration = require('../../lib/activeRecord/db/migration');
2
2
  const { Command } = require('commander');
3
3
 
4
4
 
@@ -1,80 +1,30 @@
1
- const { BaseRecord } = require('../../lib/active_record/baseRecord');
2
- const { Connection} = require('../../lib/active_record/db/connection');
3
- const { ConnectionConfiguration } = require('../../lib/active_record/db/connectionConfiguration');
4
- const { getActiveRecordSchema } = require('../../lib/active_record/db/schema');
5
- const importSchema = require('../../lib/active_record/db/importSchema');
6
- const { Model, Sequelize } = require('sequelize');
7
- const { redisClientFactory } = require('../../lib/redis');
8
- const TestHelpers = require('@vida-global/test-helpers');
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 TestHelpers = require('@vida-global/test-helpers');
23
- class MockModel {
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 client = {
62
- del: jest.fn(),
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/active_record/db/importSchema', () => jest.fn());
72
- const schema = {created_at: {type: dataTypeValue1},
73
- updated_at: {type: dataTypeValue2},
74
- [col1Name]: {type: dataTypeValue2}
75
- };
76
- importSchema.mockImplementation(() => structuredClone(schema));
77
- beforeEach(() => importSchema.mockImplementation(() => structuredClone(schema)));
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
- [col1Name]: {type: Sequelize.DataTypes[dataTypeKey2]}
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: mockSequelize,
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: mockSequelize,
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(col1Name)}`;
152
- const setterMethodName = `_set${camelize(col1Name)}`;
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
- [col1Name]: {
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: mockSequelize,
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
- describe('BaseRecord._afterSaveCacheHook', () => {
539
- it ('is called as an after save hook for cached models', () => {
540
- expect(Person.addHook).toHaveBeenCalledTimes(0);
541
- Person.initialize();
542
- expect(Person.addHook).toHaveBeenCalledTimes(1);
543
- expect(Person.addHook).toHaveBeenCalledWith('afterSave', Person._afterSaveCacheHook);
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(Person.addHook).toHaveBeenCalledTimes(0);
505
+ expect(Cacheable.addHook).toHaveBeenCalledTimes(0);
550
506
  });
551
507
 
552
508
  it ('calls BaseRecord#clearSelfCache and BaseRecord#updateCache', async () => {
553
- const person = new Person();
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 Person._afterSaveCacheHook(person,options);
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
+ });