create-mantiq 0.5.19 → 0.5.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mantiq",
3
- "version": "0.5.19",
3
+ "version": "0.5.20",
4
4
  "description": "Scaffold a new MantiqJS application",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,14 @@
1
+ import { describe, test } from 'bun:test'
2
+ import { TestCase } from '@mantiq/testing'
3
+
4
+ const t = new TestCase()
5
+ t.setup()
6
+
7
+ describe('API', () => {
8
+ test('GET /api/ping returns ok', async () => {
9
+ const res = await t.client.get('/api/ping')
10
+ res.assertOk()
11
+ await res.assertJson({ status: 'ok' })
12
+ await res.assertJsonHasKey('timestamp')
13
+ })
14
+ })
@@ -0,0 +1,17 @@
1
+ import { describe, test } from 'bun:test'
2
+ import { TestCase } from '@mantiq/testing'
3
+
4
+ const t = new TestCase()
5
+ t.setup()
6
+
7
+ describe('Home', () => {
8
+ test('GET / returns 200', async () => {
9
+ const res = await t.client.get('/')
10
+ res.assertOk()
11
+ })
12
+
13
+ test('GET / returns HTML', async () => {
14
+ const res = await t.client.get('/')
15
+ res.assertHeader('content-type')
16
+ })
17
+ })
@@ -0,0 +1,11 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+
3
+ describe('Example', () => {
4
+ test('true is truthy', () => {
5
+ expect(true).toBe(true)
6
+ })
7
+
8
+ test('math works', () => {
9
+ expect(1 + 1).toBe(2)
10
+ })
11
+ })
package/src/index.ts CHANGED
@@ -203,6 +203,7 @@ if (kit) {
203
203
  { stub: 'shared/app/Http/Requests/UpdateUserRequest.ts.stub', target: 'app/Http/Requests/UpdateUserRequest.ts' },
204
204
  { stub: 'shared/database/seeders/DatabaseSeeder.ts.stub', target: 'database/seeders/DatabaseSeeder.ts' },
205
205
  { stub: 'shared/database/factories/UserFactory.ts.stub', target: 'database/factories/UserFactory.ts' },
206
+ { stub: 'api-only/tests/feature/token-auth.test.ts.stub', target: 'tests/feature/token-auth.test.ts' },
206
207
  ]
