@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.
Files changed (28) hide show
  1. package/CHANGELOG.md +5 -21
  2. package/dist/server/src/__tests__/helpers/db-setup.d.ts +1 -0
  3. package/dist/server/src/__tests__/helpers/db-setup.js +2 -0
  4. package/dist/server/src/__tests__/helpers/db-setup.js.map +1 -1
  5. package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js +31 -23
  6. package/dist/server/src/__tests__/helpers/seed-helpers.business.test.js.map +1 -1
  7. package/dist/server/src/__tests__/helpers/seed-helpers.concurrent.test.js +8 -8
  8. package/dist/server/src/__tests__/helpers/seed-helpers.concurrent.test.js.map +1 -1
  9. package/dist/server/src/__tests__/helpers/seed-helpers.financial-entity.test.js +50 -41
  10. package/dist/server/src/__tests__/helpers/seed-helpers.financial-entity.test.js.map +1 -1
  11. package/dist/server/src/__tests__/helpers/seed-helpers.tax-category.test.js +31 -23
  12. package/dist/server/src/__tests__/helpers/seed-helpers.tax-category.test.js.map +1 -1
  13. package/dist/server/src/__tests__/helpers/test-db-config.js +1 -1
  14. package/dist/server/src/__tests__/helpers/test-db-config.js.map +1 -1
  15. package/dist/server/src/__tests__/seed-admin-context.integration.test.js +131 -128
  16. package/dist/server/src/__tests__/seed-admin-context.integration.test.js.map +1 -1
  17. package/dist/server/src/modules/business-trips/providers/business-trips.provider.d.ts +1 -1
  18. package/dist/server/src/modules/business-trips/providers/business-trips.provider.js +1 -1
  19. package/dist/server/src/modules/business-trips/providers/business-trips.provider.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/__tests__/helpers/db-setup.ts +3 -0
  22. package/src/__tests__/helpers/seed-helpers.business.test.ts +147 -145
  23. package/src/__tests__/helpers/seed-helpers.concurrent.test.ts +10 -10
  24. package/src/__tests__/helpers/seed-helpers.financial-entity.test.ts +231 -218
  25. package/src/__tests__/helpers/seed-helpers.tax-category.test.ts +164 -162
  26. package/src/__tests__/helpers/test-db-config.ts +1 -1
  27. package/src/__tests__/seed-admin-context.integration.test.ts +208 -199
  28. package/src/modules/business-trips/providers/business-trips.provider.ts +1 -1
