lesgo 0.6.3 → 0.7.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.
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
 
3
+ set -Eeuo pipefail
4
+
3
5
  ###############################################################################
4
6
  # #
5
7
  # INITIALIZE EVERYTHING #
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lesgo",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Core framework for lesgo node.js serverless framework.",
5
5
  "main": "./src/index.js",
6
6
  "author": "Sufiyan Rahmat",
@@ -57,6 +57,7 @@
57
57
  "firebase-admin": "^9.3.0",
58
58
  "jsonwebtoken": "^8.5.1",
59
59
  "memcached-elasticache": "^1.1.1",
60
+ "mysql2": "^2.2.5",
60
61
  "nanoid": "^3.1.16"
61
62
  },
62
63
  "husky": {
@@ -5,8 +5,8 @@ import {
5
5
  import ValidationErrorException from '../__mocks__/ValidationErrorException';
6
6
 
7
7
  describe('MiddlewareGroup: test errorHandler middleware', () => {
8
- it('test with thrown Error', () => {
9
- const data = errorHttpResponseHandler({
8
+ it('test with thrown Error', async () => {
9
+ const data = await errorHttpResponseHandler({
10
10
  error: new Error('Test validation error'),
11
11
  });
12
12
 
@@ -29,8 +29,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
29
29
  expect(dataBody).toHaveProperty('error.details', '');
30
30
  });
31
31
 
32
- it('test with thrown custom Error with default parameters', () => {
33
- const data = errorHttpResponseHandler({
32
+ it('test with thrown custom Error with default parameters', async () => {
33
+ const data = await errorHttpResponseHandler({
34
34
  error: new ValidationErrorException('Test validation error'),
35
35
  });
36
36
  const dataBody = JSON.parse(data.body);
@@ -45,8 +45,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
45
45
  expect(dataBody).toHaveProperty('error.details', '');
46
46
  });
47
47
 
48
- it('test with thrown custom Error with given parameters', () => {
49
- const data = errorHttpResponseHandler({
48
+ it('test with thrown custom Error with given parameters', async () => {
49
+ const data = await errorHttpResponseHandler({
50
50
  error: new ValidationErrorException(
51
51
  'Test validation error',
52
52
  'VALIDATION_ERROR_SAMPLE',
@@ -70,8 +70,10 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
70
70
  });
71
71
  });
72
72
 
73
- it('test with error message', () => {
74
- const data = errorHttpResponseHandler({ error: 'Test error message' });
73
+ it('test with error message', async () => {
74
+ const data = await errorHttpResponseHandler({
75
+ error: 'Test error message',
76
+ });
75
77
  const dataBody = JSON.parse(data.body);
76
78
 
77
79
  expect(data.statusCode).toBe(500);
@@ -81,8 +83,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
81
83
  expect(dataBody).toHaveProperty('error.details', '');
82
84
  });
83
85
 
84
- it('test with error message with event', () => {
85
- const data = errorHttpResponseHandler({
86
+ it('test with error message with event', async () => {
87
+ const data = await errorHttpResponseHandler({
86
88
  error: 'Test error message',
87
89
  event: {
88
90
  someEventKey: 'someEventValue',
@@ -97,8 +99,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
97
99
  });
98
100
  });
99
101
 
100
- it('test with error message with event in non-debug mode', () => {
101
- const data = errorHttpResponseHandler({
102
+ it('test with error message with event in non-debug mode', async () => {
103
+ const data = await errorHttpResponseHandler({
102
104
  error: 'Test error message',
103
105
  event: {
104
106
  someEventKey: 'someEventValue',
@@ -110,8 +112,8 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
110
112
  expect(dataBody).toHaveProperty('_meta', {});
111
113
  });
112
114
 
113
- it('test with undefined opts', () => {
114
- const data = errorHttpResponseHandler();
115
+ it('test with undefined opts', async () => {
116
+ const data = await errorHttpResponseHandler();
115
117
 
116
118
  expect(data.headers['Access-Control-Allow-Origin']).toBe('*');
117
119
  expect(data.headers['Cache-Control']).toBe('no-cache');
@@ -128,16 +130,31 @@ describe('MiddlewareGroup: test errorHandler middleware', () => {
128
130
  expect(dataBody).toHaveProperty('error.message', '');
129
131
  expect(dataBody).toHaveProperty('error.details', '');
130
132
  });
133
+
134
+ it('should call db.end() whenever a db options is set', async () => {
135
+ const end = jest.fn().mockResolvedValue();
136
+ await errorHttpResponseHandler({
137
+ error: 'Test error message',
138
+ event: {
139
+ someEventKey: 'someEventValue',
140
+ },
141
+ db: {
142
+ end,
143
+ },
144
+ });
145
+
146
+ expect(end).toHaveBeenCalledTimes(1);
147
+ });
131
148
  });
132
149
 
133
150
  describe('MiddlewareGroup: test errorHttpResponseAfterHandler', () => {
134
- it('test with default parameters', () => {
151
+ it('test with default parameters', async () => {
135
152
  const handler = {
136
153
  error: {},
137
154
  event: {},
138
155
  };
139
156
 
140
- errorHttpResponseAfterHandler(handler, () => {});
157
+ await errorHttpResponseAfterHandler(handler, () => {});
141
158
  expect(handler.response).toHaveProperty('statusCode', 500);
142
159
  });
143
160
  });
@@ -1,7 +1,9 @@
1
+ import app from 'Config/app'; // eslint-disable-line import/no-unresolved
1
2
  import {
2
3
  normalizeRequest,
3
4
  normalizeHttpRequestBeforeHandler,
4
5
  } from '../normalizeHttpRequestMiddleware';
6
+ import logger from '../../utils/logger';
5
7
 
6
8
  describe('MiddlewareGroup: test normalizeRequest', () => {
7
9
  it('test with default parameters', () => {
@@ -116,4 +118,26 @@ describe('MiddlewareGroup: test normalizeHttpRequestBeforeHandler', () => {
116
118
  normalizeHttpRequestBeforeHandler(handler, () => {});
117
119
  expect(handler.event.auth.sub).toBe('f2b5349d-f5e3-44f5-9c08-ae6b01e95434');
118
120
  });
121
+
122
+ it('should not set meta on debug', () => {
123
+ app.debug = true;
124
+ const handler = {
125
+ event: {
126
+ headers: {},
127
+ auth: '1',
128
+ queryStringParameters: {
129
+ foo: 'bar',
130
+ },
131
+ body: {
132
+ test: 'body',
133
+ },
134
+ },
135
+ };
136
+ normalizeHttpRequestBeforeHandler(handler, () => {});
137
+ expect(logger.meta.auth).toBe(handler.event.auth);
138
+ expect(logger.meta.queryStringParameters).toStrictEqual(
139
+ handler.event.queryStringParameters
140
+ );
141
+ expect(logger.meta.body).toStrictEqual(handler.event.body);
142
+ });
119
143
  });
@@ -4,8 +4,8 @@ import {
4
4
  } from '../successHttpResponseMiddleware';
5
5
 
6
6
  describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
7
- it('test default without parameters', () => {
8
- const data = successHttpResponseHandler();
7
+ it('test default without parameters', async () => {
8
+ const data = await successHttpResponseHandler();
9
9
 
10
10
  expect(data.headers['Access-Control-Allow-Origin']).toBe('*');
11
11
  expect(data.headers['Cache-Control']).toBe('no-cache');
@@ -20,8 +20,8 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
20
20
  expect(dataBody).toHaveProperty('_meta', {});
21
21
  });
22
22
 
23
- it('test default', () => {
24
- const data = successHttpResponseHandler({ response: 'Some message' });
23
+ it('test default', async () => {
24
+ const data = await successHttpResponseHandler({ response: 'Some message' });
25
25
 
26
26
  expect(data.headers['Access-Control-Allow-Origin']).toBe('*');
27
27
  expect(data.headers['Cache-Control']).toBe('no-cache');
@@ -36,8 +36,8 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
36
36
  expect(dataBody).toHaveProperty('_meta', {});
37
37
  });
38
38
 
39
- it('test with status code and event', () => {
40
- const data = successHttpResponseHandler({
39
+ it('test with status code and event', async () => {
40
+ const data = await successHttpResponseHandler({
41
41
  response: 'Some message',
42
42
  statusCode: 201,
43
43
  event: {
@@ -53,8 +53,8 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
53
53
  expect(dataBody).toHaveProperty('_meta', {});
54
54
  });
55
55
 
56
- it('test with status code and event in debug mode', () => {
57
- const data = successHttpResponseHandler({
56
+ it('test with status code and event in debug mode', async () => {
57
+ const data = await successHttpResponseHandler({
58
58
  response: 'Some message',
59
59
  statusCode: 201,
60
60
  event: {
@@ -73,8 +73,8 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
73
73
  });
74
74
  });
75
75
 
76
- it('test with configurable header', () => {
77
- const data = successHttpResponseHandler({
76
+ it('test with configurable header', async () => {
77
+ const data = await successHttpResponseHandler({
78
78
  response: 'Some message',
79
79
  headers: {
80
80
  'Access-Control-Allow-Credentials': false,
@@ -96,16 +96,32 @@ describe('MiddlewareGroup: test successHttpResponseHandler middleware', () => {
96
96
  expect(dataBody).toHaveProperty('data', 'Some message');
97
97
  expect(dataBody).toHaveProperty('_meta', {});
98
98
  });
99
+
100
+ it('should call db.end() whenever a db options is set', async () => {
101
+ const end = jest.fn().mockResolvedValue();
102
+ await successHttpResponseHandler({
103
+ response: 'Some message',
104
+ headers: {
105
+ 'Access-Control-Allow-Credentials': false,
106
+ 'X-Token-Id': 'token',
107
+ },
108
+ db: {
109
+ end,
110
+ },
111
+ });
112
+
113
+ expect(end).toHaveBeenCalledTimes(1);
114
+ });
99
115
  });
100
116
 
101
117
  describe('MiddlewareGroup: test successHttpResponseAfterHandler', () => {
102
- it('test with default parameters', () => {
118
+ it('test with default parameters', async () => {
103
119
  const handler = {
104
120
  response: {},
105
121
  event: {},
106
122
  };
107
123
 
108
- successHttpResponseAfterHandler(handler, () => {});
124
+ await successHttpResponseAfterHandler(handler, () => {});
109
125
  expect(handler.response).toHaveProperty('statusCode', 200);
110
126
  });
111
127
  });
@@ -1,6 +1,7 @@
1
1
  import logger from '../utils/logger';
2
+ import isEmpty from '../utils/isEmpty';
2
3
 
3
- export const errorHttpResponseHandler = opts => {
4
+ export const errorHttpResponseHandler = async opts => {
4
5
  const defaults = {
5
6
  response: '',
6
7
  statusCode: 500,
@@ -47,6 +48,12 @@ export const errorHttpResponseHandler = opts => {
47
48
  logger.warn(options.error);
48
49
  }
49
50
 
51
+ try {
52
+ if (!isEmpty(opts.db)) await opts.db.end();
53
+ } catch (err) {
54
+ // do nothing
55
+ }
56
+
50
57
  return {
51
58
  headers: options.headers,
52
59
  statusCode,
@@ -54,7 +61,7 @@ export const errorHttpResponseHandler = opts => {
54
61
  };
55
62
  };
56
63
 
57
- export const errorHttpResponseAfterHandler = (handler, next, opts) => {
64
+ export const errorHttpResponseAfterHandler = async (handler, next, opts) => {
58
65
  const defaults = {
59
66
  error: handler.error,
60
67
  event: handler.event,
@@ -64,7 +71,7 @@ export const errorHttpResponseAfterHandler = (handler, next, opts) => {
64
71
  const options = { ...defaults, ...opts };
65
72
 
66
73
  // eslint-disable-next-line no-param-reassign
67
- handler.response = errorHttpResponseHandler(options);
74
+ handler.response = await errorHttpResponseHandler(options);
68
75
  /* istanbul ignore next */
69
76
  next();
70
77
  };
@@ -31,7 +31,7 @@ export const gzip = async response => {
31
31
  * Determine request origin
32
32
  */
33
33
  export const determineRequestOrigin = handler => {
34
- const { requestContext } = handler.event;
34
+ const { requestContext = {} } = handler.event;
35
35
  let requestFrom = 'APIGATEWAY';
36
36
 
37
37
  if (requestContext.elb) {
@@ -1,6 +1,7 @@
1
1
  import gzipHttpResponse from './gzipHttpResponse';
2
+ import isEmpty from '../utils/isEmpty';
2
3
 
3
- export const successHttpResponseHandler = opts => {
4
+ export const successHttpResponseHandler = async opts => {
4
5
  const defaults = {
5
6
  response: '',
6
7
  statusCode: 200,
@@ -23,6 +24,12 @@ export const successHttpResponseHandler = opts => {
23
24
 
24
25
  const options = { ...defaults, ...optionsHeadersMerged };
25
26
 
27
+ try {
28
+ if (!isEmpty(opts.db)) await opts.db.end();
29
+ } catch (err) {
30
+ // do nothing
31
+ }
32
+
26
33
  return {
27
34
  headers: options.headers,
28
35
  statusCode: options.statusCode,
@@ -43,7 +50,7 @@ export const successHttpResponseAfterHandler = async (handler, next, opts) => {
43
50
  const options = { ...defaults, ...opts };
44
51
 
45
52
  // eslint-disable-next-line no-param-reassign
46
- handler.response = successHttpResponseHandler(options);
53
+ handler.response = await successHttpResponseHandler(options);
47
54
 
48
55
  // eslint-disable-next-line no-param-reassign
49
56
  handler.response = await gzipHttpResponse(handler, opts);
@@ -0,0 +1,183 @@
1
+ import mysql from 'mysql2/promise';
2
+ import logger from '../utils/logger';
3
+ import isEmpty from '../utils/isEmpty';
4
+ import LesgoException from '../exceptions/LesgoException';
5
+ import LengthAwarePaginator from './pagination/LengthAwarePaginator';
6
+ import Paginator from './pagination/Paginator';
7
+
8
+ const FILE = 'Lesgo/services/AuroraDbRDSProxyService';
9
+
10
+ /**
11
+ * Service to connect to AWS Aurora Provisioned MySQL Database via RDS Proxy.
12
+ * Use Services/AuroraDbServerlessService for the Serverless type via Data API.
13
+ */
14
+ export default class AuroraDbRDSProxyService {
15
+ constructor(opts = {}) {
16
+ const { host, user, password, database, persists } = opts;
17
+
18
+ this.clientOpts = {
19
+ host,
20
+ user,
21
+ password,
22
+ database,
23
+ persists,
24
+ };
25
+
26
+ this.conn = {};
27
+ }
28
+
29
+ /**
30
+ * Persists the database connection for later use
31
+ *
32
+ * @param {*} connectionOpts
33
+ * @returns
34
+ */
35
+ async pConnect(connectionOpts = {}) {
36
+ await this.connect({ ...connectionOpts, persists: true });
37
+ }
38
+
39
+ async connect(connectionOpts = {}) {
40
+ const clientOpts = {
41
+ host: connectionOpts.host ? connectionOpts.host : this.clientOpts.host,
42
+ user: connectionOpts.user ? connectionOpts.user : this.clientOpts.user,
43
+ password: connectionOpts.password
44
+ ? connectionOpts.password
45
+ : this.clientOpts.password,
46
+ database: connectionOpts.database
47
+ ? connectionOpts.database
48
+ : this.clientOpts.database,
49
+ };
50
+
51
+ const persistentConn = connectionOpts.persists || this.clientOpts.persists;
52
+
53
+ logger.debug(`${FILE}::PREPARING DB CONNECTION`, {
54
+ clientOpts,
55
+ persistentConn,
56
+ });
57
+
58
+ const conn = await mysql.createConnection(clientOpts);
59
+ conn.config.namedPlaceholders = true;
60
+ logger.debug(`${FILE}::DB CONNECTED`);
61
+
62
+ if (persistentConn) {
63
+ this.conn = conn;
64
+ }
65
+
66
+ return conn;
67
+ }
68
+
69
+ /* eslint-disable-next-line class-methods-use-this */
70
+ async end(conn = {}) {
71
+ logger.debug(`${FILE}::ENDING DB CONNECTION`);
72
+ if (!isEmpty(conn)) {
73
+ await conn.end();
74
+ logger.debug(`${FILE}::DB DISCONNECTED`);
75
+ } else {
76
+ await this.conn.end();
77
+ logger.debug(`${FILE}::PERSISTED DB DISCONNECTED`);
78
+ }
79
+ }
80
+
81
+ async query(sql, sqlParams, connectionOpts = {}) {
82
+ let conn = {};
83
+ if (!isEmpty(connectionOpts)) {
84
+ conn = await this.connect(connectionOpts);
85
+ } else if (isEmpty(this.conn)) {
86
+ conn = await this.connect();
87
+ } else {
88
+ conn = this.conn;
89
+ }
90
+
91
+ try {
92
+ logger.debug(`${FILE}::QUERYING_DB`, { sql, sqlParams });
93
+ const [results, fields] = await conn.execute(sql, sqlParams);
94
+ logger.debug(`${FILE}::DB_RESPONSE`, { results, fields });
95
+
96
+ return { results, fields };
97
+ } catch (err) {
98
+ throw new LesgoException(
99
+ 'Exception caught executing SQL Statement',
100
+ `${FILE}::QUERY_EXECUTION_EXCEPTION`,
101
+ 500,
102
+ { err }
103
+ );
104
+ } finally {
105
+ if (isEmpty(this.conn) || !isEmpty(connectionOpts)) await this.end(conn);
106
+ }
107
+ }
108
+
109
+ async select(sql, sqlParams, connectionOpts = {}) {
110
+ const resp = await this.query(sql, sqlParams, connectionOpts);
111
+ return resp.results;
112
+ }
113
+
114
+ async selectFirst(sql, sqlParams, connectionOpts = {}) {
115
+ const resp = await this.query(sql, sqlParams, connectionOpts);
116
+ return resp.results[0];
117
+ }
118
+
119
+ async selectPaginate(
120
+ sql,
121
+ sqlParams,
122
+ perPage = 10,
123
+ currentPage = 1,
124
+ total = null,
125
+ connectionOpts = {}
126
+ ) {
127
+ let paginator;
128
+ if (typeof total === 'number') {
129
+ paginator = new LengthAwarePaginator(
130
+ this,
131
+ sql,
132
+ sqlParams,
133
+ {
134
+ perPage,
135
+ currentPage,
136
+ total,
137
+ },
138
+ connectionOpts
139
+ );
140
+ } else {
141
+ paginator = new Paginator(
142
+ this,
143
+ sql,
144
+ sqlParams,
145
+ {
146
+ perPage,
147
+ currentPage,
148
+ },
149
+ connectionOpts
150
+ );
151
+ }
152
+
153
+ return (await paginator).toObject();
154
+ }
155
+
156
+ async insert(sql, sqlParams, connectionOpts = {}) {
157
+ const resp = await this.query(sql, sqlParams, connectionOpts);
158
+
159
+ if (resp.results.affectedRows <= 0) {
160
+ throw new LesgoException(
161
+ 'No records inserted from INSERT query',
162
+ `${FILE}::NO_RECORDS_INSERTED`,
163
+ 400,
164
+ { resp, sql, sqlParams }
165
+ );
166
+ }
167
+
168
+ return resp.results.insertId;
169
+ }
170
+
171
+ async update(sql, sqlParams, connectionOpts = {}) {
172
+ const resp = await this.query(sql, sqlParams, connectionOpts);
173
+
174
+ if (resp.results.changedRows <= 0) {
175
+ logger.warn(`${FILE}::No records updated from UPDATE query`, {
176
+ sql,
177
+ sqlParams,
178
+ });
179
+ }
180
+
181
+ return Promise.resolve();
182
+ }
183
+ }
@@ -0,0 +1,278 @@
1
+ import mysql from 'mysql2/promise';
2
+ import AuroraDbRDSProxyService from '../AuroraDbRDSProxyService';
3
+ import LesgoException from '../../exceptions/LesgoException';
4
+
5
+ const auroraConfig = {
6
+ host: 'some-fake-host',
7
+ user: 'fakeUsername',
8
+ password: 'fakePassword',
9
+ database: 'fakeDbName',
10
+ };
11
+
12
+ const customConfig = {
13
+ host: 'some-fake-host-2',
14
+ user: 'fakeUsername2',
15
+ password: 'fakePassword2',
16
+ database: 'fakeDbName2',
17
+ };
18
+
19
+ describe('test AuroraDbRDSProxyService instantiate', () => {
20
+ it('should not throw exception when instantiating', () => {
21
+ const db = new AuroraDbRDSProxyService(auroraConfig);
22
+ expect(db.clientOpts).toMatchObject(auroraConfig);
23
+ });
24
+
25
+ it('should allow for empty config', () => {
26
+ const db = new AuroraDbRDSProxyService();
27
+ expect(db.clientOpts).toMatchObject({});
28
+ });
29
+ });
30
+
31
+ describe('test AuroraDbRDSProxyService connect', () => {
32
+ it('should be able to connect without custom config', async () => {
33
+ const db = new AuroraDbRDSProxyService(auroraConfig);
34
+ const conn = await db.connect();
35
+
36
+ expect(conn.mocked).toMatchObject(auroraConfig);
37
+ });
38
+
39
+ it('should be able to connect with custom config', async () => {
40
+ const db = new AuroraDbRDSProxyService(auroraConfig);
41
+ const conn = await db.connect(customConfig);
42
+
43
+ expect(conn.mocked).toMatchObject(customConfig);
44
+ });
45
+ });
46
+
47
+ describe('test AuroraDbRDSProxyService query', () => {
48
+ it('should return results object when calling query function', async () => {
49
+ const db = new AuroraDbRDSProxyService(auroraConfig);
50
+ const resp = await db.query('SELECT_QUERY', {});
51
+
52
+ expect(resp).toMatchObject({
53
+ results: [
54
+ {
55
+ id: 1,
56
+ uid: 'some-uid-1',
57
+ },
58
+ {
59
+ id: 2,
60
+ uid: 'some-uid-2',
61
+ },
62
+ ],
63
+ });
64
+
65
+ expect(mysql.createConnection).toHaveBeenCalled();
66
+ });
67
+
68
+ it('should return results object when calling querying with connection options passed', async () => {
69
+ const db = new AuroraDbRDSProxyService(auroraConfig);
70
+ const resp = await db.query('SELECT_QUERY', {}, auroraConfig);
71
+
72
+ expect(resp).toMatchObject({
73
+ results: [
74
+ {
75
+ id: 1,
76
+ uid: 'some-uid-1',
77
+ },
78
+ {
79
+ id: 2,
80
+ uid: 'some-uid-2',
81
+ },
82
+ ],
83
+ });
84
+
85
+ expect(mysql.createConnection).toHaveBeenCalled();
86
+ });
87
+
88
+ it('should return results object when calling querying with no connection options passed and persistent conn', async () => {
89
+ const db = new AuroraDbRDSProxyService(auroraConfig);
90
+ await db.pConnect();
91
+ const resp = await db.query('SELECT_QUERY', {});
92
+
93
+ expect(resp).toMatchObject({
94
+ results: [
95
+ {
96
+ id: 1,
97
+ uid: 'some-uid-1',
98
+ },
99
+ {
100
+ id: 2,
101
+ uid: 'some-uid-2',
102
+ },
103
+ ],
104
+ });
105
+
106
+ expect(mysql.createConnection).toHaveBeenCalled();
107
+ });
108
+ });
109
+
110
+ describe('test AuroraDbRDSProxyService select', () => {
111
+ it('should return array results when calling select function', async () => {
112
+ const db = new AuroraDbRDSProxyService(auroraConfig);
113
+ return expect(db.select('SELECT_QUERY', {})).resolves.toMatchObject([
114
+ {
115
+ id: 1,
116
+ uid: 'some-uid-1',
117
+ },
118
+ {
119
+ id: 2,
120
+ uid: 'some-uid-2',
121
+ },
122
+ ]);
123
+ });
124
+
125
+ it('should throw an exception when writing with invalid query', async () => {
126
+ const error = new LesgoException(
127
+ 'Exception caught executing SQL Statement',
128
+ 'AURORADBSERVICE_QUERY_EXCEPTION',
129
+ 500,
130
+ {
131
+ err: {
132
+ code: 'BadRequestException',
133
+ },
134
+ }
135
+ );
136
+
137
+ const db = new AuroraDbRDSProxyService(auroraConfig);
138
+ return expect(db.select('INVALID_QUERY', {})).rejects.toMatchObject(error);
139
+ });
140
+
141
+ it('should throw an exception when calling select with invalid query parameters', async () => {
142
+ const error = new LesgoException(
143
+ 'Exception caught executing SQL Statement',
144
+ 'AURORADBSERVICE_QUERY_EXCEPTION',
145
+ 500,
146
+ {
147
+ err: {
148
+ code: 'BadRequestException',
149
+ },
150
+ }
151
+ );
152
+
153
+ const db = new AuroraDbRDSProxyService(auroraConfig);
154
+ return expect(
155
+ db.select('RANDOM_QUERY', 'INVALID_QUERY_PARAMETERS')
156
+ ).rejects.toMatchObject(error);
157
+ });
158
+ });
159
+
160
+ describe('test AuroraDbRDSProxyService selectPaginate', () => {
161
+ it('should return paginated results when calling selectPaginate function', async () => {
162
+ const db = new AuroraDbRDSProxyService(auroraConfig);
163
+ return expect(db.selectPaginate('SELECT_QUERY', {})).resolves.toMatchObject(
164
+ {
165
+ count: 2,
166
+ previous_page: false,
167
+ current_page: 1,
168
+ next_page: false,
169
+ per_page: 10,
170
+ items: [
171
+ {
172
+ id: 1,
173
+ uid: 'some-uid-1',
174
+ },
175
+ {
176
+ id: 2,
177
+ uid: 'some-uid-2',
178
+ },
179
+ ],
180
+ }
181
+ );
182
+ });
183
+
184
+ it('should return paginated results when calling selectPaginate with defined total', async () => {
185
+ const db = new AuroraDbRDSProxyService(auroraConfig);
186
+ return expect(
187
+ db.selectPaginate('SELECT_QUERY', {}, 1, 2, 1)
188
+ ).resolves.toMatchObject({
189
+ count: 1,
190
+ previous_page: 1,
191
+ current_page: 2,
192
+ next_page: 3,
193
+ per_page: 1,
194
+ items: [
195
+ {
196
+ id: 1,
197
+ uid: 'some-uid-1',
198
+ },
199
+ ],
200
+ });
201
+ });
202
+ });
203
+
204
+ describe('test AuroraDbRDSProxyService selectFirst', () => {
205
+ it('should only return the first record when calling selectFirst', async () => {
206
+ const db = new AuroraDbRDSProxyService(auroraConfig);
207
+ return expect(db.selectFirst('SELECT_QUERY', {})).resolves.toMatchObject({
208
+ id: 1,
209
+ uid: 'some-uid-1',
210
+ });
211
+ });
212
+ });
213
+
214
+ describe('test AuroraDbRDSProxyService insert', () => {
215
+ it('should return recordId when inserting record', async () => {
216
+ const db = new AuroraDbRDSProxyService(auroraConfig);
217
+ return expect(db.insert('INSERT_QUERY', {})).resolves.toEqual(20);
218
+ });
219
+
220
+ it('should throw exception when calling insert with invalid query', async () => {
221
+ const error = new LesgoException(
222
+ 'No records inserted from INSERT query',
223
+ 'AURORADBSERVICE_NO_RECORDS_INSERTED',
224
+ 400
225
+ );
226
+
227
+ const db = new AuroraDbRDSProxyService(auroraConfig);
228
+ return expect(db.insert('INVALID_INSERT_QUERY', {})).rejects.toMatchObject(
229
+ error
230
+ );
231
+ });
232
+ });
233
+
234
+ describe('test AuroraDbRDSProxyService update', () => {
235
+ it('should return success when making update query', async () => {
236
+ const db = new AuroraDbRDSProxyService(auroraConfig);
237
+ return expect(db.update('UPDATE_QUERY', {})).resolves.not.toThrow();
238
+ });
239
+
240
+ it('should throw not exception when caliing update with invalid query', async () => {
241
+ const db = new AuroraDbRDSProxyService(auroraConfig);
242
+ return expect(db.update('INVALID_UPDATE_QUERY', {})).resolves.not.toThrow();
243
+ });
244
+ });
245
+
246
+ describe('test AuroraDbRDSProxyService pConnect', () => {
247
+ it('should be able to connect without custom config', async () => {
248
+ const db = new AuroraDbRDSProxyService(auroraConfig);
249
+ await db.pConnect();
250
+
251
+ expect(db.conn.mocked).toMatchObject(auroraConfig);
252
+ });
253
+
254
+ it('should be able to connect with custom config', async () => {
255
+ const db = new AuroraDbRDSProxyService(auroraConfig);
256
+ await db.pConnect(customConfig);
257
+
258
+ expect(db.conn.mocked).toMatchObject(customConfig);
259
+ });
260
+ });
261
+
262
+ describe('test AuroraDbRDSProxyService end', () => {
263
+ it('should end the connection when passed', async () => {
264
+ const db = new AuroraDbRDSProxyService(auroraConfig);
265
+ const conn = await db.connect();
266
+ await db.end(conn);
267
+
268
+ expect(conn.end).toHaveBeenCalledTimes(1);
269
+ });
270
+
271
+ it('should end the persisted connection when none is passed', async () => {
272
+ const db = new AuroraDbRDSProxyService(auroraConfig);
273
+ await db.pConnect();
274
+ await db.end();
275
+
276
+ expect(db.conn.end).toHaveBeenCalledTimes(1);
277
+ });
278
+ });
@@ -12,8 +12,9 @@ export default class LengthAwarePaginator extends Paginator {
12
12
  * @param sql
13
13
  * @param sqlParams
14
14
  * @param options
15
+ * @param connection
15
16
  */
16
- constructor(db, sql, sqlParams, options) {
17
+ constructor(db, sql, sqlParams, options, connection = {}) {
17
18
  const validFields = [{ key: 'total', type: 'number', required: true }];
18
19
 
19
20
  let validated = {};
@@ -33,7 +34,7 @@ export default class LengthAwarePaginator extends Paginator {
33
34
 
34
35
  const { total } = validated;
35
36
 
36
- super(db, sql, sqlParams, options);
37
+ super(db, sql, sqlParams, options, connection);
37
38
  this.totalProp = total;
38
39
  }
39
40
 
@@ -12,13 +12,14 @@ export default class Paginator {
12
12
  * @param sqlParams
13
13
  * @param options
14
14
  */
15
- constructor(db, sql, sqlParams, options = {}) {
15
+ constructor(db, sql, sqlParams, options = {}, connection = {}) {
16
16
  const validFields = [
17
17
  { key: 'db', type: 'object', required: true },
18
18
  { key: 'sql', type: 'string', required: true },
19
19
  { key: 'sqlParams', type: 'object', required: true },
20
20
  { key: 'perPage', type: 'number', required: false },
21
21
  { key: 'currentPage', type: 'number', required: false },
22
+ { key: 'connection', type: 'object', required: false },
22
23
  ];
23
24
 
24
25
  let validated = {};
@@ -29,6 +30,7 @@ export default class Paginator {
29
30
  sql,
30
31
  sqlParams,
31
32
  ...options,
33
+ connection,
32
34
  },
33
35
  validFields
34
36
  );
@@ -56,6 +58,8 @@ export default class Paginator {
56
58
 
57
59
  this.response = [];
58
60
  this.totalProp = false;
61
+
62
+ this.connection = connection;
59
63
  }
60
64
 
61
65
  /**
@@ -215,7 +219,8 @@ export default class Paginator {
215
219
  async executeQuery() {
216
220
  this.response = await this.dbProp.select(
217
221
  this.generatePaginationSqlSnippet(),
218
- this.sqlParamsProp
222
+ this.sqlParamsProp,
223
+ this.connection
219
224
  );
220
225
 
221
226
  this.hasNext = this.response.length > this.perPage();
@@ -232,7 +237,11 @@ export default class Paginator {
232
237
  * @returns {Promise<number>}
233
238
  */
234
239
  async countTotalItems() {
235
- const resp = await this.dbProp.select(this.sqlProp, this.sqlParamsProp);
240
+ const resp = await this.dbProp.select(
241
+ this.sqlProp,
242
+ this.sqlParamsProp,
243
+ this.connection
244
+ );
236
245
  this.totalProp = Object.keys(resp).length;
237
246
 
238
247
  return this.totalProp;
@@ -1,5 +1,12 @@
1
1
  import config from 'Config/db'; // eslint-disable-line import/no-unresolved
2
- import db from '../db';
2
+
3
+ let db;
4
+
5
+ beforeEach(() => {
6
+ jest.isolateModules(() => {
7
+ db = require('../db').default; // eslint-disable-line global-require
8
+ });
9
+ });
3
10
 
4
11
  describe('test db utils instantiate', () => {
5
12
  it('should not throw error on instantiating AuroraDbService', () => {
@@ -29,4 +36,36 @@ describe('test db utils instantiate', () => {
29
36
  },
30
37
  });
31
38
  });
39
+
40
+ it('should update AuroraDb credentials on connect based on dataApi config', () => {
41
+ config.default = 'dataApi';
42
+ let thisDb;
43
+ jest.isolateModules(() => {
44
+ thisDb = require('../db').default; // eslint-disable-line global-require
45
+ });
46
+
47
+ return expect(thisDb.client).toMatchObject({
48
+ mocked: {
49
+ database: config.connections.dataApi.database,
50
+ resourceArn: config.connections.dataApi.resourceArn,
51
+ secretArn: config.connections.dataApi.secretArn,
52
+ },
53
+ });
54
+ });
55
+
56
+ it('should update AuroraDb credentials on connect based on rdsProxy config', () => {
57
+ config.default = 'rdsProxy';
58
+ let thisDb;
59
+ jest.isolateModules(() => {
60
+ thisDb = require('../db').default; // eslint-disable-line global-require
61
+ });
62
+
63
+ return expect(thisDb.clientOpts).toMatchObject({
64
+ database: config.connections.rdsProxy.database,
65
+ host: config.connections.rdsProxy.host,
66
+ user: config.connections.rdsProxy.user,
67
+ password: config.connections.rdsProxy.password,
68
+ persists: config.connections.rdsProxy.persists,
69
+ });
70
+ });
32
71
  });
package/src/utils/db.js CHANGED
@@ -1,6 +1,17 @@
1
- import config from 'Config/db'; // eslint-disable-line import/no-unresolved
1
+ import dbConfig from 'Config/db'; // eslint-disable-line import/no-unresolved
2
+ import AuroraDbRDSProxyService from '../services/AuroraDbRDSProxyService';
2
3
  import AuroraDbService from '../services/AuroraDbService';
3
4
 
4
- const db = new AuroraDbService(config);
5
+ /* eslint-disable-next-line import/no-mutable-exports */
6
+ let db;
7
+
8
+ if (dbConfig.default === 'rdsProxy' || dbConfig.default === 'rdsProxyRead') {
9
+ db = new AuroraDbRDSProxyService(dbConfig.connections[dbConfig.default]);
10
+ } else if (dbConfig.default === 'dataApi') {
11
+ db = new AuroraDbService(dbConfig.connections[dbConfig.default]);
12
+ } else {
13
+ // @deprecated
14
+ db = new AuroraDbService(dbConfig);
15
+ }
5
16
 
6
17
  export default db;
@@ -4,7 +4,7 @@ import LoggerService from '../services/LoggerService';
4
4
  const transports = [
5
5
  {
6
6
  logType: 'console',
7
- level: app.debug ? 'debug' : 'info',
7
+ level: /* istanbul ignore next */ app.debug ? 'debug' : 'info',
8
8
  config: {
9
9
  getCreatedAt: true,
10
10
  tags: {