@platformatic/sql-events 0.5.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/test/helper.js ADDED
@@ -0,0 +1,66 @@
1
+ 'use strict'
2
+
3
+ // Needed to work with dates & postgresql
4
+ // See https://node-postgres.com/features/types/
5
+ process.env.TZ = 'UTC'
6
+
7
+ const connInfo = {}
8
+
9
+ if (!process.env.DB || process.env.DB === 'postgresql') {
10
+ connInfo.connectionString = 'postgres://postgres:postgres@127.0.0.1/postgres'
11
+ module.exports.isPg = true
12
+ } else if (process.env.DB === 'mariadb') {
13
+ connInfo.connectionString = 'mysql://root@127.0.0.1:3307/graph'
14
+ connInfo.poolSize = 10
15
+ module.exports.isMysql = true
16
+ } else if (process.env.DB === 'mysql') {
17
+ connInfo.connectionString = 'mysql://root@127.0.0.1/graph'
18
+ connInfo.poolSize = 10
19
+ module.exports.isMysql = true
20
+ } else if (process.env.DB === 'mysql8') {
21
+ connInfo.connectionString = 'mysql://root@127.0.0.1:3308/graph'
22
+ connInfo.poolSize = 10
23
+ module.exports.isMysql = true
24
+ } else if (process.env.DB === 'sqlite') {
25
+ connInfo.connectionString = 'sqlite://:memory:'
26
+ module.exports.isSQLite = true
27
+ }
28
+
29
+ module.exports.connInfo = connInfo
30
+
31
+ module.exports.clear = async function (db, sql) {
32
+ try {
33
+ await db.query(sql`DROP TABLE pages`)
34
+ } catch (err) {
35
+ }
36
+
37
+ try {
38
+ await db.query(sql`DROP TABLE categories`)
39
+ } catch {
40
+ }
41
+
42
+ try {
43
+ await db.query(sql`DROP TABLE posts`)
44
+ } catch {
45
+ }
46
+
47
+ try {
48
+ await db.query(sql`DROP TABLE simple_types`)
49
+ } catch {
50
+ }
51
+
52
+ try {
53
+ await db.query(sql`DROP TABLE owners`)
54
+ } catch {
55
+ }
56
+
57
+ try {
58
+ await db.query(sql`DROP TABLE users`)
59
+ } catch {
60
+ }
61
+
62
+ try {
63
+ await db.query(sql`DROP TABLE versions`)
64
+ } catch {
65
+ }
66
+ }
@@ -0,0 +1,317 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const sqlMapper = require('@platformatic/sql-mapper')
5
+ const { connect } = sqlMapper
6
+ const { clear, connInfo, isSQLite } = require('./helper')
7
+ const sqlEvents = require('..')
8
+ const { setupEmitter } = sqlEvents
9
+ const MQEmitter = require('mqemitter')
10
+
11
+ const fakeLogger = {
12
+ trace () {},
13
+ error () {}
14
+ }
15
+
16
+ test('get topics', async ({ equal, same, teardown }) => {
17
+ async function onDatabaseLoad (db, sql) {
18
+ await clear(db, sql)
19
+ teardown(() => db.dispose())
20
+
21
+ if (isSQLite) {
22
+ await db.query(sql`CREATE TABLE pages (
23
+ id INTEGER PRIMARY KEY,
24
+ title VARCHAR(42)
25
+ );`)
26
+ } else {
27
+ await db.query(sql`CREATE TABLE pages (
28
+ id SERIAL PRIMARY KEY,
29
+ title VARCHAR(255) NOT NULL
30
+ );`)
31
+ }
32
+ }
33
+ const mapper = await connect({
34
+ log: fakeLogger,
35
+ ...connInfo,
36
+ onDatabaseLoad
37
+ })
38
+ const pageEntity = mapper.entities.page
39
+
40
+ const mq = MQEmitter()
41
+ equal(setupEmitter({ mapper, mq, log: fakeLogger }), undefined)
42
+ const queue = await mapper.subscribe([
43
+ await pageEntity.getSubscriptionTopic({ action: 'save' }),
44
+ await pageEntity.getSubscriptionTopic({ action: 'delete' })
45
+ ])
46
+ equal(mapper.mq, mq)
47
+
48
+ const expected = []
49
+
50
+ // save - new record
51
+ const page = await pageEntity.save({
52
+ input: { title: 'fourth page' }
53
+ })
54
+ expected.push({
55
+ topic: '/entity/page/save/' + page.id,
56
+ payload: {
57
+ id: page.id
58
+ }
59
+ })
60
+
61
+ // save - update record
62
+ await pageEntity.save({
63
+ input: {
64
+ id: page.id,
65
+ title: 'fifth page'
66
+ }
67
+ })
68
+ expected.push({
69
+ topic: '/entity/page/save/' + page.id,
70
+ payload: {
71
+ id: page.id
72
+ }
73
+ })
74
+
75
+ await pageEntity.delete({
76
+ where: {
77
+ id: {
78
+ eq: page.id
79
+ }
80
+ },
81
+ fields: ['id', 'title']
82
+ })
83
+
84
+ expected.push({
85
+ topic: '/entity/page/delete/' + page.id,
86
+ payload: {
87
+ id: page.id
88
+ }
89
+ })
90
+
91
+ for await (const ev of queue) {
92
+ same(ev, expected.shift())
93
+ if (expected.length === 0) {
94
+ break
95
+ }
96
+ }
97
+ })
98
+
99
+ test('hooks', async ({ equal, same, teardown }) => {
100
+ async function onDatabaseLoad (db, sql) {
101
+ await clear(db, sql)
102
+ teardown(() => db.dispose())
103
+
104
+ if (isSQLite) {
105
+ await db.query(sql`CREATE TABLE pages (
106
+ id INTEGER PRIMARY KEY,
107
+ title VARCHAR(42)
108
+ );`)
109
+ } else {
110
+ await db.query(sql`CREATE TABLE pages (
111
+ id SERIAL PRIMARY KEY,
112
+ title VARCHAR(255) NOT NULL
113
+ );`)
114
+ }
115
+ }
116
+ const mapper = await connect({
117
+ log: fakeLogger,
118
+ ...connInfo,
119
+ onDatabaseLoad
120
+ })
121
+ const pageEntity = mapper.entities.page
122
+
123
+ const mq = MQEmitter()
124
+ equal(setupEmitter({ mapper, mq, log: fakeLogger }), undefined)
125
+ const queue = await mapper.subscribe([
126
+ await pageEntity.getSubscriptionTopic({ action: 'save' }),
127
+ await pageEntity.getSubscriptionTopic({ action: 'delete' })
128
+ ])
129
+ equal(mapper.mq, mq)
130
+
131
+ const expected = []
132
+
133
+ // save - new record
134
+ const page = await pageEntity.save({
135
+ input: { title: 'fourth page' }
136
+ })
137
+ expected.push({
138
+ topic: '/entity/page/save/' + page.id,
139
+ payload: {
140
+ id: page.id
141
+ }
142
+ })
143
+
144
+ // save - update record
145
+ await pageEntity.save({
146
+ input: {
147
+ id: page.id,
148
+ title: 'fifth page'
149
+ }
150
+ })
151
+ expected.push({
152
+ topic: '/entity/page/save/' + page.id,
153
+ payload: {
154
+ id: page.id
155
+ }
156
+ })
157
+
158
+ await pageEntity.delete({
159
+ where: {
160
+ id: {
161
+ eq: page.id
162
+ }
163
+ },
164
+ fields: ['id', 'title']
165
+ })
166
+
167
+ expected.push({
168
+ topic: '/entity/page/delete/' + page.id,
169
+ payload: {
170
+ id: page.id
171
+ }
172
+ })
173
+
174
+ for await (const ev of queue) {
175
+ same(ev, expected.shift())
176
+ if (expected.length === 0) {
177
+ break
178
+ }
179
+ }
180
+ })
181
+
182
+ test('get topics', async ({ equal, same, teardown }) => {
183
+ async function onDatabaseLoad (db, sql) {
184
+ await clear(db, sql)
185
+ teardown(() => db.dispose())
186
+
187
+ if (isSQLite) {
188
+ await db.query(sql`CREATE TABLE pages (
189
+ id INTEGER PRIMARY KEY,
190
+ title VARCHAR(42)
191
+ );`)
192
+ } else {
193
+ await db.query(sql`CREATE TABLE pages (
194
+ id SERIAL PRIMARY KEY,
195
+ title VARCHAR(255) NOT NULL
196
+ );`)
197
+ }
198
+ }
199
+ const mapper = await connect({
200
+ log: fakeLogger,
201
+ ...connInfo,
202
+ onDatabaseLoad
203
+ })
204
+ mapper.addEntityHooks('page', {
205
+ async getSubscriptionTopic (original, { action }) {
206
+ equal('create', action)
207
+ return original({ action })
208
+ }
209
+ })
210
+
211
+ const pageEntity = mapper.entities.page
212
+
213
+ const mq = MQEmitter()
214
+ equal(setupEmitter({ mapper, mq, log: fakeLogger }), undefined)
215
+ await pageEntity.getSubscriptionTopic({ action: 'save' })
216
+ })
217
+
218
+ test('no events', async ({ equal, same, teardown, fail, comment }) => {
219
+ async function onDatabaseLoad (db, sql) {
220
+ await clear(db, sql)
221
+ teardown(() => db.dispose())
222
+
223
+ if (isSQLite) {
224
+ await db.query(sql`CREATE TABLE pages (
225
+ id INTEGER PRIMARY KEY,
226
+ title VARCHAR(42)
227
+ );`)
228
+ } else {
229
+ await db.query(sql`CREATE TABLE pages (
230
+ id SERIAL PRIMARY KEY,
231
+ title VARCHAR(255) NOT NULL
232
+ );`)
233
+ }
234
+ }
235
+ const mapper = await connect({
236
+ log: fakeLogger,
237
+ ...connInfo,
238
+ onDatabaseLoad
239
+ })
240
+
241
+ const mq = MQEmitter()
242
+ equal(setupEmitter({ mapper, mq, log: fakeLogger }), undefined)
243
+
244
+ const pageEntity = mapper.entities.page
245
+
246
+ // disable publishing
247
+ pageEntity.getPublishTopic = function () {
248
+ return false
249
+ }
250
+
251
+ const queue = await mapper.subscribe([
252
+ await pageEntity.getSubscriptionTopic({ action: 'save' }),
253
+ await pageEntity.getSubscriptionTopic({ action: 'delete' })
254
+ ])
255
+ equal(mapper.mq, mq)
256
+
257
+ queue.on('data', function (msg) {
258
+ comment(JSON.stringify(msg, null, 2))
259
+ fail('no message')
260
+ })
261
+
262
+ // save - new record
263
+ const page = await pageEntity.save({
264
+ input: { title: 'fourth page' }
265
+ })
266
+
267
+ // save - update record
268
+ await pageEntity.save({
269
+ input: {
270
+ id: page.id,
271
+ title: 'fifth page'
272
+ }
273
+ })
274
+
275
+ // delete a record
276
+ await pageEntity.delete({
277
+ where: {
278
+ id: {
279
+ eq: page.id
280
+ }
281
+ },
282
+ fields: ['id', 'title']
283
+ })
284
+ })
285
+
286
+ test('wrong action', async ({ equal, rejects, teardown, fail, comment }) => {
287
+ async function onDatabaseLoad (db, sql) {
288
+ await clear(db, sql)
289
+ teardown(() => db.dispose())
290
+
291
+ if (isSQLite) {
292
+ await db.query(sql`CREATE TABLE pages (
293
+ id INTEGER PRIMARY KEY,
294
+ title VARCHAR(42)
295
+ );`)
296
+ } else {
297
+ await db.query(sql`CREATE TABLE pages (
298
+ id SERIAL PRIMARY KEY,
299
+ title VARCHAR(255) NOT NULL
300
+ );`)
301
+ }
302
+ }
303
+ const mapper = await connect({
304
+ log: fakeLogger,
305
+ ...connInfo,
306
+ onDatabaseLoad
307
+ })
308
+
309
+ setupEmitter({ mapper, log: fakeLogger })
310
+
311
+ const pageEntity = mapper.entities.page
312
+
313
+ equal(await pageEntity.getPublishTopic({ action: 'foo', data: { id: 42 } }), false)
314
+ rejects(pageEntity.getPublishTopic({ action: 'foo', data: { } }))
315
+ rejects(pageEntity.getPublishTopic({ action: 'foo' }))
316
+ rejects(pageEntity.getSubscriptionTopic({ action: 'foo' }), 'no such action foo')
317
+ })
@@ -0,0 +1,116 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const sqlMapper = require('@platformatic/sql-mapper')
5
+ const { connect } = sqlMapper
6
+ const { clear, connInfo, isSQLite } = require('./helper')
7
+ const sqlEvents = require('..')
8
+ const { setupEmitter } = sqlEvents
9
+ const MQEmitterRedis = require('mqemitter-redis')
10
+ const { promisify } = require('util')
11
+ const { PassThrough } = require('stream')
12
+
13
+ const fakeLogger = {
14
+ trace () {},
15
+ error () {}
16
+ }
17
+
18
+ test('emit events', async ({ equal, same, teardown, comment }) => {
19
+ async function onDatabaseLoad (db, sql) {
20
+ await clear(db, sql)
21
+ teardown(() => db.dispose())
22
+
23
+ if (isSQLite) {
24
+ await db.query(sql`CREATE TABLE pages (
25
+ id INTEGER PRIMARY KEY,
26
+ title VARCHAR(42)
27
+ );`)
28
+ } else {
29
+ await db.query(sql`CREATE TABLE pages (
30
+ id SERIAL PRIMARY KEY,
31
+ title VARCHAR(255) NOT NULL
32
+ );`)
33
+ }
34
+ }
35
+ const mapper = await connect({
36
+ log: fakeLogger,
37
+ ...connInfo,
38
+ onDatabaseLoad
39
+ })
40
+ const pageEntity = mapper.entities.page
41
+
42
+ equal(setupEmitter({ mapper, connectionString: 'redis://127.0.0.1:6379', log: fakeLogger }), undefined)
43
+ teardown(promisify(mapper.mq.close.bind(mapper.mq)))
44
+
45
+ const anotherMQ = new MQEmitterRedis()
46
+ teardown(() => anotherMQ.close())
47
+
48
+ const messages = new PassThrough({ objectMode: true })
49
+ await promisify(anotherMQ.on.bind(anotherMQ))('#', function (msg, cb) {
50
+ messages.write(msg, cb)
51
+ })
52
+
53
+ const queue = await mapper.subscribe([
54
+ '/entity/page/save/+',
55
+ '/entity/page/delete/+'
56
+ ])
57
+
58
+ const expected = []
59
+
60
+ // save - new record
61
+ const page = await pageEntity.save({
62
+ input: { title: 'fourth page' }
63
+ })
64
+ expected.push({
65
+ topic: '/entity/page/save/' + page.id,
66
+ payload: {
67
+ id: page.id
68
+ }
69
+ })
70
+
71
+ // save - update record
72
+ await pageEntity.save({
73
+ input: {
74
+ id: page.id,
75
+ title: 'fifth page'
76
+ }
77
+ })
78
+ expected.push({
79
+ topic: '/entity/page/save/' + page.id,
80
+ payload: {
81
+ id: page.id
82
+ }
83
+ })
84
+
85
+ await pageEntity.delete({
86
+ where: {
87
+ id: {
88
+ eq: page.id
89
+ }
90
+ },
91
+ fields: ['id', 'title']
92
+ })
93
+
94
+ expected.push({
95
+ topic: '/entity/page/delete/' + page.id,
96
+ payload: {
97
+ id: page.id
98
+ }
99
+ })
100
+
101
+ let i = 0
102
+ for await (const ev of queue) {
103
+ same(ev, expected[i++])
104
+ if (i === expected.length) {
105
+ break
106
+ }
107
+ }
108
+
109
+ i = 0
110
+ for await (const ev of messages) {
111
+ same(ev, expected[i++])
112
+ if (i === expected.length) {
113
+ break
114
+ }
115
+ }
116
+ })