@@ -1,231 +1,240 @@
1
- import { describe, expect, it, beforeAll, afterAll } from 'vitest';
2
- import { TestDatabase } from './helpers/db-setup.js';
1
+ import { describe, expect, it, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
2
+ import pg from 'pg';
3
+ import { testDbConfig } from './helpers/test-db-config.js';
3
4
  import { seedAdminCore } from '../../scripts/seed-admin-context.js';
4
5
 
5
6
  describe('seedAdminCore integration', () => {
6
- let db: TestDatabase;
7
+ let pool: pg.Pool;
8
+ let client: pg.PoolClient;
7
9
 
8
10
  beforeAll(async () => {
9
- db = new TestDatabase();
10
- await db.connect();
11
+ pool = new pg.Pool(testDbConfig);
12
+ client = await pool.connect();
11
13
  });
12
14
 
13
15
  afterAll(async () => {
14
- await db.close();
16
+ client.release();
17
+ await pool.end();
15
18
  });
16
19
 
17
- it('should create admin business context with all required entities', async () => {
18
- await db.withTransaction(async client => {
19
- // Execute seed
20
- const { adminEntityId } = await seedAdminCore(client);
21
-
22
- // Verify admin entity exists
23
- expect(adminEntityId).toBeTruthy();
24
- expect(adminEntityId).toMatch(
25
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
26
- );
27
-
28
- // Verify admin financial entity
29
- const adminEntity = await client.query(
30
- `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
31
- [adminEntityId],
32
- );
33
- expect(adminEntity.rows).toHaveLength(1);
34
- expect(adminEntity.rows[0].name).toBe('Admin Business');
35
- expect(adminEntity.rows[0].type).toBe('business');
36
- expect(adminEntity.rows[0].owner_id).toBe(adminEntityId); // self-owned
37
-
38
- // Verify admin business record
39
- const adminBusiness = await client.query(
40
- `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
41
- [adminEntityId],
42
- );
43
- expect(adminBusiness.rows).toHaveLength(1);
44
-
45
- // Verify user_context exists and has required fields
46
- const userContext = await client.query(
47
- `SELECT * FROM accounter_schema.user_context WHERE owner_id = $1`,
48
- [adminEntityId],
49
- );
50
- expect(userContext.rows).toHaveLength(1);
51
-
52
- const context = userContext.rows[0];
53
-
54
- // Verify currencies
55
- expect(context.default_local_currency).toBe('ILS');
56
- expect(context.default_fiat_currency_for_crypto_conversions).toBe('USD');
20
+ beforeEach(async () => {
21
+ await client.query('BEGIN');
22
+ });
57
23
 
58
- // Verify required authority businesses exist
59
- const vatBusiness = await client.query(
60
- `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
61
- [context.vat_business_id],
62
- );
63
- expect(vatBusiness.rows).toHaveLength(1);
24
+ afterEach(async () => {
25
+ await client.query('ROLLBACK');
26
+ });
64
27
 
65
- const taxBusiness = await client.query(
66
- `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
67
- [context.tax_business_id],
68
- );
69
- expect(taxBusiness.rows).toHaveLength(1);
28
+ it('should create admin business context with all required entities', async () => {
70
29
 
71
- const socialSecurityBusiness = await client.query(
72
- `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
73
- [context.social_security_business_id],
30
+ // Execute seed
31
+ const { adminEntityId } = await seedAdminCore(client);
32
+
33
+ // Verify admin entity exists
34
+ expect(adminEntityId).toBeTruthy();
35
+ expect(adminEntityId).toMatch(
36
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
37
+ );
38
+
39
+ // Verify admin financial entity
40
+ const adminEntity = await client.query(
41
+ `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
42
+ [adminEntityId],
43
+ );
44
+ expect(adminEntity.rows).toHaveLength(1);
45
+ expect(adminEntity.rows[0].name).toBe('Admin Business');
46
+ expect(adminEntity.rows[0].type).toBe('business');
47
+ expect(adminEntity.rows[0].owner_id).toBe(adminEntityId); // self-owned
48
+
49
+ // Verify admin business record
50
+ const adminBusiness = await client.query(
51
+ `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
52
+ [adminEntityId],
53
+ );
54
+ expect(adminBusiness.rows).toHaveLength(1);
55
+
56
+ // Verify user_context exists and has required fields
57
+ const userContext = await client.query(
58
+ `SELECT * FROM accounter_schema.user_context WHERE owner_id = $1`,
59
+ [adminEntityId],
60
+ );
61
+ expect(userContext.rows).toHaveLength(1);
62
+
63
+ const context = userContext.rows[0];
64
+
65
+ // Verify currencies
66
+ expect(context.default_local_currency).toBe('ILS');
67
+ expect(context.default_fiat_currency_for_crypto_conversions).toBe('USD');
68
+
69
+ // Verify required authority businesses exist
70
+ const vatBusiness = await client.query(
71
+ `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
72
+ [context.vat_business_id],
73
+ );
74
+ expect(vatBusiness.rows).toHaveLength(1);
75
+
76
+ const taxBusiness = await client.query(
77
+ `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
78
+ [context.tax_business_id],
79
+ );
80
+ expect(taxBusiness.rows).toHaveLength(1);
81
+
82
+ const socialSecurityBusiness = await client.query(
83
+ `SELECT * FROM accounter_schema.businesses WHERE id = $1`,
84
+ [context.social_security_business_id],
85
+ );
86
+ expect(socialSecurityBusiness.rows).toHaveLength(1);
87
+
88
+ // Verify required tax categories exist
89
+ const requiredTaxCategoryFields = [
90
+ 'default_tax_category_id',
91
+ 'input_vat_tax_category_id',
92
+ 'output_vat_tax_category_id',
93
+ 'tax_expenses_tax_category_id',
94
+ 'exchange_rate_tax_category_id',
95
+ 'income_exchange_rate_tax_category_id',
96
+ 'exchange_rate_revaluation_tax_category_id',
97
+ 'fee_tax_category_id',
98
+ 'general_fee_tax_category_id',
99
+ 'fine_tax_category_id',
100
+ 'untaxable_gifts_tax_category_id',
101
+ 'balance_cancellation_tax_category_id',
102
+ 'development_foreign_tax_category_id',
103
+ 'development_local_tax_category_id',
104
+ 'expenses_to_pay_tax_category_id',
105
+ 'expenses_in_advance_tax_category_id',
106
+ 'income_to_collect_tax_category_id',
107
+ 'income_in_advance_tax_category_id',
108
+ 'salary_excess_expenses_tax_category_id',
109
+ ];
110
+
111
+ for (const field of requiredTaxCategoryFields) {
112
+ const taxCategoryId = context[field];
113
+ expect(taxCategoryId).toBeTruthy();
114
+
115
+ const taxCategory = await client.query(
116
+ `SELECT * FROM accounter_schema.tax_categories WHERE id = $1`,
117
+ [taxCategoryId],
74
118
  );
75
- expect(socialSecurityBusiness.rows).toHaveLength(1);
76
-
77
- // Verify required tax categories exist
78
- const requiredTaxCategoryFields = [
79
- 'default_tax_category_id',
80
- 'input_vat_tax_category_id',
81
- 'output_vat_tax_category_id',
82
- 'tax_expenses_tax_category_id',
83
- 'exchange_rate_tax_category_id',
84
- 'income_exchange_rate_tax_category_id',
85
- 'exchange_rate_revaluation_tax_category_id',
86
- 'fee_tax_category_id',
87
- 'general_fee_tax_category_id',
88
- 'fine_tax_category_id',
89
- 'untaxable_gifts_tax_category_id',
90
- 'balance_cancellation_tax_category_id',
91
- 'development_foreign_tax_category_id',
92
- 'development_local_tax_category_id',
93
- 'expenses_to_pay_tax_category_id',
94
- 'expenses_in_advance_tax_category_id',
95
- 'income_to_collect_tax_category_id',
96
- 'income_in_advance_tax_category_id',
97
- 'salary_excess_expenses_tax_category_id',
98
- ];
99
-
100
- for (const field of requiredTaxCategoryFields) {
101
- const taxCategoryId = context[field];
102
- expect(taxCategoryId).toBeTruthy();
103
-
104
- const taxCategory = await client.query(
105
- `SELECT * FROM accounter_schema.tax_categories WHERE id = $1`,
106
- [taxCategoryId],
107
- );
108
- expect(taxCategory.rows).toHaveLength(1);
109
- }
110
- });
119
+ expect(taxCategory.rows).toHaveLength(1);
120
+ }
111
121
  });
112
122
 
113
123
  it('should be idempotent (safe to call multiple times)', async () => {
114
- await db.withTransaction(async client => {
115
- // Call seed twice in same transaction
116
- await seedAdminCore(client);
117
-
118
- // Count entities before second call
119
- const countBefore = await client.query(
120
- `SELECT COUNT(*) as count FROM accounter_schema.financial_entities`,
121
- );
122
- const entitiesBeforeSecondCall = parseInt(countBefore.rows[0].count);
123
-
124
- // Second call should reuse existing entities
125
- await seedAdminCore(client);
126
-
127
- // Count entities after second call - should be same
128
- const countAfter = await client.query(
129
- `SELECT COUNT(*) as count FROM accounter_schema.financial_entities`,
130
- );
131
- const entitiesAfterSecondCall = parseInt(countAfter.rows[0].count);
132
-
133
- // Idempotent: no new entities created on second call
134
- expect(entitiesAfterSecondCall).toBe(entitiesBeforeSecondCall);
135
-
136
- // Verify only one user_context exists
137
- const userContextCount = await client.query(
138
- `SELECT COUNT(*) as count FROM accounter_schema.user_context`,
139
- );
140
- expect(userContextCount.rows[0].count).toBe('1');
141
- });
124
+ // Call seed twice in same transaction
125
+ await seedAdminCore(client);
126
+
127
+ // Count entities before second call
128
+ const countBefore = await client.query(
129
+ `SELECT COUNT(*) as count FROM accounter_schema.financial_entities`,
130
+ );
131
+ const entitiesBeforeSecondCall = parseInt(countBefore.rows[0].count);
132
+
133
+ // Second call should reuse existing entities
134
+ await seedAdminCore(client);
135
+
136
+ // Count entities after second call - should be same
137
+ const countAfter = await client.query(
138
+ `SELECT COUNT(*) as count FROM accounter_schema.financial_entities`,
139
+ );
140
+ const entitiesAfterSecondCall = parseInt(countAfter.rows[0].count);
141
+
142
+ // Idempotent: no new entities created on second call
143
+ expect(entitiesAfterSecondCall).toBe(entitiesBeforeSecondCall);
144
+
145
+ // Verify only one user_context exists
146
+ const userContextCount = await client.query(
147
+ `SELECT COUNT(*) as count FROM accounter_schema.user_context`,
148
+ );
149
+ expect(userContextCount.rows[0].count).toBe('1');
142
150
  });
143
151
 
144
152
  it('should not leak data between tests (transactional isolation)', async () => {
145
- await db.withTransaction(async client => {
146
- const TEMP_NAME = 'seed-admin-context.integration.test.ts: temp rollback entity';
153
+ const TEMP_NAME = 'seed-admin-context.integration.test.ts: temp rollback entity';
147
154
 
148
- // Insert a throwaway entity inside a transaction
149
- const insertEntity = await client.query(
150
- `INSERT INTO accounter_schema.financial_entities (name, type)
155
+ // Insert a throwaway entity inside a transaction
156
+ const insertEntity = await client.query(
157
+ `INSERT INTO accounter_schema.financial_entities (name, type)
151
158
  VALUES ($1, 'business')
152
159
  RETURNING id`,
153
- [TEMP_NAME],
154
- );
155
- const tempId = insertEntity.rows[0].id;
156
- await client.query(`INSERT INTO accounter_schema.businesses (id) VALUES ($1)`, [tempId]);
157
-
158
- // Verify exists within the same transaction
159
- const inTx = await client.query(
160
- `SELECT COUNT(*) as count FROM accounter_schema.financial_entities WHERE name = $1`,
161
- [TEMP_NAME],
162
- );
163
- expect(inTx.rows[0].count).toBe('1');
164
-
165
- // Note: withTransaction automatically rolls back at the end,
166
- // so the next test will start with a clean slate
167
- });
160
+ [TEMP_NAME],
161
+ );
162
+ const tempId = insertEntity.rows[0].id;
163
+ await client.query(
164
+ `INSERT INTO accounter_schema.businesses (id) VALUES ($1)`,
165
+ [tempId],
166
+ );
167
+
168
+ // Verify exists within the same transaction
169
+ const inTx = await client.query(
170
+ `SELECT COUNT(*) as count FROM accounter_schema.financial_entities WHERE name = $1`,
171
+ [TEMP_NAME],
172
+ );
173
+ expect(inTx.rows[0].count).toBe('1');
174
+
175
+ // Note: The afterEach hook will rollback this transaction,
176
+ // and the next test will start with a clean slate
168
177
  });
169
178
 
170
179
  it('should create all expected entity counts', async () => {
171
- await db.withTransaction(async client => {
172
- await seedAdminCore(client);
173
-
174
- // Count businesses (Admin + 3 authorities = 4)
175
- const businessCount = await client.query(
176
- `SELECT COUNT(*) as count FROM accounter_schema.businesses`,
177
- );
178
- expect(parseInt(businessCount.rows[0].count)).toBeGreaterThanOrEqual(4);
179
-
180
- // Count tax categories (3 authority + 12 general + 4 cross-year = 19)
181
- const taxCategoryCount = await client.query(
182
- `SELECT COUNT(*) as count FROM accounter_schema.tax_categories`,
183
- );
184
- expect(parseInt(taxCategoryCount.rows[0].count)).toBeGreaterThanOrEqual(19);
185
- });
180
+ await seedAdminCore(client);
181
+
182
+ // Count businesses (Admin + 3 authorities = 4)
183
+ const businessCount = await client.query(
184
+ `SELECT COUNT(*) as count FROM accounter_schema.businesses`,
185
+ );
186
+ expect(parseInt(businessCount.rows[0].count)).toBeGreaterThanOrEqual(4);
187
+
188
+ // Count tax categories (3 authority + 12 general + 4 cross-year = 19)
189
+ const taxCategoryCount = await client.query(
190
+ `SELECT COUNT(*) as count FROM accounter_schema.tax_categories`,
191
+ );
192
+ expect(parseInt(taxCategoryCount.rows[0].count)).toBeGreaterThanOrEqual(19);
186
193
  });
187
194
 
188
195
  it('should validate foreign key relationships', async () => {
189
- await db.withTransaction(async client => {
190
- const { adminEntityId } = await seedAdminCore(client);
191
-
192
- // Get user_context
193
- const userContext = await client.query(
194
- `SELECT * FROM accounter_schema.user_context WHERE owner_id = $1`,
195
- [adminEntityId],
196
+ const { adminEntityId } = await seedAdminCore(client);
197
+
198
+ // Get user_context
199
+ const userContext = await client.query(
200
+ `SELECT * FROM accounter_schema.user_context WHERE owner_id = $1`,
201
+ [adminEntityId],
202
+ );
203
+
204
+ const context = userContext.rows[0];
205
+
206
+ // Verify all business FKs point to valid financial_entities
207
+ const businessFields = [
208
+ 'vat_business_id',
209
+ 'tax_business_id',
210
+ 'social_security_business_id',
211
+ ];
212
+
213
+ for (const field of businessFields) {
214
+ const businessId = context[field];
215
+ const entity = await client.query(
216
+ `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
217
+ [businessId],
196
218
  );
197
-
198
- const context = userContext.rows[0];
199
-
200
- // Verify all business FKs point to valid financial_entities
201
- const businessFields = ['vat_business_id', 'tax_business_id', 'social_security_business_id'];
202
-
203
- for (const field of businessFields) {
204
- const businessId = context[field];
205
- const entity = await client.query(
206
- `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
207
- [businessId],
208
- );
209
- expect(entity.rows).toHaveLength(1);
210
- expect(entity.rows[0].type).toBe('business');
211
- }
212
-
213
- // Verify all tax_category FKs point to valid financial_entities
214
- const taxCategoryFields = [
215
- 'default_tax_category_id',
216
- 'input_vat_tax_category_id',
217
- 'output_vat_tax_category_id',
218
- ];
219
-
220
- for (const field of taxCategoryFields) {
221
- const taxCategoryId = context[field];
222
- const entity = await client.query(
223
- `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
224
- [taxCategoryId],
225
- );
226
- expect(entity.rows).toHaveLength(1);
227
- expect(entity.rows[0].type).toBe('tax_category');
228
- }
229
- });
219
+ expect(entity.rows).toHaveLength(1);
220
+ expect(entity.rows[0].type).toBe('business');
221
+ }
222
+
223
+ // Verify all tax_category FKs point to valid financial_entities
224
+ const taxCategoryFields = [
225
+ 'default_tax_category_id',
226
+ 'input_vat_tax_category_id',
227
+ 'output_vat_tax_category_id',
228
+ ];
229
+
230
+ for (const field of taxCategoryFields) {
231
+ const taxCategoryId = context[field];
232
+ const entity = await client.query(
233
+ `SELECT * FROM accounter_schema.financial_entities WHERE id = $1`,
234
+ [taxCategoryId],
235
+ );
236
+ expect(entity.rows).toHaveLength(1);
237
+ expect(entity.rows[0].type).toBe('tax_category');
238
+ }
230
239
  });
231
240
  });
@@ -1,8 +1,8 @@
1
1
  import DataLoader from 'dataloader';
2
2
  import { Injectable, Scope } from 'graphql-modules';
3
3
  import { sql } from '@pgtyped/runtime';
4
+ import { DBProvider } from '../../../modules/app-providers/db.provider.js';
4
5
  import { getCacheInstance } from '../../../shared/helpers/index.js';
5
- import { DBProvider } from '../../app-providers/db.provider.js';
6
6
  import type {
7
7
  BusinessTripProto,
8
8
  IDeleteChargeBusinessTripQuery,