207
208
  for (const { stub, target } of apiOnlyFiles) {
208
209
  const src = resolve(stubsDir, stub)
package/src/templates.ts CHANGED
@@ -40,6 +40,7 @@ export function getTemplates(ctx: TemplateContext): Record<string, string> {
40
40
  }
41
41
 
42
42
  const baseDevDeps: Record<string, string> = {
43
+ '@mantiq/testing': '^0.5.0',
43
44
  'bun-types': 'latest',
44
45
  'typescript': '^5.7.0',
45
46
  }
@@ -48,6 +49,7 @@ export function getTemplates(ctx: TemplateContext): Record<string, string> {
48
49
  dev: 'bun run --watch index.ts',
49
50
  start: 'bun run index.ts',
50
51
  mantiq: 'bun run mantiq.ts',
52
+ test: 'bun test tests/',
51
53
  }
52
54
 
53
55
  if (ctx.kit) {
@@ -0,0 +1,69 @@
1
+ import { describe, test, expect } from 'bun:test'
2
+ import { TestCase } from '@mantiq/testing'
3
+
4
+ const t = new TestCase()
5
+ t.refreshDatabase = true
6
+ t.setup()
7
+
8
+ const user = {
9
+ name: 'API User',
10
+ email: 'api@example.com',
11
+ password: 'password123',
12
+ }
13
+
14
+ describe('Token Authentication', () => {
15
+ test('can register and receive a token', async () => {
16
+ const res = await t.client.post('/api/register', user)
17
+ res.assertCreated()
18
+ await res.assertJsonHasKey('token')
19
+ const data = await res.json()
20
+ expect(data.token).toContain('|')
21
+ })
22
+
23
+ test('can login and receive a token', async () => {
24
+ await t.client.post('/api/register', user)
25
+ const res = await t.client.post('/api/login', {
26
+ email: user.email,
27
+ password: user.password,
28
+ })
29
+ res.assertOk()
30
+ await res.assertJsonHasKey('token')
31
+ })
32
+
33
+ test('cannot login with wrong credentials', async () => {
34
+ await t.client.post('/api/register', user)
35
+ const res = await t.client.post('/api/login', {
36
+ email: user.email,
37
+ password: 'wrong',
38
+ })
39
+ res.assertUnauthorized()
40
+ })
41
+
42
+ test('can access protected route with bearer token', async () => {
43
+ const regRes = await t.client.post('/api/register', user)
44
+ const { token } = await regRes.json()
45
+
46
+ t.client.withToken(token)
47
+ const res = await t.client.get('/api/user')
48
+ res.assertOk()
49
+ await res.assertJsonPath('user.email', user.email)
50
+ })
51
+
52
+ test('cannot access protected route without token', async () => {
53
+ const res = await t.client.get('/api/user')
54
+ res.assertUnauthorized()
55
+ })
56
+
57
+ test('can logout (revoke token)', async () => {
58
+ const regRes = await t.client.post('/api/register', user)
59
+ const { token } = await regRes.json()
60
+
61
+ t.client.withToken(token)
62
+ const logoutRes = await t.client.post('/api/logout')
63
+ logoutRes.assertOk()
64
+
65
+ // Token should be revoked
66
+ const userRes = await t.client.get('/api/user')
67
+ userRes.assertUnauthorized()
68
+ })
69
+ })
@@ -1430,6 +1430,14 @@
1430
1430
  {
1431
1431
  "stub": "config/vite.ts.stub",
1432
1432
  "target": "config/vite.ts"
1433
+ },
1434
+ {
1435
+ "stub": "tests/feature/auth.test.ts.stub",
1436
+ "target": "tests/feature/auth.test.ts"
1437
+ },
1438
+ {
1439
+ "stub": "tests/feature/users.test.ts.stub",
1440
+ "target": "tests/feature/users.test.ts"
1433
1441
  }
1434
1442
  ],
1435
1443
  "placeholders": {
@@ -0,0 +1,69 @@
1
+ import { describe, test, beforeAll } from 'bun:test'
2
+ import { TestCase } from '@mantiq/testing'
3
+
4
+ const t = new TestCase()
5
+ t.refreshDatabase = true
6
+ t.setup()
7
+
8
+ const user = {
9
+ name: 'Test User',
10
+ email: 'test@example.com',
11
+ password: 'password123',
12
+ }
13
+
14
+ describe('Authentication', () => {
15
+ test('can register a new user', async () => {
16
+ await t.client.initSession()
17
+ const res = await t.client.post('/register', user)
18
+ res.assertCreated()
19
+ await res.assertJson({ message: 'Registered.' })
20
+ await res.assertJsonMissingKey('password')
21
+ await t.assertDatabaseHas('users', { email: user.email })
22
+ })
23
+
24
+ test('cannot register with duplicate email', async () => {
25
+ await t.client.initSession()
26
+ await t.client.post('/register', user)
27
+ const res = await t.client.post('/register', user)
28
+ res.assertUnprocessable()
29
+ })
30
+
31
+ test('can login with valid credentials', async () => {
32
+ await t.client.initSession()
33
+ await t.client.post('/register', user)
34
+ t.client.flushCookies()
35
+
36
+ await t.client.initSession()
37
+ const res = await t.client.post('/login', {
38
+ email: user.email,
39
+ password: user.password,
40
+ })
41
+ res.assertOk()
42
+ await res.assertJson({ message: 'Logged in.' })
43
+ })
44
+
45
+ test('cannot login with wrong password', async () => {
46
+ await t.client.initSession()
47
+ await t.client.post('/register', user)
48
+ t.client.flushCookies()
49
+
50
+ await t.client.initSession()
51
+ const res = await t.client.post('/login', {
52
+ email: user.email,
53
+ password: 'wrong',
54
+ })
55
+ res.assertUnauthorized()
56
+ })
57
+
58
+ test('can logout', async () => {
59
+ await t.client.initSession()
60
+ await t.client.post('/register', user)
61
+ const logoutRes = await t.client.post('/logout')
62
+ logoutRes.assertOk()
63
+ })
64
+
65
+ test('protected routes require authentication', async () => {
66
+ const res = await t.client.get('/api/users')
67
+ res.assertUnauthorized()
68
+ })
69
+ })
@@ -0,0 +1,90 @@
1
+ import { describe, test } from 'bun:test'
2
+ import { TestCase } from '@mantiq/testing'
3
+
4
+ const t = new TestCase()
5
+ t.refreshDatabase = true
6
+ t.setup()
7
+
8
+ const admin = {
9
+ name: 'Admin',
10
+ email: 'admin@example.com',
11
+ password: 'password123',
12
+ }
13
+
14
+ /** Register and login before CRUD tests. */
15
+ async function login() {
16
+ await t.client.initSession()
17
+ await t.client.post('/register', admin)
18
+ }
19
+
20
+ describe('Users CRUD', () => {
21
+ test('can list users', async () => {
22
+ await login()
23
+ const res = await t.client.get('/api/users')
24
+ res.assertOk()
25
+ await res.assertJsonHasKey('data', 'meta')
26
+ await res.assertJsonPath('meta.page', 1)
27
+ })
28
+
29
+ test('can create a user', async () => {
30
+ await login()
31
+ const res = await t.client.post('/api/users', {
32
+ name: 'New User',
33
+ email: 'new@example.com',
34
+ password: 'secret123',
35
+ })
36
+ res.assertCreated()
37
+ await res.assertJsonPath('data.name', 'New User')
38
+ await res.assertJsonPath('data.email', 'new@example.com')
39
+ await t.assertDatabaseHas('users', { email: 'new@example.com' })
40
+ })
41
+
42
+ test('cannot create user with missing fields', async () => {
43
+ await login()
44
+ const res = await t.client.post('/api/users', { name: 'No Email' })
45
+ res.assertUnprocessable()
46
+ })
47
+
48
+ test('can update a user', async () => {
49
+ await login()
50
+ const createRes = await t.client.post('/api/users', {
51
+ name: 'Update Me',
52
+ email: 'update@example.com',
53
+ password: 'secret123',
54
+ })
55
+ const userId = (await createRes.json()).data.id
56
+
57
+ const res = await t.client.put(`/api/users/${userId}`, { name: 'Updated' })
58
+ res.assertOk()
59
+ await res.assertJsonPath('data.name', 'Updated')
60
+ })
61
+
62
+ test('can delete a user', async () => {
63
+ await login()
64
+ const createRes = await t.client.post('/api/users', {
65
+ name: 'Delete Me',
66
+ email: 'delete@example.com',
67
+ password: 'secret123',
68
+ })
69
+ const userId = (await createRes.json()).data.id
70
+
71
+ const res = await t.client.delete(`/api/users/${userId}`)
72
+ res.assertOk()
73
+ await t.assertDatabaseMissing('users', { email: 'delete@example.com' })
74
+ })
75
+
76
+ test('can search users', async () => {
77
+ await login()
78
+ const res = await t.client.get('/api/users?search=Admin')
79
+ res.assertOk()
80
+ const data = await res.json()
81
+ expect(data.data.length).toBeGreaterThan(0)
82
+ })
83
+
84
+ test('can paginate users', async () => {
85
+ await login()
86
+ const res = await t.client.get('/api/users?page=1&per_page=1')
87
+ res.assertOk()
88
+ await res.assertJsonPath('meta.per_page', 1)
89
+ })
90
+ })