@toa.io/storages.mongodb 0.2.0-dev.1 → 0.2.1-dev.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toa.io/storages.mongodb",
3
- "version": "0.2.0-dev.1",
3
+ "version": "0.2.1-dev.3",
4
4
  "description": "Toa MongoDB Storage Connector",
5
5
  "author": "temich <tema.gurtovoy@gmail.com>",
6
6
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -19,7 +19,11 @@
19
19
  "test": "echo \"Error: run tests from root\" && exit 1"
20
20
  },
21
21
  "dependencies": {
22
- "mongodb": "4.1.4"
22
+ "@toa.io/console": "*",
23
+ "@toa.io/core": "*",
24
+ "@toa.io/generic": "*",
25
+ "@toa.io/pointer": "*",
26
+ "mongodb": "4.13.0"
23
27
  },
24
- "gitHead": "a0df6f203bca3e26e3a4a791a213e418d2b65764"
28
+ "gitHead": "2be07592325b2e4dc823e81d882a4e50bf50de24"
25
29
  }
@@ -0,0 +1,5 @@
1
+ 'use strict'
2
+
3
+ const { uris } = require('@toa.io/pointer')
4
+
5
+ exports.annotation = uris.construct
@@ -0,0 +1,90 @@
1
+ // noinspection JSCheckFunctionSignatures
2
+
3
+ 'use strict'
4
+
5
+ const { MongoClient } = require('mongodb')
6
+ const { Connector } = require('@toa.io/core')
7
+ const { console } = require('@toa.io/console')
8
+
9
+ /**
10
+ * @implements {toa.mongodb.Connection}
11
+ */
12
+ class Connection extends Connector {
13
+ /** @type {toa.mongodb.Pointer} */
14
+ #pointer
15
+ /** @type {import('mongodb').MongoClient} */
16
+ #client
17
+ /** @type {import('mongodb').Collection<toa.mongodb.Record>} */
18
+ #collection
19
+
20
+ /**
21
+ * @param {toa.mongodb.Pointer} pointer
22
+ */
23
+ constructor (pointer) {
24
+ super()
25
+
26
+ this.#pointer = pointer
27
+ this.#client = new MongoClient(pointer.reference, OPTIONS)
28
+ }
29
+
30
+ async connection () {
31
+ await this.#client.connect()
32
+
33
+ this.#collection = this.#client.db(this.#pointer.db).collection(this.#pointer.collection)
34
+
35
+ console.info(`Storage Mongo connected to ${this.#pointer.label}/${this.#pointer.db}/${this.#pointer.collection}`)
36
+ }
37
+
38
+ async disconnection () {
39
+ await this.#client.close()
40
+
41
+ console.info(`Storage Mongo disconnected from ${this.#pointer.label}/${this.#pointer.db}/${this.#pointer.collection}`)
42
+ }
43
+
44
+ /** @hot */
45
+ async get (query, options) {
46
+ return /** @type {toa.mongodb.Record} */ this.#collection.findOne(query, options)
47
+ }
48
+
49
+ /** @hot */
50
+ async find (query, options) {
51
+ const cursor = await this.#collection.find(query, options)
52
+
53
+ return cursor.toArray()
54
+ }
55
+
56
+ /** @hot */
57
+ async add (record) {
58
+ /** @type {boolean} */
59
+ let result
60
+
61
+ try {
62
+ const response = await this.#collection.insertOne(record)
63
+
64
+ result = response.acknowledged
65
+ } catch (e) {
66
+ if (e.code === 11000) result = false // duplicate id
67
+ else throw e
68
+ }
69
+
70
+ return result
71
+ }
72
+
73
+ /** @hot */
74
+ async replace (query, record, options) {
75
+ return await this.#collection.findOneAndReplace(query, record, options)
76
+ }
77
+
78
+ /** @hot */
79
+ async update (query, update, options) {
80
+ return this.#collection.findOneAndUpdate(query, update, options)
81
+ }
82
+ }
83
+
84
+ const OPTIONS = {
85
+ useNewUrlParser: true,
86
+ useUnifiedTopology: true,
87
+ ignoreUndefined: true
88
+ }
89
+
90
+ exports.Connection = Connection
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const pointer = require('@toa.io/pointer')
4
+
5
+ /** @type {toa.deployment.dependency.Constructor} */
6
+ const deployment = (instances, annotation) => {
7
+ if (annotation === undefined) throw new Error('MongoDB pointer annotation is required')
8
+
9
+ return pointer.deployment(instances, annotation, OPTIONS)
10
+ }
11
+
12
+ /** @type {toa.pointer.deployment.Options} */
13
+ const OPTIONS = { prefix: 'storages-mongodb' }
14
+
15
+ exports.deployment = deployment
package/src/factory.js ADDED
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ const { Pointer } = require('./pointer')
4
+ const { Connection } = require('./connection')
5
+ const { Storage } = require('./storage')
6
+
7
+ /**
8
+ * @implements {toa.core.storages.Factory}
9
+ */
10
+ class Factory {
11
+ storage (locator) {
12
+ const pointer = new Pointer(locator)
13
+ const connection = new Connection(pointer)
14
+
15
+ return new Storage(connection)
16
+ }
17
+ }
18
+
19
+ exports.Factory = Factory
package/src/index.js CHANGED
@@ -1,5 +1,9 @@
1
- const { Storage } = require('./storage')
2
- const { deployments } = require('./deployments')
1
+ 'use strict'
3
2
 
