omgkit 2.1.1 → 2.3.0
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/plugin/skills/databases/mongodb/SKILL.md +81 -28
- package/plugin/skills/databases/prisma/SKILL.md +87 -32
- package/plugin/skills/databases/redis/SKILL.md +80 -27
- package/plugin/skills/devops/aws/SKILL.md +80 -26
- package/plugin/skills/devops/github-actions/SKILL.md +84 -32
- package/plugin/skills/devops/kubernetes/SKILL.md +94 -32
- package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
- package/plugin/skills/frameworks/django/SKILL.md +158 -24
- package/plugin/skills/frameworks/express/SKILL.md +153 -33
- package/plugin/skills/frameworks/fastapi/SKILL.md +153 -34
- package/plugin/skills/frameworks/laravel/SKILL.md +146 -33
- package/plugin/skills/frameworks/nestjs/SKILL.md +137 -25
- package/plugin/skills/frameworks/rails/SKILL.md +594 -28
- package/plugin/skills/frameworks/react/SKILL.md +94 -962
- package/plugin/skills/frameworks/spring/SKILL.md +528 -35
- package/plugin/skills/frameworks/vue/SKILL.md +147 -25
- package/plugin/skills/frontend/accessibility/SKILL.md +145 -36
- package/plugin/skills/frontend/frontend-design/SKILL.md +114 -29
- package/plugin/skills/frontend/responsive/SKILL.md +131 -28
- package/plugin/skills/frontend/shadcn-ui/SKILL.md +133 -43
- package/plugin/skills/frontend/tailwindcss/SKILL.md +105 -37
- package/plugin/skills/frontend/threejs/SKILL.md +110 -35
- package/plugin/skills/languages/javascript/SKILL.md +195 -34
- package/plugin/skills/methodology/brainstorming/SKILL.md +98 -30
- package/plugin/skills/methodology/defense-in-depth/SKILL.md +83 -37
- package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +92 -31
- package/plugin/skills/methodology/executing-plans/SKILL.md +117 -28
- package/plugin/skills/methodology/finishing-development-branch/SKILL.md +111 -32
- package/plugin/skills/methodology/problem-solving/SKILL.md +65 -311
- package/plugin/skills/methodology/receiving-code-review/SKILL.md +76 -27
- package/plugin/skills/methodology/requesting-code-review/SKILL.md +93 -22
- package/plugin/skills/methodology/root-cause-tracing/SKILL.md +75 -40
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +75 -224
- package/plugin/skills/methodology/systematic-debugging/SKILL.md +81 -35
- package/plugin/skills/methodology/test-driven-development/SKILL.md +120 -26
- package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +88 -35
- package/plugin/skills/methodology/token-optimization/SKILL.md +73 -34
- package/plugin/skills/methodology/verification-before-completion/SKILL.md +128 -28
- package/plugin/skills/methodology/writing-plans/SKILL.md +105 -20
- package/plugin/skills/omega/omega-architecture/SKILL.md +178 -40
- package/plugin/skills/omega/omega-coding/SKILL.md +247 -41
- package/plugin/skills/omega/omega-sprint/SKILL.md +208 -46
- package/plugin/skills/omega/omega-testing/SKILL.md +253 -42
- package/plugin/skills/omega/omega-thinking/SKILL.md +263 -51
- package/plugin/skills/security/better-auth/SKILL.md +83 -34
- package/plugin/skills/security/oauth/SKILL.md +118 -35
- package/plugin/skills/security/owasp/SKILL.md +112 -35
- package/plugin/skills/testing/playwright/SKILL.md +141 -38
- package/plugin/skills/testing/pytest/SKILL.md +137 -38
- package/plugin/skills/testing/vitest/SKILL.md +124 -39
- package/plugin/skills/tools/document-processing/SKILL.md +111 -838
- package/plugin/skills/tools/image-processing/SKILL.md +126 -659
- package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
- package/plugin/skills/tools/media-processing/SKILL.md +118 -735
- package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
|
@@ -1,57 +1,134 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: OWASP security. Use
|
|
2
|
+
name: Applying OWASP Security
|
|
3
|
+
description: Claude applies OWASP security best practices to web applications. Use when preventing vulnerabilities, implementing input validation, securing authentication, configuring security headers, or conducting security reviews.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# OWASP
|
|
6
|
+
# Applying OWASP Security
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## Quick Start
|
|
9
9
|
|
|
10
|
-
### 1. Injection
|
|
11
10
|
```typescript
|
|
12
|
-
//
|
|
13
|
-
|
|
11
|
+
// lib/security/validation.ts
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import DOMPurify from "isomorphic-dompurify";
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
// Input validation
|
|
16
|
+
export const userSchema = z.object({
|
|
17
|
+
email: z.string().email().max(254),
|
|
18
|
+
password: z.string().min(12).max(128),
|
|
19
|
+
name: z.string().min(2).max(100).regex(/^[\p{L}\s'-]+$/u),
|
|
20
|
+
});
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Implement MFA
|
|
23
|
-
// Rate limit login attempts
|
|
22
|
+
// HTML sanitization
|
|
23
|
+
export const sanitizeHtml = (dirty: string) =>
|
|
24
|
+
DOMPurify.sanitize(dirty, { ALLOWED_TAGS: ["b", "i", "em", "strong", "a", "p"] });
|
|
24
25
|
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
| Feature | Description | Reference |
|
|
30
|
+
|---------|-------------|-----------|
|
|
31
|
+
| Injection Prevention | SQL, NoSQL, command injection protection | [OWASP Injection](https://owasp.org/Top10/A03_2021-Injection/) |
|
|
32
|
+
| XSS Prevention | Output encoding and HTML sanitization | [OWASP XSS](https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html) |
|
|
33
|
+
| CSRF Protection | Token-based cross-site request forgery defense | [OWASP CSRF](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) |
|
|
34
|
+
| Authentication Security | Password hashing, rate limiting, session management | [OWASP Auth](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) |
|
|
35
|
+
| Security Headers | CSP, HSTS, X-Frame-Options configuration | [OWASP Headers](https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html) |
|
|
36
|
+
| Input Validation | Schema validation and sanitization | [OWASP Validation](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html) |
|
|
37
|
+
|
|
38
|
+
## Common Patterns
|
|
39
|
+
|
|
40
|
+
### Parameterized Queries (SQL Injection Prevention)
|
|
41
|
+
|
|
27
42
|
```typescript
|
|
28
|
-
//
|
|
29
|
-
|
|
43
|
+
// BAD - SQL injection vulnerable
|
|
44
|
+
const result = await db.$queryRawUnsafe(`SELECT * FROM users WHERE id = '${userId}'`);
|
|
30
45
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
46
|
+
// GOOD - Parameterized query
|
|
47
|
+
const result = await db.user.findUnique({ where: { id: userId } });
|
|
48
|
+
const result = await db.$queryRaw`SELECT * FROM users WHERE id = ${userId}`;
|
|
34
49
|
```
|
|
35
50
|
|
|
36
|
-
###
|
|
51
|
+
### CSRF Protection Middleware
|
|
52
|
+
|
|
37
53
|
```typescript
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
import crypto from "crypto";
|
|
55
|
+
|
|
56
|
+
export function csrfProtection(req: Request, res: Response, next: NextFunction) {
|
|
57
|
+
if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next();
|
|
58
|
+
|
|
59
|
+
const cookieToken = req.cookies["csrf_token"];
|
|
60
|
+
const headerToken = req.headers["x-csrf-token"];
|
|
61
|
+
|
|
62
|
+
if (!cookieToken || !headerToken || cookieToken !== headerToken) {
|
|
63
|
+
return res.status(403).json({ error: "CSRF validation failed" });
|
|
64
|
+
}
|
|
65
|
+
next();
|
|
66
|
+
}
|
|
41
67
|
```
|
|
42
68
|
|
|
43
|
-
###
|
|
69
|
+
### Security Headers Configuration
|
|
70
|
+
|
|
44
71
|
```typescript
|
|
72
|
+
import helmet from "helmet";
|
|
73
|
+
|
|
45
74
|
app.use(helmet({
|
|
46
|
-
contentSecurityPolicy:
|
|
47
|
-
|
|
48
|
-
|
|
75
|
+
contentSecurityPolicy: {
|
|
76
|
+
directives: {
|
|
77
|
+
defaultSrc: ["'self'"],
|
|
78
|
+
scriptSrc: ["'self'", "'strict-dynamic'"],
|
|
79
|
+
styleSrc: ["'self'", "'unsafe-inline'"],
|
|
80
|
+
imgSrc: ["'self'", "data:", "https:"],
|
|
81
|
+
frameSrc: ["'none'"],
|
|
82
|
+
objectSrc: ["'none'"],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
strictTransportSecurity: { maxAge: 31536000, includeSubDomains: true, preload: true },
|
|
86
|
+
frameguard: { action: "deny" },
|
|
49
87
|
}));
|
|
50
88
|
```
|
|
51
89
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
90
|
+
### Password Security
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import bcrypt from "bcrypt";
|
|
94
|
+
|
|
95
|
+
const SALT_ROUNDS = 12;
|
|
96
|
+
|
|
97
|
+
export async function hashPassword(password: string): Promise<string> {
|
|
98
|
+
return bcrypt.hash(password, SALT_ROUNDS);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
102
|
+
return bcrypt.compare(password, hash);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function validatePasswordStrength(password: string): string[] {
|
|
106
|
+
const errors: string[] = [];
|
|
107
|
+
if (password.length < 12) errors.push("Must be at least 12 characters");
|
|
108
|
+
if (!/[a-z]/.test(password)) errors.push("Must contain lowercase");
|
|
109
|
+
if (!/[A-Z]/.test(password)) errors.push("Must contain uppercase");
|
|
110
|
+
if (!/\d/.test(password)) errors.push("Must contain digit");
|
|
111
|
+
if (!/[!@#$%^&*]/.test(password)) errors.push("Must contain special character");
|
|
112
|
+
return errors;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Best Practices
|
|
117
|
+
|
|
118
|
+
| Do | Avoid |
|
|
119
|
+
|----|-------|
|
|
120
|
+
| Validate all input on the server side | Trusting client-side validation alone |
|
|
121
|
+
| Use parameterized queries for all DB access | String concatenation in queries |
|
|
122
|
+
| Set security headers on all responses | Disabling security features for convenience |
|
|
123
|
+
| Implement rate limiting on sensitive endpoints | Allowing unlimited attempts |
|
|
124
|
+
| Hash passwords with bcrypt (12+ rounds) | Using weak/deprecated crypto algorithms |
|
|
125
|
+
| Log security events for monitoring | Exposing detailed error messages to users |
|
|
126
|
+
| Keep dependencies updated | Ignoring security warnings |
|
|
127
|
+
| Use HTTPS for all communications | Hardcoding secrets in source code |
|
|
128
|
+
|
|
129
|
+
## References
|
|
130
|
+
|
|
131
|
+
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
|
132
|
+
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
|
|
133
|
+
- [OWASP Testing Guide](https://owasp.org/www-project-web-security-testing-guide/)
|
|
134
|
+
- [OWASP ASVS](https://owasp.org/www-project-application-security-verification-standard/)
|
|
@@ -1,60 +1,163 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
2
|
+
name: Testing with Playwright
|
|
3
|
+
description: Claude writes reliable E2E tests using Playwright for browser automation. Use when writing end-to-end tests, implementing Page Object Model, visual regression testing, API mocking, or cross-browser testing.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Playwright
|
|
6
|
+
# Testing with Playwright
|
|
7
7
|
|
|
8
|
-
##
|
|
9
|
-
```typescript
|
|
10
|
-
import { test, expect } from '@playwright/test';
|
|
8
|
+
## Quick Start
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
});
|
|
10
|
+
```typescript
|
|
11
|
+
// playwright.config.ts
|
|
12
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
export default defineConfig({
|
|
15
|
+
testDir: "./tests/e2e",
|
|
16
|
+
fullyParallel: true,
|
|
17
|
+
retries: process.env.CI ? 2 : 0,
|
|
18
|
+
reporter: [["list"], ["html"]],
|
|
19
|
+
use: {
|
|
20
|
+
baseURL: "http://localhost:3000",
|
|
21
|
+
trace: "on-first-retry",
|
|
22
|
+
screenshot: "only-on-failure",
|
|
23
|
+
},
|
|
24
|
+
projects: [
|
|
25
|
+
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
|
|
26
|
+
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
|
|
27
|
+
{ name: "mobile", use: { ...devices["iPhone 12"] } },
|
|
28
|
+
],
|
|
29
|
+
webServer: { command: "npm run dev", url: "http://localhost:3000" },
|
|
23
30
|
});
|
|
24
31
|
```
|
|
25
32
|
|
|
26
|
-
##
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
| Feature | Description | Reference |
|
|
36
|
+
|---------|-------------|-----------|
|
|
37
|
+
| Page Object Model | Maintainable test architecture pattern | [POM Guide](https://playwright.dev/docs/pom) |
|
|
38
|
+
| Auto-Waiting | Built-in waiting for elements and assertions | [Auto-Waiting](https://playwright.dev/docs/actionability) |
|
|
39
|
+
| Network Mocking | Intercept and mock API responses | [Network](https://playwright.dev/docs/network) |
|
|
40
|
+
| Visual Testing | Screenshot comparison for regression testing | [Visual Comparisons](https://playwright.dev/docs/test-snapshots) |
|
|
41
|
+
| Cross-Browser | Chrome, Firefox, Safari, mobile devices | [Browsers](https://playwright.dev/docs/browsers) |
|
|
42
|
+
| Trace Viewer | Debug failing tests with timeline | [Trace Viewer](https://playwright.dev/docs/trace-viewer) |
|
|
43
|
+
|
|
44
|
+
## Common Patterns
|
|
45
|
+
|
|
46
|
+
### Page Object Model
|
|
47
|
+
|
|
27
48
|
```typescript
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
// tests/pages/login.page.ts
|
|
50
|
+
import { Page, Locator, expect } from "@playwright/test";
|
|
51
|
+
|
|
52
|
+
export class LoginPage {
|
|
53
|
+
readonly emailInput: Locator;
|
|
54
|
+
readonly passwordInput: Locator;
|
|
55
|
+
readonly submitButton: Locator;
|
|
56
|
+
|
|
57
|
+
constructor(private page: Page) {
|
|
58
|
+
this.emailInput = page.getByLabel("Email");
|
|
59
|
+
this.passwordInput = page.getByLabel("Password");
|
|
60
|
+
this.submitButton = page.getByRole("button", { name: "Sign in" });
|
|
61
|
+
}
|
|
30
62
|
|
|
31
63
|
async login(email: string, password: string) {
|
|
32
|
-
await this.
|
|
33
|
-
await this.
|
|
34
|
-
await this.
|
|
64
|
+
await this.emailInput.fill(email);
|
|
65
|
+
await this.passwordInput.fill(password);
|
|
66
|
+
await this.submitButton.click();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async expectError(message: string) {
|
|
70
|
+
await expect(this.page.getByRole("alert")).toContainText(message);
|
|
35
71
|
}
|
|
36
72
|
}
|
|
37
73
|
```
|
|
38
74
|
|
|
39
|
-
|
|
75
|
+
### API Mocking
|
|
76
|
+
|
|
40
77
|
```typescript
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
78
|
+
import { test, expect } from "@playwright/test";
|
|
79
|
+
|
|
80
|
+
test("mock API response", async ({ page }) => {
|
|
81
|
+
await page.route("**/api/users", (route) =>
|
|
82
|
+
route.fulfill({
|
|
83
|
+
status: 200,
|
|
84
|
+
contentType: "application/json",
|
|
85
|
+
body: JSON.stringify({ users: [{ id: 1, name: "John" }] }),
|
|
86
|
+
})
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
await page.goto("/users");
|
|
90
|
+
await expect(page.getByText("John")).toBeVisible();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("capture network requests", async ({ page }) => {
|
|
94
|
+
const requestPromise = page.waitForRequest("**/api/analytics");
|
|
95
|
+
await page.goto("/dashboard");
|
|
96
|
+
const request = await requestPromise;
|
|
97
|
+
expect(request.postDataJSON()).toMatchObject({ event: "page_view" });
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Authentication Fixture
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
// tests/fixtures/auth.fixture.ts
|
|
105
|
+
import { test as base } from "@playwright/test";
|
|
106
|
+
import { LoginPage } from "../pages/login.page";
|
|
107
|
+
|
|
108
|
+
export const test = base.extend<{ authenticatedPage: Page }>({
|
|
109
|
+
authenticatedPage: async ({ page }, use) => {
|
|
110
|
+
// Fast auth via API
|
|
111
|
+
const response = await page.request.post("/api/auth/login", {
|
|
112
|
+
data: { email: "test@example.com", password: "password" },
|
|
113
|
+
});
|
|
114
|
+
const { token } = await response.json();
|
|
115
|
+
|
|
116
|
+
await page.context().addCookies([
|
|
117
|
+
{ name: "auth_token", value: token, domain: "localhost", path: "/" },
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
await page.goto("/dashboard");
|
|
121
|
+
await use(page);
|
|
47
122
|
},
|
|
48
|
-
projects: [
|
|
49
|
-
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
50
|
-
{ name: 'mobile', use: { ...devices['iPhone 12'] } },
|
|
51
|
-
],
|
|
52
123
|
});
|
|
53
124
|
```
|
|
54
125
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
126
|
+
### Visual Regression Testing
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
test("visual snapshot", async ({ page }) => {
|
|
130
|
+
await page.goto("/");
|
|
131
|
+
await page.addStyleTag({
|
|
132
|
+
content: "*, *::before, *::after { animation-duration: 0s !important; }",
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await expect(page).toHaveScreenshot("homepage.png", {
|
|
136
|
+
fullPage: true,
|
|
137
|
+
maxDiffPixels: 100,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Mask dynamic content
|
|
141
|
+
await expect(page).toHaveScreenshot("dashboard.png", {
|
|
142
|
+
mask: [page.getByTestId("timestamp"), page.getByTestId("avatar")],
|
|
143
|
+
});
|
|
144
|
+
});
|
|
60
145
|
```
|
|
146
|
+
|
|
147
|
+
## Best Practices
|
|
148
|
+
|
|
149
|
+
| Do | Avoid |
|
|
150
|
+
|----|-------|
|
|
151
|
+
| Use Page Object Model for maintainability | Fragile CSS selectors |
|
|
152
|
+
| Prefer user-facing locators (getByRole, getByLabel) | Relying on arbitrary waits |
|
|
153
|
+
| Use API auth for faster test setup | Sharing state between tests |
|
|
154
|
+
| Enable traces and screenshots for debugging | Testing third-party services directly |
|
|
155
|
+
| Run tests in parallel for speed | Skipping flaky tests without fixing |
|
|
156
|
+
| Mock external APIs for reliability | Hardcoding test data |
|
|
157
|
+
|
|
158
|
+
## References
|
|
159
|
+
|
|
160
|
+
- [Playwright Documentation](https://playwright.dev/docs/intro)
|
|
161
|
+
- [Playwright Best Practices](https://playwright.dev/docs/best-practices)
|
|
162
|
+
- [Page Object Model](https://playwright.dev/docs/pom)
|
|
163
|
+
- [Visual Comparisons](https://playwright.dev/docs/test-snapshots)
|
|
@@ -1,58 +1,157 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description: Python
|
|
2
|
+
name: Testing with Pytest
|
|
3
|
+
description: Claude writes comprehensive Python tests using pytest. Use when writing unit tests, creating fixtures, parametrizing tests, mocking dependencies, testing async code, or setting up CI test pipelines.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Pytest
|
|
6
|
+
# Testing with Pytest
|
|
7
7
|
|
|
8
|
-
##
|
|
9
|
-
```python
|
|
10
|
-
def test_add():
|
|
11
|
-
assert add(1, 2) == 3
|
|
8
|
+
## Quick Start
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
```ini
|
|
11
|
+
# pytest.ini
|
|
12
|
+
[pytest]
|
|
13
|
+
testpaths = tests
|
|
14
|
+
python_files = test_*.py
|
|
15
|
+
addopts = -v --strict-markers --cov=src --cov-report=term-missing --cov-fail-under=80
|
|
16
|
+
markers =
|
|
17
|
+
slow: marks tests as slow
|
|
18
|
+
integration: marks tests as integration tests
|
|
19
|
+
asyncio_mode = auto
|
|
16
20
|
```
|
|
17
21
|
|
|
18
|
-
## Fixtures
|
|
19
22
|
```python
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
# tests/conftest.py
|
|
24
|
+
import pytest
|
|
25
|
+
from sqlalchemy.orm import Session
|
|
23
26
|
|
|
24
27
|
@pytest.fixture
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
def db_session(engine) -> Session:
|
|
29
|
+
"""Create database session with automatic rollback."""
|
|
30
|
+
connection = engine.connect()
|
|
31
|
+
transaction = connection.begin()
|
|
32
|
+
session = sessionmaker(bind=connection)()
|
|
33
|
+
yield session
|
|
34
|
+
session.close()
|
|
35
|
+
transaction.rollback()
|
|
36
|
+
connection.close()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
| Feature | Description | Reference |
|
|
42
|
+
|---------|-------------|-----------|
|
|
43
|
+
| Fixtures | Reusable test setup with dependency injection | [Fixtures Guide](https://docs.pytest.org/en/latest/how-to/fixtures.html) |
|
|
44
|
+
| Parametrization | Test multiple inputs with @pytest.mark.parametrize | [Parametrize](https://docs.pytest.org/en/latest/how-to/parametrize.html) |
|
|
45
|
+
| Mocking | unittest.mock integration for patching | [pytest-mock](https://pytest-mock.readthedocs.io/) |
|
|
46
|
+
| Async Testing | Native async/await support with pytest-asyncio | [pytest-asyncio](https://pytest-asyncio.readthedocs.io/) |
|
|
47
|
+
| Markers | Categorize and filter tests | [Markers](https://docs.pytest.org/en/latest/how-to/mark.html) |
|
|
48
|
+
| Coverage | Code coverage with pytest-cov | [pytest-cov](https://pytest-cov.readthedocs.io/) |
|
|
49
|
+
|
|
50
|
+
## Common Patterns
|
|
51
|
+
|
|
52
|
+
### Parametrized Validation Tests
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
|
|
54
|
+
```python
|
|
55
|
+
import pytest
|
|
56
|
+
from src.validators import validate_email, ValidationError
|
|
57
|
+
|
|
58
|
+
class TestEmailValidation:
|
|
59
|
+
@pytest.mark.parametrize("email", [
|
|
60
|
+
"user@example.com",
|
|
61
|
+
"user.name@example.com",
|
|
62
|
+
"user+tag@example.co.uk",
|
|
63
|
+
])
|
|
64
|
+
def test_valid_emails(self, email: str):
|
|
65
|
+
assert validate_email(email) is True
|
|
66
|
+
|
|
67
|
+
@pytest.mark.parametrize("email,error", [
|
|
68
|
+
("", "Email is required"),
|
|
69
|
+
("invalid", "Invalid email format"),
|
|
70
|
+
("@example.com", "Invalid email format"),
|
|
71
|
+
])
|
|
72
|
+
def test_invalid_emails(self, email: str, error: str):
|
|
73
|
+
with pytest.raises(ValidationError, match=error):
|
|
74
|
+
validate_email(email)
|
|
32
75
|
```
|
|
33
76
|
|
|
34
|
-
|
|
77
|
+
### Factory Fixtures
|
|
78
|
+
|
|
35
79
|
```python
|
|
36
|
-
@pytest.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
80
|
+
@pytest.fixture
|
|
81
|
+
def user_factory(db_session):
|
|
82
|
+
"""Factory for creating test users."""
|
|
83
|
+
def _create_user(email="test@example.com", name="Test User", **kwargs):
|
|
84
|
+
user = User(email=email, name=name, **kwargs)
|
|
85
|
+
db_session.add(user)
|
|
86
|
+
db_session.commit()
|
|
87
|
+
return user
|
|
88
|
+
return _create_user
|
|
89
|
+
|
|
90
|
+
@pytest.fixture
|
|
91
|
+
def test_user(user_factory):
|
|
92
|
+
return user_factory(email="testuser@example.com")
|
|
43
93
|
```
|
|
44
94
|
|
|
45
|
-
|
|
95
|
+
### Mocking External Services
|
|
96
|
+
|
|
46
97
|
```python
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
98
|
+
from unittest.mock import patch, MagicMock
|
|
99
|
+
|
|
100
|
+
class TestUserService:
|
|
101
|
+
def test_create_user_sends_email(self, user_service, mock_email_service):
|
|
102
|
+
user = user_service.create_user(email="new@example.com", name="New")
|
|
103
|
+
|
|
104
|
+
mock_email_service.send_email.assert_called_once_with(
|
|
105
|
+
to="new@example.com",
|
|
106
|
+
template="welcome",
|
|
107
|
+
context={"name": "New"},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@patch("src.services.user_service.datetime")
|
|
111
|
+
def test_last_login_updated(self, mock_datetime, user_service, test_user):
|
|
112
|
+
from datetime import datetime
|
|
113
|
+
mock_datetime.utcnow.return_value = datetime(2024, 1, 15, 10, 30)
|
|
114
|
+
|
|
115
|
+
user_service.authenticate(test_user.email, "password")
|
|
116
|
+
assert test_user.last_login == datetime(2024, 1, 15, 10, 30)
|
|
51
117
|
```
|
|
52
118
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
pytest
|
|
57
|
-
|
|
119
|
+
### Async API Testing
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
import pytest
|
|
123
|
+
from httpx import AsyncClient
|
|
124
|
+
|
|
125
|
+
@pytest.mark.asyncio
|
|
126
|
+
class TestAPIEndpoints:
|
|
127
|
+
async def test_get_users(self, async_client: AsyncClient, test_user):
|
|
128
|
+
response = await async_client.get("/api/users")
|
|
129
|
+
assert response.status_code == 200
|
|
130
|
+
assert len(response.json()["users"]) >= 1
|
|
131
|
+
|
|
132
|
+
async def test_create_user(self, async_client: AsyncClient):
|
|
133
|
+
response = await async_client.post("/api/users", json={
|
|
134
|
+
"email": "new@example.com",
|
|
135
|
+
"name": "New User",
|
|
136
|
+
"password": "SecurePass123!",
|
|
137
|
+
})
|
|
138
|
+
assert response.status_code == 201
|
|
58
139
|
```
|
|
140
|
+
|
|
141
|
+
## Best Practices
|
|
142
|
+
|
|
143
|
+
| Do | Avoid |
|
|
144
|
+
|----|-------|
|
|
145
|
+
| Use descriptive test names explaining the scenario | Sharing state between tests |
|
|
146
|
+
| Create reusable fixtures for common setup | Using sleep for timing issues |
|
|
147
|
+
| Use parametrize for testing multiple inputs | Testing implementation details |
|
|
148
|
+
| Mock external dependencies | Writing tests that depend on order |
|
|
149
|
+
| Group related tests in classes | Using hardcoded file paths |
|
|
150
|
+
| Use factories for test data creation | Leaving commented-out test code |
|
|
151
|
+
|
|
152
|
+
## References
|
|
153
|
+
|
|
154
|
+
- [Pytest Documentation](https://docs.pytest.org/)
|
|
155
|
+
- [pytest-asyncio](https://pytest-asyncio.readthedocs.io/)
|
|
156
|
+
- [pytest-mock](https://pytest-mock.readthedocs.io/)
|
|
157
|
+
- [pytest-cov](https://pytest-cov.readthedocs.io/)
|