@entity-access/entity-access 1.0.2 → 1.0.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.
Files changed (69) hide show
  1. package/.github/workflows/node.yml +7 -2
  2. package/.vscode/settings.json +1 -0
  3. package/README.md +57 -27
  4. package/package.json +2 -2
  5. package/src/common/EntityAccessError.ts +10 -0
  6. package/src/common/IDisposable.ts +25 -0
  7. package/src/common/ImmutableObject.ts +53 -0
  8. package/src/common/Logger.ts +59 -0
  9. package/src/common/TypeInfo.ts +3 -0
  10. package/src/common/cache/TimedCache.ts +2 -2
  11. package/src/common/usingAsync.ts +42 -12
  12. package/src/compiler/QueryCompiler.ts +28 -30
  13. package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +23 -0
  14. package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +23 -0
  15. package/src/decorators/ForeignKey.ts +1 -1
  16. package/src/decorators/IClassOf.ts +2 -1
  17. package/src/decorators/IColumn.ts +2 -0
  18. package/src/decorators/parser/NameParser.ts +15 -0
  19. package/src/di/di.ts +224 -0
  20. package/src/drivers/base/BaseDriver.ts +28 -3
  21. package/src/drivers/sql-server/ExpressionToSqlServer.ts +34 -9
  22. package/src/drivers/sql-server/SqlServerDriver.ts +7 -16
  23. package/src/entity-query/EntityType.ts +45 -3
  24. package/src/model/EntityContext.ts +167 -22
  25. package/src/model/EntityQuery.ts +118 -59
  26. package/src/model/EntitySource.ts +38 -46
  27. package/src/model/IFilterWithParameter.ts +5 -0
  28. package/src/model/SourceExpression.ts +21 -25
  29. package/src/model/{ChangeEntry.ts → changes/ChangeEntry.ts} +35 -5
  30. package/src/model/{ChangeSet.ts → changes/ChangeSet.ts} +16 -11
  31. package/src/model/events/ContextEvents.ts +26 -0
  32. package/src/model/events/EntityEvents.ts +96 -0
  33. package/src/model/{IdentityService.ts → identity/IdentityService.ts} +9 -1
  34. package/src/model/identity/RelationMapper.ts +71 -0
  35. package/src/model/symbols.ts +1 -0
  36. package/src/model/verification/VerificationSession.ts +173 -0
  37. package/src/query/ast/DebugStringVisitor.ts +175 -0
  38. package/src/query/ast/ExpressionToSql.ts +277 -119
  39. package/src/query/ast/Expressions.ts +130 -13
  40. package/src/query/ast/IStringTransformer.ts +19 -5
  41. package/src/query/ast/ParameterScope.ts +97 -0
  42. package/src/query/ast/ReplaceParameter.ts +40 -0
  43. package/src/query/ast/Types.ts +0 -0
  44. package/src/query/ast/Visitor.ts +26 -5
  45. package/src/query/expander/QueryExpander.ts +147 -0
  46. package/src/query/parser/ArrowToExpression.ts +134 -19
  47. package/src/query/parser/BabelVisitor.ts +31 -43
  48. package/src/query/parser/NotSupportedError.ts +5 -0
  49. package/src/query/parser/Restructure.ts +66 -0
  50. package/src/query/parser/TransformVisitor.ts +83 -0
  51. package/src/sql/ISql.ts +10 -0
  52. package/src/tests/db-tests/tests/select-items.ts +12 -0
  53. package/src/tests/expressions/left-joins/child-joins.ts +54 -34
  54. package/src/tests/expressions/sanitize/sanitize-test.ts +17 -0
  55. package/src/tests/expressions/select/select.ts +24 -0
  56. package/src/tests/expressions/simple/parse-arrow.ts +10 -0
  57. package/src/tests/model/ShoppingContext.ts +7 -3
  58. package/src/tests/model/createContext.ts +68 -17
  59. package/src/tests/security/ShoppingContextEvents.ts +20 -0
  60. package/src/tests/security/events/OrderEvents.ts +72 -0
  61. package/src/tests/security/events/ProductEvents.ts +92 -0
  62. package/src/tests/security/events/UserEvents.ts +28 -0
  63. package/src/tests/security/events/UserInfo.ts +7 -0
  64. package/src/tests/security/tests/include-items.ts +19 -0
  65. package/src/tests/security/tests/place-order.ts +104 -0
  66. package/test.js +11 -4
  67. package/tsconfig.json +2 -0
  68. package/src/decorators/parser/MemberParser.ts +0 -8
  69. package/src/model/EntitySchema.ts +0 -21
