@tstdl/base 0.93.145 → 0.93.146
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/authentication/tests/authentication.client-service.test.js +15 -19
- package/authentication/tests/authentication.service.test.js +92 -119
- package/notification/tests/notification-client.test.js +39 -50
- package/notification/tests/notification-flow.test.js +204 -238
- package/notification/tests/notification-sse.service.test.js +20 -27
- package/notification/tests/notification-type.service.test.js +17 -20
- package/orm/tests/query-complex.test.js +80 -111
- package/orm/tests/repository-advanced.test.js +100 -143
- package/orm/tests/repository-attributes.test.js +30 -39
- package/orm/tests/repository-compound-primary-key.test.js +67 -75
- package/orm/tests/repository-comprehensive.test.js +76 -101
- package/orm/tests/repository-coverage.test.d.ts +1 -0
- package/orm/tests/repository-coverage.test.js +88 -149
- package/orm/tests/repository-cti-extensive.test.d.ts +1 -0
- package/orm/tests/repository-cti-extensive.test.js +118 -147
- package/orm/tests/repository-cti-mapping.test.d.ts +1 -0
- package/orm/tests/repository-cti-mapping.test.js +29 -42
- package/orm/tests/repository-cti-soft-delete.test.d.ts +1 -0
- package/orm/tests/repository-cti-soft-delete.test.js +25 -37
- package/orm/tests/repository-cti-transactions.test.js +19 -33
- package/orm/tests/repository-cti-upsert-many.test.d.ts +1 -0
- package/orm/tests/repository-cti-upsert-many.test.js +38 -50
- package/orm/tests/repository-cti.test.d.ts +1 -0
- package/orm/tests/repository-cti.test.js +195 -247
- package/orm/tests/repository-expiration.test.d.ts +1 -0
- package/orm/tests/repository-expiration.test.js +46 -59
- package/orm/tests/repository-extra-coverage.test.d.ts +1 -0
- package/orm/tests/repository-extra-coverage.test.js +195 -337
- package/orm/tests/repository-mapping.test.d.ts +1 -0
- package/orm/tests/repository-mapping.test.js +20 -20
- package/orm/tests/repository-regression.test.js +124 -163
- package/orm/tests/repository-search.test.js +30 -44
- package/orm/tests/repository-soft-delete.test.js +54 -79
- package/orm/tests/repository-types.test.js +77 -111
- package/package.json +1 -1
- package/task-queue/tests/worker.test.js +5 -5
- package/testing/README.md +38 -16
- package/testing/integration-setup.d.ts +11 -0
- package/testing/integration-setup.js +57 -30
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
|
2
|
-
import { runInInjectionContext } from '../../injector/index.js';
|
|
3
2
|
import { setupIntegrationTest, truncateTables } from '../../testing/index.js';
|
|
4
3
|
import { NotificationTypeService } from '../server/services/notification-type.service.js';
|
|
5
4
|
describe('NotificationTypeService', () => {
|
|
@@ -21,24 +20,22 @@ describe('NotificationTypeService', () => {
|
|
|
21
20
|
[type1]: { label: 'Type 1' },
|
|
22
21
|
[type2]: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } }
|
|
23
22
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
expect(dbTypesUpdated.find((c) => c.key == type1)?.label).toBe('Type 1 Updated');
|
|
42
|
-
});
|
|
23
|
+
const result = await service.initializeTypes(typeData);
|
|
24
|
+
expect(result[type1]?.label).toBe('Type 1');
|
|
25
|
+
expect(result[type2]?.key).toBe(type2);
|
|
26
|
+
expect(result[type2]?.throttling?.limit).toBe(1);
|
|
27
|
+
// Verify persistence
|
|
28
|
+
const dbTypes = await service.repository.loadManyByQuery({ key: { $in: [type1, type2] } });
|
|
29
|
+
expect(dbTypes).toHaveLength(2);
|
|
30
|
+
// Update
|
|
31
|
+
const updatedData = {
|
|
32
|
+
[type1]: { label: 'Type 1 Updated' },
|
|
33
|
+
[type2]: { label: 'Type 2', throttling: { limit: 1, interval: 1000 } }
|
|
34
|
+
};
|
|
35
|
+
const resultUpdated = await service.initializeTypes(updatedData);
|
|
36
|
+
expect(resultUpdated[type1]?.label).toBe('Type 1 Updated');
|
|
37
|
+
const dbTypesUpdated = await service.repository.loadManyByQuery({ key: { $in: [type1, type2] } });
|
|
38
|
+
expect(dbTypesUpdated).toHaveLength(2);
|
|
39
|
+
expect(dbTypesUpdated.find((c) => c.key == type1)?.label).toBe('Type 1 Updated');
|
|
43
40
|
});
|
|
44
41
|
});
|
|
@@ -9,15 +9,15 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
9
9
|
};
|
|
10
10
|
import { sql } from 'drizzle-orm';
|
|
11
11
|
import { beforeAll, describe, expect, test } from 'vitest';
|
|
12
|
-
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
13
12
|
import { Integer, StringProperty } from '../../schema/index.js';
|
|
13
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
14
14
|
import { Table } from '../decorators.js';
|
|
15
15
|
import { Entity } from '../entity.js';
|
|
16
|
-
import {
|
|
17
|
-
import { injectRepository } from '../server/repository.js';
|
|
16
|
+
import { getRepository } from '../server/index.js';
|
|
18
17
|
describe('ORM Query Complex (Integration)', () => {
|
|
19
18
|
let injector;
|
|
20
19
|
let db;
|
|
20
|
+
let repo;
|
|
21
21
|
const schema = 'test_orm_query_complex';
|
|
22
22
|
let QueryEntity = class QueryEntity extends Entity {
|
|
23
23
|
tag;
|
|
@@ -40,14 +40,10 @@ describe('ORM Query Complex (Integration)', () => {
|
|
|
40
40
|
Table('query_entities', { schema })
|
|
41
41
|
], QueryEntity);
|
|
42
42
|
beforeAll(async () => {
|
|
43
|
-
injector =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
host: '127.0.0.1', port: 5432, user: 'tstdl', password: 'wf7rq6glrk5jykne', database: 'tstdl',
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
db = injector.resolve(Database);
|
|
43
|
+
({ injector, database: db } = await setupIntegrationTest({
|
|
44
|
+
orm: { schema },
|
|
45
|
+
}));
|
|
46
|
+
repo = injector.resolve(getRepository(QueryEntity));
|
|
51
47
|
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
52
48
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
53
49
|
await db.execute(sql `
|
|
@@ -66,138 +62,111 @@ describe('ORM Query Complex (Integration)', () => {
|
|
|
66
62
|
});
|
|
67
63
|
test('should support nested AND/OR logic', async () => {
|
|
68
64
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
69
|
-
await
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
$
|
|
80
|
-
|
|
81
|
-
{ $and: [{ tag: 'B' }, { status: 'inactive' }] },
|
|
82
|
-
],
|
|
83
|
-
});
|
|
84
|
-
expect(results).toHaveLength(2);
|
|
85
|
-
expect(results.some((r) => r.tag === 'A' && r.status === 'active')).toBe(true);
|
|
86
|
-
expect(results.some((r) => r.tag === 'B' && r.status === 'inactive')).toBe(true);
|
|
65
|
+
await repo.insertMany([
|
|
66
|
+
Object.assign(new QueryEntity(), { tag: 'A', score: 10, status: 'active' }),
|
|
67
|
+
Object.assign(new QueryEntity(), { tag: 'A', score: 20, status: 'inactive' }),
|
|
68
|
+
Object.assign(new QueryEntity(), { tag: 'B', score: 10, status: 'active' }),
|
|
69
|
+
Object.assign(new QueryEntity(), { tag: 'B', score: 20, status: 'inactive' }),
|
|
70
|
+
]);
|
|
71
|
+
// (Tag A AND Active) OR (Tag B AND Inactive)
|
|
72
|
+
const results = await repo.loadManyByQuery({
|
|
73
|
+
$or: [
|
|
74
|
+
{ $and: [{ tag: 'A' }, { status: 'active' }] },
|
|
75
|
+
{ $and: [{ tag: 'B' }, { status: 'inactive' }] },
|
|
76
|
+
],
|
|
87
77
|
});
|
|
78
|
+
expect(results).toHaveLength(2);
|
|
79
|
+
expect(results.some((r) => r.tag === 'A' && r.status === 'active')).toBe(true);
|
|
80
|
+
expect(results.some((r) => r.tag === 'B' && r.status === 'inactive')).toBe(true);
|
|
88
81
|
});
|
|
89
82
|
test('should support implicit AND', async () => {
|
|
90
83
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
91
|
-
await
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
score: 10,
|
|
97
|
-
status: 'active',
|
|
98
|
-
});
|
|
99
|
-
expect(results).toHaveLength(1);
|
|
84
|
+
await repo.insert(Object.assign(new QueryEntity(), { tag: 'A', score: 10, status: 'active' }));
|
|
85
|
+
const results = await repo.loadManyByQuery({
|
|
86
|
+
tag: 'A',
|
|
87
|
+
score: 10,
|
|
88
|
+
status: 'active',
|
|
100
89
|
});
|
|
90
|
+
expect(results).toHaveLength(1);
|
|
101
91
|
});
|
|
102
92
|
test('should support NOT operator', async () => {
|
|
103
93
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
104
|
-
await
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const results = await repo.loadManyByQuery({
|
|
111
|
-
tag: { $not: { $eq: 'A' } },
|
|
112
|
-
});
|
|
113
|
-
expect(results).toHaveLength(1);
|
|
114
|
-
expect(results[0].tag).toBe('B');
|
|
94
|
+
await repo.insertMany([
|
|
95
|
+
Object.assign(new QueryEntity(), { tag: 'A', score: 10, status: 'active' }),
|
|
96
|
+
Object.assign(new QueryEntity(), { tag: 'B', score: 20, status: 'inactive' }),
|
|
97
|
+
]);
|
|
98
|
+
const results = await repo.loadManyByQuery({
|
|
99
|
+
tag: { $not: { $eq: 'A' } },
|
|
115
100
|
});
|
|
101
|
+
expect(results).toHaveLength(1);
|
|
102
|
+
expect(results[0].tag).toBe('B');
|
|
116
103
|
});
|
|
117
104
|
test('should support IN operator with empty array (should match nothing)', async () => {
|
|
118
105
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
119
|
-
await
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const results = await repo.loadManyByQuery({
|
|
123
|
-
tag: { $in: [] },
|
|
124
|
-
});
|
|
125
|
-
expect(results).toHaveLength(0);
|
|
106
|
+
await repo.insert(Object.assign(new QueryEntity(), { tag: 'A', score: 10, status: 'active' }));
|
|
107
|
+
const results = await repo.loadManyByQuery({
|
|
108
|
+
tag: { $in: [] },
|
|
126
109
|
});
|
|
110
|
+
expect(results).toHaveLength(0);
|
|
127
111
|
});
|
|
128
112
|
test('should support NOT IN operator with empty array (should match everything)', async () => {
|
|
129
113
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
130
|
-
await
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const results = await repo.loadManyByQuery({
|
|
134
|
-
tag: { $nin: [] },
|
|
135
|
-
});
|
|
136
|
-
expect(results).toHaveLength(1);
|
|
114
|
+
await repo.insert(Object.assign(new QueryEntity(), { tag: 'A', score: 10, status: 'active' }));
|
|
115
|
+
const results = await repo.loadManyByQuery({
|
|
116
|
+
tag: { $nin: [] },
|
|
137
117
|
});
|
|
118
|
+
expect(results).toHaveLength(1);
|
|
138
119
|
});
|
|
139
120
|
test('should support REGEX operator', async () => {
|
|
140
121
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
141
|
-
await
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const results = await repo.loadManyByQuery({
|
|
149
|
-
tag: { $regex: '^ap' },
|
|
150
|
-
});
|
|
151
|
-
expect(results).toHaveLength(2); // apple, apricot
|
|
122
|
+
await repo.insertMany([
|
|
123
|
+
Object.assign(new QueryEntity(), { tag: 'apple', score: 10, status: '' }),
|
|
124
|
+
Object.assign(new QueryEntity(), { tag: 'banana', score: 10, status: '' }),
|
|
125
|
+
Object.assign(new QueryEntity(), { tag: 'apricot', score: 10, status: '' }),
|
|
126
|
+
]);
|
|
127
|
+
const results = await repo.loadManyByQuery({
|
|
128
|
+
tag: { $regex: '^ap' },
|
|
152
129
|
});
|
|
130
|
+
expect(results).toHaveLength(2); // apple, apricot
|
|
153
131
|
});
|
|
154
132
|
test('should support complex combination of logic and comparison', async () => {
|
|
155
133
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
156
|
-
await
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
$and: [{ score: { $gt: 10 } }, { score: { $lt: 20 } }],
|
|
168
|
-
status: { $neq: 'fail' },
|
|
169
|
-
});
|
|
170
|
-
expect(results).toHaveLength(1);
|
|
171
|
-
expect(results[0].score).toBe(15);
|
|
134
|
+
await repo.insertMany([
|
|
135
|
+
Object.assign(new QueryEntity(), { tag: 'A', score: 5, status: 'ok' }),
|
|
136
|
+
Object.assign(new QueryEntity(), { tag: 'A', score: 15, status: 'ok' }),
|
|
137
|
+
Object.assign(new QueryEntity(), { tag: 'A', score: 25, status: 'fail' }),
|
|
138
|
+
Object.assign(new QueryEntity(), { tag: 'B', score: 5, status: 'ok' }),
|
|
139
|
+
]);
|
|
140
|
+
// Tag A AND (Score > 10 AND Score < 20) AND Status != fail
|
|
141
|
+
const results = await repo.loadManyByQuery({
|
|
142
|
+
tag: 'A',
|
|
143
|
+
$and: [{ score: { $gt: 10 } }, { score: { $lt: 20 } }],
|
|
144
|
+
status: { $neq: 'fail' },
|
|
172
145
|
});
|
|
146
|
+
expect(results).toHaveLength(1);
|
|
147
|
+
expect(results[0].score).toBe(15);
|
|
173
148
|
});
|
|
174
149
|
test('should support NULL check', async () => {
|
|
175
150
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const results = await repo.loadManyByQuery({
|
|
187
|
-
status: { $eq: null },
|
|
188
|
-
});
|
|
189
|
-
expect(results).toHaveLength(0);
|
|
151
|
+
// Manually insert null status (violates NOT NULL usually, but let's assume nullable if we could change schema,
|
|
152
|
+
// but Entity defines it as string. We can query for null even if no rows can be null (result 0) OR if we use a nullable column).
|
|
153
|
+
// 'attributes' is JSONB and can be queried.
|
|
154
|
+
// But query-converter maps properties.
|
|
155
|
+
// Let's use a non-existent value for $eq which is different from null.
|
|
156
|
+
// But checking $eq: null generates IS NULL.
|
|
157
|
+
// Since our columns are NOT NULL, it should return empty.
|
|
158
|
+
await repo.insert(Object.assign(new QueryEntity(), { tag: 'A', score: 10, status: 'active' }));
|
|
159
|
+
const results = await repo.loadManyByQuery({
|
|
160
|
+
status: { $eq: null },
|
|
190
161
|
});
|
|
162
|
+
expect(results).toHaveLength(0);
|
|
191
163
|
});
|
|
192
164
|
test('should support NOT NULL check', async () => {
|
|
193
165
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('query_entities')} CASCADE`);
|
|
194
|
-
await
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
const results = await repo.loadManyByQuery({
|
|
198
|
-
status: { $neq: null },
|
|
199
|
-
});
|
|
200
|
-
expect(results).toHaveLength(1);
|
|
166
|
+
await repo.insert(Object.assign(new QueryEntity(), { tag: 'A', score: 10, status: 'active' }));
|
|
167
|
+
const results = await repo.loadManyByQuery({
|
|
168
|
+
status: { $neq: null },
|
|
201
169
|
});
|
|
170
|
+
expect(results).toHaveLength(1);
|
|
202
171
|
});
|
|
203
172
|
});
|
|
@@ -7,18 +7,18 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
9
|
};
|
|
10
|
-
import { Injector, runInInjectionContext } from '../../injector/index.js';
|
|
11
|
-
import { Integer, StringProperty } from '../../schema/index.js';
|
|
12
|
-
import { toArrayAsync } from '../../utils/async-iterable-helpers/to-array.js';
|
|
13
10
|
import { sql } from 'drizzle-orm';
|
|
14
11
|
import { beforeAll, describe, expect, test } from 'vitest';
|
|
12
|
+
import { Integer, StringProperty } from '../../schema/index.js';
|
|
13
|
+
import { setupIntegrationTest } from '../../testing/index.js';
|
|
14
|
+
import { toArrayAsync } from '../../utils/async-iterable-helpers/to-array.js';
|
|
15
15
|
import { Table } from '../decorators.js';
|
|
16
16
|
import { Entity } from '../entity.js';
|
|
17
|
-
import {
|
|
18
|
-
import { injectRepository } from '../server/repository.js';
|
|
17
|
+
import { getRepository } from '../server/index.js';
|
|
19
18
|
describe('ORM Repository Advanced (Integration)', () => {
|
|
20
19
|
let injector;
|
|
21
20
|
let db;
|
|
21
|
+
let repository;
|
|
22
22
|
const schema = 'test_orm_advanced';
|
|
23
23
|
let AdvancedEntity = class AdvancedEntity extends Entity {
|
|
24
24
|
name;
|
|
@@ -36,14 +36,10 @@ describe('ORM Repository Advanced (Integration)', () => {
|
|
|
36
36
|
Table('advanced_entities', { schema })
|
|
37
37
|
], AdvancedEntity);
|
|
38
38
|
beforeAll(async () => {
|
|
39
|
-
injector =
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
host: '127.0.0.1', port: 5432, user: 'tstdl', password: 'wf7rq6glrk5jykne', database: 'tstdl',
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
db = injector.resolve(Database);
|
|
39
|
+
({ injector, database: db } = await setupIntegrationTest({
|
|
40
|
+
orm: { schema },
|
|
41
|
+
}));
|
|
42
|
+
repository = injector.resolve(getRepository(AdvancedEntity));
|
|
47
43
|
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS ${sql.identifier(schema)}`);
|
|
48
44
|
await db.execute(sql `DROP TABLE IF EXISTS ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
49
45
|
await db.execute(sql `
|
|
@@ -61,172 +57,133 @@ describe('ORM Repository Advanced (Integration)', () => {
|
|
|
61
57
|
});
|
|
62
58
|
test('should support loadManyCursor', async () => {
|
|
63
59
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
64
|
-
await
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
expect(results).toHaveLength(2);
|
|
71
|
-
expect(results.map((r) => r.name).sort()).toEqual(['E1', 'E2']);
|
|
72
|
-
});
|
|
60
|
+
const e1 = await repository.insert(Object.assign(new AdvancedEntity(), { name: 'E1', value: 1 }));
|
|
61
|
+
const e2 = await repository.insert(Object.assign(new AdvancedEntity(), { name: 'E2', value: 2 }));
|
|
62
|
+
const cursor = repository.loadManyCursor([e1.id, e2.id]);
|
|
63
|
+
const results = await toArrayAsync(cursor);
|
|
64
|
+
expect(results).toHaveLength(2);
|
|
65
|
+
expect(results.map((r) => r.name).sort()).toEqual(['E1', 'E2']);
|
|
73
66
|
});
|
|
74
67
|
test('should support loadAllCursor', async () => {
|
|
75
68
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
76
|
-
await
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const results = await toArrayAsync(cursor);
|
|
84
|
-
expect(results).toHaveLength(2);
|
|
85
|
-
});
|
|
69
|
+
await repository.insertMany([
|
|
70
|
+
Object.assign(new AdvancedEntity(), { name: 'E1', value: 1 }),
|
|
71
|
+
Object.assign(new AdvancedEntity(), { name: 'E2', value: 2 }),
|
|
72
|
+
]);
|
|
73
|
+
const cursor = repository.loadAllCursor();
|
|
74
|
+
const results = await toArrayAsync(cursor);
|
|
75
|
+
expect(results).toHaveLength(2);
|
|
86
76
|
});
|
|
87
77
|
test('should support countByQuery with various filters', async () => {
|
|
88
78
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
89
|
-
await
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
expect(await repository.countByQuery({ value: { $lt: 25 } })).toBe(2);
|
|
98
|
-
expect(await repository.countByQuery({ name: 'A' })).toBe(1);
|
|
99
|
-
});
|
|
79
|
+
await repository.insertMany([
|
|
80
|
+
Object.assign(new AdvancedEntity(), { name: 'A', value: 10 }),
|
|
81
|
+
Object.assign(new AdvancedEntity(), { name: 'B', value: 20 }),
|
|
82
|
+
Object.assign(new AdvancedEntity(), { name: 'C', value: 30 }),
|
|
83
|
+
]);
|
|
84
|
+
expect(await repository.countByQuery({ value: { $gt: 15 } })).toBe(2);
|
|
85
|
+
expect(await repository.countByQuery({ value: { $lt: 25 } })).toBe(2);
|
|
86
|
+
expect(await repository.countByQuery({ name: 'A' })).toBe(1);
|
|
100
87
|
});
|
|
101
88
|
test('should support has and hasByQuery', async () => {
|
|
102
89
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
103
|
-
await
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
expect(await repository.hasByQuery({ name: 'E1' })).toBe(true);
|
|
109
|
-
expect(await repository.hasByQuery({ name: 'E2' })).toBe(false);
|
|
110
|
-
});
|
|
90
|
+
const e1 = await repository.insert(Object.assign(new AdvancedEntity(), { name: 'E1', value: 1 }));
|
|
91
|
+
expect(await repository.has(e1.id)).toBe(true);
|
|
92
|
+
expect(await repository.has('00000000-0000-0000-0000-000000000000')).toBe(false);
|
|
93
|
+
expect(await repository.hasByQuery({ name: 'E1' })).toBe(true);
|
|
94
|
+
expect(await repository.hasByQuery({ name: 'E2' })).toBe(false);
|
|
111
95
|
});
|
|
112
96
|
test('should support tryUpdate', async () => {
|
|
113
97
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
114
|
-
await
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const missing = await repository.tryUpdate('00000000-0000-0000-0000-000000000000', { name: 'X' });
|
|
120
|
-
expect(missing).toBeUndefined();
|
|
121
|
-
});
|
|
98
|
+
const e1 = await repository.insert(Object.assign(new AdvancedEntity(), { name: 'E1', value: 1 }));
|
|
99
|
+
const updated = await repository.tryUpdate(e1.id, { name: 'E1-Updated' });
|
|
100
|
+
expect(updated.name).toBe('E1-Updated');
|
|
101
|
+
const missing = await repository.tryUpdate('00000000-0000-0000-0000-000000000000', { name: 'X' });
|
|
102
|
+
expect(missing).toBeUndefined();
|
|
122
103
|
});
|
|
123
104
|
test('should support tryUpdateByQuery', async () => {
|
|
124
105
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
125
|
-
await
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const missing = await repository.tryUpdateByQuery({ name: 'Missing' }, { value: 0 });
|
|
131
|
-
expect(missing).toBeUndefined();
|
|
132
|
-
});
|
|
106
|
+
await repository.insert(Object.assign(new AdvancedEntity(), { name: 'E1', value: 1 }));
|
|
107
|
+
const updated = await repository.tryUpdateByQuery({ name: 'E1' }, { value: 100 });
|
|
108
|
+
expect(updated.value).toBe(100);
|
|
109
|
+
const missing = await repository.tryUpdateByQuery({ name: 'Missing' }, { value: 0 });
|
|
110
|
+
expect(missing).toBeUndefined();
|
|
133
111
|
});
|
|
134
112
|
test('should support insertIfNotExists', async () => {
|
|
135
113
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
136
|
-
await
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
expect(success).toBeDefined();
|
|
145
|
-
expect(success.name).toBe('E2');
|
|
146
|
-
});
|
|
114
|
+
const e1 = await repository.insert(Object.assign(new AdvancedEntity(), { name: 'E1', value: 1 }));
|
|
115
|
+
// Conflict on ID
|
|
116
|
+
const conflict = await repository.insertIfNotExists('id', Object.assign(new AdvancedEntity(), { id: e1.id, name: 'Conflict', value: 2 }));
|
|
117
|
+
expect(conflict).toBeUndefined();
|
|
118
|
+
// No conflict
|
|
119
|
+
const success = await repository.insertIfNotExists('id', Object.assign(new AdvancedEntity(), { name: 'E2', value: 2 }));
|
|
120
|
+
expect(success).toBeDefined();
|
|
121
|
+
expect(success.name).toBe('E2');
|
|
147
122
|
});
|
|
148
123
|
test('should support pagination (limit/offset)', async () => {
|
|
149
124
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
150
|
-
await
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
expect(p2).toHaveLength(1);
|
|
163
|
-
expect(p2[0].name).toBe('E3');
|
|
164
|
-
});
|
|
125
|
+
await repository.insertMany([
|
|
126
|
+
Object.assign(new AdvancedEntity(), { name: 'E1', value: 1 }),
|
|
127
|
+
Object.assign(new AdvancedEntity(), { name: 'E2', value: 2 }),
|
|
128
|
+
Object.assign(new AdvancedEntity(), { name: 'E3', value: 3 }),
|
|
129
|
+
]);
|
|
130
|
+
const p1 = await repository.loadAll({ limit: 2, order: { value: 'asc' } });
|
|
131
|
+
expect(p1).toHaveLength(2);
|
|
132
|
+
expect(p1[0].name).toBe('E1');
|
|
133
|
+
expect(p1[1].name).toBe('E2');
|
|
134
|
+
const p2 = await repository.loadAll({ limit: 2, offset: 2, order: { value: 'asc' } });
|
|
135
|
+
expect(p2).toHaveLength(1);
|
|
136
|
+
expect(p2[0].name).toBe('E3');
|
|
165
137
|
});
|
|
166
138
|
test('should support complex ordering', async () => {
|
|
167
139
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
168
|
-
await
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
['value', 'desc'],
|
|
179
|
-
],
|
|
180
|
-
});
|
|
181
|
-
expect(results[0].name).toBe('A');
|
|
182
|
-
expect(results[0].value).toBe(20);
|
|
183
|
-
expect(results[1].name).toBe('A');
|
|
184
|
-
expect(results[1].value).toBe(10);
|
|
185
|
-
expect(results[2].name).toBe('B');
|
|
140
|
+
await repository.insertMany([
|
|
141
|
+
Object.assign(new AdvancedEntity(), { name: 'B', value: 10 }),
|
|
142
|
+
Object.assign(new AdvancedEntity(), { name: 'A', value: 20 }),
|
|
143
|
+
Object.assign(new AdvancedEntity(), { name: 'A', value: 10 }),
|
|
144
|
+
]);
|
|
145
|
+
const results = await repository.loadAll({
|
|
146
|
+
order: [
|
|
147
|
+
['name', 'asc'],
|
|
148
|
+
['value', 'desc'],
|
|
149
|
+
],
|
|
186
150
|
});
|
|
151
|
+
expect(results[0].name).toBe('A');
|
|
152
|
+
expect(results[0].value).toBe(20);
|
|
153
|
+
expect(results[1].name).toBe('A');
|
|
154
|
+
expect(results[1].value).toBe(10);
|
|
155
|
+
expect(results[2].name).toBe('B');
|
|
187
156
|
});
|
|
188
157
|
test('should return empty array for empty loadMany', async () => {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const results = await repository.loadMany([]);
|
|
192
|
-
expect(results).toEqual([]);
|
|
193
|
-
});
|
|
158
|
+
const results = await repository.loadMany([]);
|
|
159
|
+
expect(results).toEqual([]);
|
|
194
160
|
});
|
|
195
161
|
test('should fail on duplicate ID insert', async () => {
|
|
196
162
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
await repository.insert(Object.assign(new AdvancedEntity(), { id, name: 'E1', value: 1 }));
|
|
201
|
-
await expect(repository.insert(Object.assign(new AdvancedEntity(), { id, name: 'E2', value: 2 }))).rejects.toThrow();
|
|
202
|
-
});
|
|
163
|
+
const id = '00000000-0000-0000-0000-000000000001';
|
|
164
|
+
await repository.insert(Object.assign(new AdvancedEntity(), { id, name: 'E1', value: 1 }));
|
|
165
|
+
await expect(repository.insert(Object.assign(new AdvancedEntity(), { id, name: 'E2', value: 2 }))).rejects.toThrow();
|
|
203
166
|
});
|
|
204
167
|
test('should support explicit transaction usage', async () => {
|
|
205
168
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
206
|
-
await
|
|
207
|
-
const
|
|
208
|
-
await
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// Should be visible in tx
|
|
212
|
-
expect(await txRepo.count()).toBe(1);
|
|
213
|
-
});
|
|
214
|
-
// Should be committed
|
|
215
|
-
expect(await repository.count()).toBe(1);
|
|
169
|
+
await repository.transaction(async (tx) => {
|
|
170
|
+
const txRepo = repository.withTransaction(tx);
|
|
171
|
+
await txRepo.insert(Object.assign(new AdvancedEntity(), { name: 'TxEntity', value: 100 }));
|
|
172
|
+
// Should be visible in tx
|
|
173
|
+
expect(await txRepo.count()).toBe(1);
|
|
216
174
|
});
|
|
175
|
+
// Should be committed
|
|
176
|
+
expect(await repository.count()).toBe(1);
|
|
217
177
|
});
|
|
218
178
|
test('should exclude soft-deleted entities by default', async () => {
|
|
219
179
|
await db.execute(sql `TRUNCATE TABLE ${sql.identifier(schema)}.${sql.identifier('advanced_entities')} CASCADE`);
|
|
220
|
-
await
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const withDeleted = await repository.loadManyByQuery({}, { withDeleted: true });
|
|
229
|
-
expect(withDeleted).toHaveLength(2);
|
|
230
|
-
});
|
|
180
|
+
const e1 = await repository.insert(Object.assign(new AdvancedEntity(), { name: 'E1', value: 1 }));
|
|
181
|
+
await repository.insert(Object.assign(new AdvancedEntity(), { name: 'E2', value: 2 }));
|
|
182
|
+
await repository.delete(e1.id);
|
|
183
|
+
const all = await repository.loadAll();
|
|
184
|
+
expect(all).toHaveLength(1);
|
|
185
|
+
expect(all[0].name).toBe('E2');
|
|
186
|
+
const withDeleted = await repository.loadManyByQuery({}, { withDeleted: true });
|
|
187
|
+
expect(withDeleted).toHaveLength(2);
|
|
231
188
|
});
|
|
232
189
|
});
|