@r5v/mongoose-paginate 1.0.13 → 1.0.15
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/README.md +210 -17
- package/dist/{types/aggregationPagingQuery.d.ts → aggregationPagingQuery.d.ts} +6 -7
- package/dist/aggregationPagingQuery.d.ts.map +1 -0
- package/dist/aggregationPagingQuery.js +19 -4
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -1
- package/dist/{types/pagingQuery.d.ts → pagingQuery.d.ts} +6 -7
- package/dist/pagingQuery.d.ts.map +1 -0
- package/dist/pagingQuery.js +8 -3
- package/dist/tests/aggregationPagingQuery.spec.d.ts +2 -0
- package/dist/tests/aggregationPagingQuery.spec.d.ts.map +1 -0
- package/dist/tests/aggregationPagingQuery.spec.js +419 -0
- package/dist/tests/buildPopulateFromString.spec.d.ts +2 -0
- package/dist/tests/buildPopulateFromString.spec.d.ts.map +1 -0
- package/dist/tests/buildPopulateFromString.spec.js +223 -0
- package/dist/tests/dotNotation.spec.d.ts.map +1 -0
- package/dist/tests/findProtectedPaths.spec.d.ts.map +1 -0
- package/dist/tests/findProtectedPaths.spec.js +128 -6
- package/dist/tests/getPathsWithRef.spec.d.ts.map +1 -0
- package/dist/tests/getPathsWithRef.spec.js +92 -18
- package/dist/tests/getPropertyFromDotNotation.spec.d.ts.map +1 -0
- package/dist/tests/insertPopulate.spec.d.ts.map +1 -0
- package/dist/tests/isJsonString.spec.d.ts +2 -0
- package/dist/tests/isJsonString.spec.d.ts.map +1 -0
- package/dist/tests/isJsonString.spec.js +116 -0
- package/dist/tests/isValidDateString.spec.d.ts +2 -0
- package/dist/tests/isValidDateString.spec.d.ts.map +1 -0
- package/dist/tests/isValidDateString.spec.js +116 -0
- package/dist/tests/pagingQuery.spec.d.ts.map +1 -0
- package/dist/tests/pagingQuery.spec.js +238 -13
- package/dist/tests/parseParams.spec.d.ts +2 -0
- package/dist/tests/parseParams.spec.d.ts.map +1 -0
- package/dist/tests/parseParams.spec.js +212 -0
- package/dist/tests/parseSortString.spec.d.ts.map +1 -0
- package/dist/tests/schemaTraversal.spec.d.ts +2 -0
- package/dist/tests/schemaTraversal.spec.d.ts.map +1 -0
- package/dist/tests/schemaTraversal.spec.js +139 -0
- package/dist/types/index.d.ts +72 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/buildPopulateFromString.d.ts +8 -0
- package/dist/utils/buildPopulateFromString.d.ts.map +1 -0
- package/dist/utils/buildPopulateFromString.js +54 -49
- package/dist/utils/dotNotation.d.ts +11 -0
- package/dist/utils/dotNotation.d.ts.map +1 -0
- package/dist/utils/dotNotation.js +2 -2
- package/dist/utils/findKeyWithValue.d.ts.map +1 -0
- package/dist/utils/findProtectedPaths.d.ts.map +1 -0
- package/dist/utils/findProtectedPaths.js +14 -56
- package/dist/utils/getPathsWithRef.d.ts +5 -0
- package/dist/utils/getPathsWithRef.d.ts.map +1 -0
- package/dist/utils/getPathsWithRef.js +59 -96
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +33 -0
- package/dist/utils/isJsonString.d.ts.map +1 -0
- package/dist/utils/isValidDateString.d.ts.map +1 -0
- package/dist/utils/parseParams.d.ts.map +1 -0
- package/dist/utils/parseParams.js +14 -12
- package/dist/utils/parsePopulateQuery.d.ts.map +1 -0
- package/dist/utils/parsePopulateQuery.js +1 -1
- package/dist/{types/utils → utils}/parseSortString.d.ts +1 -1
- package/dist/utils/parseSortString.d.ts.map +1 -0
- package/dist/utils/schemaTraversal.d.ts +9 -0
- package/dist/utils/schemaTraversal.d.ts.map +1 -0
- package/dist/utils/schemaTraversal.js +37 -0
- package/package.json +23 -2
- package/dist/types/aggregationPagingQuery.d.ts.map +0 -1
- package/dist/types/pagingQuery.d.ts.map +0 -1
- package/dist/types/tests/dotNotation.spec.d.ts.map +0 -1
- package/dist/types/tests/findProtectedPaths.spec.d.ts.map +0 -1
- package/dist/types/tests/getPathsWithRef.spec.d.ts.map +0 -1
- package/dist/types/tests/getPropertyFromDotNotation.spec.d.ts.map +0 -1
- package/dist/types/tests/insertPopulate.spec.d.ts.map +0 -1
- package/dist/types/tests/pagingQuery.spec.d.ts.map +0 -1
- package/dist/types/tests/parseSortString.spec.d.ts.map +0 -1
- package/dist/types/types/index.d.ts +0 -61
- package/dist/types/types/index.d.ts.map +0 -1
- package/dist/types/utils/buildPopulateFromString.d.ts +0 -8
- package/dist/types/utils/buildPopulateFromString.d.ts.map +0 -1
- package/dist/types/utils/dotNotation.d.ts +0 -11
- package/dist/types/utils/dotNotation.d.ts.map +0 -1
- package/dist/types/utils/findKeyWithValue.d.ts.map +0 -1
- package/dist/types/utils/findProtectedPaths.d.ts.map +0 -1
- package/dist/types/utils/getPathsWithRef.d.ts +0 -5
- package/dist/types/utils/getPathsWithRef.d.ts.map +0 -1
- package/dist/types/utils/isJsonString.d.ts.map +0 -1
- package/dist/types/utils/isValidDateString.d.ts.map +0 -1
- package/dist/types/utils/parseParams.d.ts.map +0 -1
- package/dist/types/utils/parsePopulateQuery.d.ts.map +0 -1
- package/dist/types/utils/parseSortString.d.ts.map +0 -1
- /package/dist/{types/tests → tests}/dotNotation.spec.d.ts +0 -0
- /package/dist/{types/tests → tests}/findProtectedPaths.spec.d.ts +0 -0
- /package/dist/{types/tests → tests}/getPathsWithRef.spec.d.ts +0 -0
- /package/dist/{types/tests → tests}/getPropertyFromDotNotation.spec.d.ts +0 -0
- /package/dist/{types/tests → tests}/insertPopulate.spec.d.ts +0 -0
- /package/dist/{types/tests → tests}/pagingQuery.spec.d.ts +0 -0
- /package/dist/{types/tests → tests}/parseSortString.spec.d.ts +0 -0
- /package/dist/{types/utils → utils}/findKeyWithValue.d.ts +0 -0
- /package/dist/{types/utils → utils}/findProtectedPaths.d.ts +0 -0
- /package/dist/{types/utils → utils}/isJsonString.d.ts +0 -0
- /package/dist/{types/utils → utils}/isValidDateString.d.ts +0 -0
- /package/dist/{types/utils → utils}/parseParams.d.ts +0 -0
- /package/dist/{types/utils → utils}/parsePopulateQuery.d.ts +0 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
const aggregationPagingQuery_1 = require("../aggregationPagingQuery");
|
|
46
|
+
const node_mocks_http_1 = require("node-mocks-http");
|
|
47
|
+
const mongoose_1 = __importStar(require("mongoose"));
|
|
48
|
+
const mockingoose = require('mockingoose');
|
|
49
|
+
// Create a test schema and model
|
|
50
|
+
const AggTestSchema = new mongoose_1.Schema({
|
|
51
|
+
name: { type: String, required: true },
|
|
52
|
+
age: { type: Number },
|
|
53
|
+
email: { type: String },
|
|
54
|
+
active: { type: Boolean, default: true },
|
|
55
|
+
createdAt: { type: Date, default: Date.now },
|
|
56
|
+
category: { type: mongoose_1.Schema.Types.ObjectId, ref: 'Category' }
|
|
57
|
+
});
|
|
58
|
+
const AggTestModel = mongoose_1.default.model('AggTestModel', AggTestSchema);
|
|
59
|
+
// Create a test schema with protected fields
|
|
60
|
+
const ProtectedSchema = new mongoose_1.Schema({
|
|
61
|
+
username: { type: String, required: true },
|
|
62
|
+
email: { type: String },
|
|
63
|
+
password: { type: String, select: false },
|
|
64
|
+
secretKey: { type: String, select: false },
|
|
65
|
+
profile: {
|
|
66
|
+
name: { type: String },
|
|
67
|
+
ssn: { type: String, select: false }
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
const ProtectedModel = mongoose_1.default.model('ProtectedModel', ProtectedSchema);
|
|
71
|
+
// Default pipeline with at least one stage to avoid undefined error
|
|
72
|
+
const defaultPipeline = [{ $match: {} }];
|
|
73
|
+
describe('AggregationPagingQuery', () => {
|
|
74
|
+
beforeEach(() => {
|
|
75
|
+
mockingoose.resetAll();
|
|
76
|
+
});
|
|
77
|
+
describe('constructor', () => {
|
|
78
|
+
test('should throw error if request is invalid', () => {
|
|
79
|
+
expect(() => {
|
|
80
|
+
new aggregationPagingQuery_1.AggregationPagingQuery(null, AggTestModel, { pipeline: [] });
|
|
81
|
+
}).toThrow('Invalid request object: must have a query property');
|
|
82
|
+
});
|
|
83
|
+
test('should throw error if request has no query property', () => {
|
|
84
|
+
expect(() => {
|
|
85
|
+
new aggregationPagingQuery_1.AggregationPagingQuery({}, AggTestModel, { pipeline: [] });
|
|
86
|
+
}).toThrow('Invalid request object: must have a query property');
|
|
87
|
+
});
|
|
88
|
+
test('should throw error if model is invalid', () => {
|
|
89
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
90
|
+
expect(() => {
|
|
91
|
+
new aggregationPagingQuery_1.AggregationPagingQuery(req, null, { pipeline: [] });
|
|
92
|
+
}).toThrow('Invalid model: must be a Mongoose model');
|
|
93
|
+
});
|
|
94
|
+
test('should throw error if model does not have aggregate function', () => {
|
|
95
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
96
|
+
expect(() => {
|
|
97
|
+
new aggregationPagingQuery_1.AggregationPagingQuery(req, {}, { pipeline: [] });
|
|
98
|
+
}).toThrow('Invalid model: must be a Mongoose model');
|
|
99
|
+
});
|
|
100
|
+
test('should throw error if options is missing pipeline', () => {
|
|
101
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
102
|
+
expect(() => {
|
|
103
|
+
new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {});
|
|
104
|
+
}).toThrow('Invalid options: pipeline must be an array');
|
|
105
|
+
});
|
|
106
|
+
test('should throw error if pipeline is not an array', () => {
|
|
107
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
108
|
+
expect(() => {
|
|
109
|
+
new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: 'invalid' });
|
|
110
|
+
}).toThrow('Invalid options: pipeline must be an array');
|
|
111
|
+
});
|
|
112
|
+
test('should create instance with default params', () => {
|
|
113
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
114
|
+
// Need at least one pipeline stage to avoid undefined error
|
|
115
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: [{ $match: {} }] });
|
|
116
|
+
expect(apq.params.$limit).toBe(25);
|
|
117
|
+
expect(apq.params.$skip).toBe(0);
|
|
118
|
+
expect(apq.params.$paging).toBe(true);
|
|
119
|
+
expect(apq.params.$filter).toEqual({});
|
|
120
|
+
expect(apq.params.$sort).toEqual({});
|
|
121
|
+
expect(apq.params.$populate).toEqual([]);
|
|
122
|
+
expect(apq.params.$select).toBe('');
|
|
123
|
+
expect(apq.params.$count).toEqual([]);
|
|
124
|
+
expect(apq.params.$postFilter).toEqual({});
|
|
125
|
+
expect(apq.params.$preSort).toEqual({});
|
|
126
|
+
});
|
|
127
|
+
test('should parse filter from query string', () => {
|
|
128
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
129
|
+
query: { $filter: '{"name":"John"}' }
|
|
130
|
+
});
|
|
131
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
132
|
+
pipeline: defaultPipeline,
|
|
133
|
+
enableFilter: true
|
|
134
|
+
});
|
|
135
|
+
expect(apq.params.$filter).toEqual({ name: 'John' });
|
|
136
|
+
});
|
|
137
|
+
test('should parse sort string for aggregate', () => {
|
|
138
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
139
|
+
query: { $sort: 'name 1, age -1' }
|
|
140
|
+
});
|
|
141
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
142
|
+
expect(apq.params.$sort).toEqual({ name: 1, age: -1 });
|
|
143
|
+
});
|
|
144
|
+
test('should parse preSort string', () => {
|
|
145
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
146
|
+
query: { $preSort: 'createdAt -1' }
|
|
147
|
+
});
|
|
148
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
149
|
+
expect(apq.params.$preSort).toEqual({ createdAt: -1 });
|
|
150
|
+
});
|
|
151
|
+
test('should parse count array', () => {
|
|
152
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
153
|
+
query: { $count: 'items, orders' }
|
|
154
|
+
});
|
|
155
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
156
|
+
expect(apq.params.$count).toEqual(['items', 'orders']);
|
|
157
|
+
});
|
|
158
|
+
test('should parse postFilter', () => {
|
|
159
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
160
|
+
query: { $postFilter: '{"verified":true}' }
|
|
161
|
+
});
|
|
162
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
163
|
+
pipeline: defaultPipeline,
|
|
164
|
+
enablePostFilter: true
|
|
165
|
+
});
|
|
166
|
+
expect(apq.params.$postFilter).toEqual({ verified: true });
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe('options', () => {
|
|
170
|
+
test('should set default options', () => {
|
|
171
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
172
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
173
|
+
expect(apq.options.enableFilter).toBe(false);
|
|
174
|
+
expect(apq.options.enablePostFilter).toBe(false);
|
|
175
|
+
expect(apq.options.enablePreSort).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
test('should merge provided options', () => {
|
|
178
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
179
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
180
|
+
pipeline: [{ $match: { active: true } }],
|
|
181
|
+
enableFilter: true,
|
|
182
|
+
enablePostFilter: true,
|
|
183
|
+
staticFilter: { type: 'test' }
|
|
184
|
+
});
|
|
185
|
+
expect(apq.options.enableFilter).toBe(true);
|
|
186
|
+
expect(apq.options.enablePostFilter).toBe(true);
|
|
187
|
+
expect(apq.options.staticFilter).toEqual({ type: 'test' });
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe('typeCastObject', () => {
|
|
191
|
+
test('should convert valid ObjectId strings', () => {
|
|
192
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
193
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
194
|
+
const objectIdStr = '507f1f77bcf86cd799439011';
|
|
195
|
+
const result = apq.typeCastObject(objectIdStr);
|
|
196
|
+
expect(result).toBeInstanceOf(mongoose_1.Types.ObjectId);
|
|
197
|
+
expect(result.toString()).toBe(objectIdStr);
|
|
198
|
+
});
|
|
199
|
+
test('should keep already cast ObjectIds', () => {
|
|
200
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
201
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
202
|
+
const objectId = new mongoose_1.Types.ObjectId();
|
|
203
|
+
const result = apq.typeCastObject(objectId);
|
|
204
|
+
expect(result).toBe(objectId);
|
|
205
|
+
});
|
|
206
|
+
test('should convert valid date strings', () => {
|
|
207
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
208
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
209
|
+
const dateStr = '2023-01-15T10:30:00Z';
|
|
210
|
+
const result = apq.typeCastObject(dateStr);
|
|
211
|
+
expect(result).toBeInstanceOf(Date);
|
|
212
|
+
});
|
|
213
|
+
test('should keep numbers as-is', () => {
|
|
214
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
215
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
216
|
+
expect(apq.typeCastObject(123)).toBe(123);
|
|
217
|
+
expect(apq.typeCastObject(0)).toBe(0);
|
|
218
|
+
expect(apq.typeCastObject(-5)).toBe(-5);
|
|
219
|
+
});
|
|
220
|
+
test('should keep regular strings as-is', () => {
|
|
221
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
222
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
223
|
+
expect(apq.typeCastObject('hello')).toBe('hello');
|
|
224
|
+
expect(apq.typeCastObject('John Doe')).toBe('John Doe');
|
|
225
|
+
});
|
|
226
|
+
test('should recursively process arrays', () => {
|
|
227
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
228
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
229
|
+
const objectIdStr = '507f1f77bcf86cd799439011';
|
|
230
|
+
const result = apq.typeCastObject([objectIdStr, 'hello', 123]);
|
|
231
|
+
expect(Array.isArray(result)).toBe(true);
|
|
232
|
+
expect(result[0]).toBeInstanceOf(mongoose_1.Types.ObjectId);
|
|
233
|
+
expect(result[1]).toBe('hello');
|
|
234
|
+
expect(result[2]).toBe(123);
|
|
235
|
+
});
|
|
236
|
+
test('should recursively process objects', () => {
|
|
237
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
238
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
239
|
+
const objectIdStr = '507f1f77bcf86cd799439011';
|
|
240
|
+
const result = apq.typeCastObject({
|
|
241
|
+
_id: objectIdStr,
|
|
242
|
+
name: 'John',
|
|
243
|
+
age: 30
|
|
244
|
+
});
|
|
245
|
+
expect(result._id).toBeInstanceOf(mongoose_1.Types.ObjectId);
|
|
246
|
+
expect(result.name).toBe('John');
|
|
247
|
+
expect(result.age).toBe(30);
|
|
248
|
+
});
|
|
249
|
+
test('should handle nested objects with ObjectIds', () => {
|
|
250
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
251
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
252
|
+
const objectIdStr = '507f1f77bcf86cd799439011';
|
|
253
|
+
const result = apq.typeCastObject({
|
|
254
|
+
$and: [
|
|
255
|
+
{ userId: objectIdStr },
|
|
256
|
+
{ active: true }
|
|
257
|
+
]
|
|
258
|
+
});
|
|
259
|
+
expect(result.$and[0].userId).toBeInstanceOf(mongoose_1.Types.ObjectId);
|
|
260
|
+
expect(result.$and[1].active).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
describe('createCounts', () => {
|
|
264
|
+
test('should create count fields from $count params', () => {
|
|
265
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
266
|
+
query: { $count: 'items,orders' }
|
|
267
|
+
});
|
|
268
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
269
|
+
expect(apq.params.$count).toEqual(['items', 'orders']);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
describe('exec', () => {
|
|
273
|
+
test('should throw error if query is not present', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
274
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
275
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
276
|
+
apq.query = undefined;
|
|
277
|
+
yield expect(apq.exec()).rejects.toThrow('No Query Present in AggregationQuery');
|
|
278
|
+
}));
|
|
279
|
+
test('should execute without paging when disabled', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
280
|
+
const mockData = [{ name: 'John' }, { name: 'Jane' }];
|
|
281
|
+
mockingoose(AggTestModel).toReturn(mockData, 'aggregate');
|
|
282
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
283
|
+
query: { $paging: 'false' }
|
|
284
|
+
});
|
|
285
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
286
|
+
pipeline: defaultPipeline,
|
|
287
|
+
disablePaging: true
|
|
288
|
+
});
|
|
289
|
+
const result = yield apq.exec();
|
|
290
|
+
expect(result).toEqual(mockData);
|
|
291
|
+
}));
|
|
292
|
+
test('should execute with paging and return paginated structure', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
293
|
+
const mockData = [{
|
|
294
|
+
items: [{ name: 'John' }, { name: 'Jane' }],
|
|
295
|
+
totalRows: 100,
|
|
296
|
+
limit: 25,
|
|
297
|
+
skip: 0,
|
|
298
|
+
rows: 2
|
|
299
|
+
}];
|
|
300
|
+
mockingoose(AggTestModel).toReturn(mockData, 'aggregate');
|
|
301
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
302
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
303
|
+
const result = yield apq.exec();
|
|
304
|
+
expect(result).toHaveProperty('items');
|
|
305
|
+
expect(result).toHaveProperty('totalRows');
|
|
306
|
+
expect(result).toHaveProperty('limit');
|
|
307
|
+
expect(result).toHaveProperty('skip');
|
|
308
|
+
expect(result).toHaveProperty('rows');
|
|
309
|
+
}));
|
|
310
|
+
});
|
|
311
|
+
describe('pipeline handling', () => {
|
|
312
|
+
test('should prepend $search stage if present in first position', () => {
|
|
313
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
314
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
315
|
+
pipeline: [
|
|
316
|
+
{ $search: { index: 'default', text: { query: 'test', path: 'name' } } },
|
|
317
|
+
{ $project: { name: 1 } }
|
|
318
|
+
]
|
|
319
|
+
});
|
|
320
|
+
expect(apq.query).toBeDefined();
|
|
321
|
+
});
|
|
322
|
+
test('should prepend $geoNear stage if present in first position', () => {
|
|
323
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
324
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
325
|
+
pipeline: [
|
|
326
|
+
{ $geoNear: { near: { type: 'Point', coordinates: [0, 0] }, distanceField: 'dist' } },
|
|
327
|
+
{ $project: { name: 1 } }
|
|
328
|
+
]
|
|
329
|
+
});
|
|
330
|
+
expect(apq.query).toBeDefined();
|
|
331
|
+
});
|
|
332
|
+
test('should handle $match in first position', () => {
|
|
333
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
334
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
335
|
+
pipeline: [
|
|
336
|
+
{ $match: { active: true } },
|
|
337
|
+
{ $project: { name: 1 } }
|
|
338
|
+
]
|
|
339
|
+
});
|
|
340
|
+
expect(apq.query).toBeDefined();
|
|
341
|
+
});
|
|
342
|
+
test('should apply static filter', () => {
|
|
343
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
344
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
345
|
+
pipeline: defaultPipeline,
|
|
346
|
+
staticFilter: { type: 'premium' }
|
|
347
|
+
});
|
|
348
|
+
expect(apq.options.staticFilter).toEqual({ type: 'premium' });
|
|
349
|
+
});
|
|
350
|
+
test('should apply static post filter', () => {
|
|
351
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
352
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
353
|
+
pipeline: defaultPipeline,
|
|
354
|
+
staticPostFilter: { verified: true }
|
|
355
|
+
});
|
|
356
|
+
expect(apq.options.staticPostFilter).toEqual({ verified: true });
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
describe('error handling', () => {
|
|
360
|
+
test('should throw error for invalid JSON in filter', () => {
|
|
361
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
362
|
+
query: { $filter: 'invalid json' }
|
|
363
|
+
});
|
|
364
|
+
expect(() => {
|
|
365
|
+
new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
366
|
+
}).toThrow('Invalid JSON in $filter parameter');
|
|
367
|
+
});
|
|
368
|
+
test('should throw error for invalid JSON in postFilter', () => {
|
|
369
|
+
const req = (0, node_mocks_http_1.createRequest)({
|
|
370
|
+
query: { $postFilter: 'invalid json' }
|
|
371
|
+
});
|
|
372
|
+
expect(() => {
|
|
373
|
+
new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, { pipeline: defaultPipeline });
|
|
374
|
+
}).toThrow('Invalid JSON in $postFilter parameter');
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
describe('removeProtected', () => {
|
|
378
|
+
test('should find protected paths when removeProtected is enabled', () => {
|
|
379
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
380
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, ProtectedModel, {
|
|
381
|
+
pipeline: defaultPipeline,
|
|
382
|
+
removeProtected: true
|
|
383
|
+
});
|
|
384
|
+
expect(apq.protectedPaths).toContain('password');
|
|
385
|
+
expect(apq.protectedPaths).toContain('secretKey');
|
|
386
|
+
});
|
|
387
|
+
test('should not find protected paths when removeProtected is disabled', () => {
|
|
388
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
389
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, ProtectedModel, {
|
|
390
|
+
pipeline: defaultPipeline,
|
|
391
|
+
removeProtected: false
|
|
392
|
+
});
|
|
393
|
+
expect(apq.protectedPaths).toEqual([]);
|
|
394
|
+
});
|
|
395
|
+
test('should not find protected paths by default', () => {
|
|
396
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
397
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, ProtectedModel, {
|
|
398
|
+
pipeline: defaultPipeline
|
|
399
|
+
});
|
|
400
|
+
expect(apq.protectedPaths).toEqual([]);
|
|
401
|
+
});
|
|
402
|
+
test('should set removeProtected option correctly', () => {
|
|
403
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
404
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, ProtectedModel, {
|
|
405
|
+
pipeline: defaultPipeline,
|
|
406
|
+
removeProtected: true
|
|
407
|
+
});
|
|
408
|
+
expect(apq.options.removeProtected).toBe(true);
|
|
409
|
+
});
|
|
410
|
+
test('should have empty protected paths for model without protected fields', () => {
|
|
411
|
+
const req = (0, node_mocks_http_1.createRequest)({ query: {} });
|
|
412
|
+
const apq = new aggregationPagingQuery_1.AggregationPagingQuery(req, AggTestModel, {
|
|
413
|
+
pipeline: defaultPipeline,
|
|
414
|
+
removeProtected: true
|
|
415
|
+
});
|
|
416
|
+
expect(apq.protectedPaths).toEqual([]);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildPopulateFromString.spec.d.ts","sourceRoot":"","sources":["../../src/tests/buildPopulateFromString.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const buildPopulateFromString_1 = require("../utils/buildPopulateFromString");
|
|
4
|
+
describe('buildPopulate', () => {
|
|
5
|
+
describe('basic path parsing', () => {
|
|
6
|
+
test('should return empty array for empty string', () => {
|
|
7
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('');
|
|
8
|
+
expect(result).toEqual([]);
|
|
9
|
+
});
|
|
10
|
+
test('should return empty array for null/undefined', () => {
|
|
11
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)(null);
|
|
12
|
+
expect(result).toEqual([]);
|
|
13
|
+
});
|
|
14
|
+
test('should parse single path', () => {
|
|
15
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author');
|
|
16
|
+
expect(result).toEqual([{ path: 'author' }]);
|
|
17
|
+
});
|
|
18
|
+
test('should parse multiple paths', () => {
|
|
19
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author, books');
|
|
20
|
+
expect(result).toEqual([
|
|
21
|
+
{ path: 'author' },
|
|
22
|
+
{ path: 'books' }
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
test('should trim whitespace from paths', () => {
|
|
26
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)(' author , books ');
|
|
27
|
+
expect(result).toEqual([
|
|
28
|
+
{ path: 'author' },
|
|
29
|
+
{ path: 'books' }
|
|
30
|
+
]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe('nested path parsing', () => {
|
|
34
|
+
test('should parse nested path with dot notation', () => {
|
|
35
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author.profile');
|
|
36
|
+
expect(result).toEqual([
|
|
37
|
+
{
|
|
38
|
+
path: 'author',
|
|
39
|
+
populate: [{ path: 'profile' }]
|
|
40
|
+
}
|
|
41
|
+
]);
|
|
42
|
+
});
|
|
43
|
+
test('should parse deeply nested path', () => {
|
|
44
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author.profile.avatar');
|
|
45
|
+
expect(result).toEqual([
|
|
46
|
+
{
|
|
47
|
+
path: 'author',
|
|
48
|
+
populate: [
|
|
49
|
+
{
|
|
50
|
+
path: 'profile',
|
|
51
|
+
populate: [{ path: 'avatar' }]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
test('should merge nested paths with same root', () => {
|
|
58
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author.profile, author.books');
|
|
59
|
+
expect(result).toEqual([
|
|
60
|
+
{
|
|
61
|
+
path: 'author',
|
|
62
|
+
populate: [
|
|
63
|
+
{ path: 'profile' },
|
|
64
|
+
{ path: 'books' }
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('select field parsing', () => {
|
|
71
|
+
test('should parse path with select in brackets', () => {
|
|
72
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[name,email]');
|
|
73
|
+
expect(result).toEqual([
|
|
74
|
+
{
|
|
75
|
+
path: 'author',
|
|
76
|
+
select: 'name email'
|
|
77
|
+
}
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
test('should parse path with single select field', () => {
|
|
81
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[name]');
|
|
82
|
+
expect(result).toEqual([
|
|
83
|
+
{
|
|
84
|
+
path: 'author',
|
|
85
|
+
select: 'name'
|
|
86
|
+
}
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
test('should parse multiple paths with select', () => {
|
|
90
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[name], books[title,isbn]');
|
|
91
|
+
expect(result).toEqual([
|
|
92
|
+
{ path: 'author', select: 'name' },
|
|
93
|
+
{ path: 'books', select: 'title isbn' }
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('nested paths with select', () => {
|
|
98
|
+
test('should parse nested path with select on child', () => {
|
|
99
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author.profile[bio,avatar]');
|
|
100
|
+
expect(result).toEqual([
|
|
101
|
+
{
|
|
102
|
+
path: 'author',
|
|
103
|
+
populate: [
|
|
104
|
+
{
|
|
105
|
+
path: 'profile',
|
|
106
|
+
select: 'bio avatar'
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
test('should parse nested path with select on parent', () => {
|
|
113
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[name].profile');
|
|
114
|
+
expect(result).toEqual([
|
|
115
|
+
{
|
|
116
|
+
path: 'author',
|
|
117
|
+
select: 'name',
|
|
118
|
+
populate: [{ path: 'profile' }]
|
|
119
|
+
}
|
|
120
|
+
]);
|
|
121
|
+
});
|
|
122
|
+
test('should parse nested path with select on both', () => {
|
|
123
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[name].profile[bio]');
|
|
124
|
+
expect(result).toEqual([
|
|
125
|
+
{
|
|
126
|
+
path: 'author',
|
|
127
|
+
select: 'name',
|
|
128
|
+
populate: [
|
|
129
|
+
{
|
|
130
|
+
path: 'profile',
|
|
131
|
+
select: 'bio'
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
]);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe('complex scenarios', () => {
|
|
139
|
+
test('should handle multiple nested paths with different depths', () => {
|
|
140
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author.profile, books.publisher.location');
|
|
141
|
+
expect(result).toEqual([
|
|
142
|
+
{
|
|
143
|
+
path: 'author',
|
|
144
|
+
populate: [{ path: 'profile' }]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
path: 'books',
|
|
148
|
+
populate: [
|
|
149
|
+
{
|
|
150
|
+
path: 'publisher',
|
|
151
|
+
populate: [{ path: 'location' }]
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
157
|
+
test('should merge multiple nested paths correctly', () => {
|
|
158
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author.profile, author.books, author.followers');
|
|
159
|
+
expect(result).toEqual([
|
|
160
|
+
{
|
|
161
|
+
path: 'author',
|
|
162
|
+
populate: [
|
|
163
|
+
{ path: 'profile' },
|
|
164
|
+
{ path: 'books' },
|
|
165
|
+
{ path: 'followers' }
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
test('should handle commas inside brackets correctly', () => {
|
|
171
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[name,email,bio]');
|
|
172
|
+
expect(result).toEqual([
|
|
173
|
+
{
|
|
174
|
+
path: 'author',
|
|
175
|
+
select: 'name email bio'
|
|
176
|
+
}
|
|
177
|
+
]);
|
|
178
|
+
});
|
|
179
|
+
test('should handle complex combination', () => {
|
|
180
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[name].profile[bio], books[title]');
|
|
181
|
+
expect(result).toEqual([
|
|
182
|
+
{
|
|
183
|
+
path: 'author',
|
|
184
|
+
select: 'name',
|
|
185
|
+
populate: [
|
|
186
|
+
{
|
|
187
|
+
path: 'profile',
|
|
188
|
+
select: 'bio'
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
path: 'books',
|
|
194
|
+
select: 'title'
|
|
195
|
+
}
|
|
196
|
+
]);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
describe('edge cases', () => {
|
|
200
|
+
test('should handle path with empty brackets', () => {
|
|
201
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[]');
|
|
202
|
+
// Empty brackets are preserved in the path name
|
|
203
|
+
expect(result).toEqual([{ path: 'author[]' }]);
|
|
204
|
+
});
|
|
205
|
+
test('should handle whitespace around brackets', () => {
|
|
206
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('author[ name , email ]');
|
|
207
|
+
expect(result).toEqual([
|
|
208
|
+
{
|
|
209
|
+
path: 'author',
|
|
210
|
+
select: ' name email '
|
|
211
|
+
}
|
|
212
|
+
]);
|
|
213
|
+
});
|
|
214
|
+
test('should handle single character paths', () => {
|
|
215
|
+
const result = (0, buildPopulateFromString_1.buildPopulate)('a, b, c');
|
|
216
|
+
expect(result).toEqual([
|
|
217
|
+
{ path: 'a' },
|
|
218
|
+
{ path: 'b' },
|
|
219
|
+
{ path: 'c' }
|
|
220
|
+
]);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dotNotation.spec.d.ts","sourceRoot":"","sources":["../../src/tests/dotNotation.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findProtectedPaths.spec.d.ts","sourceRoot":"","sources":["../../src/tests/findProtectedPaths.spec.ts"],"names":[],"mappings":""}
|