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.
package/core/seeder.js CHANGED
@@ -192,7 +192,7 @@ async function seedAll(core) {
192
192
  }
193
193
 
194
194
  try {
195
- const created = create(entity.tableName, record);
195
+ const created = await create(entity.tableName, record);
196
196
  ids.push(created.id);
197
197
  } catch (err) {
198
198
  logger.warn(`Seed: failed to create record for ${entityName}:`, err.message);
@@ -206,7 +206,7 @@ async function seedAll(core) {
206
206
  // Create the admin@chadstart.com user in every authenticable entity
207
207
  const adminUsers = [];
208
208
  for (const entity of Object.values(core.authenticableEntities || {})) {
209
- const existing = findAllSimple(entity.tableName, { email: ADMIN_EMAIL });
209
+ const existing = await findAllSimple(entity.tableName, { email: ADMIN_EMAIL });
210
210
  if (existing.length === 0) {
211
211
  const extraProps = entity.properties.reduce((acc, prop) => {
212
212
  // Skip email and password — they are handled separately for authenticable entities
@@ -216,7 +216,7 @@ async function seedAll(core) {
216
216
  }
217
217
  return acc;
218
218
  }, {});
219
- create(entity.tableName, {
219
+ await create(entity.tableName, {
220
220
  email: ADMIN_EMAIL,
221
221
  password: bcrypt.hashSync(ADMIN_PASSWORD, 10),
222
222
  ...extraProps,
package/docs/config.md CHANGED
@@ -42,13 +42,13 @@ We recommend switching to [PostgreSQL](https://www.postgresql.org/) or [MySQL](h
42
42
 
43
43
  | Variable | Default | Description | Applies To |
44
44
  | ------------- | ---------------------- | ------------------------------------------------------------------------- | ------------------ |
45
- | DB_CONNECTION | `sqlite` | Choose `postgres` switching to PostgreSQL or `mysql` for MySQL or MariaDB | All |
46
- | DB_PATH | `/.chadstart/db.sqlite` | Path of the database. Your server should have access to this path locally | SQLite |
45
+ | DB_ENGINE | `sqlite` | Choose `postgres` switching to PostgreSQL or `mysql` for MySQL or MariaDB | All |
46
+ | DB_PATH | `/data/chadstart.db` | Path of the database. Your server should have access to this path locally | SQLite |
47
47
  | DB_HOST | `localhost` | Database host | PostgreSQL / MySQL |
48
48
  | DB_PORT | `5432` | Database port | PostgreSQL / MySQL |
49
49
  | DB_USERNAME | `postgres` | Database username | PostgreSQL / MySQL |
50
50
  | DB_PASSWORD | `postgres` | Database password | PostgreSQL / MySQL |
51
- | DB_DATABASE | `chadstart` | Database name | PostgreSQL / MySQL |
51
+ | DB_DATABASE | `manifest` | Database name | PostgreSQL / MySQL |
52
52
  | DB_SSL | `false` | Require SSL for DB connection. Set to true if using remote DB. | PostgreSQL / MySQL |
53
53
 
54
54
  ### Example configurations
@@ -58,16 +58,16 @@ Here are examples of `.env` files for different database connections:
58
58
  === "SQLite"
59
59
  ```env
60
60
 
61
- DB_CONNECTION=sqlite
61
+ DB_ENGINE=sqlite
62
62
 
63
- DB_PATH=/.chadstart/db.sqlite
63
+ DB_PATH=/data/chadstart.db
64
64
 
65
65
  ```
66
66
 
67
67
  === "PostgreSQL"
68
68
  ```env
69
69
 
70
- DB_CONNECTION=postgres
70
+ DB_ENGINE=postgres
71
71
 
72
72
  DB_HOST=my-host.com
73
73
  DB_USERNAME=owner
@@ -80,12 +80,12 @@ Here are examples of `.env` files for different database connections:
80
80
  === "MySQL / MariaDB"
81
81
  ```env
82
82
 
83
- DB_CONNECTION=mysql
83
+ DB_ENGINE=mysql
84
84
 
85
85
  DB_USERNAME=xxxxx
86
86
  DB_PASSWORD=xxxxx
87
87
  DB_HOST=my-host.com
88
- DB_PORT=25060
88
+ DB_PORT=3306
89
89
  DB_DATABASE=my_app
90
90
  DB_SSL=true # Required for remote managed DBs, remove if local
91
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chadstart",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "YAML-first Backend as a Service — define your entire backend in one YAML file",
5
5
  "main": "server/express-server.js",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "seed": "node cli/cli.js seed",
15
15
  "test": "mocha --timeout 10000 'test/*.test.js'",
16
16
  "test:coverage": "c8 --reporter=text --reporter=json-summary mocha --timeout 10000 'test/*.test.js'",
17
+ "test:integration": "mocha --timeout 30000 'test/integration/*.test.js'",
17
18
  "test:browser": "playwright test"
18
19
  },
19
20
  "repository": {
@@ -48,7 +49,9 @@
48
49
  "express-rate-limit": "^8.3.1",
49
50
  "htmx.org": "2.0.4",
50
51
  "jsonwebtoken": "^9.0.3",
52
+ "mysql2": "^3.20.0",
51
53
  "node-cron": "^4.2.1",
54
+ "pg": "^8.20.0",
52
55
  "sharp": "^0.34.5",
53
56
  "swagger-ui-express": "^5.0.1",
54
57
  "ws": "^8.19.0",
@@ -101,8 +101,8 @@ async function buildApp(yamlPath, reloadFn) {
101
101
  const dbPath = core.database
102
102
  ? path.resolve(path.dirname(yamlPath), core.database)
103
103
  : undefined;
104
- initDb(core, dbPath);
105
- initApiKeys();
104
+ await initDb(core, dbPath);
105
+ await initApiKeys();
106
106
 
107
107
  initErrorReporter(core);
108
108
 
@@ -300,7 +300,7 @@ async function buildApp(yamlPath, reloadFn) {
300
300
  });
301
301
 
302
302
  // HTMX table partial – returns an HTML fragment used by the Admin UI
303
- app.get('/admin/partials/table', adminRateLimiter, (req, res) => {
303
+ app.get('/admin/partials/table', adminRateLimiter, async (req, res) => {
304
304
  const header = req.headers.authorization;
305
305
  if (!header || !header.startsWith('Bearer ')) {
306
306
  return res.status(401).send('<p class="text-red-400 p-4">Unauthorized</p>');
@@ -317,7 +317,7 @@ async function buildApp(yamlPath, reloadFn) {
317
317
  const lang = parseLang(req.headers['accept-language']);
318
318
  const locale = loadLocale(lang);
319
319
  try {
320
- let rows = findAllSimple(item.tableName);
320
+ let rows = await findAllSimple(item.tableName);
321
321
  if (type === 'collection') rows = rows.map(omitPassword);
322
322
  res.send(renderAdminTable(rows, name, type === 'collection', item.name, locale));
323
323
  } catch (err) {
@@ -325,7 +325,7 @@ async function buildApp(yamlPath, reloadFn) {
325
325
  }
326
326
  });
327
327
  // ── Admin stats endpoint ────────────────────────────────────────────
328
- app.get('/admin/stats', adminRateLimiter, (req, res) => {
328
+ app.get('/admin/stats', adminRateLimiter, async (req, res) => {
329
329
  const header = req.headers.authorization;
330
330
  if (!header || !header.startsWith('Bearer ')) return res.status(401).json({ error: 'Unauthorized' });
331
331
  try { verifyToken(header.slice(7)); } catch { return res.status(401).json({ error: 'Invalid token' }); }
@@ -338,7 +338,7 @@ async function buildApp(yamlPath, reloadFn) {
338
338
  const allRecords = [];
339
339
  for (const entity of allEntities) {
340
340
  try {
341
- const rows = findAllSimple(entity.tableName);
341
+ const rows = await findAllSimple(entity.tableName);
342
342
  const total = rows.length;
343
343
  const lastWeek = rows.filter((r) => r.createdAt && new Date(r.createdAt) >= oneWeekAgo).length;
344
344
  const lastMonth = rows.filter((r) => r.createdAt && new Date(r.createdAt) >= oneMonthAgo).length;
@@ -368,7 +368,7 @@ async function buildApp(yamlPath, reloadFn) {
368
368
  });
369
369
 
370
370
  // ── Admin seed endpoint ─────────────────────────────────────────────
371
- app.post('/admin/seed', adminRateLimiter, (req, res) => {
371
+ app.post('/admin/seed', adminRateLimiter, async (req, res) => {
372
372
  const header = req.headers.authorization;
373
373
  if (!header || !header.startsWith('Bearer ')) return res.status(401).json({ error: 'Unauthorized' });
374
374
  try { verifyToken(header.slice(7)); } catch { return res.status(401).json({ error: 'Invalid token' }); }
@@ -393,7 +393,7 @@ async function buildApp(yamlPath, reloadFn) {
393
393
  default: record[pName] = `Sample ${pName} ${i}`;
394
394
  }
395
395
  }
396
- try { const row = dbCreate(tableName, record); emit(`${name}.created`, row); created++; } catch (e) { logger.warn(`Seed: failed to create record for ${name}:`, e.message); }
396
+ try { const row = await dbCreate(tableName, record); emit(`${name}.created`, row); created++; } catch (e) { logger.warn(`Seed: failed to create record for ${name}:`, e.message); }
397
397
  }
398
398
  results.push({ name, created });
399
399
  }
@@ -401,7 +401,7 @@ async function buildApp(yamlPath, reloadFn) {
401
401
  });
402
402
 
403
403
  // ── Admin data endpoint (unified, auth-bypassing) ───────────────────
404
- app.get('/admin/data', adminRateLimiter, (req, res) => {
404
+ app.get('/admin/data', adminRateLimiter, async (req, res) => {
405
405
  const header = req.headers.authorization;
406
406
  if (!header || !header.startsWith('Bearer ')) return res.status(401).json({ error: 'Unauthorized' });
407
407
  try { verifyToken(header.slice(7)); } catch { return res.status(401).json({ error: 'Invalid token' }); }
@@ -423,7 +423,7 @@ async function buildApp(yamlPath, reloadFn) {
423
423
  query[`${colName}_like`] = `%${search}%`;
424
424
  }
425
425
  }
426
- const result = findAll(item.tableName, query, { page, perPage, orderBy, order });
426
+ const result = await findAll(item.tableName, query, { page, perPage, orderBy, order });
427
427
  if (type === 'collection') result.data = result.data.map(omitPassword);
428
428
  res.json(result);
429
429
  } catch (err) {
@@ -441,18 +441,18 @@ async function buildApp(yamlPath, reloadFn) {
441
441
  }
442
442
 
443
443
  // GET /admin/api-keys — list all API keys
444
- app.get('/admin/api-keys', adminRateLimiter, (req, res) => {
444
+ app.get('/admin/api-keys', adminRateLimiter, async (req, res) => {
445
445
  if (!requireAdminToken(req, res)) return;
446
- try { res.json(listAllApiKeys()); } catch (e) { res.status(500).json({ error: e.message }); }
446
+ try { res.json(await listAllApiKeys()); } catch (e) { res.status(500).json({ error: e.message }); }
447
447
  });
448
448
 
449
449
  // POST /admin/api-keys — create an API key for any user
450
- app.post('/admin/api-keys', adminRateLimiter, (req, res) => {
450
+ app.post('/admin/api-keys', adminRateLimiter, async (req, res) => {
451
451
  if (!requireAdminToken(req, res)) return;
452
452
  const { userId, userEntity, name, permissions, entities: keyEntities, expiresAt } = req.body || {};
453
453
  if (!userId || !userEntity) return res.status(400).json({ error: 'userId and userEntity are required' });
454
454
  try {
455
- const result = createApiKey(userId, userEntity, {
455
+ const result = await createApiKey(userId, userEntity, {
456
456
  name: name || 'API Key',
457
457
  permissions: Array.isArray(permissions) ? permissions : [],
458
458
  entities: Array.isArray(keyEntities) ? keyEntities : [],
@@ -463,13 +463,13 @@ async function buildApp(yamlPath, reloadFn) {
463
463
  });
464
464
 
465
465
  // DELETE /admin/api-keys/:id — delete any API key
466
- app.delete('/admin/api-keys/:id', adminRateLimiter, (req, res) => {
466
+ app.delete('/admin/api-keys/:id', adminRateLimiter, async (req, res) => {
467
467
  if (!requireAdminToken(req, res)) return;
468
- try { deleteApiKey(req.params.id); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); }
468
+ try { await deleteApiKey(req.params.id); res.json({ success: true }); } catch (e) { res.status(500).json({ error: e.message }); }
469
469
  });
470
470
 
471
471
  // POST /admin/impersonate — generate a short-lived token as a user (for admin preview)
472
- app.post('/admin/impersonate', adminRateLimiter, (req, res) => {
472
+ app.post('/admin/impersonate', adminRateLimiter, async (req, res) => {
473
473
  const header = req.headers.authorization;
474
474
  if (!header || !header.startsWith('Bearer ')) return res.status(401).json({ error: 'Unauthorized' });
475
475
  let adminPayload;
@@ -479,7 +479,7 @@ async function buildApp(yamlPath, reloadFn) {
479
479
  const entity = Object.values(core.authenticableEntities || {}).find((e) => e.name === userEntity);
480
480
  if (!entity) return res.status(404).json({ error: 'User collection not found' });
481
481
  const { findById } = require('../core/db');
482
- const user = findById(entity.tableName, userId);
482
+ const user = await findById(entity.tableName, userId);
483
483
  if (!user) return res.status(404).json({ error: 'User not found' });
484
484
  const { signToken } = require('../core/auth');
485
485
  const token = signToken(
@@ -74,19 +74,19 @@ describe('access-policies – read condition: self', () => {
74
74
  assert.strictEqual(req._selfFilter.userId, 'user-42');
75
75
  });
76
76
 
77
- it('read condition:self – DB list is filtered to the owning user', () => {
77
+ it('read condition:self – DB list is filtered to the owning user', async () => {
78
78
  const tmp = path.join(os.tmpdir(), `chadstart-selfread-${Date.now()}.db`);
79
- dbModule.initDb(selfReadCore, tmp);
79
+ await dbModule.initDb(selfReadCore, tmp);
80
80
 
81
- const user1 = dbModule.create('user', { name: 'Alice', email: 'alice@example.com', password: bcrypt.hashSync('pass1', 1) });
82
- const user2 = dbModule.create('user', { name: 'Bob', email: 'bob@example.com', password: bcrypt.hashSync('pass2', 1) });
83
- dbModule.create('project', { title: 'Alice Project 1', user_id: user1.id });
84
- dbModule.create('project', { title: 'Alice Project 2', user_id: user1.id });
85
- dbModule.create('project', { title: 'Bob Project', user_id: user2.id });
81
+ const user1 = await dbModule.create('user', { name: 'Alice', email: 'alice@example.com', password: bcrypt.hashSync('pass1', 1) });
82
+ const user2 = await dbModule.create('user', { name: 'Bob', email: 'bob@example.com', password: bcrypt.hashSync('pass2', 1) });
83
+ await dbModule.create('project', { title: 'Alice Project 1', user_id: user1.id });
84
+ await dbModule.create('project', { title: 'Alice Project 2', user_id: user1.id });
85
+ await dbModule.create('project', { title: 'Bob Project', user_id: user2.id });
86
86
 
87
87
  const selfFilter = { fk: 'user_id', userId: user1.id };
88
88
  const query = { [selfFilter.fk]: selfFilter.userId };
89
- const result = dbModule.findAll('project', query, { perPage: 100 });
89
+ const result = await dbModule.findAll('project', query, { perPage: 100 });
90
90
 
91
91
  assert.strictEqual(result.total, 2, 'only Alice\'s projects returned');
92
92
  assert.ok(result.data.every((r) => r.user_id === user1.id));
@@ -71,8 +71,8 @@ describe('API Keys', function () {
71
71
  initApiKeys, createApiKey, verifyApiKeyStr, listApiKeys, deleteApiKey,
72
72
  } = require('../core/auth');
73
73
 
74
- it('createApiKey returns a key string and record', function () {
75
- const { key, record } = createApiKey('user-1', 'Admin', { name: 'TestKey', permissions: ['read'], entities: [] });
74
+ it('createApiKey returns a key string and record', async function () {
75
+ const { key, record } = await createApiKey('user-1', 'Admin', { name: 'TestKey', permissions: ['read'], entities: [] });
76
76
  assert.ok(key.startsWith('cs_'), 'key should start with cs_');
77
77
  assert.strictEqual(key.length, 3 + 64, 'key should be cs_ + 64 hex chars');
78
78
  assert.strictEqual(record.name, 'TestKey');
@@ -82,38 +82,38 @@ describe('API Keys', function () {
82
82
  assert.ok(!record.keyHash, 'keyHash should not be in returned record');
83
83
  });
84
84
 
85
- it('verifyApiKeyStr returns record for valid key', function () {
86
- const { key } = createApiKey('user-2', 'Admin', { name: 'Verify' });
87
- const record = verifyApiKeyStr(key);
85
+ it('verifyApiKeyStr returns record for valid key', async function () {
86
+ const { key } = await createApiKey('user-2', 'Admin', { name: 'Verify' });
87
+ const record = await verifyApiKeyStr(key);
88
88
  assert.ok(record, 'should return a record');
89
89
  assert.strictEqual(record.userId, 'user-2');
90
90
  });
91
91
 
92
- it('verifyApiKeyStr returns null for wrong key', function () {
93
- assert.strictEqual(verifyApiKeyStr('cs_notavalidkey'), null);
92
+ it('verifyApiKeyStr returns null for wrong key', async function () {
93
+ assert.strictEqual(await verifyApiKeyStr('cs_notavalidkey'), null);
94
94
  });
95
95
 
96
- it('verifyApiKeyStr returns null for non-cs_ prefixed string', function () {
97
- assert.strictEqual(verifyApiKeyStr('eyJhbGciOiJIUzI1NiJ9.x.y'), null);
96
+ it('verifyApiKeyStr returns null for non-cs_ prefixed string', async function () {
97
+ assert.strictEqual(await verifyApiKeyStr('eyJhbGciOiJIUzI1NiJ9.x.y'), null);
98
98
  });
99
99
 
100
- it('verifyApiKeyStr returns null for expired key', function () {
100
+ it('verifyApiKeyStr returns null for expired key', async function () {
101
101
  const pastDate = new Date(Date.now() - 1000).toISOString();
102
- const { key } = createApiKey('user-3', 'Admin', { expiresAt: pastDate });
103
- assert.strictEqual(verifyApiKeyStr(key), null, 'expired key should return null');
102
+ const { key } = await createApiKey('user-3', 'Admin', { expiresAt: pastDate });
103
+ assert.strictEqual(await verifyApiKeyStr(key), null, 'expired key should return null');
104
104
  });
105
105
 
106
- it('listApiKeys returns user keys', function () {
107
- const { key } = createApiKey('user-list', 'Admin', { name: 'ListKey' });
108
- const keys = listApiKeys('user-list', 'Admin');
106
+ it('listApiKeys returns user keys', async function () {
107
+ await createApiKey('user-list', 'Admin', { name: 'ListKey' });
108
+ const keys = await listApiKeys('user-list', 'Admin');
109
109
  assert.ok(keys.length >= 1);
110
110
  assert.ok(keys.every((k) => !k.keyHash), 'keyHash should be stripped');
111
111
  });
112
112
 
113
- it('deleteApiKey removes the key', function () {
114
- const { record } = createApiKey('user-del', 'Admin', { name: 'ToDelete' });
115
- deleteApiKey(record.id);
116
- const verifiedAfterDelete = listApiKeys('user-del', 'Admin').find((k) => k.id === record.id);
113
+ it('deleteApiKey removes the key', async function () {
114
+ const { record } = await createApiKey('user-del', 'Admin', { name: 'ToDelete' });
115
+ await deleteApiKey(record.id);
116
+ const verifiedAfterDelete = (await listApiKeys('user-del', 'Admin')).find((k) => k.id === record.id);
117
117
  assert.ok(!verifiedAfterDelete, 'key should be deleted');
118
118
  });
119
119
  });
@@ -123,23 +123,23 @@ describe('API Keys', function () {
123
123
  describe('resolveAuthHeader', function () {
124
124
  const { resolveAuthHeader, signToken, createApiKey } = require('../core/auth');
125
125
 
126
- it('resolves JWT Bearer tokens', function () {
126
+ it('resolves JWT Bearer tokens', async function () {
127
127
  const token = signToken({ id: 'u1', entity: 'Admin' });
128
- const { user, error } = resolveAuthHeader(`Bearer ${token}`);
128
+ const { user, error } = await resolveAuthHeader(`Bearer ${token}`);
129
129
  assert.ok(user);
130
130
  assert.strictEqual(user.id, 'u1');
131
131
  assert.strictEqual(error, null);
132
132
  });
133
133
 
134
- it('returns error for no header', function () {
135
- const { user, error } = resolveAuthHeader(undefined);
134
+ it('returns error for no header', async function () {
135
+ const { user, error } = await resolveAuthHeader(undefined);
136
136
  assert.ok(!user);
137
137
  assert.strictEqual(error, 'no_header');
138
138
  });
139
139
 
140
- it('resolves API key Bearer tokens', function () {
141
- const { key } = createApiKey('u-resolve', 'Admin', { permissions: ['read'], entities: ['posts'] });
142
- const { user, apiKeyPermissions, error } = resolveAuthHeader(`Bearer ${key}`);
140
+ it('resolves API key Bearer tokens', async function () {
141
+ const { key } = await createApiKey('u-resolve', 'Admin', { permissions: ['read'], entities: ['posts'] });
142
+ const { user, apiKeyPermissions, error } = await resolveAuthHeader(`Bearer ${key}`);
143
143
  assert.ok(user);
144
144
  assert.strictEqual(user.id, 'u-resolve');
145
145
  assert.strictEqual(error, null);
@@ -147,8 +147,8 @@ describe('API Keys', function () {
147
147
  assert.deepStrictEqual(apiKeyPermissions.entities, ['posts']);
148
148
  });
149
149
 
150
- it('returns error for invalid token', function () {
151
- const { user, error } = resolveAuthHeader('Bearer bad-token-here');
150
+ it('returns error for invalid token', async function () {
151
+ const { user, error } = await resolveAuthHeader('Bearer bad-token-here');
152
152
  assert.ok(!user);
153
153
  assert.strictEqual(error, 'invalid_token');
154
154
  });
package/test/auth.test.js CHANGED
@@ -27,94 +27,94 @@ describe('auth', () => {
27
27
  });
28
28
 
29
29
  describe('auth – middleware', () => {
30
- it('requireAuth: 401 when no Authorization header', () => {
30
+ it('requireAuth: 401 when no Authorization header', async () => {
31
31
  const mw = requireAuth();
32
32
  const req = mockReq();
33
33
  const res = mockRes();
34
34
  let nextCalled = false;
35
- mw(req, res, () => { nextCalled = true; });
35
+ await mw(req, res, () => { nextCalled = true; });
36
36
  assert.strictEqual(res._status, 401);
37
37
  assert.ok(!nextCalled);
38
38
  });
39
39
 
40
- it('requireAuth: 401 when header lacks Bearer prefix', () => {
40
+ it('requireAuth: 401 when header lacks Bearer prefix', async () => {
41
41
  const mw = requireAuth();
42
42
  const req = mockReq({ authorization: 'Basic abc123' });
43
43
  const res = mockRes();
44
44
  let nextCalled = false;
45
- mw(req, res, () => { nextCalled = true; });
45
+ await mw(req, res, () => { nextCalled = true; });
46
46
  assert.strictEqual(res._status, 401);
47
47
  assert.ok(!nextCalled);
48
48
  });
49
49
 
50
- it('requireAuth: 401 for invalid token', () => {
50
+ it('requireAuth: 401 for invalid token', async () => {
51
51
  const mw = requireAuth();
52
52
  const req = mockReq({ authorization: 'Bearer not-a-valid-jwt' });
53
53
  const res = mockRes();
54
54
  let nextCalled = false;
55
- mw(req, res, () => { nextCalled = true; });
55
+ await mw(req, res, () => { nextCalled = true; });
56
56
  assert.strictEqual(res._status, 401);
57
57
  assert.ok(!nextCalled);
58
58
  });
59
59
 
60
- it('requireAuth: 403 when entity does not match', () => {
60
+ it('requireAuth: 403 when entity does not match', async () => {
61
61
  const token = signToken({ id: 'u1', entity: 'Admin' });
62
62
  const mw = requireAuth('User');
63
63
  const req = mockReq({ authorization: `Bearer ${token}` });
64
64
  const res = mockRes();
65
65
  let nextCalled = false;
66
- mw(req, res, () => { nextCalled = true; });
66
+ await mw(req, res, () => { nextCalled = true; });
67
67
  assert.strictEqual(res._status, 403);
68
68
  assert.ok(!nextCalled);
69
69
  });
70
70
 
71
- it('requireAuth: sets req.user and calls next for valid token (with entity filter)', () => {
71
+ it('requireAuth: sets req.user and calls next for valid token (with entity filter)', async () => {
72
72
  const token = signToken({ id: 'u2', entity: 'Admin' });
73
73
  const mw = requireAuth('Admin');
74
74
  const req = mockReq({ authorization: `Bearer ${token}` });
75
75
  const res = mockRes();
76
76
  let nextCalled = false;
77
- mw(req, res, () => { nextCalled = true; });
77
+ await mw(req, res, () => { nextCalled = true; });
78
78
  assert.ok(nextCalled);
79
79
  assert.strictEqual(req.user.id, 'u2');
80
80
  assert.strictEqual(req.user.entity, 'Admin');
81
81
  });
82
82
 
83
- it('requireAuth: sets req.user and calls next without entity filter', () => {
83
+ it('requireAuth: sets req.user and calls next without entity filter', async () => {
84
84
  const token = signToken({ id: 'u3', entity: 'Member' });
85
85
  const mw = requireAuth();
86
86
  const req = mockReq({ authorization: `Bearer ${token}` });
87
87
  const res = mockRes();
88
88
  let nextCalled = false;
89
- mw(req, res, () => { nextCalled = true; });
89
+ await mw(req, res, () => { nextCalled = true; });
90
90
  assert.ok(nextCalled);
91
91
  assert.strictEqual(req.user.entity, 'Member');
92
92
  });
93
93
 
94
- it('optionalAuth: calls next without user when no header', () => {
94
+ it('optionalAuth: calls next without user when no header', async () => {
95
95
  const req = mockReq();
96
96
  const res = mockRes();
97
97
  let nextCalled = false;
98
- optionalAuth(req, res, () => { nextCalled = true; });
98
+ await optionalAuth(req, res, () => { nextCalled = true; });
99
99
  assert.ok(nextCalled);
100
100
  assert.ok(!req.user);
101
101
  });
102
102
 
103
- it('optionalAuth: calls next without user when token is invalid', () => {
103
+ it('optionalAuth: calls next without user when token is invalid', async () => {
104
104
  const req = mockReq({ authorization: 'Bearer bad-token' });
105
105
  const res = mockRes();
106
106
  let nextCalled = false;
107
- optionalAuth(req, res, () => { nextCalled = true; });
107
+ await optionalAuth(req, res, () => { nextCalled = true; });
108
108
  assert.ok(nextCalled);
109
109
  assert.ok(!req.user);
110
110
  });
111
111
 
112
- it('optionalAuth: sets req.user when token is valid', () => {
112
+ it('optionalAuth: sets req.user when token is valid', async () => {
113
113
  const token = signToken({ id: 'u4', entity: 'Guest' });
114
114
  const req = mockReq({ authorization: `Bearer ${token}` });
115
115
  const res = mockRes();
116
116
  let nextCalled = false;
117
- optionalAuth(req, res, () => { nextCalled = true; });
117
+ await optionalAuth(req, res, () => { nextCalled = true; });
118
118
  assert.ok(nextCalled);
119
119
  assert.strictEqual(req.user.id, 'u4');
120
120
  assert.strictEqual(req.user.entity, 'Guest');