@assertrx/dino 1.0.9 → 1.0.11
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 +1 -1
- package/prompts/generate-bdd-from-url.md +5 -1
- package/prompts/generate-k6-test-from-endpoint.md +68 -0
- package/prompts/generate-k6-test-from-openapi.md +85 -0
- package/prompts/setup-k6.md +130 -0
- package/scripts/api-server.mjs +32 -2
- package/skills/analyzing-test-results/SKILL.md +150 -0
- package/skills/analyzing-test-results/reference/bottleneck-patterns.md +130 -0
- package/skills/analyzing-test-results/reference/metrics.md +134 -0
- package/skills/analyzing-test-results/reference/visualization.md +152 -0
- package/skills/designing-test-scenarios/SKILL.md +247 -0
- package/skills/designing-test-scenarios/reference/executors.md +215 -0
- package/skills/designing-test-scenarios/reference/test-types.md +215 -0
- package/skills/designing-test-scenarios/reference/thresholds.md +208 -0
- package/skills/generating-api-load-tests/SKILL.md +199 -0
- package/skills/generating-api-load-tests/reference/data-parameterization.md +343 -0
- package/skills/generating-api-load-tests/reference/grpc.md +315 -0
- package/skills/generating-api-load-tests/reference/http.md +380 -0
- package/skills/generating-api-load-tests/reference/websocket.md +369 -0
- package/skills/generating-browser-tests/SKILL.md +269 -0
- package/skills/generating-browser-tests/reference/browser-api.md +251 -0
- package/skills/generating-browser-tests/reference/web-vitals.md +148 -0
- package/skills/generating-tests-from-code/SKILL.md +142 -0
- package/skills/generating-tests-from-code/reference/auth-analysis.md +220 -0
- package/skills/generating-tests-from-code/reference/framework-patterns.md +242 -0
- package/skills/generating-tests-from-openapi/SKILL.md +234 -0
- package/skills/generating-tests-from-openapi/reference/openapi-auth-mapping.md +247 -0
- package/skills/generating-tests-from-openapi/reference/openapi-parsing.md +194 -0
- package/skills/operating-k6-in-ci-cd/SKILL.md +98 -0
- package/skills/operating-k6-in-ci-cd/reference/ci-cd-pipelines.md +265 -0
- package/skills/operating-k6-in-ci-cd/reference/extensions.md +144 -0
- package/skills/operating-k6-in-ci-cd/reference/k6-operator.md +214 -0
- package/skills/testing-resilience/SKILL.md +230 -0
- package/skills/testing-resilience/reference/fault-injection.md +153 -0
- package/skills/testing-resilience/reference/resilience-patterns.md +500 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Authentication Analysis Reference
|
|
2
|
+
|
|
3
|
+
## Auth Detection Patterns by Framework
|
|
4
|
+
|
|
5
|
+
### Express (Node.js)
|
|
6
|
+
|
|
7
|
+
**JWT Authentication:**
|
|
8
|
+
```javascript
|
|
9
|
+
// Look for: passport, jsonwebtoken, express-jwt, middleware patterns
|
|
10
|
+
const jwt = require('jsonwebtoken');
|
|
11
|
+
app.use('/api', authenticateToken); // Global auth middleware
|
|
12
|
+
|
|
13
|
+
function authenticateToken(req, res, next) {
|
|
14
|
+
const token = req.headers['authorization']?.split(' ')[1];
|
|
15
|
+
jwt.verify(token, process.env.SECRET, (err, user) => { /* ... */ });
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
**Detection:** Search for `jsonwebtoken`, `passport-jwt`, `express-jwt`, `Authorization` header parsing.
|
|
19
|
+
|
|
20
|
+
**Session Authentication:**
|
|
21
|
+
```javascript
|
|
22
|
+
// Look for: express-session, passport with session strategy
|
|
23
|
+
app.use(session({ secret: '...', resave: false }));
|
|
24
|
+
app.use(passport.initialize());
|
|
25
|
+
app.use(passport.session());
|
|
26
|
+
```
|
|
27
|
+
**Detection:** Search for `express-session`, `passport.session()`, `req.session`.
|
|
28
|
+
|
|
29
|
+
**API Key:**
|
|
30
|
+
```javascript
|
|
31
|
+
// Look for: custom middleware checking headers or query params
|
|
32
|
+
app.use((req, res, next) => {
|
|
33
|
+
const apiKey = req.headers['x-api-key'];
|
|
34
|
+
if (!apiKey || !validKeys.includes(apiKey)) return res.status(401).json({});
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
**Detection:** Search for `x-api-key`, `apikey`, `api_key` in middleware.
|
|
38
|
+
|
|
39
|
+
### NestJS
|
|
40
|
+
|
|
41
|
+
**Guards (JWT):**
|
|
42
|
+
```typescript
|
|
43
|
+
// Look for: @UseGuards, AuthGuard, JwtAuthGuard
|
|
44
|
+
@UseGuards(JwtAuthGuard)
|
|
45
|
+
@Controller('users')
|
|
46
|
+
export class UsersController { /* ... */ }
|
|
47
|
+
|
|
48
|
+
// Or per-route
|
|
49
|
+
@UseGuards(AuthGuard('jwt'))
|
|
50
|
+
@Get('profile')
|
|
51
|
+
getProfile() { /* ... */ }
|
|
52
|
+
```
|
|
53
|
+
**Detection:** Search for `@UseGuards`, `AuthGuard`, `JwtStrategy`, `@nestjs/passport`.
|
|
54
|
+
|
|
55
|
+
**Public Routes:**
|
|
56
|
+
```typescript
|
|
57
|
+
// Look for: @Public(), @SkipAuth(), or custom decorators
|
|
58
|
+
@Public()
|
|
59
|
+
@Get('health')
|
|
60
|
+
healthCheck() { /* ... */ }
|
|
61
|
+
```
|
|
62
|
+
**Detection:** Search for `@Public`, `@SkipAuth`, `SetMetadata('isPublic', true)`.
|
|
63
|
+
|
|
64
|
+
### Spring Boot (Java)
|
|
65
|
+
|
|
66
|
+
**Spring Security:**
|
|
67
|
+
```java
|
|
68
|
+
// Look for: SecurityFilterChain, WebSecurityConfigurerAdapter
|
|
69
|
+
@Bean
|
|
70
|
+
public SecurityFilterChain filterChain(HttpSecurity http) {
|
|
71
|
+
http.authorizeHttpRequests(auth -> auth
|
|
72
|
+
.requestMatchers("/api/public/**").permitAll()
|
|
73
|
+
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
|
74
|
+
.anyRequest().authenticated()
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
**Detection:** Search for `SecurityFilterChain`, `@EnableWebSecurity`, `authorizeHttpRequests`, `antMatchers`.
|
|
79
|
+
|
|
80
|
+
**JWT in Spring:**
|
|
81
|
+
```java
|
|
82
|
+
// Look for: OncePerRequestFilter, JWT token parsing
|
|
83
|
+
@Component
|
|
84
|
+
public class JwtAuthFilter extends OncePerRequestFilter {
|
|
85
|
+
protected void doFilterInternal(request, response, chain) {
|
|
86
|
+
String token = request.getHeader("Authorization");
|
|
87
|
+
// verify token
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
**Detection:** Search for `JwtAuthFilter`, `OncePerRequestFilter`, `@PreAuthorize`.
|
|
92
|
+
|
|
93
|
+
### FastAPI (Python)
|
|
94
|
+
|
|
95
|
+
**OAuth2/JWT:**
|
|
96
|
+
```python
|
|
97
|
+
# Look for: Depends(), OAuth2PasswordBearer, HTTPBearer
|
|
98
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
99
|
+
|
|
100
|
+
@app.get("/users/me")
|
|
101
|
+
async def read_users_me(token: str = Depends(oauth2_scheme)):
|
|
102
|
+
user = decode_token(token)
|
|
103
|
+
return user
|
|
104
|
+
```
|
|
105
|
+
**Detection:** Search for `Depends(`, `OAuth2PasswordBearer`, `HTTPBearer`, `Security(`.
|
|
106
|
+
|
|
107
|
+
**API Key:**
|
|
108
|
+
```python
|
|
109
|
+
api_key_header = APIKeyHeader(name="X-API-Key")
|
|
110
|
+
|
|
111
|
+
@app.get("/data")
|
|
112
|
+
async def get_data(api_key: str = Depends(api_key_header)):
|
|
113
|
+
pass
|
|
114
|
+
```
|
|
115
|
+
**Detection:** Search for `APIKeyHeader`, `APIKeyQuery`, `APIKeyCookie`.
|
|
116
|
+
|
|
117
|
+
### Django REST Framework (Python)
|
|
118
|
+
|
|
119
|
+
**Permission Classes:**
|
|
120
|
+
```python
|
|
121
|
+
# Look for: permission_classes, authentication_classes
|
|
122
|
+
class UserViewSet(viewsets.ModelViewSet):
|
|
123
|
+
authentication_classes = [TokenAuthentication, SessionAuthentication]
|
|
124
|
+
permission_classes = [IsAuthenticated]
|
|
125
|
+
```
|
|
126
|
+
**Detection:** Search for `permission_classes`, `authentication_classes`, `IsAuthenticated`, `AllowAny`, `TokenAuthentication`, `JWTAuthentication`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## k6 Auth Setup Generation
|
|
131
|
+
|
|
132
|
+
### JWT Token Flow
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
export function setup() {
|
|
136
|
+
const loginRes = http.post(`${BASE_URL}/auth/login`,
|
|
137
|
+
JSON.stringify({
|
|
138
|
+
username: __ENV.TEST_USERNAME || 'testuser',
|
|
139
|
+
password: __ENV.TEST_PASSWORD || 'testpass',
|
|
140
|
+
}),
|
|
141
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
check(loginRes, { 'login successful': (r) => r.status === 200 });
|
|
145
|
+
const token = loginRes.json('access_token');
|
|
146
|
+
return { token };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default function (data) {
|
|
150
|
+
const headers = {
|
|
151
|
+
'Content-Type': 'application/json',
|
|
152
|
+
'Authorization': `Bearer ${data.token}`,
|
|
153
|
+
};
|
|
154
|
+
// Use headers in all requests
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Session/Cookie Flow
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
export default function () {
|
|
162
|
+
// Login (cookies auto-stored in VU cookie jar)
|
|
163
|
+
const loginRes = http.post(`${BASE_URL}/login`, {
|
|
164
|
+
username: 'testuser',
|
|
165
|
+
password: 'testpass',
|
|
166
|
+
});
|
|
167
|
+
check(loginRes, { 'logged in': (r) => r.status === 200 || r.status === 302 });
|
|
168
|
+
|
|
169
|
+
// Subsequent requests include session cookie automatically
|
|
170
|
+
http.get(`${BASE_URL}/dashboard`);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### API Key Flow
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
const API_KEY = __ENV.API_KEY || 'test-api-key';
|
|
178
|
+
|
|
179
|
+
export default function () {
|
|
180
|
+
http.get(`${BASE_URL}/data`, {
|
|
181
|
+
headers: { 'X-API-Key': API_KEY },
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### OAuth2 Client Credentials Flow
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
import encoding from 'k6/encoding';
|
|
190
|
+
|
|
191
|
+
export function setup() {
|
|
192
|
+
const res = http.post(`${AUTH_URL}/oauth/token`,
|
|
193
|
+
'grant_type=client_credentials&scope=read write',
|
|
194
|
+
{
|
|
195
|
+
headers: {
|
|
196
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
197
|
+
'Authorization': `Basic ${encoding.b64encode(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
|
|
198
|
+
},
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
return { token: res.json('access_token') };
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Identifying Protected vs Public Endpoints
|
|
206
|
+
|
|
207
|
+
Build a table of endpoints with their auth requirements:
|
|
208
|
+
|
|
209
|
+
| Endpoint | Method | Auth Required | Auth Type | Notes |
|
|
210
|
+
|----------|--------|---------------|-----------|-------|
|
|
211
|
+
| `/health` | GET | No | - | Health check |
|
|
212
|
+
| `/auth/login` | POST | No | - | Login endpoint |
|
|
213
|
+
| `/api/users` | GET | Yes | JWT Bearer | List users |
|
|
214
|
+
| `/api/admin/settings` | GET | Yes | JWT + Admin Role | Admin only |
|
|
215
|
+
|
|
216
|
+
Use this table to:
|
|
217
|
+
1. Generate `setup()` with appropriate login flow
|
|
218
|
+
2. Test public endpoints without auth
|
|
219
|
+
3. Group protected endpoints under authenticated scenarios
|
|
220
|
+
4. Test authorization (access control) with different user roles
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Framework Route Analysis Patterns
|
|
2
|
+
|
|
3
|
+
## Express (Node.js)
|
|
4
|
+
|
|
5
|
+
### Where to Find Routes
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/routes/ — Route files
|
|
9
|
+
src/app.js — Main app with app.use() mounts
|
|
10
|
+
src/index.js — Entry point
|
|
11
|
+
src/controllers/ — Controller functions
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Route Patterns
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
// Direct route definition
|
|
18
|
+
app.get('/users', getUsers);
|
|
19
|
+
app.post('/users', createUser);
|
|
20
|
+
app.put('/users/:id', updateUser);
|
|
21
|
+
app.delete('/users/:id', deleteUser);
|
|
22
|
+
|
|
23
|
+
// Router module
|
|
24
|
+
const router = express.Router();
|
|
25
|
+
router.get('/', listItems);
|
|
26
|
+
router.post('/', createItem);
|
|
27
|
+
router.get('/:id', getItem);
|
|
28
|
+
|
|
29
|
+
// Mount point
|
|
30
|
+
app.use('/api/items', router);
|
|
31
|
+
// → Full paths: GET /api/items, POST /api/items, GET /api/items/:id
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Extracting Info
|
|
35
|
+
|
|
36
|
+
- **URL params:** `:id`, `:userId` → dynamic path segments
|
|
37
|
+
- **Query params:** Look for `req.query.page`, `req.query.limit`
|
|
38
|
+
- **Body:** Look for `req.body.name`, `req.body.email` or validation schemas
|
|
39
|
+
- **Middleware:** `app.use(auth)`, `router.use(validateBody)` → auth/validation requirements
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## NestJS
|
|
44
|
+
|
|
45
|
+
### Where to Find Routes
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
src/modules/*/ — Feature modules
|
|
49
|
+
*.controller.ts — Controllers with decorators
|
|
50
|
+
*.service.ts — Business logic
|
|
51
|
+
*.dto.ts — Request/Response DTOs
|
|
52
|
+
*.module.ts — Module definitions
|
|
53
|
+
src/app.module.ts — Root module
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Route Patterns
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
@Controller('users') // Base path: /users
|
|
60
|
+
export class UsersController {
|
|
61
|
+
@Get() // GET /users
|
|
62
|
+
findAll() {}
|
|
63
|
+
|
|
64
|
+
@Get(':id') // GET /users/:id
|
|
65
|
+
findOne(@Param('id') id: string) {}
|
|
66
|
+
|
|
67
|
+
@Post() // POST /users
|
|
68
|
+
create(@Body() dto: CreateUserDto) {}
|
|
69
|
+
|
|
70
|
+
@Put(':id') // PUT /users/:id
|
|
71
|
+
update(@Param('id') id: string, @Body() dto: UpdateUserDto) {}
|
|
72
|
+
|
|
73
|
+
@Delete(':id') // DELETE /users/:id
|
|
74
|
+
remove(@Param('id') id: string) {}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Extracting Info
|
|
79
|
+
|
|
80
|
+
- **Path prefix:** `@Controller('prefix')` decorator
|
|
81
|
+
- **Global prefix:** Check `app.setGlobalPrefix('api')` in main.ts
|
|
82
|
+
- **DTOs:** `CreateUserDto`, `UpdateUserDto` → request body shape
|
|
83
|
+
- **Params:** `@Param()`, `@Query()`, `@Body()` decorators
|
|
84
|
+
- **Guards:** `@UseGuards()` → authentication requirements
|
|
85
|
+
- **Pipes:** `@UsePipes(ValidationPipe)` → validation rules
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Spring Boot (Java)
|
|
90
|
+
|
|
91
|
+
### Where to Find Routes
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
src/main/java/**/controller/ — Controller classes
|
|
95
|
+
src/main/java/**/config/ — Security config
|
|
96
|
+
src/main/resources/ — application.yml (context path)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Route Patterns
|
|
100
|
+
|
|
101
|
+
```java
|
|
102
|
+
@RestController
|
|
103
|
+
@RequestMapping("/api/users") // Base path
|
|
104
|
+
public class UserController {
|
|
105
|
+
|
|
106
|
+
@GetMapping // GET /api/users
|
|
107
|
+
public List<User> findAll() {}
|
|
108
|
+
|
|
109
|
+
@GetMapping("/{id}") // GET /api/users/{id}
|
|
110
|
+
public User findById(@PathVariable Long id) {}
|
|
111
|
+
|
|
112
|
+
@PostMapping // POST /api/users
|
|
113
|
+
public User create(@RequestBody @Valid CreateUserRequest req) {}
|
|
114
|
+
|
|
115
|
+
@PutMapping("/{id}") // PUT /api/users/{id}
|
|
116
|
+
public User update(@PathVariable Long id, @RequestBody UpdateUserRequest req) {}
|
|
117
|
+
|
|
118
|
+
@DeleteMapping("/{id}") // DELETE /api/users/{id}
|
|
119
|
+
public void delete(@PathVariable Long id) {}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Extracting Info
|
|
124
|
+
|
|
125
|
+
- **Context path:** Check `server.servlet.context-path` in application.yml
|
|
126
|
+
- **Path prefix:** `@RequestMapping` on class
|
|
127
|
+
- **Request body:** `@RequestBody` with DTO class → field types
|
|
128
|
+
- **Path vars:** `@PathVariable` → dynamic segments
|
|
129
|
+
- **Query params:** `@RequestParam` → query parameters
|
|
130
|
+
- **Validation:** `@Valid`, `@NotNull`, `@Size` annotations
|
|
131
|
+
- **Security:** `@PreAuthorize`, `SecurityFilterChain` → auth requirements
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## FastAPI (Python)
|
|
136
|
+
|
|
137
|
+
### Where to Find Routes
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
app/main.py — Main app with app.include_router()
|
|
141
|
+
app/routers/ — Router modules
|
|
142
|
+
app/models/ — Pydantic models
|
|
143
|
+
app/dependencies.py — Shared dependencies
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Route Patterns
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
router = APIRouter(prefix="/users", tags=["users"])
|
|
150
|
+
|
|
151
|
+
@router.get("/") # GET /users
|
|
152
|
+
async def list_users(
|
|
153
|
+
skip: int = 0, # Query param
|
|
154
|
+
limit: int = 100, # Query param
|
|
155
|
+
):
|
|
156
|
+
pass
|
|
157
|
+
|
|
158
|
+
@router.get("/{user_id}") # GET /users/{user_id}
|
|
159
|
+
async def get_user(user_id: int): # Path param (typed)
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
@router.post("/", status_code=201) # POST /users
|
|
163
|
+
async def create_user(user: UserCreate): # Body from Pydantic model
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
@router.put("/{user_id}") # PUT /users/{user_id}
|
|
167
|
+
async def update_user(user_id: int, user: UserUpdate):
|
|
168
|
+
pass
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Extracting Info
|
|
172
|
+
|
|
173
|
+
- **Router prefix:** `APIRouter(prefix="/prefix")`
|
|
174
|
+
- **App mount:** `app.include_router(router, prefix="/api")`
|
|
175
|
+
- **Pydantic models:** Define request/response shapes with types
|
|
176
|
+
- **Dependencies:** `Depends()` → auth, DB session, etc.
|
|
177
|
+
- **Status codes:** `status_code=201` → expected response codes
|
|
178
|
+
- **Response model:** `response_model=UserResponse` → response shape
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Django REST Framework (Python)
|
|
183
|
+
|
|
184
|
+
### Where to Find Routes
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
project/urls.py — Root URL configuration
|
|
188
|
+
app/urls.py — App URL patterns
|
|
189
|
+
app/views.py — View classes/functions
|
|
190
|
+
app/serializers.py — Request/Response serializers
|
|
191
|
+
app/permissions.py — Permission classes
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Route Patterns
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
# urls.py with ViewSet + Router
|
|
198
|
+
router = DefaultRouter()
|
|
199
|
+
router.register(r'users', UserViewSet)
|
|
200
|
+
urlpatterns = [path('api/', include(router.urls))]
|
|
201
|
+
# → GET/POST /api/users/
|
|
202
|
+
# → GET/PUT/PATCH/DELETE /api/users/{pk}/
|
|
203
|
+
|
|
204
|
+
# ViewSet
|
|
205
|
+
class UserViewSet(viewsets.ModelViewSet):
|
|
206
|
+
queryset = User.objects.all()
|
|
207
|
+
serializer_class = UserSerializer
|
|
208
|
+
permission_classes = [IsAuthenticated]
|
|
209
|
+
|
|
210
|
+
@action(detail=False, methods=['get'])
|
|
211
|
+
def me(self, request): # GET /api/users/me/
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
# Function-based views
|
|
215
|
+
@api_view(['GET', 'POST'])
|
|
216
|
+
@permission_classes([AllowAny])
|
|
217
|
+
def user_list(request):
|
|
218
|
+
pass
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Extracting Info
|
|
222
|
+
|
|
223
|
+
- **URL patterns:** `path()`, `re_path()`, `router.register()`
|
|
224
|
+
- **Serializers:** Define request/response shapes
|
|
225
|
+
- **Permissions:** `permission_classes` → auth requirements
|
|
226
|
+
- **ViewSet actions:** Standard CRUD + custom `@action` methods
|
|
227
|
+
- **Pagination:** Check `DEFAULT_PAGINATION_CLASS` in settings
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Common Analysis Steps
|
|
232
|
+
|
|
233
|
+
For any framework:
|
|
234
|
+
|
|
235
|
+
1. **Find the entry point** (main app file)
|
|
236
|
+
2. **Trace route registration** (how routes are mounted)
|
|
237
|
+
3. **List all endpoints** with methods and paths
|
|
238
|
+
4. **Check for global prefix** (e.g., `/api/v1`)
|
|
239
|
+
5. **Identify auth middleware** (global vs per-route)
|
|
240
|
+
6. **Extract request shapes** from DTOs/models/schemas
|
|
241
|
+
7. **Note response codes** for checks
|
|
242
|
+
8. **Find validation rules** for generating valid test data
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: generating-tests-from-openapi
|
|
3
|
+
description: Use when generating k6 load test scripts from OpenAPI specification files (YAML or JSON). Use when the user has an OpenAPI spec, API specification, or wants to create k6 tests from API documentation defined in OpenAPI 3.0 or 3.1 format.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Generating k6 Tests from OpenAPI
|
|
7
|
+
|
|
8
|
+
Generate k6 load test scripts by parsing OpenAPI specification files (YAML format). Extracts endpoints, parameters, request bodies, and security schemes to create comprehensive test suites.
|
|
9
|
+
|
|
10
|
+
## Workflow
|
|
11
|
+
|
|
12
|
+
### Step 1: Parse OpenAPI Spec
|
|
13
|
+
|
|
14
|
+
Read the OpenAPI YAML file and extract:
|
|
15
|
+
- API info (title, version, base URL)
|
|
16
|
+
- All paths and operations
|
|
17
|
+
- Request parameters, bodies, and response schemas
|
|
18
|
+
- Security definitions and requirements
|
|
19
|
+
|
|
20
|
+
### Step 2: Security Scheme Analysis (Prerequisite)
|
|
21
|
+
|
|
22
|
+
**Before generating test scripts, analyze security requirements.**
|
|
23
|
+
|
|
24
|
+
1. **Extract security definitions** from `components.securitySchemes`:
|
|
25
|
+
- OAuth2 flows (authorization_code, client_credentials, password)
|
|
26
|
+
- API Key (header, query, cookie)
|
|
27
|
+
- HTTP Bearer / Basic Auth
|
|
28
|
+
- OpenID Connect
|
|
29
|
+
|
|
30
|
+
2. **Map per-endpoint security:**
|
|
31
|
+
- Check global `security` array
|
|
32
|
+
- Check per-operation `security` overrides
|
|
33
|
+
- Identify public endpoints (empty security array `security: []`)
|
|
34
|
+
|
|
35
|
+
3. **Generate k6 auth setup:**
|
|
36
|
+
- Create `setup()` function matching the required auth flow
|
|
37
|
+
- Configure token/key handling for requests
|
|
38
|
+
|
|
39
|
+
See [reference/openapi-auth-mapping.md](reference/openapi-auth-mapping.md) for mapping security schemes to k6 code.
|
|
40
|
+
|
|
41
|
+
### Step 3: Endpoint Extraction
|
|
42
|
+
|
|
43
|
+
For each path and operation, extract:
|
|
44
|
+
|
|
45
|
+
```yaml
|
|
46
|
+
paths:
|
|
47
|
+
/users:
|
|
48
|
+
get: # HTTP method
|
|
49
|
+
operationId: listUsers # Operation identifier
|
|
50
|
+
tags: [Users] # Grouping
|
|
51
|
+
parameters: # Query/header/path params
|
|
52
|
+
- name: page
|
|
53
|
+
in: query
|
|
54
|
+
schema:
|
|
55
|
+
type: integer
|
|
56
|
+
security: # Per-operation security
|
|
57
|
+
- bearerAuth: []
|
|
58
|
+
responses:
|
|
59
|
+
'200':
|
|
60
|
+
description: Success
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Map to k6:
|
|
64
|
+
- **Path + method** → HTTP request
|
|
65
|
+
- **Parameters** → URL params, query string, headers
|
|
66
|
+
- **Request body** → `http.post()` body
|
|
67
|
+
- **Tags** → k6 groups
|
|
68
|
+
- **Security** → Auth headers
|
|
69
|
+
|
|
70
|
+
### Step 4: Generate Request Bodies
|
|
71
|
+
|
|
72
|
+
Convert schema definitions to example values:
|
|
73
|
+
|
|
74
|
+
| Schema Type | Example Value |
|
|
75
|
+
|------------|---------------|
|
|
76
|
+
| `string` | `"test-string"` |
|
|
77
|
+
| `string (email)` | `"test@example.com"` |
|
|
78
|
+
| `string (date-time)` | `"2024-01-01T00:00:00Z"` |
|
|
79
|
+
| `string (uuid)` | `"550e8400-e29b-41d4-a716-446655440000"` |
|
|
80
|
+
| `integer` | `1` |
|
|
81
|
+
| `number` | `1.0` |
|
|
82
|
+
| `boolean` | `true` |
|
|
83
|
+
| `array` | `[<item_example>]` |
|
|
84
|
+
| `object` | `{<property_examples>}` |
|
|
85
|
+
| `enum` | First enum value |
|
|
86
|
+
|
|
87
|
+
Use `example` or `default` values from the spec when available.
|
|
88
|
+
|
|
89
|
+
See [reference/openapi-parsing.md](reference/openapi-parsing.md) for detailed parsing logic.
|
|
90
|
+
|
|
91
|
+
### Step 5: Script Generation
|
|
92
|
+
|
|
93
|
+
Organize the generated script by API tags:
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
import http from 'k6/http';
|
|
97
|
+
import { check, group, sleep } from 'k6';
|
|
98
|
+
|
|
99
|
+
const BASE_URL = __ENV.BASE_URL || 'https://api.example.com';
|
|
100
|
+
|
|
101
|
+
export const options = {
|
|
102
|
+
scenarios: {
|
|
103
|
+
api_test: {
|
|
104
|
+
executor: 'ramping-vus',
|
|
105
|
+
startVUs: 0,
|
|
106
|
+
stages: [
|
|
107
|
+
{ duration: '2m', target: 10 },
|
|
108
|
+
{ duration: '5m', target: 10 },
|
|
109
|
+
{ duration: '1m', target: 0 },
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
thresholds: {
|
|
114
|
+
http_req_duration: ['p(95)<500'],
|
|
115
|
+
http_req_failed: ['rate<0.01'],
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export function setup() {
|
|
120
|
+
// Auth flow based on security scheme
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export default function (data) {
|
|
124
|
+
const headers = {
|
|
125
|
+
'Content-Type': 'application/json',
|
|
126
|
+
'Authorization': `Bearer ${data.token}`,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
group('Users', () => {
|
|
130
|
+
// GET /users
|
|
131
|
+
const listRes = http.get(`${BASE_URL}/users?page=1&limit=10`, {
|
|
132
|
+
headers, tags: { name: 'ListUsers' },
|
|
133
|
+
});
|
|
134
|
+
check(listRes, { 'list users 200': (r) => r.status === 200 });
|
|
135
|
+
|
|
136
|
+
// POST /users
|
|
137
|
+
const createRes = http.post(`${BASE_URL}/users`,
|
|
138
|
+
JSON.stringify({ name: 'Test User', email: 'test@example.com' }),
|
|
139
|
+
{ headers, tags: { name: 'CreateUser' } }
|
|
140
|
+
);
|
|
141
|
+
check(createRes, { 'create user 201': (r) => r.status === 201 });
|
|
142
|
+
|
|
143
|
+
// GET /users/{id}
|
|
144
|
+
const userId = createRes.json('id') || 1;
|
|
145
|
+
const getRes = http.get(`${BASE_URL}/users/${userId}`, {
|
|
146
|
+
headers, tags: { name: 'GetUser' },
|
|
147
|
+
});
|
|
148
|
+
check(getRes, { 'get user 200': (r) => r.status === 200 });
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
sleep(1);
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Step 6: Configure Scenarios
|
|
156
|
+
|
|
157
|
+
Based on endpoint characteristics:
|
|
158
|
+
- **Read-heavy endpoints** (GET list) → Higher rate
|
|
159
|
+
- **Write endpoints** (POST, PUT) → Lower rate
|
|
160
|
+
- **Admin endpoints** → Separate scenario or excluded
|
|
161
|
+
- **Public endpoints** → No auth scenario
|
|
162
|
+
|
|
163
|
+
## OpenAPI Spec Quick Reference
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
openapi: '3.1.0'
|
|
167
|
+
info:
|
|
168
|
+
title: My API
|
|
169
|
+
version: '1.0.0'
|
|
170
|
+
servers:
|
|
171
|
+
- url: https://api.example.com/v1
|
|
172
|
+
security:
|
|
173
|
+
- bearerAuth: [] # Global security
|
|
174
|
+
components:
|
|
175
|
+
securitySchemes:
|
|
176
|
+
bearerAuth:
|
|
177
|
+
type: http
|
|
178
|
+
scheme: bearer
|
|
179
|
+
apiKey:
|
|
180
|
+
type: apiKey
|
|
181
|
+
in: header
|
|
182
|
+
name: X-API-Key
|
|
183
|
+
schemas:
|
|
184
|
+
User:
|
|
185
|
+
type: object
|
|
186
|
+
required: [name, email]
|
|
187
|
+
properties:
|
|
188
|
+
id:
|
|
189
|
+
type: integer
|
|
190
|
+
name:
|
|
191
|
+
type: string
|
|
192
|
+
email:
|
|
193
|
+
type: string
|
|
194
|
+
format: email
|
|
195
|
+
paths:
|
|
196
|
+
/users:
|
|
197
|
+
get:
|
|
198
|
+
tags: [Users]
|
|
199
|
+
parameters:
|
|
200
|
+
- name: page
|
|
201
|
+
in: query
|
|
202
|
+
schema: { type: integer, default: 1 }
|
|
203
|
+
responses:
|
|
204
|
+
'200':
|
|
205
|
+
description: User list
|
|
206
|
+
post:
|
|
207
|
+
tags: [Users]
|
|
208
|
+
requestBody:
|
|
209
|
+
required: true
|
|
210
|
+
content:
|
|
211
|
+
application/json:
|
|
212
|
+
schema:
|
|
213
|
+
$ref: '#/components/schemas/User'
|
|
214
|
+
responses:
|
|
215
|
+
'201':
|
|
216
|
+
description: Created
|
|
217
|
+
/users/{id}:
|
|
218
|
+
get:
|
|
219
|
+
tags: [Users]
|
|
220
|
+
parameters:
|
|
221
|
+
- name: id
|
|
222
|
+
in: path
|
|
223
|
+
required: true
|
|
224
|
+
schema: { type: integer }
|
|
225
|
+
responses:
|
|
226
|
+
'200':
|
|
227
|
+
description: User details
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Related Skills
|
|
231
|
+
|
|
232
|
+
- For HTTP/gRPC/WebSocket API patterns: `/k6:generating-api-load-tests`
|
|
233
|
+
- For scenario and threshold design: `/k6:designing-test-scenarios`
|
|
234
|
+
- For code-based test generation: `/k6:generating-tests-from-code`
|