@qubiit/lmagent 2.5.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/.editorconfig +18 -0
- package/AGENTS.md +169 -0
- package/CLAUDE.md +122 -0
- package/CONTRIBUTING.md +90 -0
- package/LICENSE +21 -0
- package/README.md +195 -0
- package/config/commands.yaml +194 -0
- package/config/levels.yaml +135 -0
- package/config/models.yaml +192 -0
- package/config/settings.yaml +405 -0
- package/config/tools-extended.yaml +534 -0
- package/config/tools.yaml +437 -0
- package/docs/assets/logo.png +0 -0
- package/docs/commands.md +132 -0
- package/docs/customization-guide.md +445 -0
- package/docs/getting-started.md +154 -0
- package/docs/how-to-start.md +242 -0
- package/docs/navigation-index.md +227 -0
- package/docs/usage-guide.md +113 -0
- package/install.js +1044 -0
- package/package.json +35 -0
- package/pyproject.toml +182 -0
- package/rules/_bootstrap.md +138 -0
- package/rules/agents-ia.md +607 -0
- package/rules/api-design.md +337 -0
- package/rules/automations-n8n.md +646 -0
- package/rules/code-style.md +570 -0
- package/rules/documentation.md +98 -0
- package/rules/security.md +316 -0
- package/rules/stack.md +395 -0
- package/rules/testing.md +326 -0
- package/rules/workflow.md +353 -0
- package/scripts/create_skill.js +300 -0
- package/scripts/validate_skills.js +283 -0
- package/skills/ai-agent-engineer/SKILL.md +394 -0
- package/skills/ai-agent-engineer/references/agent-patterns.md +149 -0
- package/skills/api-designer/SKILL.md +429 -0
- package/skills/api-designer/references/api-standards.md +13 -0
- package/skills/architect/SKILL.md +285 -0
- package/skills/architect/references/c4-model.md +133 -0
- package/skills/automation-engineer/SKILL.md +352 -0
- package/skills/automation-engineer/references/n8n-patterns.md +127 -0
- package/skills/backend-engineer/SKILL.md +261 -0
- package/skills/backend-engineer/assets/fastapi-project-structure.yaml +74 -0
- package/skills/backend-engineer/references/debugging-guide.md +174 -0
- package/skills/backend-engineer/references/design-patterns.md +208 -0
- package/skills/backend-engineer/scripts/scaffold_backend.py +313 -0
- package/skills/bmad-methodology/SKILL.md +202 -0
- package/skills/bmad-methodology/references/scale-adaptive-levels.md +141 -0
- package/skills/browser-agent/SKILL.md +502 -0
- package/skills/browser-agent/scripts/playwright_setup.ts +16 -0
- package/skills/code-reviewer/SKILL.md +306 -0
- package/skills/code-reviewer/references/code-review-checklist.md +16 -0
- package/skills/data-engineer/SKILL.md +474 -0
- package/skills/data-engineer/assets/pg-monitoring-queries.sql +154 -0
- package/skills/data-engineer/references/index-strategy.md +128 -0
- package/skills/data-engineer/scripts/backup_postgres.py +221 -0
- package/skills/devops-engineer/SKILL.md +547 -0
- package/skills/devops-engineer/references/ci-cd-patterns.md +265 -0
- package/skills/devops-engineer/scripts/docker_healthcheck.py +125 -0
- package/skills/document-generator/SKILL.md +746 -0
- package/skills/document-generator/references/pdf-generation.md +22 -0
- package/skills/frontend-engineer/SKILL.md +532 -0
- package/skills/frontend-engineer/references/accessibility-guide.md +146 -0
- package/skills/frontend-engineer/scripts/audit_bundle.py +144 -0
- package/skills/git-workflow/SKILL.md +374 -0
- package/skills/git-workflow/references/git-flow.md +25 -0
- package/skills/mcp-builder/SKILL.md +471 -0
- package/skills/mcp-builder/references/mcp-server-guide.md +23 -0
- package/skills/mobile-engineer/SKILL.md +502 -0
- package/skills/mobile-engineer/references/platform-guidelines.md +160 -0
- package/skills/orchestrator/SKILL.md +246 -0
- package/skills/orchestrator/references/methodology-routing.md +117 -0
- package/skills/orchestrator/references/persona-mapping.md +85 -0
- package/skills/orchestrator/references/routing-logic.md +110 -0
- package/skills/performance-engineer/SKILL.md +549 -0
- package/skills/performance-engineer/references/caching-patterns.md +181 -0
- package/skills/performance-engineer/scripts/profile_endpoint.py +170 -0
- package/skills/product-manager/SKILL.md +488 -0
- package/skills/product-manager/references/prioritization-frameworks.md +126 -0
- package/skills/prompt-engineer/SKILL.md +433 -0
- package/skills/prompt-engineer/references/prompt-patterns.md +158 -0
- package/skills/qa-engineer/SKILL.md +441 -0
- package/skills/qa-engineer/references/testing-strategy.md +166 -0
- package/skills/qa-engineer/scripts/run_coverage.py +147 -0
- package/skills/scrum-master/SKILL.md +225 -0
- package/skills/scrum-master/references/sprint-ceremonies.md +159 -0
- package/skills/security-analyst/SKILL.md +390 -0
- package/skills/security-analyst/references/owasp-top10.md +188 -0
- package/skills/security-analyst/scripts/audit_security.py +242 -0
- package/skills/seo-auditor/SKILL.md +523 -0
- package/skills/seo-auditor/references/seo-checklist.md +17 -0
- package/skills/spec-driven-dev/SKILL.md +342 -0
- package/skills/spec-driven-dev/references/phase-gates.md +107 -0
- package/skills/supabase-expert/SKILL.md +602 -0
- package/skills/supabase-expert/references/supabase-patterns.md +19 -0
- package/skills/swe-agent/SKILL.md +311 -0
- package/skills/swe-agent/references/trajectory-format.md +134 -0
- package/skills/systematic-debugger/SKILL.md +512 -0
- package/skills/systematic-debugger/references/debugging-guide.md +12 -0
- package/skills/tech-lead/SKILL.md +409 -0
- package/skills/tech-lead/references/code-review-checklist.md +111 -0
- package/skills/technical-writer/SKILL.md +631 -0
- package/skills/technical-writer/references/doc-templates.md +218 -0
- package/skills/testing-strategist/SKILL.md +476 -0
- package/skills/testing-strategist/references/testing-pyramid.md +16 -0
- package/skills/ux-ui-designer/SKILL.md +419 -0
- package/skills/ux-ui-designer/references/design-system-foundation.md +168 -0
- package/skills_overview.txt +94 -0
- package/templates/PROJECT_KICKOFF.md +284 -0
- package/templates/SKILL_TEMPLATE.md +131 -0
- package/templates/USAGE.md +95 -0
- package/templates/agent-python/README.md +71 -0
- package/templates/agent-python/agent.py +272 -0
- package/templates/agent-python/config.yaml +76 -0
- package/templates/agent-python/prompts/system.md +109 -0
- package/templates/agent-python/requirements.txt +7 -0
- package/templates/automation-n8n/README.md +14 -0
- package/templates/automation-n8n/webhook-handler.json +57 -0
- package/templates/backend-node/Dockerfile +12 -0
- package/templates/backend-node/README.md +15 -0
- package/templates/backend-node/package.json +30 -0
- package/templates/backend-node/src/index.ts +19 -0
- package/templates/backend-node/src/routes.ts +7 -0
- package/templates/backend-node/tsconfig.json +22 -0
- package/templates/backend-python/Dockerfile +11 -0
- package/templates/backend-python/README.md +78 -0
- package/templates/backend-python/app/core/config.py +12 -0
- package/templates/backend-python/app/core/database.py +12 -0
- package/templates/backend-python/app/main.py +17 -0
- package/templates/backend-python/app/routers/__init__.py +1 -0
- package/templates/backend-python/app/routers/health.py +7 -0
- package/templates/backend-python/requirements-dev.txt +6 -0
- package/templates/backend-python/requirements.txt +4 -0
- package/templates/backend-python/tests/test_health.py +9 -0
- package/templates/checkpoint.yaml +117 -0
- package/templates/database/README.md +474 -0
- package/templates/frontend-react/README.md +446 -0
- package/templates/plan.yaml +320 -0
- package/templates/session.yaml +125 -0
- package/templates/spec.yaml +229 -0
- package/templates/tasks.yaml +330 -0
- package/workflows/bugfix-backend.md +380 -0
- package/workflows/documentation.md +232 -0
- package/workflows/generate-prd.md +320 -0
- package/workflows/ideation.md +396 -0
- package/workflows/new-agent-ia.md +497 -0
- package/workflows/new-automation.md +374 -0
- package/workflows/new-feature.md +290 -0
- package/workflows/optimize-performance.md +373 -0
- package/workflows/resolve-github-issue.md +524 -0
- package/workflows/security-review.md +291 -0
- package/workflows/spec-driven.md +476 -0
- package/workflows/testing-strategy.md +296 -0
- package/workflows/third-party-integration.md +277 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Workflow para testing completo de una aplicación
|
|
3
|
+
level: 2
|
|
4
|
+
personas: [qa-engineer, backend-engineer, frontend-engineer]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Testing Strategy Workflow
|
|
8
|
+
|
|
9
|
+
Este workflow define la estrategia de testing para el proyecto.
|
|
10
|
+
|
|
11
|
+
## Pirámide de Tests
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌───────┐
|
|
15
|
+
│ E2E │ Pocos, lentos, costosos
|
|
16
|
+
/─────────\
|
|
17
|
+
/ Integration\ Algunos, moderados
|
|
18
|
+
/───────────────\
|
|
19
|
+
/ Unit Tests \ Muchos, rápidos, baratos
|
|
20
|
+
/─────────────────────\
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Unit Tests
|
|
26
|
+
|
|
27
|
+
### Qué testear
|
|
28
|
+
- Funciones de utilidad
|
|
29
|
+
- Lógica de negocio
|
|
30
|
+
- Transformaciones de datos
|
|
31
|
+
- Validaciones
|
|
32
|
+
|
|
33
|
+
### Python (pytest)
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
# tests/unit/test_utils.py
|
|
37
|
+
import pytest
|
|
38
|
+
from app.utils import calculate_discount
|
|
39
|
+
|
|
40
|
+
class TestCalculateDiscount:
|
|
41
|
+
def test_percentage_discount(self):
|
|
42
|
+
result = calculate_discount(100, percentage=10)
|
|
43
|
+
assert result == 90
|
|
44
|
+
|
|
45
|
+
def test_fixed_discount(self):
|
|
46
|
+
result = calculate_discount(100, fixed=15)
|
|
47
|
+
assert result == 85
|
|
48
|
+
|
|
49
|
+
def test_negative_result_returns_zero(self):
|
|
50
|
+
result = calculate_discount(10, fixed=20)
|
|
51
|
+
assert result == 0
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize("amount,percentage,expected", [
|
|
54
|
+
(100, 10, 90),
|
|
55
|
+
(50, 20, 40),
|
|
56
|
+
(0, 50, 0),
|
|
57
|
+
])
|
|
58
|
+
def test_parametrized(self, amount, percentage, expected):
|
|
59
|
+
assert calculate_discount(amount, percentage) == expected
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### TypeScript (Jest)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// __tests__/utils.test.ts
|
|
66
|
+
import { formatCurrency } from '../utils';
|
|
67
|
+
|
|
68
|
+
describe('formatCurrency', () => {
|
|
69
|
+
it('formats positive numbers', () => {
|
|
70
|
+
expect(formatCurrency(1234.56)).toBe('$1,234.56');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('handles zero', () => {
|
|
74
|
+
expect(formatCurrency(0)).toBe('$0.00');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('formats negative numbers', () => {
|
|
78
|
+
expect(formatCurrency(-100)).toBe('-$100.00');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Integration Tests
|
|
86
|
+
|
|
87
|
+
### Qué testear
|
|
88
|
+
- Endpoints API
|
|
89
|
+
- Database queries
|
|
90
|
+
- Service interactions
|
|
91
|
+
- External API calls (mocked)
|
|
92
|
+
|
|
93
|
+
### API Tests (pytest + httpx)
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# tests/integration/test_users_api.py
|
|
97
|
+
import pytest
|
|
98
|
+
from httpx import AsyncClient
|
|
99
|
+
|
|
100
|
+
@pytest.fixture
|
|
101
|
+
async def client(app):
|
|
102
|
+
async with AsyncClient(app=app, base_url="http://test") as client:
|
|
103
|
+
yield client
|
|
104
|
+
|
|
105
|
+
class TestUsersAPI:
|
|
106
|
+
async def test_create_user(self, client):
|
|
107
|
+
response = await client.post("/api/users", json={
|
|
108
|
+
"email": "test@example.com",
|
|
109
|
+
"name": "Test User"
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
assert response.status_code == 201
|
|
113
|
+
data = response.json()
|
|
114
|
+
assert data["email"] == "test@example.com"
|
|
115
|
+
assert "id" in data
|
|
116
|
+
|
|
117
|
+
async def test_create_user_duplicate_email(self, client, existing_user):
|
|
118
|
+
response = await client.post("/api/users", json={
|
|
119
|
+
"email": existing_user.email,
|
|
120
|
+
"name": "Another User"
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
assert response.status_code == 409
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Database Tests
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# tests/integration/test_user_repository.py
|
|
130
|
+
class TestUserRepository:
|
|
131
|
+
async def test_create_and_find(self, db_session):
|
|
132
|
+
repo = UserRepository(db_session)
|
|
133
|
+
|
|
134
|
+
# Create
|
|
135
|
+
user = await repo.create(UserCreate(email="test@example.com"))
|
|
136
|
+
|
|
137
|
+
# Find
|
|
138
|
+
found = await repo.get_by_email("test@example.com")
|
|
139
|
+
|
|
140
|
+
assert found is not None
|
|
141
|
+
assert found.id == user.id
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## E2E Tests
|
|
147
|
+
|
|
148
|
+
### Qué testear
|
|
149
|
+
- User flows críticos
|
|
150
|
+
- Happy paths principales
|
|
151
|
+
- Flujos de autenticación
|
|
152
|
+
|
|
153
|
+
### Playwright
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// e2e/login.spec.ts
|
|
157
|
+
import { test, expect } from '@playwright/test';
|
|
158
|
+
|
|
159
|
+
test.describe('Login flow', () => {
|
|
160
|
+
test('successful login', async ({ page }) => {
|
|
161
|
+
await page.goto('/login');
|
|
162
|
+
|
|
163
|
+
await page.fill('input[name="email"]', 'user@example.com');
|
|
164
|
+
await page.fill('input[name="password"]', 'password123');
|
|
165
|
+
await page.click('button[type="submit"]');
|
|
166
|
+
|
|
167
|
+
await expect(page).toHaveURL('/dashboard');
|
|
168
|
+
await expect(page.locator('text=Welcome')).toBeVisible();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('invalid credentials', async ({ page }) => {
|
|
172
|
+
await page.goto('/login');
|
|
173
|
+
|
|
174
|
+
await page.fill('input[name="email"]', 'wrong@example.com');
|
|
175
|
+
await page.fill('input[name="password"]', 'wrong');
|
|
176
|
+
await page.click('button[type="submit"]');
|
|
177
|
+
|
|
178
|
+
await expect(page.locator('text=Invalid credentials')).toBeVisible();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Component Tests (Frontend)
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
// __tests__/UserCard.test.tsx
|
|
189
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
190
|
+
import { UserCard } from '../UserCard';
|
|
191
|
+
|
|
192
|
+
const mockUser = {
|
|
193
|
+
id: '1',
|
|
194
|
+
name: 'John Doe',
|
|
195
|
+
email: 'john@example.com',
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
describe('UserCard', () => {
|
|
199
|
+
it('renders user information', () => {
|
|
200
|
+
render(<UserCard user={mockUser} />);
|
|
201
|
+
|
|
202
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
203
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument();
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('calls onEdit when edit button clicked', () => {
|
|
207
|
+
const onEdit = jest.fn();
|
|
208
|
+
render(<UserCard user={mockUser} onEdit={onEdit} />);
|
|
209
|
+
|
|
210
|
+
fireEvent.click(screen.getByRole('button', { name: /edit/i }));
|
|
211
|
+
|
|
212
|
+
expect(onEdit).toHaveBeenCalledWith(mockUser);
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Code Coverage
|
|
220
|
+
|
|
221
|
+
### Objetivos
|
|
222
|
+
|
|
223
|
+
| Tipo | Cobertura Mínima |
|
|
224
|
+
|------|------------------|
|
|
225
|
+
| Unit Tests | 80% |
|
|
226
|
+
| Integration | Key paths |
|
|
227
|
+
| E2E | Critical flows |
|
|
228
|
+
|
|
229
|
+
### Comandos
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# Python
|
|
233
|
+
pytest --cov=app --cov-report=html
|
|
234
|
+
|
|
235
|
+
# JavaScript
|
|
236
|
+
npm test -- --coverage
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Testing Checklist
|
|
242
|
+
|
|
243
|
+
```markdown
|
|
244
|
+
## Para cada PR
|
|
245
|
+
- [ ] Unit tests para nueva lógica
|
|
246
|
+
- [ ] Integration tests para nuevos endpoints
|
|
247
|
+
- [ ] E2E tests si es flujo crítico
|
|
248
|
+
- [ ] Coverage no disminuyó
|
|
249
|
+
|
|
250
|
+
## Para refactoring
|
|
251
|
+
- [ ] Tests existentes siguen pasando
|
|
252
|
+
- [ ] No cambios a tests (salvo estructura)
|
|
253
|
+
|
|
254
|
+
## Para bugs
|
|
255
|
+
- [ ] Test que reproduce el bug
|
|
256
|
+
- [ ] Fix hace pasar el test
|
|
257
|
+
- [ ] Regression test agregado
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Test Data
|
|
263
|
+
|
|
264
|
+
### Fixtures (pytest)
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
# conftest.py
|
|
268
|
+
@pytest.fixture
|
|
269
|
+
def sample_user():
|
|
270
|
+
return User(
|
|
271
|
+
id=uuid4(),
|
|
272
|
+
email="test@example.com",
|
|
273
|
+
name="Test User"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
@pytest.fixture
|
|
277
|
+
async def db_user(db_session, sample_user):
|
|
278
|
+
db_session.add(sample_user)
|
|
279
|
+
await db_session.commit()
|
|
280
|
+
return sample_user
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Factories
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
# tests/factories.py
|
|
287
|
+
import factory
|
|
288
|
+
|
|
289
|
+
class UserFactory(factory.Factory):
|
|
290
|
+
class Meta:
|
|
291
|
+
model = User
|
|
292
|
+
|
|
293
|
+
id = factory.LazyFunction(uuid4)
|
|
294
|
+
email = factory.Sequence(lambda n: f"user{n}@example.com")
|
|
295
|
+
name = factory.Faker("name")
|
|
296
|
+
```
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Workflow para integración con APIs de terceros
|
|
3
|
+
level: 2
|
|
4
|
+
personas: [backend-engineer, architect]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Third-Party Integration Workflow
|
|
8
|
+
|
|
9
|
+
Este workflow guía la integración segura con APIs de terceros.
|
|
10
|
+
|
|
11
|
+
## Pre-requisitos
|
|
12
|
+
|
|
13
|
+
1. Documentación de la API externa
|
|
14
|
+
2. Credenciales de prueba
|
|
15
|
+
3. Límites de rate conocidos
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Fase 1: Análisis
|
|
20
|
+
|
|
21
|
+
### 1.1 Evaluar la API
|
|
22
|
+
|
|
23
|
+
| Aspecto | Verificar |
|
|
24
|
+
|---------|-----------|
|
|
25
|
+
| Autenticación | API Key, OAuth, Bearer? |
|
|
26
|
+
| Rate Limits | Requests por minuto/hora |
|
|
27
|
+
| Formato | REST, GraphQL, SOAP |
|
|
28
|
+
| Versioning | ¿La versión es estable? |
|
|
29
|
+
| SLA | ¿Garantías de uptime? |
|
|
30
|
+
|
|
31
|
+
### 1.2 Mapear Endpoints Necesarios
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
| Endpoint | Método | Uso en nuestro sistema |
|
|
35
|
+
|----------|--------|------------------------|
|
|
36
|
+
| /users | GET | Sync usuarios |
|
|
37
|
+
| /orders | POST | Crear pedidos |
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 1.3 Diseñar Abstracción
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
# Interfaz abstracta para evitar vendor lock-in
|
|
44
|
+
class PaymentProvider(Protocol):
|
|
45
|
+
async def create_payment(self, amount: Decimal) -> PaymentResult:
|
|
46
|
+
...
|
|
47
|
+
|
|
48
|
+
async def get_payment(self, payment_id: str) -> Payment:
|
|
49
|
+
...
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Fase 2: Implementación
|
|
55
|
+
|
|
56
|
+
### 2.1 Configuración
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# config/settings.py
|
|
60
|
+
class ThirdPartySettings(BaseSettings):
|
|
61
|
+
api_key: str
|
|
62
|
+
api_url: str = "https://api.example.com/v1"
|
|
63
|
+
timeout: int = 30
|
|
64
|
+
max_retries: int = 3
|
|
65
|
+
|
|
66
|
+
class Config:
|
|
67
|
+
env_prefix = "THIRD_PARTY_"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# .env
|
|
72
|
+
THIRD_PARTY_API_KEY=sk_test_xxxx
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 2.2 Cliente HTTP
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# clients/third_party_client.py
|
|
79
|
+
import httpx
|
|
80
|
+
from tenacity import retry, wait_exponential, stop_after_attempt
|
|
81
|
+
|
|
82
|
+
class ThirdPartyClient:
|
|
83
|
+
def __init__(self, settings: ThirdPartySettings):
|
|
84
|
+
self.settings = settings
|
|
85
|
+
self.client = httpx.AsyncClient(
|
|
86
|
+
base_url=settings.api_url,
|
|
87
|
+
headers={"Authorization": f"Bearer {settings.api_key}"},
|
|
88
|
+
timeout=settings.timeout,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
@retry(
|
|
92
|
+
wait=wait_exponential(min=1, max=10),
|
|
93
|
+
stop=stop_after_attempt(3)
|
|
94
|
+
)
|
|
95
|
+
async def request(self, method: str, path: str, **kwargs):
|
|
96
|
+
response = await self.client.request(method, path, **kwargs)
|
|
97
|
+
response.raise_for_status()
|
|
98
|
+
return response.json()
|
|
99
|
+
|
|
100
|
+
async def get_user(self, user_id: str):
|
|
101
|
+
return await self.request("GET", f"/users/{user_id}")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2.3 Error Handling
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# exceptions/third_party.py
|
|
108
|
+
class ThirdPartyError(Exception):
|
|
109
|
+
"""Base error for third party integration"""
|
|
110
|
+
|
|
111
|
+
class ThirdPartyRateLimitError(ThirdPartyError):
|
|
112
|
+
"""Rate limit exceeded"""
|
|
113
|
+
|
|
114
|
+
class ThirdPartyAuthError(ThirdPartyError):
|
|
115
|
+
"""Authentication failed"""
|
|
116
|
+
|
|
117
|
+
# En el cliente
|
|
118
|
+
async def request(self, method: str, path: str, **kwargs):
|
|
119
|
+
try:
|
|
120
|
+
response = await self.client.request(method, path, **kwargs)
|
|
121
|
+
response.raise_for_status()
|
|
122
|
+
return response.json()
|
|
123
|
+
except httpx.HTTPStatusError as e:
|
|
124
|
+
if e.response.status_code == 429:
|
|
125
|
+
raise ThirdPartyRateLimitError("Rate limit exceeded")
|
|
126
|
+
elif e.response.status_code == 401:
|
|
127
|
+
raise ThirdPartyAuthError("Invalid API key")
|
|
128
|
+
raise ThirdPartyError(f"API error: {e}")
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 2.4 Caching
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# services/cached_third_party.py
|
|
135
|
+
from functools import lru_cache
|
|
136
|
+
import redis
|
|
137
|
+
|
|
138
|
+
redis_client = redis.Redis()
|
|
139
|
+
|
|
140
|
+
async def get_user_cached(user_id: str) -> dict:
|
|
141
|
+
cache_key = f"third_party:user:{user_id}"
|
|
142
|
+
|
|
143
|
+
# Check cache
|
|
144
|
+
cached = await redis_client.get(cache_key)
|
|
145
|
+
if cached:
|
|
146
|
+
return json.loads(cached)
|
|
147
|
+
|
|
148
|
+
# Fetch from API
|
|
149
|
+
user = await client.get_user(user_id)
|
|
150
|
+
|
|
151
|
+
# Cache for 1 hour
|
|
152
|
+
await redis_client.setex(cache_key, 3600, json.dumps(user))
|
|
153
|
+
|
|
154
|
+
return user
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Fase 3: Testing
|
|
160
|
+
|
|
161
|
+
### 3.1 Mocking
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
# tests/conftest.py
|
|
165
|
+
import respx
|
|
166
|
+
|
|
167
|
+
@pytest.fixture
|
|
168
|
+
def mock_third_party():
|
|
169
|
+
with respx.mock:
|
|
170
|
+
respx.get("https://api.example.com/v1/users/123").mock(
|
|
171
|
+
return_value=httpx.Response(200, json={"id": "123", "name": "Test"})
|
|
172
|
+
)
|
|
173
|
+
yield
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 3.2 Test Unitarios
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
# tests/test_third_party_client.py
|
|
180
|
+
async def test_get_user(mock_third_party):
|
|
181
|
+
client = ThirdPartyClient(settings)
|
|
182
|
+
user = await client.get_user("123")
|
|
183
|
+
assert user["id"] == "123"
|
|
184
|
+
|
|
185
|
+
async def test_rate_limit_handling(mock_third_party):
|
|
186
|
+
respx.get("https://api.example.com/v1/users/123").mock(
|
|
187
|
+
return_value=httpx.Response(429)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
with pytest.raises(ThirdPartyRateLimitError):
|
|
191
|
+
await client.get_user("123")
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### 3.3 Integration Tests
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
# tests/integration/test_third_party_real.py
|
|
198
|
+
@pytest.mark.integration
|
|
199
|
+
@pytest.mark.skipif(not os.getenv("THIRD_PARTY_API_KEY"), reason="No API key")
|
|
200
|
+
async def test_real_api_connection():
|
|
201
|
+
"""Test with real API (use sparingly)"""
|
|
202
|
+
client = ThirdPartyClient(settings)
|
|
203
|
+
# Use a safe, idempotent endpoint
|
|
204
|
+
result = await client.health_check()
|
|
205
|
+
assert result["status"] == "ok"
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Fase 4: Monitoring
|
|
211
|
+
|
|
212
|
+
### 4.1 Métricas
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from prometheus_client import Counter, Histogram
|
|
216
|
+
|
|
217
|
+
third_party_requests = Counter(
|
|
218
|
+
'third_party_requests_total',
|
|
219
|
+
'Total requests to third party API',
|
|
220
|
+
['method', 'endpoint', 'status']
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
third_party_latency = Histogram(
|
|
224
|
+
'third_party_request_duration_seconds',
|
|
225
|
+
'Request duration to third party API'
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### 4.2 Alertas
|
|
230
|
+
|
|
231
|
+
```yaml
|
|
232
|
+
# Prometheus alert rules
|
|
233
|
+
groups:
|
|
234
|
+
- name: third_party
|
|
235
|
+
rules:
|
|
236
|
+
- alert: ThirdPartyHighErrorRate
|
|
237
|
+
expr: |
|
|
238
|
+
rate(third_party_requests_total{status=~"5.."}[5m])
|
|
239
|
+
/ rate(third_party_requests_total[5m]) > 0.05
|
|
240
|
+
for: 5m
|
|
241
|
+
annotations:
|
|
242
|
+
summary: "High error rate with third party API"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Checklist
|
|
248
|
+
|
|
249
|
+
```markdown
|
|
250
|
+
## Integration: {nombre}
|
|
251
|
+
|
|
252
|
+
### Seguridad
|
|
253
|
+
- [ ] Credenciales en variables de entorno
|
|
254
|
+
- [ ] No hardcoded secrets
|
|
255
|
+
- [ ] Credenciales rotables
|
|
256
|
+
|
|
257
|
+
### Reliability
|
|
258
|
+
- [ ] Retry logic implementado
|
|
259
|
+
- [ ] Timeout configurado
|
|
260
|
+
- [ ] Circuit breaker (opcional)
|
|
261
|
+
- [ ] Fallback strategy
|
|
262
|
+
|
|
263
|
+
### Performance
|
|
264
|
+
- [ ] Caching implementado
|
|
265
|
+
- [ ] Rate limiting respetado
|
|
266
|
+
- [ ] Connection pooling
|
|
267
|
+
|
|
268
|
+
### Observability
|
|
269
|
+
- [ ] Logging de requests/responses
|
|
270
|
+
- [ ] Métricas expuestas
|
|
271
|
+
- [ ] Alertas configuradas
|
|
272
|
+
|
|
273
|
+
### Testing
|
|
274
|
+
- [ ] Unit tests con mocks
|
|
275
|
+
- [ ] Integration tests
|
|
276
|
+
- [ ] Error scenarios cubiertos
|
|
277
|
+
```
|