@loomcore/api 0.2.4 → 0.2.6
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/__tests__/common-test.utils.d.ts +12 -12
- package/dist/__tests__/common-test.utils.js +94 -101
- package/dist/__tests__/postgres.test-database.d.ts +2 -2
- package/dist/__tests__/postgres.test-database.js +16 -15
- package/dist/__tests__/test-objects.d.ts +1 -6
- package/dist/__tests__/test-objects.js +34 -73
- package/dist/config/base-api-config.d.ts +2 -2
- package/dist/config/base-api-config.js +17 -10
- package/dist/controllers/auth.controller.d.ts +3 -5
- package/dist/controllers/auth.controller.js +22 -26
- package/dist/controllers/index.d.ts +10 -11
- package/dist/controllers/index.js +10 -11
- package/dist/databases/mongo-db/migrations/mongo-initial-schema.d.ts +2 -2
- package/dist/databases/mongo-db/migrations/mongo-initial-schema.js +185 -128
- package/dist/services/auth.service.d.ts +8 -9
- package/dist/services/auth.service.js +66 -82
- package/dist/services/email.service.js +5 -5
- package/dist/services/jwt.service.js +2 -2
- package/dist/services/multi-tenant-api.service.d.ts +4 -4
- package/dist/services/multi-tenant-api.service.js +8 -8
- package/dist/services/organization.service.d.ts +4 -4
- package/dist/services/organization.service.js +12 -8
- package/dist/services/password-reset-token.service.d.ts +4 -4
- package/dist/services/password-reset-token.service.js +13 -9
- package/dist/services/tenant-query-decorator.d.ts +1 -1
- package/dist/services/tenant-query-decorator.js +17 -11
- package/dist/services/user.service.d.ts +6 -6
- package/dist/services/user.service.js +7 -7
- package/dist/services/utils/audit-for-create.util.d.ts +1 -1
- package/dist/services/utils/audit-for-update.util.d.ts +1 -1
- package/dist/services/utils/audit-for-update.util.js +0 -1
- package/dist/services/utils/getUserContextAuthorizations.util.d.ts +2 -2
- package/dist/services/utils/strip-sender-provided-system-properties.util.d.ts +1 -1
- package/dist/services/utils/strip-sender-provided-system-properties.util.js +5 -3
- package/package.json +5 -2
- package/dist/controllers/persons.controller.d.ts +0 -8
- package/dist/controllers/persons.controller.js +0 -18
- package/dist/services/person.service.d.ts +0 -6
- package/dist/services/person.service.js +0 -7
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ApiController } from
|
|
4
|
-
import {
|
|
5
|
-
import { Operation } from
|
|
6
|
-
import { GenericApiService } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
1
|
+
import { IQueryOptions, IUser, IUserContext } from "@loomcore/common/models";
|
|
2
|
+
import { Application, NextFunction, Request, Response } from "express";
|
|
3
|
+
import { ApiController } from "../controllers/api.controller.js";
|
|
4
|
+
import { IDatabase } from "../databases/models/index.js";
|
|
5
|
+
import { Operation } from "../databases/operations/operation.js";
|
|
6
|
+
import { GenericApiService } from "../services/index.js";
|
|
7
|
+
import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
|
|
8
|
+
import type { DbType } from "../databases/db-type.type.js";
|
|
9
|
+
import { type ICategory } from "./models/category.model.js";
|
|
10
|
+
import { type IProduct } from "./models/product.model.js";
|
|
11
|
+
import { type IProductWithCategory } from "./models/product-with-category.model.js";
|
|
12
12
|
declare function initialize(database: IDatabase): void;
|
|
13
13
|
declare function getRandomId(): string;
|
|
14
14
|
declare function isMongoDatabase(database: IDatabase): boolean;
|
|
15
15
|
declare function isPostgresDatabase(database: IDatabase): boolean;
|
|
16
|
-
declare function getExpectedIdType(database: IDatabase):
|
|
16
|
+
declare function getExpectedIdType(database: IDatabase): "string" | "number";
|
|
17
17
|
declare function createMetaOrg(): Promise<void>;
|
|
18
18
|
declare function deleteMetaOrg(): Promise<void>;
|
|
19
19
|
declare function setupTestUsers(): Promise<{
|
|
@@ -1,39 +1,36 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import { ApiController } from
|
|
8
|
-
import {
|
|
9
|
-
import { LeftJoin } from
|
|
10
|
-
import {
|
|
11
|
-
import { AuthService, GenericApiService } from
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import * as testObjectsModule from
|
|
16
|
-
const { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId, setTestMetaOrgUserId, setTestOrgUserId } = testObjectsModule;
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import { TestEmailClient } from
|
|
24
|
-
import {
|
|
25
|
-
import { apiUtils } from '../utils/index.js';
|
|
1
|
+
import { EmptyUserContext, } from "@loomcore/common/models";
|
|
2
|
+
import { Type } from "@sinclair/typebox";
|
|
3
|
+
import { Value } from "@sinclair/typebox/value";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import jwt from "jsonwebtoken";
|
|
6
|
+
import { ObjectId } from "mongodb";
|
|
7
|
+
import { ApiController } from "../controllers/api.controller.js";
|
|
8
|
+
import { MongoDBDatabase } from "../databases/mongo-db/mongo-db.database.js";
|
|
9
|
+
import { LeftJoin } from "../databases/operations/left-join.operation.js";
|
|
10
|
+
import { PostgresDatabase } from "../databases/postgres/postgres.database.js";
|
|
11
|
+
import { AuthService, GenericApiService } from "../services/index.js";
|
|
12
|
+
import { JwtService } from "../services/jwt.service.js";
|
|
13
|
+
import { MultiTenantApiService } from "../services/multi-tenant-api.service.js";
|
|
14
|
+
import { OrganizationService } from "../services/organization.service.js";
|
|
15
|
+
import * as testObjectsModule from "./test-objects.js";
|
|
16
|
+
const { getTestMetaOrg, getTestOrg, getTestMetaOrgUser, getTestMetaOrgUserContext, getTestOrgUserContext, setTestOrgId, setTestMetaOrgId, setTestMetaOrgUserId, setTestOrgUserId, } = testObjectsModule;
|
|
17
|
+
import { entityUtils } from "@loomcore/common/utils";
|
|
18
|
+
import { config, setBaseApiConfig } from "../config/index.js";
|
|
19
|
+
import { apiUtils } from "../utils/index.js";
|
|
20
|
+
import { CategorySpec } from "./models/category.model.js";
|
|
21
|
+
import { ProductSpec } from "./models/product.model.js";
|
|
22
|
+
import { ProductWithCategoryPublicSpec, ProductWithCategorySpec, } from "./models/product-with-category.model.js";
|
|
23
|
+
import { TestEmailClient } from "./test-email-client.js";
|
|
24
|
+
import { getTestOrgUser } from "./test-objects.js";
|
|
26
25
|
let deviceIdCookie;
|
|
27
26
|
let authService;
|
|
28
27
|
let organizationService;
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const constDeviceIdCookie = crypto.randomBytes(16).toString('hex');
|
|
28
|
+
const JWT_SECRET = "test-secret";
|
|
29
|
+
const newUser1Email = "one@test.com";
|
|
30
|
+
const newUser1Password = "testone";
|
|
31
|
+
const constDeviceIdCookie = crypto.randomBytes(16).toString("hex");
|
|
34
32
|
function initialize(database) {
|
|
35
33
|
authService = new AuthService(database);
|
|
36
|
-
personService = new PersonService(database);
|
|
37
34
|
organizationService = new OrganizationService(database);
|
|
38
35
|
deviceIdCookie = constDeviceIdCookie;
|
|
39
36
|
}
|
|
@@ -47,14 +44,14 @@ function isPostgresDatabase(database) {
|
|
|
47
44
|
return database instanceof PostgresDatabase;
|
|
48
45
|
}
|
|
49
46
|
function getExpectedIdType(database) {
|
|
50
|
-
return isPostgresDatabase(database) ?
|
|
47
|
+
return isPostgresDatabase(database) ? "number" : "string";
|
|
51
48
|
}
|
|
52
49
|
async function createMetaOrg() {
|
|
53
50
|
if (!config.app.isMultiTenant) {
|
|
54
51
|
return;
|
|
55
52
|
}
|
|
56
53
|
if (!organizationService) {
|
|
57
|
-
throw new Error(
|
|
54
|
+
throw new Error("OrganizationService not initialized. Call initialize() first.");
|
|
58
55
|
}
|
|
59
56
|
try {
|
|
60
57
|
const existingMetaOrg = await organizationService.getMetaOrg(EmptyUserContext);
|
|
@@ -69,7 +66,7 @@ async function createMetaOrg() {
|
|
|
69
66
|
}
|
|
70
67
|
}
|
|
71
68
|
catch (error) {
|
|
72
|
-
console.log(
|
|
69
|
+
console.log("Error in createMetaOrg:", error);
|
|
73
70
|
throw error;
|
|
74
71
|
}
|
|
75
72
|
}
|
|
@@ -78,10 +75,12 @@ async function deleteMetaOrg() {
|
|
|
78
75
|
return Promise.resolve();
|
|
79
76
|
}
|
|
80
77
|
try {
|
|
81
|
-
await organizationService.deleteMany(getTestMetaOrgUserContext(), {
|
|
78
|
+
await organizationService.deleteMany(getTestMetaOrgUserContext(), {
|
|
79
|
+
filters: { isMetaOrg: { eq: true } },
|
|
80
|
+
});
|
|
82
81
|
}
|
|
83
82
|
catch (error) {
|
|
84
|
-
console.log(
|
|
83
|
+
console.log("Error deleting meta org:", error);
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
86
|
async function setupTestUsers() {
|
|
@@ -96,47 +95,37 @@ async function setupTestUsers() {
|
|
|
96
95
|
}
|
|
97
96
|
}
|
|
98
97
|
async function createTestUsers() {
|
|
99
|
-
if (!authService || !organizationService
|
|
100
|
-
throw new Error(
|
|
98
|
+
if (!authService || !organizationService) {
|
|
99
|
+
throw new Error("Database not initialized. Call initialize() first.");
|
|
101
100
|
}
|
|
102
101
|
try {
|
|
103
102
|
const existingMetaOrg = await organizationService.getMetaOrg(EmptyUserContext);
|
|
104
103
|
if (!existingMetaOrg) {
|
|
105
|
-
throw new Error(
|
|
104
|
+
throw new Error("Meta organization does not exist. Test setup is incorrect - meta org should be created by migrations or createMetaOrg().");
|
|
106
105
|
}
|
|
107
106
|
setTestMetaOrgId(existingMetaOrg._id);
|
|
108
107
|
const existingTestOrg = await organizationService.findOne(getTestMetaOrgUserContext(), { filters: { _id: { eq: getTestOrg()._id } } });
|
|
109
108
|
if (!existingTestOrg) {
|
|
110
109
|
const createdTestOrg = await organizationService.create(getTestMetaOrgUserContext(), getTestOrg());
|
|
111
110
|
if (!createdTestOrg) {
|
|
112
|
-
throw new Error(
|
|
111
|
+
throw new Error("Failed to create test organization");
|
|
113
112
|
}
|
|
114
113
|
setTestOrgId(createdTestOrg._id);
|
|
115
114
|
}
|
|
116
115
|
else {
|
|
117
116
|
setTestOrgId(existingTestOrg._id);
|
|
118
117
|
}
|
|
119
|
-
const createdTestOrgUserPerson = await personService.create(getTestOrgUserContext(), getTestOrgUserPerson());
|
|
120
|
-
if (!createdTestOrgUserPerson) {
|
|
121
|
-
throw new Error('Failed to create test organization user person');
|
|
122
|
-
}
|
|
123
|
-
setTestOrgUserPersonId(createdTestOrgUserPerson._id);
|
|
124
|
-
const createdMetaOrgUserPerson = await personService.create(getTestMetaOrgUserContext(), getTestMetaOrgUserPerson());
|
|
125
|
-
if (!createdMetaOrgUserPerson) {
|
|
126
|
-
throw new Error('Failed to create meta organization user person');
|
|
127
|
-
}
|
|
128
|
-
setTestMetaOrgUserPersonId(createdMetaOrgUserPerson._id);
|
|
129
118
|
const createdTestOrgUser = await authService.createUser(getTestOrgUserContext(), getTestOrgUser());
|
|
130
119
|
const createdMetaOrgUser = await authService.createUser(getTestMetaOrgUserContext(), getTestMetaOrgUser());
|
|
131
120
|
if (!createdTestOrgUser || !createdMetaOrgUser) {
|
|
132
|
-
throw new Error(
|
|
121
|
+
throw new Error("Failed to create test user");
|
|
133
122
|
}
|
|
134
123
|
setTestMetaOrgUserId(createdMetaOrgUser._id);
|
|
135
124
|
setTestOrgUserId(createdTestOrgUser._id);
|
|
136
125
|
return { metaOrgUser: createdMetaOrgUser, testOrgUser: createdTestOrgUser };
|
|
137
126
|
}
|
|
138
127
|
catch (error) {
|
|
139
|
-
console.log(
|
|
128
|
+
console.log("Error in createTestUser:", error);
|
|
140
129
|
throw error;
|
|
141
130
|
}
|
|
142
131
|
}
|
|
@@ -144,34 +133,38 @@ async function deleteTestUser() {
|
|
|
144
133
|
if (!authService || !organizationService) {
|
|
145
134
|
return;
|
|
146
135
|
}
|
|
147
|
-
await authService
|
|
136
|
+
await authService
|
|
137
|
+
.deleteById(getTestMetaOrgUserContext(), getTestMetaOrgUser()._id)
|
|
138
|
+
.catch((error) => {
|
|
148
139
|
return null;
|
|
149
140
|
});
|
|
150
|
-
await organizationService
|
|
141
|
+
await organizationService
|
|
142
|
+
.deleteById(getTestMetaOrgUserContext(), getTestOrg()._id)
|
|
143
|
+
.catch((error) => {
|
|
151
144
|
return null;
|
|
152
145
|
});
|
|
153
146
|
}
|
|
154
147
|
async function simulateloginWithTestUser() {
|
|
155
148
|
const req = {
|
|
156
|
-
cookies: {}
|
|
149
|
+
cookies: {},
|
|
157
150
|
};
|
|
158
151
|
if (deviceIdCookie) {
|
|
159
|
-
req.cookies[
|
|
152
|
+
req.cookies["deviceId"] = deviceIdCookie;
|
|
160
153
|
}
|
|
161
154
|
const res = {
|
|
162
155
|
cookie: function (name, value) {
|
|
163
|
-
if (name ===
|
|
156
|
+
if (name === "deviceId") {
|
|
164
157
|
deviceIdCookie = value;
|
|
165
158
|
}
|
|
166
159
|
return res;
|
|
167
|
-
}
|
|
160
|
+
},
|
|
168
161
|
};
|
|
169
162
|
if (!authService) {
|
|
170
|
-
throw new Error(
|
|
163
|
+
throw new Error("AuthService not initialized. Call initialize() first.");
|
|
171
164
|
}
|
|
172
165
|
const loginResponse = await authService.attemptLogin(req, res, getTestMetaOrgUser().email, testObjectsModule.TEST_META_ORG_USER_PASSWORD);
|
|
173
166
|
if (!loginResponse?.tokens?.accessToken) {
|
|
174
|
-
throw new Error(
|
|
167
|
+
throw new Error("Failed to login with test user");
|
|
175
168
|
}
|
|
176
169
|
return `Bearer ${loginResponse.tokens.accessToken}`;
|
|
177
170
|
}
|
|
@@ -185,13 +178,13 @@ function verifyToken(token) {
|
|
|
185
178
|
}
|
|
186
179
|
export class CategoryService extends GenericApiService {
|
|
187
180
|
constructor(database) {
|
|
188
|
-
super(database,
|
|
181
|
+
super(database, "categories", "category", CategorySpec);
|
|
189
182
|
}
|
|
190
183
|
}
|
|
191
184
|
export class CategoryController extends ApiController {
|
|
192
185
|
constructor(app, database) {
|
|
193
186
|
const categoryService = new CategoryService(database);
|
|
194
|
-
super(
|
|
187
|
+
super("categories", app, categoryService, "category", CategorySpec);
|
|
195
188
|
}
|
|
196
189
|
}
|
|
197
190
|
export function setupTestConfig(isMultiTenant = true, dbType) {
|
|
@@ -199,37 +192,37 @@ export function setupTestConfig(isMultiTenant = true, dbType) {
|
|
|
199
192
|
app: {
|
|
200
193
|
isMultiTenant: isMultiTenant,
|
|
201
194
|
isAuthEnabled: true,
|
|
202
|
-
name:
|
|
195
|
+
name: "test-app",
|
|
203
196
|
dbType: dbType,
|
|
204
197
|
},
|
|
205
198
|
auth: {
|
|
206
|
-
clientSecret:
|
|
199
|
+
clientSecret: "test-secret",
|
|
207
200
|
saltWorkFactor: 10,
|
|
208
201
|
jwtExpirationInSeconds: 3600,
|
|
209
202
|
refreshTokenExpirationInDays: 7,
|
|
210
203
|
deviceIdCookieMaxAgeInDays: 730,
|
|
211
|
-
passwordResetTokenExpirationInMinutes: 20
|
|
204
|
+
passwordResetTokenExpirationInMinutes: 20,
|
|
212
205
|
},
|
|
213
206
|
database: {
|
|
214
|
-
name:
|
|
215
|
-
host:
|
|
216
|
-
password:
|
|
207
|
+
name: "test-db",
|
|
208
|
+
host: "localhost",
|
|
209
|
+
password: "test-password",
|
|
217
210
|
port: 27017,
|
|
218
|
-
username:
|
|
211
|
+
username: "test-user",
|
|
219
212
|
},
|
|
220
|
-
env:
|
|
213
|
+
env: "test",
|
|
221
214
|
email: {
|
|
222
|
-
fromAddress:
|
|
223
|
-
systemEmailAddress:
|
|
215
|
+
fromAddress: "test@test.com",
|
|
216
|
+
systemEmailAddress: "system@test.com",
|
|
224
217
|
},
|
|
225
218
|
thirdPartyClients: {
|
|
226
|
-
emailClient: new TestEmailClient()
|
|
219
|
+
emailClient: new TestEmailClient(),
|
|
227
220
|
},
|
|
228
221
|
network: {
|
|
229
|
-
hostName:
|
|
222
|
+
hostName: "localhost",
|
|
230
223
|
internalPort: 8083,
|
|
231
224
|
externalPort: 4000,
|
|
232
|
-
corsAllowedOrigins: [
|
|
225
|
+
corsAllowedOrigins: ["*"],
|
|
233
226
|
},
|
|
234
227
|
});
|
|
235
228
|
}
|
|
@@ -238,54 +231,54 @@ const prepareQueryCustom = (userContext, queryObject, operations) => {
|
|
|
238
231
|
queryObject: queryObject,
|
|
239
232
|
operations: [
|
|
240
233
|
...operations,
|
|
241
|
-
new LeftJoin(
|
|
242
|
-
]
|
|
234
|
+
new LeftJoin("categories", "category_id", "_id", "category"),
|
|
235
|
+
],
|
|
243
236
|
};
|
|
244
237
|
};
|
|
245
238
|
const postProcessEntityCustom = (userContext, entity) => {
|
|
246
239
|
return {
|
|
247
240
|
...entity,
|
|
248
|
-
category: entity._joinData?.category
|
|
241
|
+
category: entity._joinData?.category,
|
|
249
242
|
};
|
|
250
243
|
};
|
|
251
244
|
export class ProductService extends GenericApiService {
|
|
252
245
|
constructor(database) {
|
|
253
|
-
super(database,
|
|
246
|
+
super(database, "products", "product", ProductSpec);
|
|
254
247
|
}
|
|
255
248
|
}
|
|
256
249
|
export class ProductsController extends ApiController {
|
|
257
250
|
constructor(app, database) {
|
|
258
251
|
const productService = new ProductService(database);
|
|
259
|
-
super(
|
|
252
|
+
super("products", app, productService, "product", ProductSpec);
|
|
260
253
|
}
|
|
261
254
|
async get(req, res, next) {
|
|
262
|
-
res.set(
|
|
255
|
+
res.set("Content-Type", "application/json");
|
|
263
256
|
const userContext = req.userContext;
|
|
264
257
|
if (!userContext) {
|
|
265
|
-
throw new Error(
|
|
258
|
+
throw new Error("User context not found");
|
|
266
259
|
}
|
|
267
260
|
const queryOptions = apiUtils.getQueryOptionsFromRequest(req);
|
|
268
261
|
const pagedResult = await this.service.get(userContext, queryOptions, prepareQueryCustom, postProcessEntityCustom);
|
|
269
262
|
apiUtils.apiResponse(res, 200, { data: pagedResult }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
|
|
270
263
|
}
|
|
271
264
|
async getAll(req, res, next) {
|
|
272
|
-
res.set(
|
|
265
|
+
res.set("Content-Type", "application/json");
|
|
273
266
|
const userContext = req.userContext;
|
|
274
267
|
if (!userContext) {
|
|
275
|
-
throw new Error(
|
|
268
|
+
throw new Error("User context not found");
|
|
276
269
|
}
|
|
277
270
|
const entities = await this.service.getAll(userContext, prepareQueryCustom, postProcessEntityCustom);
|
|
278
271
|
apiUtils.apiResponse(res, 200, { data: entities }, ProductWithCategorySpec, ProductWithCategoryPublicSpec);
|
|
279
272
|
}
|
|
280
273
|
async getById(req, res, next) {
|
|
281
|
-
res.set(
|
|
274
|
+
res.set("Content-Type", "application/json");
|
|
282
275
|
const userContext = req.userContext;
|
|
283
276
|
if (!userContext) {
|
|
284
|
-
throw new Error(
|
|
277
|
+
throw new Error("User context not found");
|
|
285
278
|
}
|
|
286
279
|
const idParam = req.params?.id;
|
|
287
280
|
if (!idParam) {
|
|
288
|
-
throw new Error(
|
|
281
|
+
throw new Error("ID parameter is required");
|
|
289
282
|
}
|
|
290
283
|
try {
|
|
291
284
|
const id = Value.Convert(this.idSchema, idParam);
|
|
@@ -300,13 +293,13 @@ export class ProductsController extends ApiController {
|
|
|
300
293
|
export class MultiTenantProductService extends MultiTenantApiService {
|
|
301
294
|
db;
|
|
302
295
|
constructor(database) {
|
|
303
|
-
super(database,
|
|
296
|
+
super(database, "products", "product", ProductSpec);
|
|
304
297
|
this.db = database;
|
|
305
298
|
}
|
|
306
299
|
prepareQuery(userContext, queryObject, operations) {
|
|
307
300
|
const newOperations = [
|
|
308
301
|
...operations,
|
|
309
|
-
new LeftJoin(
|
|
302
|
+
new LeftJoin("categories", "categoryId", "_id", "category"),
|
|
310
303
|
];
|
|
311
304
|
return super.prepareQuery(userContext, queryObject, newOperations);
|
|
312
305
|
}
|
|
@@ -324,12 +317,14 @@ export class MultiTenantProductsController extends ApiController {
|
|
|
324
317
|
const AggregatedProductSchema = Type.Intersect([
|
|
325
318
|
ProductSpec.fullSchema,
|
|
326
319
|
Type.Partial(Type.Object({
|
|
327
|
-
category: CategorySpec.fullSchema
|
|
328
|
-
}))
|
|
320
|
+
category: CategorySpec.fullSchema,
|
|
321
|
+
})),
|
|
322
|
+
]);
|
|
323
|
+
const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, [
|
|
324
|
+
"internalNumber",
|
|
329
325
|
]);
|
|
330
|
-
const PublicAggregatedProductSchema = Type.Omit(AggregatedProductSchema, ['internalNumber']);
|
|
331
326
|
const PublicAggregatedProductSpec = entityUtils.getModelSpec(PublicAggregatedProductSchema);
|
|
332
|
-
super(
|
|
327
|
+
super("multi-tenant-products", app, productService, "product", ProductSpec, PublicAggregatedProductSpec);
|
|
333
328
|
}
|
|
334
329
|
}
|
|
335
330
|
function configureJwtSecret() {
|
|
@@ -339,17 +334,15 @@ function configureJwtSecret() {
|
|
|
339
334
|
};
|
|
340
335
|
}
|
|
341
336
|
async function loginWithTestUser(agent) {
|
|
342
|
-
agent.set(
|
|
337
|
+
agent.set("Cookie", [`deviceId=${deviceIdCookie}`]);
|
|
343
338
|
const testUser = getTestMetaOrgUser();
|
|
344
|
-
const response = await agent
|
|
345
|
-
.post('/api/auth/login')
|
|
346
|
-
.send({
|
|
339
|
+
const response = await agent.post("/api/auth/login").send({
|
|
347
340
|
email: testUser.email,
|
|
348
341
|
password: testUser.password,
|
|
349
342
|
});
|
|
350
343
|
if (!response.body?.data?.tokens?.accessToken) {
|
|
351
|
-
console.error(
|
|
352
|
-
throw new Error(
|
|
344
|
+
console.error("Login failed:", response.body);
|
|
345
|
+
throw new Error("Failed to login with test user");
|
|
353
346
|
}
|
|
354
347
|
const authorizationHeaderValue = `Bearer ${response.body?.data?.tokens?.accessToken}`;
|
|
355
348
|
return authorizationHeaderValue;
|
|
@@ -360,7 +353,7 @@ async function cleanup() {
|
|
|
360
353
|
await deleteMetaOrg();
|
|
361
354
|
}
|
|
362
355
|
catch (error) {
|
|
363
|
-
console.log(
|
|
356
|
+
console.log("Error during cleanup:", error);
|
|
364
357
|
}
|
|
365
358
|
}
|
|
366
359
|
const testUtils = {
|
|
@@ -382,6 +375,6 @@ const testUtils = {
|
|
|
382
375
|
verifyToken,
|
|
383
376
|
isMongoDatabase,
|
|
384
377
|
isPostgresDatabase,
|
|
385
|
-
getExpectedIdType
|
|
378
|
+
getExpectedIdType,
|
|
386
379
|
};
|
|
387
380
|
export default testUtils;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { IDatabase } from "../databases/models/index.js";
|
|
2
|
+
import type { ITestDatabase } from "./test-database.interface.js";
|
|
3
3
|
export declare class TestPostgresDatabase implements ITestDatabase {
|
|
4
4
|
private database;
|
|
5
5
|
private postgresClient;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { Pool } from "pg";
|
|
2
2
|
import { newDb } from "pg-mem";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
const USE_REAL_POSTGRES = process.env.USE_REAL_POSTGRES ===
|
|
3
|
+
import { config } from "../config/base-api-config.js";
|
|
4
|
+
import { runInitialSchemaMigrations, runTestSchemaMigrations, } from "../databases/postgres/migrations/__tests__/test-migration-helper.js";
|
|
5
|
+
import { PostgresDatabase } from "../databases/postgres/postgres.database.js";
|
|
6
|
+
import testUtils from "./common-test.utils.js";
|
|
7
|
+
const USE_REAL_POSTGRES = process.env.USE_REAL_POSTGRES === "true";
|
|
8
8
|
export class TestPostgresDatabase {
|
|
9
9
|
database = null;
|
|
10
10
|
postgresClient = null;
|
|
@@ -18,7 +18,7 @@ export class TestPostgresDatabase {
|
|
|
18
18
|
return this.initPromise;
|
|
19
19
|
}
|
|
20
20
|
getRandomId() {
|
|
21
|
-
throw new Error(
|
|
21
|
+
throw new Error("getRandomId() should not be used for PostgreSQL _id fields. PostgreSQL uses auto-generated integer IDs. Remove _id from entities and let the database generate it.");
|
|
22
22
|
}
|
|
23
23
|
async _performInit() {
|
|
24
24
|
if (!this.database) {
|
|
@@ -32,13 +32,14 @@ export class TestPostgresDatabase {
|
|
|
32
32
|
});
|
|
33
33
|
this.migrationPool = pool;
|
|
34
34
|
try {
|
|
35
|
-
await pool.query(
|
|
35
|
+
await pool.query("SELECT now(), current_database()");
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
38
|
await pool.end();
|
|
39
39
|
this.migrationPool = null;
|
|
40
40
|
const errorMessage = error.message || String(error);
|
|
41
|
-
const isPermissionError = errorMessage.includes(
|
|
41
|
+
const isPermissionError = errorMessage.includes("permission denied") ||
|
|
42
|
+
errorMessage.includes("operation not permitted");
|
|
42
43
|
if (isPermissionError) {
|
|
43
44
|
throw new Error(`Docker permission error. Please ensure:\n` +
|
|
44
45
|
`1. Docker Desktop is running\n` +
|
|
@@ -64,9 +65,9 @@ export class TestPostgresDatabase {
|
|
|
64
65
|
}
|
|
65
66
|
this.database = new PostgresDatabase(connection);
|
|
66
67
|
this.postgresClient = connection;
|
|
67
|
-
const { initializeSystemUserContext, isSystemUserContextInitialized } = await import(
|
|
68
|
+
const { initializeSystemUserContext, isSystemUserContextInitialized } = await import("@loomcore/common/models");
|
|
68
69
|
if (!isSystemUserContextInitialized()) {
|
|
69
|
-
initializeSystemUserContext(config.email?.systemEmailAddress ||
|
|
70
|
+
initializeSystemUserContext(config.email?.systemEmailAddress || "system@test.com", undefined);
|
|
70
71
|
}
|
|
71
72
|
await runInitialSchemaMigrations(pool, config);
|
|
72
73
|
await runTestSchemaMigrations(pool, config);
|
|
@@ -84,12 +85,12 @@ export class TestPostgresDatabase {
|
|
|
84
85
|
`);
|
|
85
86
|
}
|
|
86
87
|
catch (error) {
|
|
87
|
-
console.log(
|
|
88
|
+
console.log("Note: Indexes may be created later when tables are initialized:", error.message);
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
async clearCollections() {
|
|
91
92
|
if (!this.postgresClient) {
|
|
92
|
-
throw new Error(
|
|
93
|
+
throw new Error("Database not initialized");
|
|
93
94
|
}
|
|
94
95
|
const result = await this.postgresClient.query(`
|
|
95
96
|
SELECT "table_name"
|
|
@@ -98,7 +99,7 @@ export class TestPostgresDatabase {
|
|
|
98
99
|
AND "table_type" = 'BASE TABLE'
|
|
99
100
|
`);
|
|
100
101
|
if (USE_REAL_POSTGRES) {
|
|
101
|
-
await this.postgresClient.query(`TRUNCATE TABLE ${result.rows.map(row => `"${row.table_name}"`).join(
|
|
102
|
+
await this.postgresClient.query(`TRUNCATE TABLE ${result.rows.map((row) => `"${row.table_name}"`).join(", ")} RESTART IDENTITY CASCADE`);
|
|
102
103
|
}
|
|
103
104
|
else {
|
|
104
105
|
result.rows.forEach(async (row) => {
|
|
@@ -120,7 +121,7 @@ export class TestPostgresDatabase {
|
|
|
120
121
|
}
|
|
121
122
|
}
|
|
122
123
|
catch (error) {
|
|
123
|
-
console.warn(
|
|
124
|
+
console.warn("Error closing PostgreSQL connection:", error);
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
this.migrationPool = null;
|
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
import { IOrganization,
|
|
1
|
+
import type { IOrganization, IUser, IUserContext } from "@loomcore/common/models";
|
|
2
2
|
export declare let TEST_META_ORG_ID: string | number;
|
|
3
3
|
export declare let TEST_META_ORG_USER_ID: string | number;
|
|
4
|
-
export declare let TEST_META_ORG_USER_PERSON_ID: string | number;
|
|
5
4
|
export declare function setTestMetaOrgId(metaOrgId: string | number): void;
|
|
6
5
|
export declare function setTestMetaOrgUserId(userId: string | number): void;
|
|
7
|
-
export declare function setTestMetaOrgUserPersonId(personId: string | number): void;
|
|
8
6
|
export declare const TEST_META_ORG_USER_PASSWORD = "test-meta-org-user-password";
|
|
9
7
|
export declare function getTestMetaOrg(): IOrganization;
|
|
10
8
|
export declare function getTestMetaOrgUser(): IUser;
|
|
11
|
-
export declare function getTestMetaOrgUserPerson(): IPersonModel;
|
|
12
9
|
export declare function getTestMetaOrgUserContext(): IUserContext;
|
|
13
10
|
export declare function setTestOrgId(orgId: string | number): void;
|
|
14
11
|
export declare function setTestOrgUserId(userId: string | number): void;
|
|
15
|
-
export declare function setTestOrgUserPersonId(personId: string | number): void;
|
|
16
12
|
export declare function getTestOrg(): IOrganization;
|
|
17
13
|
export declare const TEST_ORG_USER_PASSWORD = "test-org-user-password";
|
|
18
14
|
export declare function getTestOrgUser(): IUser;
|
|
19
|
-
export declare function getTestOrgUserPerson(): IPersonModel;
|
|
20
15
|
export declare function getTestOrgUserContext(): IUserContext;
|