@pikku/mongodb 0.12.1
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/CHANGELOG.md +15 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +9 -0
- package/dist/src/mongodb-agent-run-service.d.ts +21 -0
- package/dist/src/mongodb-agent-run-service.js +127 -0
- package/dist/src/mongodb-ai-storage-service.d.ts +41 -0
- package/dist/src/mongodb-ai-storage-service.js +408 -0
- package/dist/src/mongodb-channel-store.d.ts +19 -0
- package/dist/src/mongodb-channel-store.js +52 -0
- package/dist/src/mongodb-deployment-service.d.ts +17 -0
- package/dist/src/mongodb-deployment-service.js +75 -0
- package/dist/src/mongodb-eventhub-store.d.ts +13 -0
- package/dist/src/mongodb-eventhub-store.js +37 -0
- package/dist/src/mongodb-secret-service.d.ts +30 -0
- package/dist/src/mongodb-secret-service.js +110 -0
- package/dist/src/mongodb-workflow-run-service.d.ts +36 -0
- package/dist/src/mongodb-workflow-run-service.js +146 -0
- package/dist/src/mongodb-workflow-service.d.ts +55 -0
- package/dist/src/mongodb-workflow-service.js +351 -0
- package/dist/src/pikku-mongodb.d.ts +12 -0
- package/dist/src/pikku-mongodb.js +37 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +29 -0
- package/run-tests.sh +53 -0
- package/src/index.ts +14 -0
- package/src/mongodb-agent-run-service.ts +206 -0
- package/src/mongodb-ai-storage-service.ts +576 -0
- package/src/mongodb-channel-store.ts +96 -0
- package/src/mongodb-deployment-service.ts +114 -0
- package/src/mongodb-eventhub-store.ts +52 -0
- package/src/mongodb-secret-service.ts +178 -0
- package/src/mongodb-services.test.ts +149 -0
- package/src/mongodb-workflow-run-service.ts +246 -0
- package/src/mongodb-workflow-service.ts +583 -0
- package/src/pikku-mongodb.ts +41 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DeploymentService,
|
|
3
|
+
DeploymentServiceConfig,
|
|
4
|
+
DeploymentConfig,
|
|
5
|
+
DeploymentInfo,
|
|
6
|
+
} from '@pikku/core/services'
|
|
7
|
+
import { getAllFunctionNames } from '@pikku/core/function'
|
|
8
|
+
import type { Db, Collection } from 'mongodb'
|
|
9
|
+
|
|
10
|
+
interface DeploymentDoc {
|
|
11
|
+
_id: string
|
|
12
|
+
endpoint: string
|
|
13
|
+
lastHeartbeat: Date
|
|
14
|
+
createdAt: Date
|
|
15
|
+
functions: string[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class MongoDBDeploymentService implements DeploymentService {
|
|
19
|
+
private initialized = false
|
|
20
|
+
private heartbeatTimer?: ReturnType<typeof setInterval>
|
|
21
|
+
private deploymentConfig?: DeploymentConfig
|
|
22
|
+
private heartbeatInterval: number
|
|
23
|
+
private heartbeatTtl: number
|
|
24
|
+
private deployments!: Collection<DeploymentDoc>
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
config: DeploymentServiceConfig,
|
|
28
|
+
private db: Db
|
|
29
|
+
) {
|
|
30
|
+
this.heartbeatInterval = config.heartbeatInterval ?? 10000
|
|
31
|
+
this.heartbeatTtl = config.heartbeatTtl ?? 30000
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async init(): Promise<void> {
|
|
35
|
+
if (this.initialized) return
|
|
36
|
+
|
|
37
|
+
this.deployments = this.db.collection<DeploymentDoc>('pikku_deployments')
|
|
38
|
+
|
|
39
|
+
await this.deployments.createIndex({ lastHeartbeat: 1 })
|
|
40
|
+
await this.deployments.createIndex({ functions: 1 })
|
|
41
|
+
|
|
42
|
+
this.initialized = true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async start(config: DeploymentConfig): Promise<void> {
|
|
46
|
+
const functions = config.functions ?? getAllFunctionNames()
|
|
47
|
+
this.deploymentConfig = { ...config, functions }
|
|
48
|
+
const now = new Date()
|
|
49
|
+
|
|
50
|
+
await this.deployments.updateOne(
|
|
51
|
+
{ _id: config.deploymentId },
|
|
52
|
+
{
|
|
53
|
+
$set: {
|
|
54
|
+
endpoint: config.endpoint,
|
|
55
|
+
lastHeartbeat: now,
|
|
56
|
+
functions,
|
|
57
|
+
},
|
|
58
|
+
$setOnInsert: {
|
|
59
|
+
_id: config.deploymentId,
|
|
60
|
+
createdAt: now,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{ upsert: true }
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
this.heartbeatTimer = setInterval(
|
|
67
|
+
() => this.sendHeartbeat(),
|
|
68
|
+
this.heartbeatInterval
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async stop(): Promise<void> {
|
|
73
|
+
if (this.heartbeatTimer) {
|
|
74
|
+
clearInterval(this.heartbeatTimer)
|
|
75
|
+
this.heartbeatTimer = undefined
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (this.deploymentConfig) {
|
|
79
|
+
await this.deployments.deleteOne({
|
|
80
|
+
_id: this.deploymentConfig.deploymentId,
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async findFunction(name: string): Promise<DeploymentInfo[]> {
|
|
86
|
+
const cutoff = new Date(Date.now() - this.heartbeatTtl)
|
|
87
|
+
|
|
88
|
+
const result = await this.deployments
|
|
89
|
+
.find({
|
|
90
|
+
functions: name,
|
|
91
|
+
lastHeartbeat: { $gt: cutoff },
|
|
92
|
+
})
|
|
93
|
+
.sort({ lastHeartbeat: -1 })
|
|
94
|
+
.toArray()
|
|
95
|
+
|
|
96
|
+
return result.map((row) => ({
|
|
97
|
+
deploymentId: row._id,
|
|
98
|
+
endpoint: row.endpoint,
|
|
99
|
+
}))
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async sendHeartbeat(): Promise<void> {
|
|
103
|
+
if (!this.deploymentConfig) return
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await this.deployments.updateOne(
|
|
107
|
+
{ _id: this.deploymentConfig.deploymentId },
|
|
108
|
+
{ $set: { lastHeartbeat: new Date() } }
|
|
109
|
+
)
|
|
110
|
+
} catch {
|
|
111
|
+
// Heartbeat failed, will retry on next interval
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EventHubStore } from '@pikku/core/channel'
|
|
2
|
+
import type { Db, Collection } from 'mongodb'
|
|
3
|
+
|
|
4
|
+
interface ChannelSubscriptionDoc {
|
|
5
|
+
channelId: string
|
|
6
|
+
topic: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class MongoDBEventHubStore extends EventHubStore {
|
|
10
|
+
private initialized = false
|
|
11
|
+
private subscriptions!: Collection<ChannelSubscriptionDoc>
|
|
12
|
+
|
|
13
|
+
constructor(private db: Db) {
|
|
14
|
+
super()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async init(): Promise<void> {
|
|
18
|
+
if (this.initialized) return
|
|
19
|
+
this.subscriptions = this.db.collection<ChannelSubscriptionDoc>(
|
|
20
|
+
'channel_subscriptions'
|
|
21
|
+
)
|
|
22
|
+
this.initialized = true
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async getChannelIdsForTopic(topic: string): Promise<string[]> {
|
|
26
|
+
const result = await this.subscriptions
|
|
27
|
+
.find({ topic })
|
|
28
|
+
.project({ channelId: 1 })
|
|
29
|
+
.toArray()
|
|
30
|
+
return result.map((row) => row.channelId)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public async subscribe(topic: string, channelId: string): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
await this.subscriptions.updateOne(
|
|
36
|
+
{ channelId, topic },
|
|
37
|
+
{ $setOnInsert: { channelId, topic } },
|
|
38
|
+
{ upsert: true }
|
|
39
|
+
)
|
|
40
|
+
return true
|
|
41
|
+
} catch {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public async unsubscribe(topic: string, channelId: string): Promise<boolean> {
|
|
47
|
+
const result = await this.subscriptions.deleteOne({ channelId, topic })
|
|
48
|
+
return result.deletedCount > 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public async close(): Promise<void> {}
|
|
52
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { SecretService } from '@pikku/core/services'
|
|
2
|
+
import type { Db, Collection } from 'mongodb'
|
|
3
|
+
import {
|
|
4
|
+
envelopeEncrypt,
|
|
5
|
+
envelopeDecrypt,
|
|
6
|
+
envelopeRewrap,
|
|
7
|
+
} from '@pikku/core/crypto-utils'
|
|
8
|
+
|
|
9
|
+
export interface MongoDBSecretServiceConfig {
|
|
10
|
+
key: string
|
|
11
|
+
keyVersion?: number
|
|
12
|
+
previousKey?: string
|
|
13
|
+
audit?: boolean
|
|
14
|
+
auditReads?: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface SecretDoc {
|
|
18
|
+
_id: string
|
|
19
|
+
ciphertext: string
|
|
20
|
+
wrappedDek: string
|
|
21
|
+
keyVersion: number
|
|
22
|
+
createdAt: Date
|
|
23
|
+
updatedAt: Date
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SecretAuditDoc {
|
|
27
|
+
_id: string
|
|
28
|
+
secretKey: string
|
|
29
|
+
action: string
|
|
30
|
+
performedAt: Date
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class MongoDBSecretService implements SecretService {
|
|
34
|
+
private initialized = false
|
|
35
|
+
private key: string
|
|
36
|
+
private keyVersion: number
|
|
37
|
+
private previousKey?: string
|
|
38
|
+
private audit: boolean
|
|
39
|
+
private auditReads: boolean
|
|
40
|
+
private secrets!: Collection<SecretDoc>
|
|
41
|
+
private secretsAudit!: Collection<SecretAuditDoc>
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
private db: Db,
|
|
45
|
+
config: MongoDBSecretServiceConfig
|
|
46
|
+
) {
|
|
47
|
+
this.key = config.key
|
|
48
|
+
this.keyVersion = config.keyVersion ?? 1
|
|
49
|
+
this.previousKey = config.previousKey
|
|
50
|
+
this.audit = config.audit ?? false
|
|
51
|
+
this.auditReads = config.auditReads ?? false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public async init(): Promise<void> {
|
|
55
|
+
if (this.initialized) return
|
|
56
|
+
|
|
57
|
+
this.secrets = this.db.collection<SecretDoc>('secrets')
|
|
58
|
+
this.secretsAudit = this.db.collection<SecretAuditDoc>('secrets_audit')
|
|
59
|
+
|
|
60
|
+
await this.secrets.createIndex({ _id: 1 })
|
|
61
|
+
|
|
62
|
+
if (this.audit) {
|
|
63
|
+
await this.secretsAudit.createIndex({ secretKey: 1 })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.initialized = true
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async logAudit(
|
|
70
|
+
secretKey: string,
|
|
71
|
+
action: 'read' | 'write' | 'delete' | 'rotate'
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
if (!this.audit) return
|
|
74
|
+
if (action === 'read' && !this.auditReads) return
|
|
75
|
+
|
|
76
|
+
await this.secretsAudit.insertOne({
|
|
77
|
+
_id: crypto.randomUUID(),
|
|
78
|
+
secretKey,
|
|
79
|
+
action,
|
|
80
|
+
performedAt: new Date(),
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private getKEK(version: number): string {
|
|
85
|
+
if (version === this.keyVersion) return this.key
|
|
86
|
+
if (this.previousKey) return this.previousKey
|
|
87
|
+
throw new Error(`No KEK available for key_version ${version}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async getSecret(key: string): Promise<string> {
|
|
91
|
+
const row = await this.secrets.findOne({ _id: key })
|
|
92
|
+
if (!row) throw new Error(`Secret not found: ${key}`)
|
|
93
|
+
|
|
94
|
+
const kek = this.getKEK(row.keyVersion)
|
|
95
|
+
const result = await envelopeDecrypt<string>(
|
|
96
|
+
kek,
|
|
97
|
+
row.ciphertext,
|
|
98
|
+
row.wrappedDek
|
|
99
|
+
)
|
|
100
|
+
await this.logAudit(key, 'read')
|
|
101
|
+
return result
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async getSecretJSON<R = {}>(key: string): Promise<R> {
|
|
105
|
+
const raw = await this.getSecret(key)
|
|
106
|
+
return JSON.parse(raw) as R
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async hasSecret(key: string): Promise<boolean> {
|
|
110
|
+
const count = await this.secrets.countDocuments({ _id: key }, { limit: 1 })
|
|
111
|
+
return count > 0
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async setSecretJSON(key: string, value: unknown): Promise<void> {
|
|
115
|
+
const plaintext = JSON.stringify(value)
|
|
116
|
+
const { ciphertext, wrappedDEK } = await envelopeEncrypt(
|
|
117
|
+
this.key,
|
|
118
|
+
plaintext
|
|
119
|
+
)
|
|
120
|
+
const now = new Date()
|
|
121
|
+
|
|
122
|
+
await this.secrets.updateOne(
|
|
123
|
+
{ _id: key },
|
|
124
|
+
{
|
|
125
|
+
$set: {
|
|
126
|
+
ciphertext,
|
|
127
|
+
wrappedDek: wrappedDEK,
|
|
128
|
+
keyVersion: this.keyVersion,
|
|
129
|
+
updatedAt: now,
|
|
130
|
+
},
|
|
131
|
+
$setOnInsert: {
|
|
132
|
+
_id: key,
|
|
133
|
+
createdAt: now,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{ upsert: true }
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
await this.logAudit(key, 'write')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async deleteSecret(key: string): Promise<void> {
|
|
143
|
+
await this.secrets.deleteOne({ _id: key })
|
|
144
|
+
await this.logAudit(key, 'delete')
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async rotateKEK(): Promise<number> {
|
|
148
|
+
if (!this.previousKey) {
|
|
149
|
+
throw new Error('No previousKey configured — nothing to rotate from')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const rows = await this.secrets
|
|
153
|
+
.find({ keyVersion: { $lt: this.keyVersion } })
|
|
154
|
+
.project({ _id: 1, wrappedDek: 1 })
|
|
155
|
+
.toArray()
|
|
156
|
+
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
const newWrappedDEK = await envelopeRewrap(
|
|
159
|
+
this.previousKey,
|
|
160
|
+
this.key,
|
|
161
|
+
row.wrappedDek
|
|
162
|
+
)
|
|
163
|
+
await this.secrets.updateOne(
|
|
164
|
+
{ _id: row._id },
|
|
165
|
+
{
|
|
166
|
+
$set: {
|
|
167
|
+
wrappedDek: newWrappedDEK,
|
|
168
|
+
keyVersion: this.keyVersion,
|
|
169
|
+
updatedAt: new Date(),
|
|
170
|
+
},
|
|
171
|
+
}
|
|
172
|
+
)
|
|
173
|
+
await this.logAudit(row._id as string, 'rotate')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return rows.length
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, test, before, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import { MongoClient, type Db } from 'mongodb'
|
|
4
|
+
import { MongoMemoryServer } from 'mongodb-memory-server'
|
|
5
|
+
import { defineServiceTests } from '@pikku/core/testing'
|
|
6
|
+
|
|
7
|
+
import { MongoDBChannelStore } from './mongodb-channel-store.js'
|
|
8
|
+
import { MongoDBEventHubStore } from './mongodb-eventhub-store.js'
|
|
9
|
+
import { MongoDBWorkflowService } from './mongodb-workflow-service.js'
|
|
10
|
+
import { MongoDBWorkflowRunService } from './mongodb-workflow-run-service.js'
|
|
11
|
+
import { MongoDBDeploymentService } from './mongodb-deployment-service.js'
|
|
12
|
+
import { MongoDBAIStorageService } from './mongodb-ai-storage-service.js'
|
|
13
|
+
import { MongoDBAgentRunService } from './mongodb-agent-run-service.js'
|
|
14
|
+
import { MongoDBSecretService } from './mongodb-secret-service.js'
|
|
15
|
+
|
|
16
|
+
function registerTests(name: string, getDb: () => Db) {
|
|
17
|
+
defineServiceTests({
|
|
18
|
+
name,
|
|
19
|
+
services: {
|
|
20
|
+
channelStore: async () => {
|
|
21
|
+
const s = new MongoDBChannelStore(getDb())
|
|
22
|
+
await s.init()
|
|
23
|
+
return s
|
|
24
|
+
},
|
|
25
|
+
eventHubStore: async () => {
|
|
26
|
+
const s = new MongoDBEventHubStore(getDb())
|
|
27
|
+
await s.init()
|
|
28
|
+
return s
|
|
29
|
+
},
|
|
30
|
+
workflowService: async () => {
|
|
31
|
+
const s = new MongoDBWorkflowService(getDb())
|
|
32
|
+
await s.init()
|
|
33
|
+
return s
|
|
34
|
+
},
|
|
35
|
+
workflowRunService: async () => new MongoDBWorkflowRunService(getDb()),
|
|
36
|
+
deploymentService: async () => {
|
|
37
|
+
const s = new MongoDBDeploymentService(
|
|
38
|
+
{ heartbeatInterval: 60000, heartbeatTtl: 120000 },
|
|
39
|
+
getDb()
|
|
40
|
+
)
|
|
41
|
+
await s.init()
|
|
42
|
+
return s
|
|
43
|
+
},
|
|
44
|
+
aiStorageService: async () => {
|
|
45
|
+
const s = new MongoDBAIStorageService(getDb())
|
|
46
|
+
await s.init()
|
|
47
|
+
return s
|
|
48
|
+
},
|
|
49
|
+
agentRunService: async () => new MongoDBAgentRunService(getDb()),
|
|
50
|
+
secretService: async (config) => {
|
|
51
|
+
const s = new MongoDBSecretService(getDb(), config)
|
|
52
|
+
await s.init()
|
|
53
|
+
return s
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe(`MongoDBSecretService audit [${name}]`, () => {
|
|
59
|
+
const kek = 'test-key-encryption-key-32chars!'
|
|
60
|
+
|
|
61
|
+
test('audit logs writes, reads, and deletes', async () => {
|
|
62
|
+
const service = new MongoDBSecretService(getDb(), {
|
|
63
|
+
key: kek,
|
|
64
|
+
audit: true,
|
|
65
|
+
auditReads: true,
|
|
66
|
+
})
|
|
67
|
+
await service.init()
|
|
68
|
+
await service.setSecretJSON('audit-test', 'value')
|
|
69
|
+
await service.getSecret('audit-test')
|
|
70
|
+
await service.deleteSecret('audit-test')
|
|
71
|
+
|
|
72
|
+
const logs = await getDb()
|
|
73
|
+
.collection('secrets_audit')
|
|
74
|
+
.find({ secretKey: 'audit-test' })
|
|
75
|
+
.sort({ performedAt: 1 })
|
|
76
|
+
.toArray()
|
|
77
|
+
|
|
78
|
+
assert.equal(logs.length, 3)
|
|
79
|
+
assert.equal(logs[0]!.action, 'write')
|
|
80
|
+
assert.equal(logs[1]!.action, 'read')
|
|
81
|
+
assert.equal(logs[2]!.action, 'delete')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('audit skips reads when auditReads is false', async () => {
|
|
85
|
+
const service = new MongoDBSecretService(getDb(), {
|
|
86
|
+
key: kek,
|
|
87
|
+
audit: true,
|
|
88
|
+
auditReads: false,
|
|
89
|
+
})
|
|
90
|
+
await service.init()
|
|
91
|
+
await service.setSecretJSON('no-read-audit', 'value')
|
|
92
|
+
await service.getSecret('no-read-audit')
|
|
93
|
+
|
|
94
|
+
const logs = await getDb()
|
|
95
|
+
.collection('secrets_audit')
|
|
96
|
+
.find({ secretKey: 'no-read-audit' })
|
|
97
|
+
.toArray()
|
|
98
|
+
|
|
99
|
+
assert.equal(logs.length, 1)
|
|
100
|
+
assert.equal(logs[0]!.action, 'write')
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
describe('MongoDB Services - In-Memory', () => {
|
|
106
|
+
let mongod: MongoMemoryServer
|
|
107
|
+
let client: MongoClient
|
|
108
|
+
let db: Db
|
|
109
|
+
|
|
110
|
+
before(async () => {
|
|
111
|
+
mongod = await MongoMemoryServer.create()
|
|
112
|
+
const uri = mongod.getUri()
|
|
113
|
+
client = new MongoClient(uri)
|
|
114
|
+
await client.connect()
|
|
115
|
+
db = client.db('pikku_test')
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
after(async () => {
|
|
119
|
+
await client.close()
|
|
120
|
+
await mongod.stop()
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
registerTests('MongoMemory', () => db)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe(
|
|
127
|
+
'MongoDB Services - Real',
|
|
128
|
+
{
|
|
129
|
+
skip: !process.env.MONGODB_URL ? 'MONGODB_URL not set' : undefined,
|
|
130
|
+
},
|
|
131
|
+
() => {
|
|
132
|
+
let client: MongoClient
|
|
133
|
+
let db: Db
|
|
134
|
+
|
|
135
|
+
before(async () => {
|
|
136
|
+
client = new MongoClient(process.env.MONGODB_URL!)
|
|
137
|
+
await client.connect()
|
|
138
|
+
db = client.db('pikku_test')
|
|
139
|
+
await db.dropDatabase()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
after(async () => {
|
|
143
|
+
if (db) await db.dropDatabase()
|
|
144
|
+
if (client) await client.close()
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
registerTests('Real MongoDB', () => db)
|
|
148
|
+
}
|
|
149
|
+
)
|