@flowerforce/flowerbase 1.7.2 → 1.7.3-beta.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/dist/features/functions/controller.js +1 -1
- package/dist/services/mongodb-atlas/index.d.ts.map +1 -1
- package/dist/services/mongodb-atlas/index.js +247 -61
- package/dist/services/mongodb-atlas/model.d.ts +2 -1
- package/dist/services/mongodb-atlas/model.d.ts.map +1 -1
- package/dist/utils/context/helpers.d.ts +5 -2
- package/dist/utils/context/helpers.d.ts.map +1 -1
- package/dist/utils/context/helpers.js +2 -2
- package/dist/utils/context/index.d.ts +1 -0
- package/dist/utils/context/index.d.ts.map +1 -1
- package/dist/utils/context/index.js +62 -26
- package/dist/utils/context/interface.d.ts +1 -0
- package/dist/utils/context/interface.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/features/functions/controller.ts +2 -2
- package/src/services/mongodb-atlas/__tests__/findOneAndUpdate.test.ts +0 -1
- package/src/services/mongodb-atlas/__tests__/realmCompatibility.test.ts +274 -0
- package/src/services/mongodb-atlas/index.ts +281 -65
- package/src/services/mongodb-atlas/model.ts +3 -0
- package/src/utils/__tests__/contextExecuteCompatibility.test.ts +60 -0
- package/src/utils/__tests__/generateContextData.test.ts +3 -1
- package/src/utils/context/helpers.ts +2 -1
- package/src/utils/context/index.ts +94 -47
- package/src/utils/context/interface.ts +1 -0
|
@@ -13,6 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
13
13
|
};
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.GenerateContext = GenerateContext;
|
|
16
|
+
exports.GenerateContextSync = GenerateContextSync;
|
|
16
17
|
const node_module_1 = require("node:module");
|
|
17
18
|
const node_path_1 = __importDefault(require("node:path"));
|
|
18
19
|
const node_url_1 = require("node:url");
|
|
@@ -108,6 +109,34 @@ const shouldFallbackFromVmModules = (error) => {
|
|
|
108
109
|
const code = error.code;
|
|
109
110
|
return code === 'ERR_VM_MODULES_DISABLED' || code === 'ERR_VM_MODULES_NOT_SUPPORTED';
|
|
110
111
|
};
|
|
112
|
+
const isExportedFunction = (value) => typeof value === 'function';
|
|
113
|
+
const getDefaultExport = (value) => {
|
|
114
|
+
if (!value || typeof value !== 'object')
|
|
115
|
+
return undefined;
|
|
116
|
+
if (!('default' in value))
|
|
117
|
+
return undefined;
|
|
118
|
+
const maybeDefault = value.default;
|
|
119
|
+
return isExportedFunction(maybeDefault) ? maybeDefault : undefined;
|
|
120
|
+
};
|
|
121
|
+
const resolveExport = (ctx) => {
|
|
122
|
+
var _a, _b, _c, _d, _e;
|
|
123
|
+
const moduleExports = (_b = (_a = ctx.module) === null || _a === void 0 ? void 0 : _a.exports) !== null && _b !== void 0 ? _b : (_c = ctx.__fb_module) === null || _c === void 0 ? void 0 : _c.exports;
|
|
124
|
+
if (isExportedFunction(moduleExports))
|
|
125
|
+
return moduleExports;
|
|
126
|
+
const contextExports = (_d = ctx.exports) !== null && _d !== void 0 ? _d : ctx.__fb_exports;
|
|
127
|
+
if (isExportedFunction(contextExports))
|
|
128
|
+
return contextExports;
|
|
129
|
+
return (_e = getDefaultExport(moduleExports)) !== null && _e !== void 0 ? _e : getDefaultExport(contextExports);
|
|
130
|
+
};
|
|
131
|
+
const buildVmContext = (contextData) => {
|
|
132
|
+
var _a, _b;
|
|
133
|
+
const sandboxModule = { exports: {} };
|
|
134
|
+
const entryFile = (_b = (_a = require.main) === null || _a === void 0 ? void 0 : _a.filename) !== null && _b !== void 0 ? _b : process.cwd();
|
|
135
|
+
const customRequire = (0, node_module_1.createRequire)(entryFile);
|
|
136
|
+
const vmContext = vm_1.default.createContext(Object.assign(Object.assign({}, contextData), { require: customRequire, exports: sandboxModule.exports, module: sandboxModule, __filename,
|
|
137
|
+
__dirname, __fb_require: customRequire, __fb_filename: __filename, __fb_dirname: __dirname }));
|
|
138
|
+
return { sandboxModule, entryFile, customRequire, vmContext };
|
|
139
|
+
};
|
|
111
140
|
/**
|
|
112
141
|
* > Used to generate the current context
|
|
113
142
|
* @testable
|
|
@@ -126,7 +155,7 @@ function GenerateContext(_a) {
|
|
|
126
155
|
const functionsQueue = state_1.StateManager.select("functionsQueue");
|
|
127
156
|
const functionToRun = Object.assign({ run_as_system: runAsSystem }, currentFunction);
|
|
128
157
|
const run = () => __awaiter(this, void 0, void 0, function* () {
|
|
129
|
-
var _a
|
|
158
|
+
var _a;
|
|
130
159
|
const contextData = (0, helpers_1.generateContextData)({
|
|
131
160
|
user,
|
|
132
161
|
services,
|
|
@@ -136,32 +165,10 @@ function GenerateContext(_a) {
|
|
|
136
165
|
functionName,
|
|
137
166
|
functionsList,
|
|
138
167
|
GenerateContext,
|
|
168
|
+
GenerateContextSync,
|
|
139
169
|
request
|
|
140
170
|
});
|
|
141
|
-
const
|
|
142
|
-
const getDefaultExport = (value) => {
|
|
143
|
-
if (!value || typeof value !== 'object')
|
|
144
|
-
return undefined;
|
|
145
|
-
if (!('default' in value))
|
|
146
|
-
return undefined;
|
|
147
|
-
const maybeDefault = value.default;
|
|
148
|
-
return isExportedFunction(maybeDefault) ? maybeDefault : undefined;
|
|
149
|
-
};
|
|
150
|
-
const resolveExport = (ctx) => {
|
|
151
|
-
var _a, _b, _c, _d, _e;
|
|
152
|
-
const moduleExports = (_b = (_a = ctx.module) === null || _a === void 0 ? void 0 : _a.exports) !== null && _b !== void 0 ? _b : (_c = ctx.__fb_module) === null || _c === void 0 ? void 0 : _c.exports;
|
|
153
|
-
if (isExportedFunction(moduleExports))
|
|
154
|
-
return moduleExports;
|
|
155
|
-
const contextExports = (_d = ctx.exports) !== null && _d !== void 0 ? _d : ctx.__fb_exports;
|
|
156
|
-
if (isExportedFunction(contextExports))
|
|
157
|
-
return contextExports;
|
|
158
|
-
return (_e = getDefaultExport(moduleExports)) !== null && _e !== void 0 ? _e : getDefaultExport(contextExports);
|
|
159
|
-
};
|
|
160
|
-
const sandboxModule = { exports: {} };
|
|
161
|
-
const entryFile = (_b = (_a = require.main) === null || _a === void 0 ? void 0 : _a.filename) !== null && _b !== void 0 ? _b : process.cwd();
|
|
162
|
-
const customRequire = (0, node_module_1.createRequire)(entryFile);
|
|
163
|
-
const vmContext = vm_1.default.createContext(Object.assign(Object.assign({}, contextData), { require: customRequire, exports: sandboxModule.exports, module: sandboxModule, __filename,
|
|
164
|
-
__dirname, __fb_require: customRequire, __fb_filename: __filename, __fb_dirname: __dirname }));
|
|
171
|
+
const { sandboxModule, entryFile, customRequire, vmContext } = buildVmContext(contextData);
|
|
165
172
|
const vmModules = vm_1.default;
|
|
166
173
|
const hasStaticImport = /\bimport\s+/.test(functionToRun.code);
|
|
167
174
|
let usedVmModules = false;
|
|
@@ -211,7 +218,7 @@ function GenerateContext(_a) {
|
|
|
211
218
|
: functionToRun.code;
|
|
212
219
|
vm_1.default.runInContext(codeToRun, vmContext);
|
|
213
220
|
}
|
|
214
|
-
sandboxModule.exports = (
|
|
221
|
+
sandboxModule.exports = (_a = resolveExport(vmContext)) !== null && _a !== void 0 ? _a : sandboxModule.exports;
|
|
215
222
|
if (deserializeArgs) {
|
|
216
223
|
return yield sandboxModule.exports(...bson_1.EJSON.deserialize(args));
|
|
217
224
|
}
|
|
@@ -221,3 +228,32 @@ function GenerateContext(_a) {
|
|
|
221
228
|
return res;
|
|
222
229
|
});
|
|
223
230
|
}
|
|
231
|
+
function GenerateContextSync({ args, app, rules, user, currentFunction, functionsList, services, functionName, runAsSystem, deserializeArgs = true, request }) {
|
|
232
|
+
var _a;
|
|
233
|
+
if (!currentFunction)
|
|
234
|
+
return;
|
|
235
|
+
const functionToRun = Object.assign({ run_as_system: runAsSystem }, currentFunction);
|
|
236
|
+
const contextData = (0, helpers_1.generateContextData)({
|
|
237
|
+
user,
|
|
238
|
+
services,
|
|
239
|
+
app,
|
|
240
|
+
rules,
|
|
241
|
+
currentFunction: functionToRun,
|
|
242
|
+
functionName,
|
|
243
|
+
functionsList,
|
|
244
|
+
GenerateContext,
|
|
245
|
+
GenerateContextSync,
|
|
246
|
+
request
|
|
247
|
+
});
|
|
248
|
+
const { sandboxModule, vmContext } = buildVmContext(contextData);
|
|
249
|
+
const codeToRun = functionToRun.code.includes('import ')
|
|
250
|
+
? transformImportsToRequire(functionToRun.code)
|
|
251
|
+
: functionToRun.code;
|
|
252
|
+
vm_1.default.runInContext(codeToRun, vmContext);
|
|
253
|
+
sandboxModule.exports = (_a = resolveExport(vmContext)) !== null && _a !== void 0 ? _a : sandboxModule.exports;
|
|
254
|
+
const fn = sandboxModule.exports;
|
|
255
|
+
if (deserializeArgs) {
|
|
256
|
+
return fn(...bson_1.EJSON.deserialize(args));
|
|
257
|
+
}
|
|
258
|
+
return fn(...args);
|
|
259
|
+
}
|
|
@@ -20,6 +20,7 @@ export interface GenerateContextParams {
|
|
|
20
20
|
type ContextRequest = Pick<FastifyRequest, "ips" | "host" | "hostname" | "url" | "method" | "ip" | "id">;
|
|
21
21
|
export interface GenerateContextDataParams extends Omit<GenerateContextParams, 'args'> {
|
|
22
22
|
GenerateContext: (params: GenerateContextParams) => Promise<unknown>;
|
|
23
|
+
GenerateContextSync: (params: GenerateContextParams) => unknown;
|
|
23
24
|
}
|
|
24
25
|
export {};
|
|
25
26
|
//# sourceMappingURL=interface.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/utils/context/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,eAAe,CAAA;IACpB,eAAe,EAAE,QAAQ,CAAA;IACzB,aAAa,EAAE,SAAS,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,IAAI,CAAA;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,CAAA;AACxG,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/utils/context/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACzD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAA;AACxE,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAEnD,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,eAAe,CAAA;IACpB,eAAe,EAAE,QAAQ,CAAA;IACzB,aAAa,EAAE,SAAS,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,KAAK,CAAA;IACZ,IAAI,EAAE,IAAI,CAAA;IACV,QAAQ,EAAE,QAAQ,CAAA;IAClB,IAAI,EAAE,SAAS,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM,GAAG,UAAU,GAAG,KAAK,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,CAAA;AACxG,MAAM,WAAW,yBAA0B,SAAQ,IAAI,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACpF,eAAe,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IACpE,mBAAmB,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAA;CAChE"}
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ObjectId } from 'bson'
|
|
1
|
+
import { EJSON, ObjectId } from 'bson'
|
|
2
2
|
import type { FastifyRequest } from 'fastify'
|
|
3
3
|
import { ChangeStream, Document } from 'mongodb';
|
|
4
4
|
import { services } from '../../services'
|
|
@@ -141,7 +141,7 @@ export const functionsController: FunctionController = async (
|
|
|
141
141
|
return JSON.stringify({ message: result.message, name: result.name })
|
|
142
142
|
}
|
|
143
143
|
res.type('application/json')
|
|
144
|
-
return JSON.stringify(result)
|
|
144
|
+
return JSON.stringify(EJSON.serialize(result, { relaxed: false }));
|
|
145
145
|
} catch (error) {
|
|
146
146
|
res.status(400)
|
|
147
147
|
res.type('application/json')
|
|
@@ -60,7 +60,6 @@ describe('mongodb-atlas findOneAndUpdate', () => {
|
|
|
60
60
|
const result = await operators.findOneAndUpdate({ _id: id }, { $set: { title: 'New' } })
|
|
61
61
|
|
|
62
62
|
expect(findOne).toHaveBeenCalled()
|
|
63
|
-
expect(aggregate).toHaveBeenCalled()
|
|
64
63
|
expect(findOneAndUpdate).toHaveBeenCalledWith(
|
|
65
64
|
{ $and: [{ _id: id }] },
|
|
66
65
|
{ $set: { title: 'New' } }
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { ObjectId } from 'mongodb'
|
|
2
|
+
import MongoDbAtlas from '..'
|
|
3
|
+
import { Role, Rules } from '../../../features/rules/interface'
|
|
4
|
+
|
|
5
|
+
const createAppWithCollection = (collection: Record<string, unknown>) => ({
|
|
6
|
+
mongo: {
|
|
7
|
+
client: {
|
|
8
|
+
db: jest.fn().mockReturnValue({
|
|
9
|
+
collection: jest.fn().mockReturnValue(collection)
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const createRules = (roleOverrides: Partial<Role> = {}): Rules => ({
|
|
16
|
+
todos: {
|
|
17
|
+
database: 'db',
|
|
18
|
+
collection: 'todos',
|
|
19
|
+
filters: [],
|
|
20
|
+
roles: [
|
|
21
|
+
{
|
|
22
|
+
name: 'owner',
|
|
23
|
+
apply_when: {},
|
|
24
|
+
insert: true,
|
|
25
|
+
delete: true,
|
|
26
|
+
search: true,
|
|
27
|
+
read: true,
|
|
28
|
+
write: true,
|
|
29
|
+
...roleOverrides
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('mongodb-atlas Realm compatibility', () => {
|
|
36
|
+
it('allows updateOne upsert when no document matches', async () => {
|
|
37
|
+
const updateResult = { acknowledged: true, matchedCount: 0, modifiedCount: 0, upsertedCount: 1 }
|
|
38
|
+
const id = new ObjectId()
|
|
39
|
+
const findOne = jest.fn().mockResolvedValue(null)
|
|
40
|
+
const updateOne = jest.fn().mockResolvedValue(updateResult)
|
|
41
|
+
const collection = {
|
|
42
|
+
collectionName: 'todos',
|
|
43
|
+
findOne,
|
|
44
|
+
updateOne
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
48
|
+
rules: createRules(),
|
|
49
|
+
user: { id: 'user-1' }
|
|
50
|
+
}).db('db').collection('todos')
|
|
51
|
+
|
|
52
|
+
const result = await operators.updateOne(
|
|
53
|
+
{ _id: id },
|
|
54
|
+
{ $set: { label: 'created-by-upsert' } },
|
|
55
|
+
{ upsert: true }
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
expect(result).toEqual(updateResult)
|
|
59
|
+
expect(updateOne).toHaveBeenCalledWith(
|
|
60
|
+
{ $and: [{ _id: id }] },
|
|
61
|
+
{ $set: { label: 'created-by-upsert' } },
|
|
62
|
+
{ upsert: true }
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('accepts plain documents in updateOne by normalizing them to $set', async () => {
|
|
67
|
+
const updateResult = { acknowledged: true, matchedCount: 1, modifiedCount: 1, upsertedCount: 0 }
|
|
68
|
+
const id = new ObjectId()
|
|
69
|
+
const findOne = jest.fn().mockResolvedValue({ _id: id, label: 'before' })
|
|
70
|
+
const aggregate = jest.fn().mockReturnValue({
|
|
71
|
+
toArray: jest.fn().mockResolvedValue([{ _id: id, label: 'after', count: 5 }])
|
|
72
|
+
})
|
|
73
|
+
const updateOne = jest.fn().mockResolvedValue(updateResult)
|
|
74
|
+
const collection = {
|
|
75
|
+
collectionName: 'todos',
|
|
76
|
+
findOne,
|
|
77
|
+
aggregate,
|
|
78
|
+
updateOne
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
82
|
+
rules: createRules(),
|
|
83
|
+
user: { id: 'user-1' }
|
|
84
|
+
}).db('db').collection('todos')
|
|
85
|
+
|
|
86
|
+
const replacementLikePayload = { label: 'after', count: 5 }
|
|
87
|
+
const result = await operators.updateOne({ _id: id }, replacementLikePayload)
|
|
88
|
+
|
|
89
|
+
expect(result).toEqual(updateResult)
|
|
90
|
+
expect(updateOne).toHaveBeenCalledWith(
|
|
91
|
+
{ $and: [{ _id: id }] },
|
|
92
|
+
{ $set: replacementLikePayload },
|
|
93
|
+
undefined
|
|
94
|
+
)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('supports $inc in updateOne without using invalid aggregate stages', async () => {
|
|
98
|
+
const id = new ObjectId()
|
|
99
|
+
const findOne = jest.fn().mockResolvedValue({ _id: id, count: 1 })
|
|
100
|
+
const aggregate = jest.fn(() => {
|
|
101
|
+
throw new Error('aggregate should not be used for operator simulation')
|
|
102
|
+
})
|
|
103
|
+
const updateOne = jest.fn().mockResolvedValue({
|
|
104
|
+
acknowledged: true,
|
|
105
|
+
matchedCount: 1,
|
|
106
|
+
modifiedCount: 1
|
|
107
|
+
})
|
|
108
|
+
const collection = {
|
|
109
|
+
collectionName: 'todos',
|
|
110
|
+
findOne,
|
|
111
|
+
aggregate,
|
|
112
|
+
updateOne
|
|
113
|
+
}
|
|
114
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
115
|
+
rules: createRules(),
|
|
116
|
+
user: { id: 'user-1' }
|
|
117
|
+
}).db('db').collection('todos')
|
|
118
|
+
|
|
119
|
+
await operators.updateOne({ _id: id }, { $inc: { count: 1 } })
|
|
120
|
+
|
|
121
|
+
expect(updateOne).toHaveBeenCalledWith(
|
|
122
|
+
{ $and: [{ _id: id }] },
|
|
123
|
+
{ $inc: { count: 1 } },
|
|
124
|
+
undefined
|
|
125
|
+
)
|
|
126
|
+
expect(aggregate).not.toHaveBeenCalled()
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('supports $push in findOneAndUpdate without treating it as a pipeline stage', async () => {
|
|
130
|
+
const id = new ObjectId()
|
|
131
|
+
const findOne = jest.fn().mockResolvedValue({ _id: id, tags: ['old'] })
|
|
132
|
+
const aggregate = jest.fn(() => {
|
|
133
|
+
throw new Error('aggregate should not be used for operator simulation')
|
|
134
|
+
})
|
|
135
|
+
const findOneAndUpdate = jest.fn().mockResolvedValue({ _id: id, tags: ['old', 'new'] })
|
|
136
|
+
const collection = {
|
|
137
|
+
collectionName: 'todos',
|
|
138
|
+
findOne,
|
|
139
|
+
aggregate,
|
|
140
|
+
findOneAndUpdate
|
|
141
|
+
}
|
|
142
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
143
|
+
rules: createRules(),
|
|
144
|
+
user: { id: 'user-1' }
|
|
145
|
+
}).db('db').collection('todos')
|
|
146
|
+
|
|
147
|
+
await operators.findOneAndUpdate({ _id: id }, { $push: { tags: 'new' } } as any)
|
|
148
|
+
|
|
149
|
+
expect(findOneAndUpdate).toHaveBeenCalledWith(
|
|
150
|
+
{ $and: [{ _id: id }] },
|
|
151
|
+
{ $push: { tags: 'new' } }
|
|
152
|
+
)
|
|
153
|
+
expect(aggregate).not.toHaveBeenCalled()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('supports operator updates in updateMany without using invalid aggregate stages', async () => {
|
|
157
|
+
const id = new ObjectId()
|
|
158
|
+
const find = jest.fn().mockReturnValue({
|
|
159
|
+
toArray: jest.fn().mockResolvedValue([{ _id: id, tags: ['old'] }])
|
|
160
|
+
})
|
|
161
|
+
const aggregate = jest.fn(() => {
|
|
162
|
+
throw new Error('aggregate should not be used for operator simulation')
|
|
163
|
+
})
|
|
164
|
+
const updateMany = jest.fn().mockResolvedValue({
|
|
165
|
+
acknowledged: true,
|
|
166
|
+
matchedCount: 1,
|
|
167
|
+
modifiedCount: 1
|
|
168
|
+
})
|
|
169
|
+
const collection = {
|
|
170
|
+
collectionName: 'todos',
|
|
171
|
+
find,
|
|
172
|
+
aggregate,
|
|
173
|
+
updateMany
|
|
174
|
+
}
|
|
175
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
176
|
+
rules: createRules(),
|
|
177
|
+
user: { id: 'user-1' }
|
|
178
|
+
}).db('db').collection('todos')
|
|
179
|
+
|
|
180
|
+
await operators.updateMany({ _id: id }, { $push: { tags: 'new' } } as any)
|
|
181
|
+
|
|
182
|
+
expect(updateMany).toHaveBeenCalledWith(
|
|
183
|
+
{ $and: [{ _id: id }] },
|
|
184
|
+
{ $push: { tags: 'new' } },
|
|
185
|
+
undefined
|
|
186
|
+
)
|
|
187
|
+
expect(aggregate).not.toHaveBeenCalled()
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('treats findOne second argument as options when it matches option keys', async () => {
|
|
191
|
+
const findOne = jest.fn().mockResolvedValue({ _id: new ObjectId() })
|
|
192
|
+
const collection = {
|
|
193
|
+
collectionName: 'todos',
|
|
194
|
+
findOne
|
|
195
|
+
}
|
|
196
|
+
const session = { id: 'tx-1' }
|
|
197
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
198
|
+
run_as_system: true
|
|
199
|
+
}).db('db').collection('todos')
|
|
200
|
+
|
|
201
|
+
await operators.findOne({ key: 'value' }, { session } as any)
|
|
202
|
+
|
|
203
|
+
expect(findOne).toHaveBeenCalledWith({ key: 'value' }, { session })
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('treats find second argument as options when it matches option keys', async () => {
|
|
207
|
+
const cursor = {
|
|
208
|
+
toArray: jest.fn().mockResolvedValue([]),
|
|
209
|
+
sort: jest.fn(),
|
|
210
|
+
skip: jest.fn(),
|
|
211
|
+
limit: jest.fn()
|
|
212
|
+
}
|
|
213
|
+
const find = jest.fn().mockReturnValue(cursor)
|
|
214
|
+
const collection = {
|
|
215
|
+
collectionName: 'todos',
|
|
216
|
+
find
|
|
217
|
+
}
|
|
218
|
+
const session = { id: 'tx-2' }
|
|
219
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
220
|
+
run_as_system: true
|
|
221
|
+
}).db('db').collection('todos')
|
|
222
|
+
|
|
223
|
+
operators.find({ active: true }, { session } as any)
|
|
224
|
+
|
|
225
|
+
expect(find).toHaveBeenCalledWith({ active: true }, { session })
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('returns insertMany insertedIds as an array', async () => {
|
|
229
|
+
const id0 = new ObjectId()
|
|
230
|
+
const id1 = new ObjectId()
|
|
231
|
+
const insertMany = jest.fn().mockResolvedValue({
|
|
232
|
+
acknowledged: true,
|
|
233
|
+
insertedCount: 2,
|
|
234
|
+
insertedIds: { 0: id0, 1: id1 }
|
|
235
|
+
})
|
|
236
|
+
const collection = {
|
|
237
|
+
collectionName: 'todos',
|
|
238
|
+
insertMany
|
|
239
|
+
}
|
|
240
|
+
const operators = MongoDbAtlas(createAppWithCollection(collection) as any, {
|
|
241
|
+
run_as_system: true
|
|
242
|
+
}).db('db').collection('todos')
|
|
243
|
+
|
|
244
|
+
const result = await operators.insertMany([{ a: 1 }, { a: 2 }])
|
|
245
|
+
|
|
246
|
+
expect(result.insertedIds).toEqual([id0, id1])
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('exposes startSession and delegates to the underlying MongoClient', async () => {
|
|
250
|
+
const mockSession = { withTransaction: jest.fn() }
|
|
251
|
+
const startSession = jest.fn().mockReturnValue(mockSession)
|
|
252
|
+
const app = {
|
|
253
|
+
mongo: {
|
|
254
|
+
client: {
|
|
255
|
+
startSession,
|
|
256
|
+
db: jest.fn().mockReturnValue({
|
|
257
|
+
collection: jest.fn().mockReturnValue({
|
|
258
|
+
collectionName: 'todos'
|
|
259
|
+
})
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const service = MongoDbAtlas(app as any, {
|
|
266
|
+
run_as_system: true
|
|
267
|
+
})
|
|
268
|
+
const options = { causalConsistency: true }
|
|
269
|
+
const session = service.startSession(options as any)
|
|
270
|
+
|
|
271
|
+
expect(startSession).toHaveBeenCalledWith(options)
|
|
272
|
+
expect(session).toBe(mockSession)
|
|
273
|
+
})
|
|
274
|
+
})
|