4
- exports.Storage = Storage
5
- exports.deployments = deployments
3
+ const { annotation } = require('./annotation')
4
+ const { deployment } = require('./deployment')
5
+ const { Factory } = require('./factory')
6
+
7
+ exports.annotation = annotation
8
+ exports.deployment = deployment
9
+ exports.Factory = Factory
package/src/pointer.js ADDED
@@ -0,0 +1,28 @@
1
+ 'use strict'
2
+
3
+ const { Pointer: Base } = require('@toa.io/pointer')
4
+
5
+ // noinspection JSClosureCompilerSyntax
6
+ /**
7
+ * @implements {toa.mongodb.Pointer}
8
+ */
9
+ class Pointer extends Base {
10
+ db
11
+ collection
12
+
13
+ /**
14
+ * @param {toa.core.Locator} locator
15
+ */
16
+ constructor (locator) {
17
+ super(PREFIX, locator, OPTIONS)
18
+
19
+ this.db = locator.namespace
20
+ this.collection = locator.name
21
+ }
22
+ }
23
+
24
+ /** @type {toa.pointer.Options} */
25
+ const OPTIONS = { protocol: 'mongodb:' }
26
+ const PREFIX = 'storages-mongodb'
27
+
28
+ exports.Pointer = Pointer
package/src/record.js CHANGED
@@ -1,11 +1,19 @@
1
1
  'use strict'
2
2
 
3
+ /**
4
+ * @param {toa.core.storages.Record} entity
5
+ * @returns {toa.mongodb.Record}
6
+ */
3
7
  const to = (entity) => {
4
8
  const { id, _version, ...rest } = entity
5
9
 
6
- return { _id: id, _version: _version === undefined ? 1 : _version + 1, ...rest }
10
+ return /** @type {toa.mongodb.Record} */ { _id: id, _version: _version + 1, ...rest }
7
11
  }
8
12
 