@@ -43,4 +43,14 @@ export default function () {
43
43
  assert.equal(`starts_with("x"."firstName", $1)`, r.text);
44
44
  assert.equal("Akash", r.values[0]);
45
45
 
46
+ const code = "1235";
47
+ const key = 13434;
48
+ r = compiler.execute({name, code, key},
49
+ (p) => (x: KeyCode) =>
50
+ x.code === Sql.cast.asNumber(p.code) && x.key === Sql.cast.asText(p.key) );
51
+
52
+ assert.equal(`("x"."code" = ($1 ::double)) AND ("x"."key" = ($2 ::text))`, r.text);
53
+
46
54
  }
55
+
56
+ type KeyCode = { name: string, code: number, key: string };
@@ -2,9 +2,8 @@ import EntityContext from "../../model/EntityContext.js";
2
2
  import Column from "../../decorators/Column.js";
3
3
  import ForeignKey from "../../decorators/ForeignKey.js";
4
4
  import Table from "../../decorators/Table.js";
5
- import PostgreSqlDriver from "../../drivers/postgres/PostgreSqlDriver.js";
6
- import { BaseDriver } from "../../drivers/base/BaseDriver.js";
7
- import { Console } from "console";
5
+
6
+ export const statusPublished = "published";
8
7
 
