@malamute/ai-rules 1.4.0 → 1.5.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/README.md +25 -9
- package/configs/_shared/rules/conventions/interaction.md +38 -0
- package/configs/_shared/rules/domain/backend/api-design.md +58 -2
- package/configs/_shared/skills/analysis/sudden-death/SKILL.md +148 -0
- package/configs/adonisjs/rules/auth.md +192 -0
- package/configs/adonisjs/rules/controllers.md +111 -0
- package/configs/adonisjs/rules/core.md +95 -0
- package/configs/adonisjs/rules/database/lucid.md +167 -0
- package/configs/adonisjs/rules/middleware.md +140 -0
- package/configs/adonisjs/rules/services.md +154 -0
- package/configs/adonisjs/rules/testing.md +170 -0
- package/configs/adonisjs/rules/validation.md +130 -0
- package/configs/adonisjs/settings.json +34 -0
- package/package.json +1 -1
- package/src/tech-config.json +4 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "tests/**/*.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# AdonisJS Testing (Japa)
|
|
7
|
+
|
|
8
|
+
## Test Structure
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
tests/
|
|
12
|
+
├── bootstrap.ts # Test setup
|
|
13
|
+
├── unit/
|
|
14
|
+
│ └── services/
|
|
15
|
+
├── functional/
|
|
16
|
+
│ └── controllers/
|
|
17
|
+
└── integration/
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Unit Test
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { test } from '@japa/runner'
|
|
24
|
+
import UserService from '#services/user_service'
|
|
25
|
+
|
|
26
|
+
test.group('UserService', () => {
|
|
27
|
+
test('creates a user with valid data', async ({ assert }) => {
|
|
28
|
+
const service = new UserService()
|
|
29
|
+
const user = await service.create({
|
|
30
|
+
email: 'test@example.com',
|
|
31
|
+
password: 'password123',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
assert.exists(user.id)
|
|
35
|
+
assert.equal(user.email, 'test@example.com')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Functional Test (HTTP)
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import { test } from '@japa/runner'
|
|
44
|
+
import { UserFactory } from '#database/factories/user_factory'
|
|
45
|
+
|
|
46
|
+
test.group('Users Controller', (group) => {
|
|
47
|
+
group.each.setup(async () => {
|
|
48
|
+
// Runs before each test
|
|
49
|
+
await Database.beginGlobalTransaction()
|
|
50
|
+
return () => Database.rollbackGlobalTransaction()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('list all users', async ({ client, assert }) => {
|
|
54
|
+
await UserFactory.createMany(3)
|
|
55
|
+
|
|
56
|
+
const response = await client.get('/users')
|
|
57
|
+
|
|
58
|
+
response.assertStatus(200)
|
|
59
|
+
assert.lengthOf(response.body(), 3)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('create a user', async ({ client }) => {
|
|
63
|
+
const response = await client.post('/users').json({
|
|
64
|
+
email: 'new@example.com',
|
|
65
|
+
password: 'password123',
|
|
66
|
+
name: 'New User',
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
response.assertStatus(201)
|
|
70
|
+
response.assertBodyContains({ email: 'new@example.com' })
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('requires authentication', async ({ client }) => {
|
|
74
|
+
const response = await client.get('/profile')
|
|
75
|
+
response.assertStatus(401)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Authenticated Requests
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
test('authenticated user can view profile', async ({ client }) => {
|
|
84
|
+
const user = await UserFactory.create()
|
|
85
|
+
|
|
86
|
+
const response = await client
|
|
87
|
+
.get('/profile')
|
|
88
|
+
.loginAs(user)
|
|
89
|
+
|
|
90
|
+
response.assertStatus(200)
|
|
91
|
+
response.assertBodyContains({ id: user.id })
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Database Helpers
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import Database from '@adonisjs/lucid/services/db'
|
|
99
|
+
import { test } from '@japa/runner'
|
|
100
|
+
|
|
101
|
+
test.group('Database tests', (group) => {
|
|
102
|
+
// Transaction per test (auto rollback)
|
|
103
|
+
group.each.setup(async () => {
|
|
104
|
+
await Database.beginGlobalTransaction()
|
|
105
|
+
return () => Database.rollbackGlobalTransaction()
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Or truncate tables
|
|
109
|
+
group.each.setup(async () => {
|
|
110
|
+
await Database.truncate('users')
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Assertions
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
test('response assertions', async ({ client, assert }) => {
|
|
119
|
+
const response = await client.get('/users/1')
|
|
120
|
+
|
|
121
|
+
// Status
|
|
122
|
+
response.assertStatus(200)
|
|
123
|
+
response.assertStatus(201)
|
|
124
|
+
response.assertStatus(404)
|
|
125
|
+
|
|
126
|
+
// Body
|
|
127
|
+
response.assertBody({ id: 1, name: 'John' })
|
|
128
|
+
response.assertBodyContains({ name: 'John' })
|
|
129
|
+
|
|
130
|
+
// Headers
|
|
131
|
+
response.assertHeader('content-type', 'application/json')
|
|
132
|
+
|
|
133
|
+
// Custom assertions
|
|
134
|
+
assert.equal(response.body().email, 'test@example.com')
|
|
135
|
+
assert.lengthOf(response.body().users, 3)
|
|
136
|
+
assert.isTrue(response.body().isActive)
|
|
137
|
+
})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Mocking
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { test } from '@japa/runner'
|
|
144
|
+
import mail from '@adonisjs/mail/services/main'
|
|
145
|
+
|
|
146
|
+
test('sends welcome email on registration', async ({ client, assert }) => {
|
|
147
|
+
const { mails } = mail.fake()
|
|
148
|
+
|
|
149
|
+
await client.post('/auth/register').json({
|
|
150
|
+
email: 'test@example.com',
|
|
151
|
+
password: 'password123',
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
assert.lengthOf(mails.sent, 1)
|
|
155
|
+
mails.assertSent(WelcomeMail, (mail) => {
|
|
156
|
+
return mail.to[0].address === 'test@example.com'
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
mail.restore()
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Running Tests
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
node ace test # All tests
|
|
167
|
+
node ace test --files="tests/functional/**"
|
|
168
|
+
node ace test --tags="@slow"
|
|
169
|
+
node ace test --watch # Watch mode
|
|
170
|
+
```
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/validators/**/*.ts"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# VineJS Validation
|
|
7
|
+
|
|
8
|
+
## Basic Validator
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import vine from '@vinejs/vine'
|
|
12
|
+
|
|
13
|
+
export const createUserValidator = vine.compile(
|
|
14
|
+
vine.object({
|
|
15
|
+
email: vine.string().email().normalizeEmail(),
|
|
16
|
+
password: vine.string().minLength(8),
|
|
17
|
+
name: vine.string().minLength(2).maxLength(100),
|
|
18
|
+
})
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
export const updateUserValidator = vine.compile(
|
|
22
|
+
vine.object({
|
|
23
|
+
email: vine.string().email().normalizeEmail().optional(),
|
|
24
|
+
name: vine.string().minLength(2).maxLength(100).optional(),
|
|
25
|
+
})
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Common Rules
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
vine.object({
|
|
33
|
+
// Strings
|
|
34
|
+
name: vine.string().minLength(2).maxLength(100),
|
|
35
|
+
email: vine.string().email(),
|
|
36
|
+
url: vine.string().url(),
|
|
37
|
+
slug: vine.string().regex(/^[a-z0-9-]+$/),
|
|
38
|
+
|
|
39
|
+
// Numbers
|
|
40
|
+
age: vine.number().min(0).max(150),
|
|
41
|
+
price: vine.number().positive().decimal(2),
|
|
42
|
+
|
|
43
|
+
// Booleans
|
|
44
|
+
isActive: vine.boolean(),
|
|
45
|
+
|
|
46
|
+
// Dates
|
|
47
|
+
birthDate: vine.date({ formats: ['YYYY-MM-DD'] }),
|
|
48
|
+
|
|
49
|
+
// Arrays
|
|
50
|
+
tags: vine.array(vine.string()).minLength(1).maxLength(10),
|
|
51
|
+
|
|
52
|
+
// Enums
|
|
53
|
+
status: vine.enum(['draft', 'published', 'archived']),
|
|
54
|
+
|
|
55
|
+
// Optional & Nullable
|
|
56
|
+
bio: vine.string().optional(),
|
|
57
|
+
deletedAt: vine.date().nullable(),
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Custom Error Messages
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
export const createUserValidator = vine.compile(
|
|
65
|
+
vine.object({
|
|
66
|
+
email: vine.string().email(),
|
|
67
|
+
password: vine.string().minLength(8),
|
|
68
|
+
})
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
createUserValidator.messagesProvider = new SimpleMessagesProvider({
|
|
72
|
+
'email.required': 'Email is required',
|
|
73
|
+
'email.email': 'Invalid email format',
|
|
74
|
+
'password.minLength': 'Password must be at least 8 characters',
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Unique Validation
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import vine from '@vinejs/vine'
|
|
82
|
+
import { FieldContext } from '@vinejs/vine/types'
|
|
83
|
+
import User from '#models/user'
|
|
84
|
+
|
|
85
|
+
const uniqueEmail = vine.createRule(async (value: unknown, _options: undefined, field: FieldContext) => {
|
|
86
|
+
if (typeof value !== 'string') return
|
|
87
|
+
|
|
88
|
+
const user = await User.findBy('email', value)
|
|
89
|
+
if (user) {
|
|
90
|
+
field.report('Email already exists', 'unique', field)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
export const createUserValidator = vine.compile(
|
|
95
|
+
vine.object({
|
|
96
|
+
email: vine.string().email().use(uniqueEmail()),
|
|
97
|
+
})
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Conditional Validation
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
vine.object({
|
|
105
|
+
accountType: vine.enum(['personal', 'business']),
|
|
106
|
+
companyName: vine
|
|
107
|
+
.string()
|
|
108
|
+
.minLength(2)
|
|
109
|
+
.requiredWhen('accountType', '=', 'business'),
|
|
110
|
+
taxId: vine
|
|
111
|
+
.string()
|
|
112
|
+
.optional()
|
|
113
|
+
.requiredWhen('accountType', '=', 'business'),
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Usage in Controller
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { createUserValidator } from '#validators/user'
|
|
121
|
+
|
|
122
|
+
export default class UsersController {
|
|
123
|
+
async store({ request, response }: HttpContext) {
|
|
124
|
+
const payload = await request.validateUsing(createUserValidator)
|
|
125
|
+
// payload is fully typed: { email: string, password: string, name: string }
|
|
126
|
+
const user = await User.create(payload)
|
|
127
|
+
return response.created(user)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(node ace serve --watch)",
|
|
5
|
+
"Bash(node ace build)",
|
|
6
|
+
"Bash(node ace test*)",
|
|
7
|
+
"Bash(node ace make:*)",
|
|
8
|
+
"Bash(node ace migration:*)",
|
|
9
|
+
"Bash(node ace db:*)",
|
|
10
|
+
"Bash(node ace generate:*)",
|
|
11
|
+
"Bash(node ace list)",
|
|
12
|
+
"Bash(npm run dev)",
|
|
13
|
+
"Bash(npm run build)",
|
|
14
|
+
"Bash(npm run test*)",
|
|
15
|
+
"Bash(npm run lint*)",
|
|
16
|
+
"Bash(npm install *)",
|
|
17
|
+
"Bash(npm ci)",
|
|
18
|
+
"Read",
|
|
19
|
+
"Edit",
|
|
20
|
+
"Write"
|
|
21
|
+
],
|
|
22
|
+
"deny": [
|
|
23
|
+
"Bash(git push *)",
|
|
24
|
+
"Bash(git push)",
|
|
25
|
+
"Bash(rm -rf *)",
|
|
26
|
+
"Read(.env)",
|
|
27
|
+
"Read(.env.*)",
|
|
28
|
+
"Read(**/secrets/**)"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"env": {
|
|
32
|
+
"NODE_ENV": "development"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/package.json
CHANGED