13
+ /**
14
+ * @param {toa.mongodb.Record} record
15
+ * @returns {toa.core.storages.Record}
16
+ */
9
17
  const from = (record) => {
10
18
  if (record === undefined || record === null) return null
11
19
 
package/src/storage.js CHANGED
@@ -2,53 +2,58 @@
2
2
 
3
3
  const { Connector } = require('@toa.io/core')
4
4
 
5
- const { Client } = require('./client')
6
5
  const { translate } = require('./translate')
7
6
  const { to, from } = require('./record')
8
7
 
8
+ /**
9
+ * @implements {toa.core.Storage}
10
+ */
9
11
  class Storage extends Connector {
10
- #client
12
+ /** @type {toa.mongodb.Connection} */
13
+ #connection
11
14
 
12
- constructor (locator) {
15
+ /**
16
+ * @param {toa.mongodb.Connection} connection
17
+ */
18
+ constructor (connection) {
13
19
  super()
14
20
 
15
- const host = locator.host('mongodb')
21
+ this.#connection = connection
16
22
 
17
- // TODO: create Factory, add test for host
18
- this.#client = new Client(host, locator.domain, locator.name)
19
- this.depends(this.#client)
23
+ this.depends(connection)
20
24
  }
21
25
 
22
26
  async get (query) {
23
27
  const { criteria, options } = translate(query)
24
28
 
25
- const record = await this.#client.get(criteria, options)
29
+ const record = await this.#connection.get(criteria, options)
26
30
 
27
31
  return from(record)
28
32
  }
29
33
 
30
- async add (entity) {
31
- let result
34
+ async find (query) {
35
+ const { criteria, options } = translate(query)
36
+ const recordset = await this.#connection.find(criteria, options)
32
37
 
33
- try {
34
- result = await this.#client.add(to(entity))
35
- } catch (e) {
36
- if (e.code === 11000) result = false // duplicate id
37
- else throw e
38
- }
38
+ return recordset.map((item) => from(item))
39
+ }
40
+
41
+ async add (entity) {
42
+ const record = to(entity)
39
43
 
40
- return result
44
+ return await this.#connection.add(record)
41
45
  }
42
46
 
43
47
  async set (entity) {
44
48
  const criteria = { _id: entity.id, _version: entity._version }
45
- const result = await this.#client.replace(criteria, to(entity))
49
+ const result = await this.#connection.replace(criteria, to(entity))
46
50
 
47
51
  return result.value !== null
48
52
  }
49
53
 
50
54
  async store (entity) {
51
- return entity._version === 0 ? this.add(entity) : this.set(entity)
55
+ if (entity._version === 0) return this.add(entity)
56
+ else return this.set(entity)
52
57
  }
53
58
 
54
59
  async upsert (query, changeset, insert) {
@@ -68,17 +73,10 @@ class Storage extends Connector {
68
73
 
69
74
  options.returnDocument = 'after'
70
75
 
71
- const result = await this.#client.update(criteria, update, options)
76
+ const result = await this.#connection.update(criteria, update, options)
72
77
 
73
78
  return from(result.value)
74
79
  }
75
-
76
- async find (query) {
77
- const { criteria, options } = translate(query)
78
- const recordset = await this.#client.find(criteria, options)
79
-
80
- return recordset.map(from)
81
- }
82
80
  }
83
81
 
84
82
  exports.Storage = Storage
@@ -1,8 +1,13 @@
1
1
  'use strict'
2
2
 
3
3
  const { rename } = require('./rename')
4
+
5
+ /**
6
+ * @param {toa.core.storages.ast.Node} node
7
+ * @returns {import('mongodb').Filter}
8
+ */
4
9
  const criteria = (node) => {
5
- if (!TYPES[node.type]) { throw new Error('Query criteria AST parse error') }
10
+ if (TYPES[node.type] === undefined) throw new Error(`AST parse error: unknown node type '${node.type}'`)
6
11
 
7
12
  return TYPES[node.type](node)
8
13
  }
@@ -38,8 +43,11 @@ TYPES.LOGIC = (expression) => {
38
43
  TYPES.COMPARISON = (expression) => {
39
44
  const left = criteria(expression.left)
40
45
  const right = criteria(expression.right)
46
+ const operator = OPERATORS.COMPARISON[expression.operator]
47
+
48
+ if (operator === undefined) throw new Error(`AST parse error: unknown operator '${expression.operator}'`)
41
49
 
42
- return { [left]: { [OPERATORS.COMPARISON[expression.operator]]: right } }
50
+ return { [left]: { [operator]: right } }
43
51
  }
44
52
 
45
53
  TYPES.SELECTOR = (expression) => rename(expression.selector)
package/src/translate.js CHANGED
@@ -2,13 +2,17 @@
2
2
 
3
3
  const parse = { ...require('./translate/criteria'), ...require('./translate/options') }
4
4
 
5
+ /**
6
+ * @param {toa.core.storages.Query} query
7
+ * @returns {{criteria: Object, options: Object}}
8
+ */
5
9
  const translate = (query) => {
6
10
  const result = { criteria: {}, options: {} }
7
11
 
8
- if (query.criteria) result.criteria = parse.criteria(query.criteria)
9
- if (query.options) result.options = parse.options(query.options)
10
- if (query.id) result.criteria._id = query.id
11
- if (query.version) result.criteria._version = query.version
12
+ if (query.criteria !== undefined) result.criteria = parse.criteria(query.criteria)
13
+ if (query.options !== undefined) result.options = parse.options(query.options)
14
+ if (query.id !== undefined) result.criteria._id = query.id
15
+ if (query.version !== undefined) result.criteria._version = query.version
12
16
 
13
17
  return result
14
18
  }
@@ -0,0 +1,16 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const mock = { uris: { construct: () => generate() }, Pointer: class {} }
6
+
7
+ jest.mock('@toa.io/pointer', () => mock)
8
+ const { annotation } = require('../')
9
+
10
+ it('should export annotations', () => {
11
+ expect(annotation).toBeDefined()
12
+ })
13
+
14
+ it('should export connectors.uris.construct', () => {
15
+ expect(annotation).toStrictEqual(mock.uris.construct)
16
+ })
@@ -0,0 +1,13 @@
1
+ 'use strict'
2
+
3
+ const { deployment } = require('../')
4
+
5
+ it('should be', () => {
6
+ expect(deployment).toBeDefined()
7
+ })
8
+
9
+ it('should throw if annotation is not defined', () => {
10
+ const instances = []
11
+
12
+ expect(() => deployment(instances, undefined)).toThrow('is required')
13
+ })
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const Connection = jest.fn().mockImplementation(function () {})
6
+ const Storage = jest.fn().mockImplementation(function () {})
7
+
8
+ /** @type {toa.core.Locator} */ const locator = {
9
+ name: generate(),
10
+ namespace: generate(),
11
+ id: generate(),
12
+ label: generate(),
13
+ uppercase: generate().toUpperCase(),
14
+ hostname: jest.fn(() => generate())
15
+ }
16
+
17
+ exports.mock = { Connection, Storage }
18
+ exports.locator = locator
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ const { encode } = require('@toa.io/generic')
4
+
5
+ const fixtures = require('./factory.fixtures')
6
+ const mock = fixtures.mock
7
+
8
+ jest.mock('../src/storage', () => ({ Storage: mock.Storage }))
9
+ jest.mock('../src/connection', () => ({ Connection: mock.Connection }))
10
+
11
+ const { Factory } = require('../src/')
12
+
13
+ /** @type {toa.core.storages.Factory} */
14
+ let factory
15
+
16
+ const uris = { default: 'mongodb://whatever' }
17
+ const value = encode(uris)
18
+
19
+ process.env.TOA_STORAGES_MONGODB_POINTER = value
20
+
21
+ beforeEach(() => {
22
+ factory = new Factory()
23
+ })
24
+
25
+ it('should create url', () => {
26
+ factory.storage(fixtures.locator)
27
+
28
+ expect(mock.Connection).toHaveBeenCalled()
29
+
30
+ const instance = mock.Connection.mock.instances[0]
31
+ /** @type {toa.mongodb.Pointer} */
32
+ const pointer = mock.Connection.mock.calls[0][0]
33
+
34
+ expect(mock.Storage).toHaveBeenLastCalledWith(instance)
35
+
36
+ expect(pointer).toBeDefined()
37
+ })
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+ const { Locator } = require('@toa.io/core')
5
+ const { encode } = require('@toa.io/generic')
6
+
7
+ const { Pointer } = require('../src/pointer')
8
+
9
+ it('should be', () => undefined)
10
+
11
+ /** @type {toa.core.Locator} */
12
+ let locator
13
+
14
+ /** @type {toa..Pointer} */
15
+ let pointer
16
+
17
+ const uris = { default: 'mongodb://whatever' }
18
+ const value = encode(uris)
19
+
20
+ process.env.TOA_STORAGES_MONGODB_POINTER = value
21
+
22
+ beforeEach(() => {
23
+ const name = generate()
24
+ const namespace = generate()
25
+
26
+ locator = new Locator(name, namespace)
27
+ pointer = new Pointer(locator)
28
+ })
29
+
30
+ it('should define schema', () => {
31
+ expect(pointer.protocol).toStrictEqual('mongodb:')
32
+ })
33
+
34
+ it('should expose db', () => {
35
+ expect(pointer.db).toStrictEqual(locator.namespace)
36
+ })
37
+
38
+ it('should expose collection', () => {
39
+ expect(pointer.collection).toStrictEqual(locator.name)
40
+ })
41
+
42
+ it('should define schema on local environment', () => {
43
+ process.env.TOA_ENV = 'local'
44
+
45
+ expect(() => (pointer = new Pointer(locator))).not.toThrow()
46
+
47
+ expect(pointer.protocol).toStrictEqual('mongodb:')
48
+
49
+ delete process.env.TOA_ENV
50
+ })
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ const { to, from } = require('../src/record')
4
+ const { random } = require('@toa.io/generic')
5
+
6
+ describe('to', () => {
7
+ it('should rename id to _id', () => {
8
+ /** @type {toa.core.storages.Record} */
9
+ const entity = { id: '1', _version: 0 }
10
+ const record = to(entity)
11
+
12
+ expect(record).toMatchObject({ _id: '1' })
13
+ })
14
+
15
+ it('should not modify argument', () => {
16
+ /** @type {toa.core.storages.Record} */
17
+ const entity = { id: '1', _version: 0 }
18
+
19
+ to(entity)
20
+
21
+ expect(entity).toStrictEqual({ id: '1', _version: 0 })
22
+ })
23
+
24
+ it('should increment _version', () => {
25
+ /** @type {toa.core.storages.Record} */
26
+ const entity = { id: '1', _version: random() }
27
+ const record = to(entity)
28
+
29
+ expect(record).toMatchObject({ _version: entity._version + 1 })
30
+ })
31
+ })
32
+
33
+ describe('from', () => {
34
+ it('should rename _id to id', () => {
35
+ /** @type {toa.mongodb.Record} */
36
+ const record = { _id: '1', _version: 0 }
37
+ const entity = from(record)
38
+
39
+ expect(entity).toStrictEqual({ id: '1', _version: 0 })
40
+ })
41
+
42
+ it('should not modify argument', () => {
43
+ /** @type {toa.mongodb.Record} */
44
+ const record = { _id: '1', _version: 0 }
45
+
46
+ from(record)
47
+
48
+ expect(record).toStrictEqual({ _id: '1', _version: 0 })
49
+ })
50
+ })
@@ -0,0 +1,29 @@
1
+ // noinspection ES6UnusedImports
2
+
3
+ import type {
4
+ Document,
5
+ Filter,
6
+ FindOneAndReplaceOptions,
7
+ FindOneAndUpdateOptions,
8
+ FindOptions,
9
+ UpdateFilter
10
+ } from 'mongodb'
11
+
12
+ import type { Connector } from '@toa.io/core'
13
+ import type { Record } from './record'
14
+
15
+ declare namespace toa.mongodb {
16
+
17
+ interface Connection extends Connector {
18
+ get(query: Filter<Record>, options?: FindOptions<Record>): Promise<Record>
19
+
20
+ find(query: Filter<Record>, options?: FindOptions<Record>): Promise<Record[]>
21
+
22
+ add(record: Record): Promise<boolean>
23
+
24
+ replace(query: Filter<Record>, record: UpdateFilter<Record>, options?: FindOneAndReplaceOptions): Promise<any>
25
+
26
+ update(query: Filter<Record>, update: UpdateFilter<Record>, options?: FindOneAndUpdateOptions): Promise<any>
27
+ }
28
+
29
+ }
@@ -0,0 +1,10 @@
1
+ import type { Pointer as Base } from '@toa.io/pointer/types'
2
+
3
+ declare namespace toa.mongodb {
4
+
5
+ interface Pointer extends Base {
6
+ db: string
7
+ collection: string
8
+ }
9
+
10
+ }
@@ -0,0 +1,12 @@
1
+ declare namespace toa.mongodb {
2
+
3
+ interface Record {
4
+ _id: string
5
+ _version: number
6
+
7
+ [key: string]: any
8
+ }
9
+
10
+ }
11
+
12
+ export type Record = toa.mongodb.Record
package/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2020-present Artem Gurtovoi
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,21 +0,0 @@
1
- apiVersion: v1
2
- kind: Service
3
- metadata:
4
- name: messages-mongodb
5
- spec:
6
- selector:
7
- app.kubernetes.io/name: rabbitmq
8
- ports:
9
- - port: 27017
10
- targetPort: 27017
11
- ---
12
- apiVersion: v1
13
- kind: Service
14
- metadata:
15
- name: credits-mongodb
16
- spec:
17
- selector:
18
- app.kubernetes.io/name: rabbitmq
19
- ports:
20
- - port: 27017
21
- targetPort: 27017
package/src/client.js DELETED
@@ -1,76 +0,0 @@
1
- 'use strict'
2
-
3
- const { MongoClient } = require('mongodb')
4
- const { Connector } = require('@toa.io/core')
5
- const { console } = require('@toa.io/gears')
6
-
7
- class Client extends Connector {
8
- #connection
9
-
10
- #client
11
- #collection
12
-
13
- constructor (host, db, collection) {
14
- super()
15
-
16
- this.#connection = { host, db, collection }
17
-
18
- const url = this.#url()
19
- this.#client = new MongoClient(url, OPTIONS)
20
- }
21
-
22
- async connection () {
23
- await this.#client.connect()
24
-
25
- this.#collection = this.#client.db(this.#connection.db).collection(this.#connection.collection)
26
-
27
- console.info(`Storage MongoDB connected to ${this.#url()}`)
28
- }
29
-
30
- async disconnection () {
31
- await this.#client.close()
32
-
33
- console.info('Storage MongoDB disconnected from ' +
34
- `${this.#url}/${this.#connection.db}/${this.#connection.collection}`)
35
- }
36
-
37
- async add (record) {
38
- const { acknowledged } = await this.#collection.insertOne(record)
39
-
40
- return acknowledged
41
- }
42
-
43
- async get (query, options) {
44
- return await this.#collection.findOne(query, options)
45
- }
46
-
47
- async replace (query, record, options) {
48
- return this.#collection.findOneAndReplace(query, record, options)
49
- }
50
-
51
- async update (query, update, options) {
52
- return this.#collection.findOneAndUpdate(query, update, options)
53
- }
54
-
55
- async find (query, options) {
56
- const cursor = await this.#collection.find(query, options)
57
-
58
- return cursor.toArray()
59
- }
60
-
61
- #url () {
62
- // TODO: see ./deployment.js
63
- const user = 'user'
64
- const password = 'password'
65
-
66
- return `mongodb://${user}:${password}@${this.#connection.host}:27017/?authSource=${this.#connection.db}&readPreference=primary&appname=svc&ssl=false`
67
- }
68
- }
69
-
70
- const OPTIONS = {
71
- useNewUrlParser: true,
72
- useUnifiedTopology: true,
73
- ignoreUndefined: true
74
- }
75
-
76
- exports.Client = Client
@@ -1,36 +0,0 @@
1
- 'use strict'
2
-
3
- const deployments = (values) => {
4
- const domains = new Set(values.map((value) => value.domain))
5
-
6
- return [...domains].map(deployment)
7
- }
8
-
9
- const deployment = (domain) => {
10
- const alias = domain + '-mongodb'
11
-
12
- // TODO: credentials management
13
- const usernames = ['user']
14
- const passwords = ['password']
15
- const databases = [domain]
16
-
17
- return {
18
- chart: {
19
- name: 'mongodb',
20
- version: '10.29.2',
21
- repository: 'https://charts.bitnami.com/bitnami',
22
- alias
23
- },
24
- values: {
25
- architecture: 'standalone',
26
- fullnameOverride: alias,
27
- auth: {
28
- usernames,
29
- passwords,
30
- databases
31
- }
32
- }
33
- }
34
- }
35
-
36
- exports.deployments = deployments
@@ -1,46 +0,0 @@
1
- 'use strict'
2
-
3
- const MongoClient = jest.fn().mockImplementation(function () {
4
- this.db = jest.fn(() => new DB())
5
-
6
- this.connect = jest.fn()
7
- this.close = jest.fn()
8
- })
9
-
10
- const DB = jest.fn().mockImplementation(function () {
11
- this.collection = jest.fn(() => new Collection())
12
- })
13
-
14
- const results = {
15
- insertOne: { acknowledged: 1 }
16
- }
17
-
18
- const Collection = jest.fn().mockImplementation(function () {
19
- this.find = jest.fn(() => ({
20
- toArray: jest.fn(() => ({ id: 1, foo: 'bar' }))
21
- }))
22
- this.findOne = jest.fn(() => ({ id: 1, foo: 'bar' }))
23
- this.insertOne = jest.fn(() => results.insertOne)
24
- this.findOneAndReplace = jest.fn(() => ({ ok: 1 }))
25
- })
26
-
27
- const OPTIONS = {
28
- useNewUrlParser: true,
29
- useUnifiedTopology: true,
30
- ignoreUndefined: true
31
- }
32
-
33
- const query = {
34
- criteria: {
35
- id: 1
36
- },
37
- options: {
38
- limit: 1
39
- }
40
- }
41
-
42
- exports.mock = { MongoClient }
43
- exports.OPTIONS = OPTIONS
44
- exports.locator = { host: 'bar.foo', db: 'foo', collection: 'bar' }
45
- exports.object = { id: '1', foo: 'bar' }
46
- exports.query = query
@@ -1,75 +0,0 @@
1
- 'use strict'
2
-
3
- const fixtures = require('./client.fixtures')
4
- const mock = fixtures.mock
5
-
6
- jest.mock('mongodb', () => ({ MongoClient: mock.MongoClient }))
7
-
8
- const { Client } = require('../src/client')
9
-
10
- let instance, client, collection
11
-
12
- beforeEach(async () => {
13
- jest.clearAllMocks()
14
-
15
- instance = new Client(fixtures.locator.host, fixtures.locator.db, fixtures.locator.collection)
16
- await instance.connect()
17
-
18
- client = fixtures.mock.MongoClient.mock.instances[0]
19
-
20
- collection = fixtures.mock.MongoClient.mock.instances[0]
21
- .db.mock.results[0].value
22
- .collection.mock.results[0].value
23
- })
24
-
25
- it('should create client', () => {
26
- expect(client).toBeDefined()
27
- expect(fixtures.mock.MongoClient).toHaveBeenCalledWith(
28
- expect.any(String),
29
- fixtures.OPTIONS
30
- )
31
- })
32
-
33
- it('should connect', async () => {
34
- expect(client.connect).toHaveBeenCalled()
35
- expect(client.db).toHaveBeenCalledWith(fixtures.locator.db)
36
-
37
- const db = client.db.mock.results[0].value
38
-
39
- expect(db.collection).toHaveBeenCalledWith(fixtures.locator.collection)
40
- })
41
-
42
- it('should disconnect', async () => {
43
- await instance.disconnect()
44
-
45
- expect(client.connect).toHaveBeenCalled()
46
- expect(client.close).toHaveBeenCalled()
47
- })
48
-
49
- it('should add', async () => {
50
- const ok = await instance.add(fixtures.object)
51
-
52
- expect(collection.insertOne).toHaveBeenCalledWith(fixtures.object)
53
- expect(ok).toBe(collection.insertOne.mock.results[0].value.acknowledged)
54
- })
55
-
56
- it('should get', async () => {
57
- const entity = await instance.get(fixtures.query.criteria, fixtures.query.options)
58
-
59
- expect(entity).toBe(collection.findOne.mock.results[0].value)
60
- expect(collection.findOne).toHaveBeenCalledWith(fixtures.query.criteria, fixtures.query.options)
61
- })
62
-
63
- it('should replace', async () => {
64
- const update = { ...fixtures.object, foo: 'foo' }
65
- await instance.replace(fixtures.query.criteria, update, fixtures.query.options)
66
-
67
- expect(collection.findOneAndReplace).toHaveBeenCalledWith(fixtures.query.criteria, update, fixtures.query.options)
68
- })
69
-
70
- it('should find', async () => {
71
- const set = await instance.find(fixtures.query.criteria, fixtures.query.options)
72
-
73
- expect(set).toStrictEqual(collection.find.mock.results[0].value.toArray.mock.results[0].value)
74
- expect(collection.find).toHaveBeenCalledWith(fixtures.query.criteria, fixtures.query.options)
75
- })
@@ -1,45 +0,0 @@
1
- 'use strict'
2
-
3
- const { to, from } = require('../src/record')
4
- const { random } = require('@toa.io/gears')
5
-
6
- describe('to', () => {
7
- it('should rename id to _id', () => {
8
- const entity = { id: 1 }
9
- const record = to(entity)
10
-
11
- expect(record).toMatchObject({ _id: 1 })
12
- })
13
-
14
- it('should not modify argument', () => {
15
- const entity = { id: 1 }
16
-
17
- to(entity)
18
-
19
- expect(entity).toStrictEqual({ id: 1 })
20
- })
21
-
22
- it('should increment _version', () => {
23
- const entity = { _version: random() }
24
- const record = to(entity)
25
-
26
- expect(record).toMatchObject({ _version: entity._version + 1 })
27
- })
28
- })
29
-
30
- describe('from', () => {
31
- it('should rename _id to id', () => {
32
- const record = { _id: 1 }
33
- const entity = from(record)
34
-
35
- expect(entity).toStrictEqual({ id: 1 })
36
- })
37
-
38
- it('should not modify argument', () => {
39
- const record = { _id: 1 }
40
-
41
- from(record)
42
-
43
- expect(record).toStrictEqual({ _id: 1 })
44
- })
45
- })
@@ -1,25 +0,0 @@
1
- 'use strict'
2
-
3
- const Client = jest.fn().mockImplementation(function () {
4
- this.connect = jest.fn()
5
- this.disconnect = jest.fn()
6
- this.add = jest.fn(() => true)
7
- this.get = jest.fn(() => ({ foo: 'bar' }))
8
- this.replace = jest.fn(() => ({ ok: 1 }))
9
- this.find = jest.fn(() => [{ foo: 'bar' }, { bar: 'foo' }])
10
- })
11
-
12
- const query = {
13
- criteria: {},
14
- options: {}
15
- }
16
-
17
- const translate = jest.fn(() => ({ criteria: 'foo', options: 'bar' }))
18
-
19
- const to = jest.fn((entity) => entity)
20
- const from = jest.fn((record) => record)
21
-
22
- exports.mock = { Client, translate, to, from }
23
- exports.locator = { host: jest.fn(() => 'bar.foo.local'), domain: 'foo', name: 'bar' }
24
- exports.entity = { id: '1', foo: 'bar' }
25
- exports.query = query
@@ -1,73 +0,0 @@
1
- 'use strict'
2
-
3
- const fixtures = require('./storage.fixtures')
4
- const mock = fixtures.mock
5
-
6
- jest.mock('../src/client', () => ({ Client: mock.Client }))
7
- jest.mock('../src/translate', () => ({ translate: mock.translate }))
8
- jest.mock('../src/record', () => ({ to: mock.to, from: mock.from }))
9
-
10
- const { Storage } = require('../src/storage')
11
-
12
- let connector, client
13
-
14
- beforeAll(() => {
15
- connector = new Storage(fixtures.locator)
16
-
17
- client = fixtures.mock.Client.mock.instances[0]
18
-
19
- expect(client).toBeDefined()
20
-
21
- expect(fixtures.mock.Client).toHaveBeenCalledWith(
22
- fixtures.locator.host.mock.results[0].value,
23
- fixtures.locator.domain,
24
- fixtures.locator.name
25
- )
26
-
27
- expect(fixtures.locator.host.mock.calls[0][0].toLowerCase()).toBe('mongodb')
28
- })
29
-
30
- beforeEach(() => {
31
- jest.clearAllMocks()
32
- })
33
-
34
- it('should add', async () => {
35
- const result = await connector.add(fixtures.entity)
36
-
37
- expect(client.add).toHaveBeenCalledWith(mock.to.mock.results[0].value)
38
- expect(mock.to).toHaveBeenCalledWith(fixtures.entity)
39
- expect(result).toBe(client.add.mock.results[0].value)
40
- })
41
-
42
- it('should get', async () => {
43
- const entity = await connector.get(fixtures.query)
44
-
45
- expect(entity).toBe(mock.from.mock.results[0].value)
46
- expect(mock.from).toHaveBeenCalledWith(client.get.mock.results[0].value)
47
-
48
- const { criteria, options } = mock.translate.mock.results[0].value
49
-
50
- expect(client.get).toHaveBeenCalledWith(criteria, options)
51
- expect(mock.translate).toHaveBeenCalledWith(fixtures.query)
52
- })
53
-
54
- it('should set', async () => {
55
- await connector.set(fixtures.entity)
56
- const criteria = { _id: fixtures.entity.id }
57
-
58
- expect(client.replace).toHaveBeenCalledWith(criteria, mock.to.mock.results[0].value)
59
- expect(mock.to).toHaveBeenCalledWith(fixtures.entity)
60
- })
61
-
62
- it('should find', async () => {
63
- const set = await connector.find(fixtures.query)
64
- const found = client.find.mock.results[0].value
65
-
66
- const expected = found.map((result, index) => {
67
- expect(mock.from).toHaveBeenNthCalledWith(index + 1, result, index, found)
68
-
69
- return mock.from.mock.results[index].value
70
- })
71
-
72
- expect(set).toStrictEqual(expected)
73
- })