9
8
  export class ShoppingContext extends EntityContext {
10
9
 
@@ -65,6 +64,9 @@ export class Product {
65
64
  @Column({ nullable: true })
66
65
  public ownerID: number;
67
66
 
67
+ @Column({ dataType: "Char", length: 20})
68
+ public status: string;
69
+
68
70
  public orderItems: OrderItem[];
69
71
 
70
72
  public prices: ProductPrice[];
@@ -123,8 +125,10 @@ export class ProductPrice {
123
125
  @Column({ nullable: true})
124
126
  public endDate?: Date;
125
127
 
128
+ @Column({ })
126
129
  public amount: number;
127
130
 
131
+ @Column({})
128
132
  public productID: number;
129
133
 
130
134
  @ForeignKey({
@@ -1,13 +1,13 @@
1
1
  import { IClassOf } from "../../decorators/IClassOf.js";
2
2
  import { BaseDriver } from "../../drivers/base/BaseDriver.js";
3
- import { ShoppingContext } from "./ShoppingContext.js";
3
+ import { ShoppingContext, User, statusPublished } from "./ShoppingContext.js";
4
+
5
+ const status = statusPublished;
4
6
 
5
7
  export async function createContext(driver: BaseDriver) {
6
8
 
7
- const rn = "d" + Date.now();
8
9
  const copy = { ... driver } as BaseDriver;
9
10
  (copy as any).connectionString = { ... driver.connectionString };
10
- copy.connectionString.database = rn;
11
11
  Object.setPrototypeOf(copy, Object.getPrototypeOf(driver));
12
12
  const context = new ShoppingContext(copy);
13
13
 
@@ -28,16 +28,42 @@ export async function createContext(driver: BaseDriver) {
28
28
  async function seed(context: ShoppingContext) {
29
29
  const now = new Date();
30
30
 
31
- addHeadPhones(context, now);
31
+ // add admin user...
32
+ context.users.add({ dateCreated: new Date()});
33
+ // add seller
34
+ const seller = context.users.add({ dateCreated: new Date()});
35
+
36
+ addHeadPhones(context, now, seller);
37
+
38
+ const clothes = addMaleClothes(context, seller);
39
+
40
+ addFemaleClothes(context, seller);
32
41
 
33
- const maleClothes = addMaleClothes(context);
42
+ await context.saveChanges();
34
43
 
35
- const femaleClothes = addFemaleClothes(context);
44
+ const product = clothes[0];
45
+ const productPrice = product.prices[0];
46
+
47
+ const user = context.users.add({
48
+ dateCreated: new Date(),
49
+ orders: [
50
+ context.orders.add({
51
+ orderDate: new Date(),
52
+ orderItems:[
53
+ context.orderItems.add({
54
+ product,
55
+ productPrice,
56
+ amount: productPrice.amount
57
+ })
58
+ ]
59
+ })
60
+ ]
61
+ });
36
62
 
37
63
  await context.saveChanges();
38
64
  }
39
65
 
40
- function addFemaleClothes(context: ShoppingContext) {
66
+ function addFemaleClothes(context: ShoppingContext, owner: User) {
41
67
  const category = context.categories.add({
42
68
  name: "Female Clothes",
43
69
  categoryID: "clothes/female"
@@ -48,6 +74,8 @@ function addFemaleClothes(context: ShoppingContext) {
48
74
 
49
75
  context.products.add({
50
76
  name: "White T-Shirt",
77
+ owner,
78
+ status,
51
79
  categories: [
52
80
  context.productCategories.add({
53
81
  category
@@ -64,6 +92,8 @@ function addFemaleClothes(context: ShoppingContext) {
64
92
 
65
93
  context.products.add({
66
94
  name: "Red T-Shirt",
95
+ status,
96
+ owner,
67
97
  categories: [
68
98
  context.productCategories.add({
69
99
  category
@@ -80,6 +110,8 @@ function addFemaleClothes(context: ShoppingContext) {
80
110
 
81
111
  context.products.add({
82
112
  name: "Blue T-Shirt",
113
+ status,
114
+ owner,
83
115
  categories: [
84
116
  context.productCategories.add({
85
117
  category
@@ -96,6 +128,8 @@ function addFemaleClothes(context: ShoppingContext) {
96
128
 
97
129
  context.products.add({
98
130
  name: "Pink T-Shirt",
131
+ status,
132
+ owner,
99
133
  categories: [
100
134
  context.productCategories.add({
101
135
  category
@@ -111,7 +145,7 @@ function addFemaleClothes(context: ShoppingContext) {
111
145
  });
112
146
  }
113
147
 
114
- function addMaleClothes(context: ShoppingContext) {
148
+ function addMaleClothes(context: ShoppingContext, owner: User) {
115
149
  const category = context.categories.add({
116
150
  name: "Male Clothes",
117
151
  categoryID: "clothes/male"
@@ -120,8 +154,10 @@ function addMaleClothes(context: ShoppingContext) {
120
154
  const startDate = new Date();
121
155
  const active = true;
122
156
 
123
- context.products.add({
157
+ return [context.products.add({
124
158
  name: "White T-Shirt",
159
+ status,
160
+ owner,
125
161
  categories: [
126
162
  context.productCategories.add({
127
163
  category
@@ -134,10 +170,11 @@ function addMaleClothes(context: ShoppingContext) {
134
170
  startDate
135
171
  })
136
172
  ]
137
- });
138
-
173
+ }),
139
174
  context.products.add({
140
175
  name: "Red T-Shirt",
176
+ status,
177
+ owner,
141
178
  categories: [
142
179
  context.productCategories.add({
143
180
  category
@@ -150,10 +187,11 @@ function addMaleClothes(context: ShoppingContext) {
150
187
  startDate
151
188
  })
152
189
  ]
153
- });
154
-
190
+ }),
155
191
  context.products.add({
156
192
  name: "Blue T-Shirt",
193
+ status,
194
+ owner,
157
195
  categories: [
158
196
  context.productCategories.add({
159
197
  category
@@ -166,10 +204,11 @@ function addMaleClothes(context: ShoppingContext) {
166
204
  startDate
167
205
  })
168
206
  ]
169
- });
170
-
207
+ }),
171
208
  context.products.add({
172
209
  name: "Pink T-Shirt",
210
+ status,
211
+ owner,
173
212
  categories: [
174
213
  context.productCategories.add({
175
214
  category
@@ -182,11 +221,11 @@ function addMaleClothes(context: ShoppingContext) {
182
221
  startDate
183
222
  })
184
223
  ]
185
- });
224
+ })];
186
225
  }
187
226
 
188
227
  export const headPhoneCategory = "head-phones";
189
- function addHeadPhones(context: ShoppingContext, now: Date) {
228
+ function addHeadPhones(context: ShoppingContext, now: Date, owner: User) {
190
229
  const category = context.categories.add({
191
230
  name: "Headphones",
192
231
  categoryID: headPhoneCategory
@@ -197,6 +236,8 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
197
236
 
198
237
  context.products.add({
199
238
  name: "Jabber Head Phones",
239
+ owner,
240
+ status,
200
241
  categories: [
201
242
  context.productCategories.add({
202
243
  category
@@ -213,6 +254,8 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
213
254
 
214
255
  context.products.add({
215
256
  name: "Sony Head Phones",
257
+ owner,
258
+ status,
216
259
  categories: [
217
260
  context.productCategories.add({
218
261
  category
@@ -229,6 +272,8 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
229
272
 
230
273
  context.products.add({
231
274
  name: "Sony Head Phones Black",
275
+ status,
276
+ owner,
232
277
  categories: [
233
278
  context.productCategories.add({
234
279
  category
@@ -245,6 +290,8 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
245
290
 
246
291
  context.products.add({
247
292
  name: "Sony Head Phones Blue",
293
+ owner,
294
+ status,
248
295
  categories: [
249
296
  context.productCategories.add({
250
297
  category
@@ -261,6 +308,8 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
261
308
 
262
309
  context.products.add({
263
310
  name: "Jabber Head Phones Black",
311
+ status,
312
+ owner,
264
313
  categories: [
265
314
  context.productCategories.add({
266
315
  category
@@ -277,6 +326,8 @@ function addHeadPhones(context: ShoppingContext, now: Date) {
277
326
 
278
327
  context.products.add({
279
328
  name: "Jabber Head Phones Blue",
329
+ owner,
330
+ status,
280
331
  categories: [
281
332
  context.productCategories.add({
282
333
  category
@@ -0,0 +1,20 @@
1
+ import ContextEvents from "../../model/events/ContextEvents.js";
2
+ import { Order, OrderItem, Product, ProductCategory, ProductPrice, User } from "../model/ShoppingContext.js";
3
+ import { OrderEvents, OrderItemEvents } from "./events/OrderEvents.js";
4
+ import { ProductEvents, ProductCategoryEvents, ProductPriceEvents } from "./events/ProductEvents.js";
5
+ import { UserEvents } from "./events/UserEvents.js";
6
+
7
+ export class ShoppingContextEvents extends ContextEvents {
8
+
9
+ constructor() {
10
+ super();
11
+
12
+ this.register(User, UserEvents);
13
+ this.register(Product, ProductEvents);
14
+ this.register(ProductCategory, ProductCategoryEvents);
15
+ this.register(ProductPrice, ProductPriceEvents);
16
+ this.register(Order, OrderEvents);
17
+ this.register(OrderItem, OrderItemEvents);
18
+ }
19
+
20
+ }
@@ -0,0 +1,72 @@
1
+ import Inject from "../../../di/di.js";
2
+ import { IEntityQuery } from "../../../model/IFilterWithParameter.js";
3
+ import EntityEvents, { ForeignKeyFilter } from "../../../model/events/EntityEvents.js";
4
+ import { Order, OrderItem, User } from "../../model/ShoppingContext.js";
5
+ import { UserInfo } from "./UserInfo.js";
6
+
7
+ export class OrderEvents extends EntityEvents<Order> {
8
+
9
+ @Inject
10
+ user: UserInfo;
11
+
12
+ filter(query: IEntityQuery<Order>): IEntityQuery<Order> {
13
+ if (this.user.admin) {
14
+ return null;
15
+ }
16
+ const { userID } = this.user;
17
+
18
+ // user can access orders placed by the user or orders with products owned by user
19
+
20
+ return query.where({ userID }, (p) => (x) => x.customerID === p.userID || x.orderItems.some((item) => item.product.ownerID === p.userID));
21
+ }
22
+
23
+ modify(query: IEntityQuery<Order>): IEntityQuery<Order> {
24
+ if (this.user.admin) {
25
+ return null;
26
+ }
27
+ const { userID } = this.user;
28
+ // user can only modify placed orders
29
+ return query.where({ userID }, (p) => (x) => x.customerID === p.userID || x.orderItems.some((item) => item.product.ownerID === p.userID));
30
+ }
31
+
32
+ onForeignKeyFilter(filter: ForeignKeyFilter<Order>): IEntityQuery<any> {
33
+ if (filter.is((x) => x.customer)) {
34
+ return filter.read();
35
+ }
36
+ }
37
+ }
38
+
39
+ export class OrderItemEvents extends EntityEvents<OrderItem> {
40
+
41
+ @Inject
42
+ user: UserInfo;
43
+
44
+ filter(query: IEntityQuery<OrderItem>): IEntityQuery<OrderItem> {
45
+ if (this.user.admin) {
46
+ return null;
47
+ }
48
+ const { userID } = this.user;
49
+
50
+ // user can access orders placed by the user or orders with products owned by user
51
+
52
+ return query.where({ userID }, (p) => (x) => x.order.customerID === p.userID || x.product.ownerID === p.userID);
53
+ }
54
+
55
+ modify(query: IEntityQuery<OrderItem>): IEntityQuery<OrderItem> {
56
+ if (this.user.admin) {
57
+ return null;
58
+ }
59
+ const { userID } = this.user;
60
+ // user can only modify placed orders
61
+ return query.where({ userID }, (p) => (x) => x.order.customerID === p.userID || x.product.ownerID === p.userID);
62
+ }
63
+
64
+ onForeignKeyFilter(filter: ForeignKeyFilter<OrderItem>): IEntityQuery<any> {
65
+ if (filter.is((x) => x.product)) {
66
+ return filter.read();
67
+ }
68
+ if (filter.is((x) => x.productPrice)) {
69
+ return filter.read();
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,92 @@
1
+ import EntityAccessError from "../../../common/EntityAccessError.js";
2
+ import Inject from "../../../di/di.js";
3
+ import { IEntityQuery } from "../../../model/IFilterWithParameter.js";
4
+ import EntityEvents from "../../../model/events/EntityEvents.js";
5
+ import { Product, ProductCategory, ProductPrice } from "../../model/ShoppingContext.js";
6
+ import { UserInfo } from "./UserInfo.js";
7
+ const statusPublished = "published";
8
+
9
+ export class ProductEvents extends EntityEvents<Product> {
10
+
11
+ @Inject
12
+ user: UserInfo;
13
+
14
+ filter(query: IEntityQuery<Product>): IEntityQuery<Product> {
15
+ const { userID } = this.user;
16
+
17
+ // admin can access everything so return null
18
+ if (this.user.admin) {
19
+ return null;
20
+ }
21
+ // everyone can read published or own products
22
+ return query.where({ userID, statusPublished }, (p) => (x) => x.ownerID === p.userID || x.status === p.statusPublished);
23
+ }
24
+
25
+ modify(query: IEntityQuery<Product>): IEntityQuery<Product> {
26
+ const { userID } = this.user;
27
+
28
+ // admin can access everything so return null
29
+ if (this.user.admin) {
30
+ return null;
31
+ }
32
+
33
+ // customer can modify its own products
34
+ return query.where({ userID }, (p) => (x) => x.ownerID === p.userID);
35
+ }
36
+
37
+ }
38
+
39
+ export class ProductCategoryEvents extends EntityEvents<ProductCategory> {
40
+
41
+ @Inject
42
+ user: UserInfo;
43
+
44
+ filter(query: IEntityQuery<ProductCategory>): IEntityQuery<ProductCategory> {
45
+ return null;
46
+ }
47
+
48
+ modify(query: IEntityQuery<ProductCategory>): IEntityQuery<ProductCategory> {
49
+ const { userID } = this.user;
50
+
51
+ // admin can access everything so return null
52
+ if (this.user.admin) {
53
+ return null;
54
+ }
55
+
56
+ EntityAccessError.throw("Access denied");
57
+ }
58
+
59
+ }
60
+
61
+
62
+ export class ProductPriceEvents extends EntityEvents<ProductPrice> {
63
+
64
+ @Inject
65
+ user: UserInfo;
66
+
67
+ filter(query: IEntityQuery<ProductPrice>): IEntityQuery<ProductPrice> {
68
+
69
+ const { userID } = this.user;
70
+
71
+ // admin can access everything so return null
72
+ if (this.user.admin) {
73
+ return null;
74
+ }
75
+
76
+ // user can view prices of only published or own products
77
+ return query.where({ userID, statusPublished } , (p) => (x) => x.product.status === p.statusPublished || x.product.ownerID === p.userID);
78
+ }
79
+
80
+ modify(query: IEntityQuery<ProductPrice>): IEntityQuery<ProductPrice> {
81
+ const { userID } = this.user;
82
+
83
+ // admin can access everything so return null
84
+ if (this.user.admin) {
85
+ return null;
86
+ }
87
+
88
+ // user can only edit its own prices
89
+ return query.where({ userID }, (p) => (x) => x.product.ownerID === p.userID);
90
+ }
91
+
92
+ }
@@ -0,0 +1,28 @@
1
+ import Inject from "../../../di/di.js";
2
+ import { IEntityQuery } from "../../../model/IFilterWithParameter.js";
3
+ import EntityEvents from "../../../model/events/EntityEvents.js";
4
+ import { User } from "../../model/ShoppingContext.js";
5
+ import { UserInfo } from "./UserInfo.js";
6
+ export class UserEvents extends EntityEvents<User> {
7
+
8
+ @Inject
9
+ user: UserInfo;
10
+
11
+ filter(query: IEntityQuery<User>): IEntityQuery<User> {
12
+ if (this.user.admin) {
13
+ return null;
14
+ }
15
+ const { userID } = this.user;
16
+ return query.where({ userID }, (p) => (x) => x.userID === p.userID
17
+ || x.orders.some(
18
+ (op) => op.orderItems.some((oi) => oi.product.ownerID === p.userID)));
19
+ }
20
+
21
+ modify(query: IEntityQuery<User>): IEntityQuery<User> {
22
+ if (this.user.admin) {
23
+ return null;
24
+ }
25
+ const { userID } = this.user;
26
+ return query.where({ userID}, (p) => (x) => x.userID === p.userID);
27
+ }
28
+ }
@@ -0,0 +1,7 @@
1
+ import { RegisterScoped } from "../../../di/di.js";
2
+
3
+ @RegisterScoped
4
+ export class UserInfo {
5
+ public userID: number;
6
+ public admin: boolean;
7
+ }
@@ -0,0 +1,19 @@
1
+ import assert from "assert";
2
+ import { TestConfig } from "../../TestConfig.js";
3
+ import { createContext } from "../../model/createContext.js";
4
+ import { ShoppingContext } from "../../model/ShoppingContext.js";
5
+ import Logger from "../../../common/Logger.js";
6
+
7
+ export default async function (this: TestConfig) {
8
+
9
+ const old = await createContext(this.driver);
10
+
11
+ const context = new ShoppingContext(old.driver, void 0, Logger.instance);
12
+ const order = await context.orders.all()
13
+ .where({id: 0}, (p) => (x) => x.orderID > p.id)
14
+ .include((x) => x.orderItems.forEach((oi) => oi.productPrice.product))
15
+ .first();
16
+
17
+ assert.notEqual(null, order);
18
+
19
+ }
@@ -0,0 +1,104 @@
1
+ import assert from "assert";
2
+ import Logger from "../../../common/Logger.js";
3
+ import { ServiceProvider } from "../../../di/di.js";
4
+ import { BaseDriver } from "../../../drivers/base/BaseDriver.js";
5
+ import ContextEvents from "../../../model/events/ContextEvents.js";
6
+ import { TestConfig } from "../../TestConfig.js";
7
+ import { ShoppingContext, User } from "../../model/ShoppingContext.js";
8
+ import { createContext } from "../../model/createContext.js";
9
+ import { ShoppingContextEvents } from "../ShoppingContextEvents.js";
10
+ import { UserInfo } from "../events/UserInfo.js";
11
+
12
+ export default async function(this: TestConfig) {
13
+
14
+ const customer = await createUser(this);
15
+
16
+ await addNewOrder.call(this, customer);
17
+
18
+ try {
19
+ await addNewOrder.call(this, customer, 1);
20
+ assert.fail("No error thrown");
21
+ } catch(error) {
22
+
23
+ }
24
+
25
+ await getNewOrders.call(this);
26
+
27
+ }
28
+
29
+ async function getNewOrders(this: TestConfig) {
30
+ const scope = ServiceProvider.global.createScope();
31
+ try {
32
+ const user = new UserInfo();
33
+ user.userID = 2;
34
+ scope.add(Logger, Logger.instance);
35
+ scope.add(BaseDriver, this.driver);
36
+ scope.add(UserInfo, user);
37
+ scope.add(ContextEvents, new ShoppingContextEvents());
38
+ const context = scope.create(ShoppingContext);
39
+ context.verifyFilters = true;
40
+
41
+ const order = await context.orders.all().first();
42
+
43
+ order.orderDate = new Date();
44
+ await context.saveChanges();
45
+
46
+ } finally {
47
+ scope.dispose();
48
+ }
49
+ }
50
+
51
+ async function addNewOrder(this: TestConfig, customer: User, userID?) {
52
+ const scope = ServiceProvider.global.createScope();
53
+ try {
54
+ const user = new UserInfo();
55
+ user.userID = userID ?? customer.userID;
56
+ scope.add(Logger, Logger.instance);
57
+ scope.add(BaseDriver, this.driver);
58
+ scope.add(UserInfo, user);
59
+ scope.add(ContextEvents, new ShoppingContextEvents());
60
+ const context = scope.create(ShoppingContext);
61
+ context.verifyFilters = true;
62
+
63
+ // get first headphone...
64
+ const headPhone = await context.products.all().firstOrFail();
65
+ const headPhonePrice = await context.productPrices.where({ id: headPhone.productID }, (p) => (x) => x.productID === p.id).firstOrFail();
66
+
67
+ context.orders.add({
68
+ customer,
69
+ orderDate: new Date(),
70
+ orderItems: [
71
+ context.orderItems.add({
72
+ product: headPhone,
73
+ productPrice: headPhonePrice,
74
+ amount: headPhonePrice.amount,
75
+ })
76
+ ]
77
+ });
78
+
79
+ await context.saveChanges();
80
+
81
+ // lets filter the orders
82
+
83
+ const f = context.orders.filtered();
84
+ const myOrders = await f.count();
85
+ assert.equal(1, myOrders);
86
+
87
+ const all = await context.orders.all().count();
88
+ assert.notEqual(all, myOrders);
89
+
90
+ } finally {
91
+ scope.dispose();
92
+ }
93
+ }
94
+
95
+ async function createUser(config: TestConfig) {
96
+ const context = await createContext(config.driver);
97
+ config.driver.connectionString.database = context.driver.connectionString.database;
98
+ const user = context.users.add({
99
+ dateCreated: new Date()
100
+ });
101
+ await context.saveChanges();
102
+ return user;
103
+ }
104
+
package/test.js CHANGED
@@ -40,10 +40,8 @@ export default class TestRunner {
40
40
  options: {
41
41
  encrypt: true, // for azure
42
42
  trustServerCertificate: true // change to true for local dev / self-signed certs
43
- },
44
- debug: true
43
+ }
45
44
  })
46
-
47
45
  ];
48
46
  }
49
47
 
@@ -89,14 +87,17 @@ export default class TestRunner {
89
87
 
90
88
  }
91
89
 
90
+ const testDb = !process.argv.includes("no-db");
92
91
 
93
- await TestRunner.runAll("./dist/tests", true);
92
+ await TestRunner.runAll("./dist/tests", testDb);
94
93
 
95
94
  let exitCode = 0;
95
+ let failed = 0;
96
96
 
97
97
  for (const { error, name } of results) {
98
98
  if (error) {
99
99
  exitCode = 1;
100
+ failed++;
100
101
  console.error(`${name} failed`);
101
102
  console.error(error?.stack ?? error);
102
103
  continue;
@@ -104,4 +105,10 @@ for (const { error, name } of results) {
104
105
  console.log(`${name} executed.`);
105
106
  }
106
107
 
108
+ if (exitCode === 0) {
109
+ console.log(`${results.length} tests ran successfully.`);
110
+ } else {
111
+ console.log(`${failed} Tests out of ${results.length} failed.`);
112
+ }
113
+
107
114
  process.exit(exitCode);
package/tsconfig.json CHANGED
@@ -2,10 +2,12 @@
2
2
  "compilerOptions": {
3
3
  "target": "ESNext",
4
4
  "module":"NodeNext",
5
+ "incremental": true,
5
6
  "sourceMap": true,
6
7
  "declaration": true,
7
8
  "declarationMap": true,
8
9
  "outDir": "dist",
10
+ "skipDefaultLibCheck": true,
9
11
  "experimentalDecorators": true,
10
12
  "emitDecoratorMetadata": true,
11
13
  "lib": [
@@ -1,8 +0,0 @@
1
- export default class NameParser {
2
- public static parseMember(text: ((a: any) => any)) {
3
- const t: string = text.toString();
4
-
5
- const index = t.lastIndexOf(".");
6
- return t.substring(index + 1);
7
- }
8
- }