@accounter/server 0.0.9-alpha-20251210171954-c9c0e7693ebe08d3643d9ee2c00c03606a53e334 → 0.0.9-alpha-20251210173721-a5bb944d9abd3f354daf897072b78bea4d8269c9
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/CHANGELOG.md +5 -21
- package/dist/server/src/__tests__/helpers/db-setup.d.ts +1 -0
- package/dist/server/src/__tests__/helpers/db-setup.js +2 -0
- package/dist/server/src/__tests__/helpers/db-setup.js.map +1 -1
- package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js +31 -23
- package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js.map +1 -1
- package/dist/server/src/__tests__/helpers/seed-helpers.concurrent.test.js +8 -8
- package/dist/server/src/__tests__/helpers/seed-helpers.concurrent.test.js.map +1 -1
- package/dist/server/src/__tests__/helpers/seed-helpers.financial-entity.test.js +50 -41
- package/dist/server/src/__tests__/helpers/seed-helpers.financial-entity.test.js.map +1 -1
- package/dist/server/src/__tests__/helpers/seed-helpers.tax-category.test.js +31 -23
- package/dist/server/src/__tests__/helpers/seed-helpers.tax-category.test.js.map +1 -1
- package/dist/server/src/__tests__/helpers/test-db-config.js +1 -1
- package/dist/server/src/__tests__/helpers/test-db-config.js.map +1 -1
- package/dist/server/src/__tests__/seed-admin-context.integration.test.js +131 -128
- package/dist/server/src/__tests__/seed-admin-context.integration.test.js.map +1 -1
- package/dist/server/src/modules/business-trips/providers/business-trips.provider.d.ts +1 -1
- package/dist/server/src/modules/business-trips/providers/business-trips.provider.js +1 -1
- package/dist/server/src/modules/business-trips/providers/business-trips.provider.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/helpers/db-setup.ts +3 -0
- package/src/__tests__/helpers/seed-helpers.business.test.ts +147 -145
- package/src/__tests__/helpers/seed-helpers.concurrent.test.ts +10 -10
- package/src/__tests__/helpers/seed-helpers.financial-entity.test.ts +231 -218
- package/src/__tests__/helpers/seed-helpers.tax-category.test.ts +164 -162
- package/src/__tests__/helpers/test-db-config.ts +1 -1
- package/src/__tests__/seed-admin-context.integration.test.ts +208 -199
- package/src/modules/business-trips/providers/business-trips.provider.ts +1 -1
|
@@ -1,245 +1,258 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import pg from 'pg';
|
|
2
3
|
import { ensureFinancialEntity } from './seed-helpers.js';
|
|
3
|
-
import { qualifyTable } from './test-db-config.js';
|
|
4
|
+
import { testDbConfig, qualifyTable } from './test-db-config.js';
|
|
4
5
|
import { EntityValidationError } from './seed-errors.js';
|
|
5
|
-
import { TestDatabase } from './db-setup.js';
|
|
6
6
|
|
|
7
7
|
describe('ensureFinancialEntity', () => {
|
|
8
|
-
let
|
|
8
|
+
let pool: pg.Pool;
|
|
9
|
+
let client: pg.PoolClient;
|
|
9
10
|
|
|
10
11
|
beforeAll(async () => {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
// Create connection pool with shared config
|
|
13
|
+
pool = new pg.Pool(testDbConfig);
|
|
14
|
+
|
|
15
|
+
// Get a client for transactions
|
|
16
|
+
client = await pool.connect();
|
|
13
17
|
});
|
|
14
18
|
|
|
15
19
|
afterAll(async () => {
|
|
16
|
-
|
|
20
|
+
// Release client and close pool
|
|
21
|
+
if (client) {
|
|
22
|
+
client.release();
|
|
23
|
+
}
|
|
24
|
+
if (pool) {
|
|
25
|
+
await pool.end();
|
|
26
|
+
}
|
|
17
27
|
});
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
// Start transaction before each test
|
|
31
|
+
await client.query('BEGIN');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
// Rollback transaction after each test to clean up
|
|
36
|
+
await client.query('ROLLBACK');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('should create a new financial entity when it does not exist', async () => {
|
|
40
|
+
const result = await ensureFinancialEntity(client, {
|
|
41
|
+
name: 'Test Entity',
|
|
42
|
+
type: 'business',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(result).toHaveProperty('id');
|
|
46
|
+
expect(typeof result.id).toBe('string');
|
|
47
|
+
|
|
48
|
+
// Verify it was inserted
|
|
49
|
+
const checkResult = await client.query(
|
|
50
|
+
`SELECT id, name, type, owner_id FROM ${qualifyTable('financial_entities')} WHERE id = $1`,
|
|
51
|
+
[result.id],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(checkResult.rows.length).toBe(1);
|
|
55
|
+
expect(checkResult.rows[0].name).toBe('Test Entity');
|
|
56
|
+
expect(checkResult.rows[0].type).toBe('business');
|
|
57
|
+
expect(checkResult.rows[0].owner_id).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should return existing entity when called twice with same parameters', async () => {
|
|
61
|
+
const firstResult = await ensureFinancialEntity(client, {
|
|
62
|
+
name: 'Duplicate Test',
|
|
63
|
+
type: 'tax_category',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const secondResult = await ensureFinancialEntity(client, {
|
|
67
|
+
name: 'Duplicate Test',
|
|
68
|
+
type: 'tax_category',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(firstResult.id).toBe(secondResult.id);
|
|
72
|
+
|
|
73
|
+
// Verify only one row exists
|
|
74
|
+
const countResult = await client.query(
|
|
75
|
+
`SELECT COUNT(*) FROM ${qualifyTable('financial_entities')} WHERE name = $1 AND type = $2`,
|
|
76
|
+
['Duplicate Test', 'tax_category'],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(parseInt(countResult.rows[0].count)).toBe(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should handle entities with owner_id', async () => {
|
|
83
|
+
// First create an owner entity (business)
|
|
84
|
+
const ownerEntityResult = await ensureFinancialEntity(client, {
|
|
85
|
+
name: 'Owner Entity',
|
|
86
|
+
type: 'business',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Create corresponding business record (required for foreign key)
|
|
90
|
+
await client.query(
|
|
91
|
+
`INSERT INTO ${qualifyTable('businesses')} (id) VALUES ($1)`,
|
|
92
|
+
[ownerEntityResult.id],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// Create owned entity (tax_category)
|
|
96
|
+
const result = await ensureFinancialEntity(client, {
|
|
97
|
+
name: 'Owned Entity',
|
|
98
|
+
type: 'tax_category',
|
|
99
|
+
ownerId: ownerEntityResult.id,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(result).toHaveProperty('id');
|
|
103
|
+
|
|
104
|
+
// Verify owner_id is set correctly
|
|
105
|
+
const checkResult = await client.query(
|
|
106
|
+
`SELECT owner_id FROM ${qualifyTable('financial_entities')} WHERE id = $1`,
|
|
107
|
+
[result.id],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(checkResult.rows[0].owner_id).toBe(ownerEntityResult.id);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should be idempotent for entities with owner_id', async () => {
|
|
114
|
+
const ownerEntityResult = await ensureFinancialEntity(client, {
|
|
115
|
+
name: 'Owner Business',
|
|
116
|
+
type: 'business',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Create corresponding business record
|
|
120
|
+
await client.query(
|
|
121
|
+
`INSERT INTO ${qualifyTable('businesses')} (id) VALUES ($1)`,
|
|
122
|
+
[ownerEntityResult.id],
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const firstResult = await ensureFinancialEntity(client, {
|
|
126
|
+
name: 'Child Entity',
|
|
127
|
+
type: 'tax_category',
|
|
128
|
+
ownerId: ownerEntityResult.id,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const secondResult = await ensureFinancialEntity(client, {
|
|
132
|
+
name: 'Child Entity',
|
|
133
|
+
type: 'tax_category',
|
|
134
|
+
ownerId: ownerEntityResult.id,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
expect(firstResult.id).toBe(secondResult.id);
|
|
138
|
+
|
|
139
|
+
// Verify only one row
|
|
140
|
+
const countResult = await client.query(
|
|
141
|
+
`SELECT COUNT(*) FROM ${qualifyTable('financial_entities')}
|
|
124
142
|
WHERE name = $1 AND type = $2 AND owner_id = $3`,
|
|
125
|
-
|
|
126
|
-
|
|
143
|
+
['Child Entity', 'tax_category', ownerEntityResult.id],
|
|
144
|
+
);
|
|
127
145
|
|
|
128
|
-
|
|
129
|
-
|
|
146
|
+
expect(parseInt(countResult.rows[0].count)).toBe(1);
|
|
147
|
+
});
|
|
130
148
|
|
|
131
|
-
it('should distinguish entities by type', async () =>
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
});
|
|
149
|
+
it('should distinguish entities by type', async () => {
|
|
150
|
+
const businessResult = await ensureFinancialEntity(client, {
|
|
151
|
+
name: 'Same Name',
|
|
152
|
+
type: 'business',
|
|
153
|
+
});
|
|
137
154
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
155
|
+
const taxCategoryResult = await ensureFinancialEntity(client, {
|
|
156
|
+
name: 'Same Name',
|
|
157
|
+
type: 'tax_category',
|
|
158
|
+
});
|
|
142
159
|
|
|
143
|
-
|
|
144
|
-
|
|
160
|
+
expect(businessResult.id).not.toBe(taxCategoryResult.id);
|
|
161
|
+
});
|
|
145
162
|
|
|
146
|
-
it('should distinguish entities by owner_id', async () =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
163
|
+
it('should distinguish entities by owner_id', async () => {
|
|
164
|
+
const owner1Entity = await ensureFinancialEntity(client, {
|
|
165
|
+
name: 'Owner 1',
|
|
166
|
+
type: 'business',
|
|
167
|
+
});
|
|
168
|
+
await client.query(
|
|
169
|
+
`INSERT INTO ${qualifyTable('businesses')} (id) VALUES ($1)`,
|
|
170
|
+
[owner1Entity.id],
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const owner2Entity = await ensureFinancialEntity(client, {
|
|
174
|
+
name: 'Owner 2',
|
|
175
|
+
type: 'business',
|
|
176
|
+
});
|
|
177
|
+
await client.query(
|
|
178
|
+
`INSERT INTO ${qualifyTable('businesses')} (id) VALUES ($1)`,
|
|
179
|
+
[owner2Entity.id],
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const entity1 = await ensureFinancialEntity(client, {
|
|
183
|
+
name: 'Same Child',
|
|
184
|
+
type: 'tax_category',
|
|
185
|
+
ownerId: owner1Entity.id,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const entity2 = await ensureFinancialEntity(client, {
|
|
189
|
+
name: 'Same Child',
|
|
190
|
+
type: 'tax_category',
|
|
191
|
+
ownerId: owner2Entity.id,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(entity1.id).not.toBe(entity2.id);
|
|
195
|
+
});
|
|
155
196
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
]);
|
|
163
|
-
|
|
164
|
-
const entity1 = await ensureFinancialEntity(client, {
|
|
165
|
-
name: 'Same Child',
|
|
166
|
-
type: 'tax_category',
|
|
167
|
-
ownerId: owner1Entity.id,
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
const entity2 = await ensureFinancialEntity(client, {
|
|
171
|
-
name: 'Same Child',
|
|
172
|
-
type: 'tax_category',
|
|
173
|
-
ownerId: owner2Entity.id,
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
expect(entity1.id).not.toBe(entity2.id);
|
|
177
|
-
}));
|
|
178
|
-
|
|
179
|
-
it('should handle null vs undefined owner_id consistently', async () =>
|
|
180
|
-
db.withTransaction(async client => {
|
|
181
|
-
const result1 = await ensureFinancialEntity(client, {
|
|
182
|
-
name: 'No Owner Entity',
|
|
183
|
-
type: 'business',
|
|
184
|
-
ownerId: undefined,
|
|
185
|
-
});
|
|
197
|
+
it('should handle null vs undefined owner_id consistently', async () => {
|
|
198
|
+
const result1 = await ensureFinancialEntity(client, {
|
|
199
|
+
name: 'No Owner Entity',
|
|
200
|
+
type: 'business',
|
|
201
|
+
ownerId: undefined,
|
|
202
|
+
});
|
|
186
203
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
204
|
+
const result2 = await ensureFinancialEntity(client, {
|
|
205
|
+
name: 'No Owner Entity',
|
|
206
|
+
type: 'business',
|
|
207
|
+
});
|
|
191
208
|
|
|
192
|
-
|
|
193
|
-
|
|
209
|
+
expect(result1.id).toBe(result2.id);
|
|
210
|
+
});
|
|
194
211
|
|
|
195
|
-
it('should not leak data between tests', async () =>
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
212
|
+
it('should not leak data between tests', async () => {
|
|
213
|
+
// This test verifies that ROLLBACK works correctly
|
|
214
|
+
// Create an entity in this test
|
|
215
|
+
await ensureFinancialEntity(client, {
|
|
216
|
+
name: 'Transient Entity',
|
|
217
|
+
type: 'business',
|
|
218
|
+
});
|
|
203
219
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
// The entity should exist within this transaction
|
|
221
|
+
const checkInTransaction = await client.query(
|
|
222
|
+
`SELECT COUNT(*) FROM ${qualifyTable('financial_entities')} WHERE name = $1`,
|
|
223
|
+
['Transient Entity'],
|
|
224
|
+
);
|
|
209
225
|
|
|
210
|
-
|
|
226
|
+
expect(parseInt(checkInTransaction.rows[0].count)).toBe(1);
|
|
211
227
|
|
|
212
|
-
|
|
213
|
-
|
|
228
|
+
// After ROLLBACK in afterEach, this data won't be visible in next test
|
|
229
|
+
});
|
|
214
230
|
|
|
215
231
|
// Validation tests
|
|
216
|
-
it('should reject empty entity name', async () =>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}),
|
|
243
|
-
).rejects.toThrow(EntityValidationError);
|
|
244
|
-
}));
|
|
232
|
+
it('should reject empty entity name', async () => {
|
|
233
|
+
await expect(
|
|
234
|
+
ensureFinancialEntity(client, {
|
|
235
|
+
name: '',
|
|
236
|
+
type: 'business',
|
|
237
|
+
}),
|
|
238
|
+
).rejects.toThrow(EntityValidationError);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should reject invalid entity type', async () => {
|
|
242
|
+
await expect(
|
|
243
|
+
ensureFinancialEntity(client, {
|
|
244
|
+
name: 'Test Entity',
|
|
245
|
+
type: 'invalid_type' as any,
|
|
246
|
+
}),
|
|
247
|
+
).rejects.toThrow(EntityValidationError);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should reject whitespace-only name', async () => {
|
|
251
|
+
await expect(
|
|
252
|
+
ensureFinancialEntity(client, {
|
|
253
|
+
name: ' ',
|
|
254
|
+
type: 'business',
|
|
255
|
+
}),
|
|
256
|
+
).rejects.toThrow(EntityValidationError);
|
|
257
|
+
});
|
|
245
258
|
});
|