chadstart 1.0.0 → 1.0.1

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.
@@ -0,0 +1,368 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Engine-agnostic HTTP-level integration tests.
5
+ *
6
+ * Runs against a live database specified by DB_ENGINE (postgres | mysql).
7
+ * Execute via: npm run test:integration
8
+ *
9
+ * Required env vars when DB_ENGINE != sqlite:
10
+ * DB_HOST, DB_PORT, DB_USERNAME, DB_PASSWORD, DB_DATABASE
11
+ */
12
+
13
+ const assert = require('assert');
14
+ const http = require('http');
15
+ const path = require('path');
16
+ const os = require('os');
17
+ const fs = require('fs');
18
+
19
+ const { buildApp } = require('../../server/express-server');
20
+ const dbModule = require('../../core/db');
21
+
22
+ const YAML_PATH = path.resolve(__dirname, '../../chadstart.yaml');
23
+ const DB_ENGINE = (process.env.DB_ENGINE || 'sqlite').toLowerCase();
24
+
25
+ // ── HTTP helper ──────────────────────────────────────────────────────────────
26
+
27
+ function req(options) {
28
+ return new Promise((resolve, reject) => {
29
+ const { method = 'GET', path: p, body, headers = {}, port } = options;
30
+ const data = body ? JSON.stringify(body) : undefined;
31
+ const opts = {
32
+ hostname: 'localhost',
33
+ port,
34
+ path: p,
35
+ method,
36
+ headers: {
37
+ 'Content-Type': 'application/json',
38
+ ...headers,
39
+ ...(data ? { 'Content-Length': Buffer.byteLength(data) } : {}),
40
+ },
41
+ };
42
+ const r = http.request(opts, (res) => {
43
+ let buf = '';
44
+ res.on('data', (c) => { buf += c; });
45
+ res.on('end', () => {
46
+ let json;
47
+ try { json = JSON.parse(buf); } catch { json = buf; }
48
+ resolve({ status: res.statusCode, body: json });
49
+ });
50
+ });
51
+ r.on('error', reject);
52
+ if (data) r.write(data);
53
+ r.end();
54
+ });
55
+ }
56
+
57
+ // ── Test suite ───────────────────────────────────────────────────────────────
58
+
59
+ describe(`DB integration – ${DB_ENGINE}`, function () {
60
+ this.timeout(30000);
61
+
62
+ let server, port, adminToken, adminId;
63
+ let _sqliteTmpPath; // used only in SQLite mode
64
+
65
+ before(async function () {
66
+ // In SQLite mode (local dev smoke-test), write to a temp file
67
+ if (DB_ENGINE === 'sqlite') {
68
+ _sqliteTmpPath = path.join(os.tmpdir(), `integ-test-${Date.now()}.db`);
69
+ process.env.DB_PATH = _sqliteTmpPath;
70
+ }
71
+
72
+ const { app } = await buildApp(YAML_PATH, null);
73
+ server = http.createServer(app);
74
+ await new Promise((resolve) => server.listen(0, resolve));
75
+ port = server.address().port;
76
+
77
+ // Unique e-mail per run so re-runs on the same DB don't collide
78
+ const email = `integ-admin-${Date.now()}@test.com`;
79
+ const su = await req({
80
+ port, method: 'POST', path: '/api/auth/admin/signup',
81
+ body: { email, password: 'secret123', name: 'Integration Admin' },
82
+ });
83
+ assert.strictEqual(su.status, 201, `Admin signup failed: ${JSON.stringify(su.body)}`);
84
+ adminToken = su.body.token;
85
+ adminId = su.body.user.id;
86
+ });
87
+
88
+ after(async function () {
89
+ if (server) await new Promise((resolve) => server.close(resolve));
90
+ await dbModule.closeDb();
91
+ if (_sqliteTmpPath) {
92
+ try { fs.unlinkSync(_sqliteTmpPath); } catch { /* ignore */ }
93
+ }
94
+ });
95
+
96
+ // ── Health ─────────────────────────────────────────────────────────────────
97
+
98
+ describe('GET /health', () => {
99
+ it('returns ok', async () => {
100
+ const r = await req({ port, path: '/health' });
101
+ assert.strictEqual(r.status, 200);
102
+ assert.strictEqual(r.body.status, 'ok');
103
+ });
104
+ });
105
+
106
+ // ── Auth ───────────────────────────────────────────────────────────────────
107
+
108
+ describe('Auth – Admin collection', () => {
109
+ it('signup returns 201 with token and user', async () => {
110
+ const email = `integ-signup-${Date.now()}@test.com`;
111
+ const r = await req({
112
+ port, method: 'POST', path: '/api/auth/admin/signup',
113
+ body: { email, password: 'pass123', name: 'Signup User' },
114
+ });
115
+ assert.strictEqual(r.status, 201);
116
+ assert.ok(r.body.token, 'should return a token');
117
+ assert.ok(r.body.user.id, 'should return a user id');
118
+ assert.strictEqual(r.body.user.email, email);
119
+ assert.ok(!r.body.user.password, 'password must not be exposed');
120
+ });
121
+
122
+ it('signup returns 409 for duplicate email', async () => {
123
+ const email = `integ-dup-${Date.now()}@test.com`;
124
+ await req({ port, method: 'POST', path: '/api/auth/admin/signup', body: { email, password: 'x' } });
125
+ const r = await req({ port, method: 'POST', path: '/api/auth/admin/signup', body: { email, password: 'y' } });
126
+ assert.strictEqual(r.status, 409);
127
+ });
128
+
129
+ it('login returns 200 with token for valid credentials', async () => {
130
+ const email = `integ-login-${Date.now()}@test.com`;
131
+ await req({ port, method: 'POST', path: '/api/auth/admin/signup', body: { email, password: 'myPass' } });
132
+ const r = await req({ port, method: 'POST', path: '/api/auth/admin/login', body: { email, password: 'myPass' } });
133
+ assert.strictEqual(r.status, 200);
134
+ assert.ok(r.body.token);
135
+ });
136
+
137
+ it('login returns 401 for wrong password', async () => {
138
+ const email = `integ-badpw-${Date.now()}@test.com`;
139
+ await req({ port, method: 'POST', path: '/api/auth/admin/signup', body: { email, password: 'correct' } });
140
+ const r = await req({ port, method: 'POST', path: '/api/auth/admin/login', body: { email, password: 'wrong' } });
141
+ assert.strictEqual(r.status, 401);
142
+ });
143
+
144
+ it('GET /me returns current user', async () => {
145
+ const r = await req({
146
+ port, path: '/api/auth/admin/me',
147
+ headers: { Authorization: `Bearer ${adminToken}` },
148
+ });
149
+ assert.strictEqual(r.status, 200);
150
+ assert.strictEqual(r.body.id, adminId);
151
+ });
152
+
153
+ it('GET /me returns 401 without token', async () => {
154
+ const r = await req({ port, path: '/api/auth/admin/me' });
155
+ assert.strictEqual(r.status, 401);
156
+ });
157
+ });
158
+
159
+ // ── CRUD – Post collection ─────────────────────────────────────────────────
160
+
161
+ describe('CRUD – Post collection', () => {
162
+ let postId;
163
+
164
+ it('POST creates a record (admin-restricted)', async () => {
165
+ const r = await req({
166
+ port, method: 'POST', path: '/api/collections/post',
167
+ body: { title: 'Integration Test Post', content: 'Some content', published: true },
168
+ headers: { Authorization: `Bearer ${adminToken}` },
169
+ });
170
+ assert.strictEqual(r.status, 201, JSON.stringify(r.body));
171
+ assert.ok(r.body.id);
172
+ assert.strictEqual(r.body.title, 'Integration Test Post');
173
+ assert.ok(r.body.createdAt);
174
+ assert.ok(r.body.updatedAt);
175
+ postId = r.body.id;
176
+ });
177
+
178
+ it('POST returns 401 without token', async () => {
179
+ const r = await req({
180
+ port, method: 'POST', path: '/api/collections/post',
181
+ body: { title: 'No Auth Post', content: 'x' },
182
+ });
183
+ assert.strictEqual(r.status, 401);
184
+ });
185
+
186
+ it('GET list returns paginated data (public)', async () => {
187
+ const r = await req({ port, path: '/api/collections/post' });
188
+ assert.strictEqual(r.status, 200);
189
+ assert.ok(Array.isArray(r.body.data));
190
+ assert.ok(typeof r.body.total === 'number');
191
+ assert.ok(typeof r.body.currentPage === 'number');
192
+ assert.ok(r.body.total >= 1, 'should have at least the post we created');
193
+ });
194
+
195
+ it('GET list respects pagination params', async () => {
196
+ const r = await req({ port, path: '/api/collections/post?page=1&perPage=1' });
197
+ assert.strictEqual(r.status, 200);
198
+ assert.strictEqual(r.body.perPage, 1);
199
+ assert.ok(r.body.data.length <= 1);
200
+ });
201
+
202
+ it('GET by id returns the record (public)', async () => {
203
+ const r = await req({ port, path: `/api/collections/post/${postId}` });
204
+ assert.strictEqual(r.status, 200);
205
+ assert.strictEqual(r.body.id, postId);
206
+ assert.strictEqual(r.body.title, 'Integration Test Post');
207
+ });
208
+
209
+ it('GET by id returns 404 for unknown id', async () => {
210
+ const r = await req({ port, path: '/api/collections/post/nonexistent-id' });
211
+ assert.strictEqual(r.status, 404);
212
+ });
213
+
214
+ it('PATCH updates a field (admin-restricted)', async () => {
215
+ const r = await req({
216
+ port, method: 'PATCH', path: `/api/collections/post/${postId}`,
217
+ body: { title: 'Updated Title' },
218
+ headers: { Authorization: `Bearer ${adminToken}` },
219
+ });
220
+ assert.strictEqual(r.status, 200, JSON.stringify(r.body));
221
+ assert.strictEqual(r.body.title, 'Updated Title');
222
+ assert.strictEqual(r.body.content, 'Some content', 'other fields should not change');
223
+ });
224
+
225
+ it('PATCH returns 401 without token', async () => {
226
+ const r = await req({
227
+ port, method: 'PATCH', path: `/api/collections/post/${postId}`,
228
+ body: { title: 'Unauthorized' },
229
+ });
230
+ assert.strictEqual(r.status, 401);
231
+ });
232
+
233
+ it('DELETE removes the record (admin-restricted)', async () => {
234
+ const r = await req({
235
+ port, method: 'DELETE', path: `/api/collections/post/${postId}`,
236
+ headers: { Authorization: `Bearer ${adminToken}` },
237
+ });
238
+ assert.strictEqual(r.status, 200);
239
+ assert.strictEqual(r.body.id, postId);
240
+ });
241
+
242
+ it('GET by id returns 404 after deletion', async () => {
243
+ const r = await req({ port, path: `/api/collections/post/${postId}` });
244
+ assert.strictEqual(r.status, 404);
245
+ });
246
+ });
247
+
248
+ // ── Relations – Comment belongsTo Post ────────────────────────────────────
249
+
250
+ describe('Relations – Comment belongsTo Post', () => {
251
+ let parentPostId, commentId;
252
+
253
+ before(async () => {
254
+ // Create a parent post
255
+ const r = await req({
256
+ port, method: 'POST', path: '/api/collections/post',
257
+ body: { title: 'Parent Post', content: 'For comments', published: true },
258
+ headers: { Authorization: `Bearer ${adminToken}` },
259
+ });
260
+ parentPostId = r.body.id;
261
+ });
262
+
263
+ it('POST comment with post FK', async () => {
264
+ const r = await req({
265
+ port, method: 'POST', path: '/api/collections/comment',
266
+ body: { text: 'Great post!', post_id: parentPostId },
267
+ headers: { Authorization: `Bearer ${adminToken}` },
268
+ });
269
+ assert.strictEqual(r.status, 201, JSON.stringify(r.body));
270
+ assert.ok(r.body.id);
271
+ assert.strictEqual(r.body.post_id, parentPostId);
272
+ commentId = r.body.id;
273
+ });
274
+
275
+ it('GET comment with ?relations=Post resolves parent', async () => {
276
+ const r = await req({ port, path: `/api/collections/comment/${commentId}?relations=Post` });
277
+ assert.strictEqual(r.status, 200);
278
+ assert.ok(r.body.Post, 'Post relation should be loaded');
279
+ assert.strictEqual(r.body.Post.id, parentPostId);
280
+ });
281
+
282
+ it('GET post list with ?relations=comment resolves children', async () => {
283
+ const r = await req({ port, path: `/api/collections/post/${parentPostId}?relations=comment` });
284
+ assert.strictEqual(r.status, 200);
285
+ assert.ok(Array.isArray(r.body.comment), 'comment relation should be an array');
286
+ assert.ok(r.body.comment.length >= 1);
287
+ });
288
+ });
289
+
290
+ // ── API Keys ───────────────────────────────────────────────────────────────
291
+
292
+ describe('API Keys', () => {
293
+ let apiKey, apiKeyId;
294
+
295
+ it('creates an API key', async () => {
296
+ const r = await req({
297
+ port, method: 'POST', path: '/api/auth/admin/api-keys',
298
+ body: { name: 'IntegKey', permissions: ['read'], entities: [] },
299
+ headers: { Authorization: `Bearer ${adminToken}` },
300
+ });
301
+ assert.strictEqual(r.status, 201);
302
+ assert.ok(r.body.key.startsWith('cs_'));
303
+ assert.strictEqual(r.body.record.name, 'IntegKey');
304
+ assert.ok(!r.body.record.keyHash, 'keyHash must not be exposed');
305
+ apiKey = r.body.key;
306
+ apiKeyId = r.body.record.id;
307
+ });
308
+
309
+ it('API key authenticates a public route', async () => {
310
+ const r = await req({
311
+ port, path: '/api/collections/post',
312
+ headers: { Authorization: `Bearer ${apiKey}` },
313
+ });
314
+ assert.strictEqual(r.status, 200);
315
+ });
316
+
317
+ it('API key is rejected for a write operation when permission is read-only', async () => {
318
+ const r = await req({
319
+ port, method: 'POST', path: '/api/collections/post',
320
+ body: { title: 'Should fail', content: 'x' },
321
+ headers: { Authorization: `Bearer ${apiKey}` },
322
+ });
323
+ // read-only key: create is not allowed
324
+ assert.strictEqual(r.status, 403);
325
+ });
326
+
327
+ it('lists own API keys', async () => {
328
+ const r = await req({
329
+ port, path: '/api/auth/admin/api-keys',
330
+ headers: { Authorization: `Bearer ${adminToken}` },
331
+ });
332
+ assert.strictEqual(r.status, 200);
333
+ assert.ok(Array.isArray(r.body));
334
+ assert.ok(r.body.some((k) => k.id === apiKeyId));
335
+ assert.ok(r.body.every((k) => !k.keyHash), 'keyHash must never be exposed');
336
+ });
337
+
338
+ it('deletes the API key', async () => {
339
+ const r = await req({
340
+ port, method: 'DELETE', path: `/api/auth/admin/api-keys/${apiKeyId}`,
341
+ headers: { Authorization: `Bearer ${adminToken}` },
342
+ });
343
+ assert.strictEqual(r.status, 200);
344
+ assert.ok(r.body.success);
345
+ });
346
+
347
+ it('deleted API key is rejected on restricted route', async () => {
348
+ // Use /api/auth/admin/me which requires valid auth
349
+ const r = await req({
350
+ port, path: '/api/auth/admin/me',
351
+ headers: { Authorization: `Bearer ${apiKey}` },
352
+ });
353
+ assert.strictEqual(r.status, 401);
354
+ });
355
+ });
356
+
357
+ // ── Admin schema ───────────────────────────────────────────────────────────
358
+
359
+ describe('GET /admin/schema', () => {
360
+ it('returns entities and userCollections', async () => {
361
+ const r = await req({ port, path: '/admin/schema' });
362
+ assert.strictEqual(r.status, 200);
363
+ assert.ok(Array.isArray(r.body.entities));
364
+ assert.ok(Array.isArray(r.body.userCollections));
365
+ assert.ok(r.body.userCollections.every((e) => e.authenticable));
366
+ });
367
+ });
368
+ });
@@ -25,7 +25,7 @@ describe('runMiddlewares – SDK injection', () => {
25
25
 
26
26
  before(async () => {
27
27
  tmp = path.join(os.tmpdir(), `chadstart-mw-${Date.now()}.db`);
28
- dbModule.initDb(mwCore, tmp);
28
+ await dbModule.initDb(mwCore, tmp);
29
29
 
30
30
  functionsDir = path.join(os.tmpdir(), `chadstart-functions-${Date.now()}`);
31
31
  fs.mkdirSync(functionsDir, { recursive: true });
package/test/sdk.test.js CHANGED
@@ -18,10 +18,10 @@ describe('createBackendSdk', () => {
18
18
  },
19
19
  });
20
20
 
21
- before(() => {
21
+ before(async () => {
22
22
  tmp = path.join(os.tmpdir(), `chadstart-sdk-${Date.now()}.db`);
23
- dbModule.initDb(sdkCore, tmp);
24
- dbModule.create('config', { value: 'initial' });
23
+ await dbModule.initDb(sdkCore, tmp);
24
+ await dbModule.create('config', { value: 'initial' });
25
25
  sdk = createBackendSdk(sdkCore);
26
26
  });
27
27
 
@@ -37,30 +37,30 @@ describe('createBackendSdk', () => {
37
37
  assert.strictEqual(typeof iface.delete, 'function');
38
38
  });
39
39
 
40
- it('from().create and findOneById work', () => {
41
- const book = sdk.from('book').create({ title: 'Dune', author: 'Herbert' });
40
+ it('from().create and findOneById work', async () => {
41
+ const book = await sdk.from('book').create({ title: 'Dune', author: 'Herbert' });
42
42
  assert.strictEqual(book.title, 'Dune');
43
- const found = sdk.from('book').findOneById(book.id);
43
+ const found = await sdk.from('book').findOneById(book.id);
44
44
  assert.strictEqual(found.author, 'Herbert');
45
45
  });
46
46
 
47
- it('from().find returns paginated result', () => {
48
- const result = sdk.from('book').find();
47
+ it('from().find returns paginated result', async () => {
48
+ const result = await sdk.from('book').find();
49
49
  assert.ok(Array.isArray(result.data));
50
50
  assert.ok(typeof result.total === 'number');
51
51
  });
52
52
 
53
- it('from().patch updates a field', () => {
54
- const book = sdk.from('book').create({ title: 'Old Title', author: 'Author' });
55
- const updated = sdk.from('book').patch(book.id, { title: 'New Title' });
53
+ it('from().patch updates a field', async () => {
54
+ const book = await sdk.from('book').create({ title: 'Old Title', author: 'Author' });
55
+ const updated = await sdk.from('book').patch(book.id, { title: 'New Title' });
56
56
  assert.strictEqual(updated.title, 'New Title');
57
57
  assert.strictEqual(updated.author, 'Author');
58
58
  });
59
59
 
60
- it('from().delete removes a record', () => {
61
- const book = sdk.from('book').create({ title: 'To Delete', author: 'X' });
62
- sdk.from('book').delete(book.id);
63
- assert.strictEqual(sdk.from('book').findOneById(book.id), null);
60
+ it('from().delete removes a record', async () => {
61
+ const book = await sdk.from('book').create({ title: 'To Delete', author: 'X' });
62
+ await sdk.from('book').delete(book.id);
63
+ assert.strictEqual(await sdk.from('book').findOneById(book.id), null);
64
64
  });
65
65
 
66
66
  it('from() throws for unknown slug', () => {
@@ -74,13 +74,13 @@ describe('createBackendSdk', () => {
74
74
  assert.strictEqual(typeof iface.patch, 'function');
75
75
  });
76
76
 
77
- it('single().get retrieves the record', () => {
78
- const record = sdk.single('config').get();
77
+ it('single().get retrieves the record', async () => {
78
+ const record = await sdk.single('config').get();
79
79
  assert.strictEqual(record.value, 'initial');
80
80
  });
81
81
 
82
- it('single().patch updates a field', () => {
83
- const updated = sdk.single('config').patch({ value: 'changed' });
82
+ it('single().patch updates a field', async () => {
83
+ const updated = await sdk.single('config').patch({ value: 'changed' });
84
84
  assert.strictEqual(updated.value, 'changed');
85
85
  });
86
86
 
@@ -35,7 +35,7 @@ describe('seeder', () => {
35
35
 
36
36
  before(async () => {
37
37
  seedDbPath = path.join(os.tmpdir(), `chadstart-seed-${Date.now()}.db`);
38
- dbModule.initDb(seedCore, seedDbPath);
38
+ await dbModule.initDb(seedCore, seedDbPath);
39
39
  firstSeedResult = await seedAll(seedCore);
40
40
  });
41
41
 
@@ -46,23 +46,23 @@ describe('seeder', () => {
46
46
  assert.strictEqual(firstSeedResult.summary.Article, 5);
47
47
  });
48
48
 
49
- it('seedAll inserts rows into the database', () => {
50
- const authors = dbModule.findAll('author', {}, { perPage: 100 });
49
+ it('seedAll inserts rows into the database', async () => {
50
+ const authors = await dbModule.findAll('author', {}, { perPage: 100 });
51
51
  assert.ok(authors.total >= 3);
52
- const articles = dbModule.findAll('article', {}, { perPage: 100 });
52
+ const articles = await dbModule.findAll('article', {}, { perPage: 100 });
53
53
  assert.ok(articles.total >= 5);
54
54
  });
55
55
 
56
- it('seedAll creates authenticable records with email field', () => {
57
- const authors = dbModule.findAll('author', {}, { perPage: 100 });
56
+ it('seedAll creates authenticable records with email field', async () => {
57
+ const authors = await dbModule.findAll('author', {}, { perPage: 100 });
58
58
  for (const a of authors.data) {
59
59
  assert.ok(typeof a.email === 'string' && a.email.includes('@'));
60
60
  assert.ok(typeof a.password === 'string' && a.password.length > 0);
61
61
  }
62
62
  });
63
63
 
64
- it('seedAll links belongsTo FK to a seeded parent', () => {
65
- const articles = dbModule.findAll('article', {}, { perPage: 100 });
64
+ it('seedAll links belongsTo FK to a seeded parent', async () => {
65
+ const articles = await dbModule.findAll('article', {}, { perPage: 100 });
66
66
  for (const art of articles.data) {
67
67
  assert.ok(art.author_id !== null && art.author_id !== undefined);
68
68
  }
@@ -74,12 +74,12 @@ describe('seeder', () => {
74
74
  entities: { Tag: { properties: ['label'] } },
75
75
  });
76
76
  const defaultDbPath = path.join(os.tmpdir(), `chadstart-seed-default-${Date.now()}.db`);
77
- dbModule.initDb(defaultCore, defaultDbPath);
77
+ await dbModule.initDb(defaultCore, defaultDbPath);
78
78
  const result = await seedAll(defaultCore);
79
79
  assert.strictEqual(result.summary.Tag, 50);
80
80
  fs.unlinkSync(defaultDbPath);
81
81
  // Restore the original seedCore DB for subsequent tests in this describe block
82
- dbModule.initDb(seedCore, seedDbPath);
82
+ await dbModule.initDb(seedCore, seedDbPath);
83
83
  });
84
84
 
85
85
  it('seedAll creates admin@chadstart.com in authenticable entities', () => {
@@ -88,9 +88,9 @@ describe('seeder', () => {
88
88
  assert.strictEqual(firstSeedResult.adminPassword, ADMIN_PASSWORD);
89
89
  });
90
90
 
91
- it('seedAll creates admin user with correct email in the database', () => {
92
- dbModule.initDb(seedCore, seedDbPath);
93
- const admins = dbModule.findAllSimple('author', { email: ADMIN_EMAIL });
91
+ it('seedAll creates admin user with correct email in the database', async () => {
92
+ await dbModule.initDb(seedCore, seedDbPath);
93
+ const admins = await dbModule.findAllSimple('author', { email: ADMIN_EMAIL });
94
94
  assert.strictEqual(admins.length, 1);
95
95
  assert.strictEqual(admins[0].email, ADMIN_EMAIL);
96
96
  });
@@ -107,10 +107,10 @@ describe('seeder', () => {
107
107
  },
108
108
  });
109
109
  const freshDbPath = path.join(os.tmpdir(), `chadstart-seed-admin-${Date.now()}.db`);
110
- dbModule.initDb(freshCore, freshDbPath);
110
+ await dbModule.initDb(freshCore, freshDbPath);
111
111
  const result = await seedAll(freshCore);
112
112
  assert.ok(result.adminEntities.includes('User'));
113
- const admins = dbModule.findAllSimple('user', { email: ADMIN_EMAIL });
113
+ const admins = await dbModule.findAllSimple('user', { email: ADMIN_EMAIL });
114
114
  assert.strictEqual(admins.length, 1);
115
115
  assert.strictEqual(admins[0].email, ADMIN_EMAIL);
116
116
  fs.unlinkSync(freshDbPath);
@@ -129,9 +129,9 @@ describe('seeder', () => {
129
129
  },
130
130
  });
131
131
  const dupDbPath = path.join(os.tmpdir(), `chadstart-seed-dup-${Date.now()}.db`);
132
- dbModule.initDb(dupCore, dupDbPath);
132
+ await dbModule.initDb(dupCore, dupDbPath);
133
133
  // Manually create the admin user before seeding
134
- dbModule.create('member', {
134
+ await dbModule.create('member', {
135
135
  email: ADMIN_EMAIL,
136
136
  password: bcrypt.hashSync(ADMIN_PASSWORD, 10),
137
137
  name: 'pre-existing admin',
@@ -139,7 +139,7 @@ describe('seeder', () => {
139
139
  // seedAll should not create a duplicate
140
140
  const result = await seedAll(dupCore);
141
141
  assert.strictEqual(result.adminEntities.length, 0);
142
- const admins = dbModule.findAllSimple('member', { email: ADMIN_EMAIL });
142
+ const admins = await dbModule.findAllSimple('member', { email: ADMIN_EMAIL });
143
143
  assert.strictEqual(admins.length, 1);
144
144
  fs.unlinkSync(dupDbPath);
145
145
  });
@@ -177,9 +177,9 @@ describe('seeder – property types', () => {
177
177
  },
178
178
  });
179
179
 
180
- before(() => {
180
+ before(async () => {
181
181
  tmp = path.join(os.tmpdir(), `chadstart-seedtypes-${Date.now()}.db`);
182
- dbModule.initDb(core, tmp);
182
+ await dbModule.initDb(core, tmp);
183
183
  });
184
184
 
185
185
  after(() => { fs.unlinkSync(tmp); });
@@ -187,7 +187,7 @@ describe('seeder – property types', () => {
187
187
  it('seedAll generates values for every property type', async () => {
188
188
  const result = await seedAll(core);
189
189
  assert.strictEqual(result.summary.Sample, 3);
190
- const rows = dbModule.findAll('sample', {}, { perPage: 100 });
190
+ const rows = await dbModule.findAll('sample', {}, { perPage: 100 });
191
191
  assert.strictEqual(rows.total, 3);
192
192
  const r = rows.data[0];
193
193
  assert.ok(typeof r.myText === 'string' && r.myText.length > 0);
@@ -217,7 +217,7 @@ describe('seeder – property types', () => {
217
217
  entities: { Config: { single: true, properties: ['key', 'value'] } },
218
218
  });
219
219
  const singleTmp = path.join(os.tmpdir(), `chadstart-seedsingle-${Date.now()}.db`);
220
- dbModule.initDb(singleCore, singleTmp);
220
+ await dbModule.initDb(singleCore, singleTmp);
221
221
  const result = await seedAll(singleCore);
222
222
  assert.strictEqual(result.summary.Config, 1);
223
223
  fs.unlinkSync(singleTmp);
@@ -242,9 +242,9 @@ describe('seeder – authenticable entities with explicit email/password propert
242
242
  },
243
243
  });
244
244
 
245
- before(() => {
245
+ before(async () => {
246
246
  tmp = path.join(os.tmpdir(), `chadstart-authprop-${Date.now()}.db`);
247
- dbModule.initDb(core, tmp);
247
+ await dbModule.initDb(core, tmp);
248
248
  });
249
249
 
250
250
  after(() => { fs.unlinkSync(tmp); });
@@ -263,7 +263,7 @@ describe('seeder – authenticable entities with explicit email/password propert
263
263
  it('seedAll succeeds and creates records with valid email addresses', async () => {
264
264
  const result = await seedAll(core);
265
265
  assert.strictEqual(result.summary.Customer, 3);
266
- const rows = dbModule.findAll('customer', {}, { perPage: 100 });
266
+ const rows = await dbModule.findAll('customer', {}, { perPage: 100 });
267
267
  assert.ok(rows.total >= 3);
268
268
  for (const r of rows.data) {
269
269
  assert.ok(typeof r.email === 'string' && r.email.includes('@'), `email should contain @, got: ${r.email}`);
@@ -272,7 +272,7 @@ describe('seeder – authenticable entities with explicit email/password propert
272
272
  });
273
273
 
274
274
  it('seedAll creates admin user with correct email when entity has explicit email property', async () => {
275
- const admins = dbModule.findAllSimple('customer', { email: ADMIN_EMAIL });
275
+ const admins = await dbModule.findAllSimple('customer', { email: ADMIN_EMAIL });
276
276
  assert.strictEqual(admins.length, 1);
277
277
  assert.strictEqual(admins[0].email, ADMIN_EMAIL);
278
278
  });