# Coding Standards

Technical coding standards and patterns for Syndarix development.

## Table of Contents

- [Backend Standards](#backend-standards)
- [Frontend Standards](#frontend-standards)
- [Testing Patterns](#testing-patterns)
- [Security Guidelines](#security-guidelines)

---

## Backend Standards

### Dependency Management

- Use **uv** (modern Python package manager), not pip
- Always use `uv run` prefix for commands
- Add dependencies: `uv add <package>` or `uv add --dev <package>`

```bash
# Running tests
IS_TEST=True uv run pytest

# Or use Makefile
make test
make install-dev
```

### Database Migrations

- Use the `migrate.py` helper script, not Alembic directly
- Never commit migrations without testing them first

```bash
# Generate and apply migration
python migrate.py auto "message"

# Check current state
python migrate.py current
```

### Authentication Testing Fixtures

Backend fixtures in `tests/conftest.py`:

| Fixture | Purpose |
|---------|---------|
| `async_test_db` | Fresh SQLite per test |
| `async_test_user` | Pre-created regular user |
| `async_test_superuser` | Pre-created admin user |
| `user_token` | Access token for regular user |
| `superuser_token` | Access token for admin |

```python
# Always use these decorators for async tests
@pytest.mark.asyncio
async def test_something():
    pass

# Use for async fixtures
@pytest_asyncio.fixture
async def my_fixture():
    pass
```

### Database Exception Mocking

```python
from unittest.mock import patch, AsyncMock

async def mock_commit():
    raise OperationalError("Connection lost", {}, Exception())

with patch.object(session, 'commit', side_effect=mock_commit):
    with patch.object(session, 'rollback', new_callable=AsyncMock) as mock_rollback:
        with pytest.raises(OperationalError):
            await crud_method(session, obj_in=data)
        mock_rollback.assert_called_once()
```

### Security Considerations

- Backend has comprehensive security tests (JWT attacks, session hijacking)
- Never skip security headers in production
- Rate limiting: `@limiter.limit("10/minute")`
- Session revocation is database-backed, not just JWT expiry

---

## Frontend Standards

### Auth Store Dependency Injection

**CRITICAL: ALWAYS use `useAuth()` from `AuthContext`, NEVER import `useAuthStore` directly!**

```typescript
// ❌ WRONG - Bypasses dependency injection
import { useAuthStore } from '@/lib/stores/authStore';
const { user, isAuthenticated } = useAuthStore();

// ✅ CORRECT - Uses dependency injection
import { useAuth } from '@/lib/auth/AuthContext';
const { user, isAuthenticated } = useAuth();
```

**Why This Matters:**
- E2E tests inject mock stores via `window.__TEST_AUTH_STORE__`
- Unit tests inject via `<AuthProvider store={mockStore}>`
- Direct `useAuthStore` imports bypass this injection → **tests fail**
- ESLint will catch violations

**Exceptions:**
1. `AuthContext.tsx` - DI boundary, legitimately needs real store
2. `client.ts` - Non-React context, uses dynamic import + `__TEST_AUTH_STORE__` check

### API Client Generation

```bash
# After backend schema changes
npm run generate:api
```

- Client is auto-generated from OpenAPI spec
- Located in `frontend/src/lib/api/generated/`
- **NEVER manually edit generated files**

### Component Development

- Follow design system docs in `frontend/docs/design-system/`
- Read `08-ai-guidelines.md` for AI code generation rules
- Use parent-controlled spacing (see `04-spacing-philosophy.md`)
- WCAG AA compliance required (see `07-accessibility.md`)

### Route Groups Structure

```
src/app/[locale]/
├── (auth)/           # Public auth pages (login, register, password reset)
│                     # Uses AuthLayoutClient - redirects authenticated users
├── (authenticated)/  # Protected app pages
│                     # Uses AuthGuard + Header/Footer
├── admin/            # Admin pages with sidebar
│                     # Uses AuthGuard requireAdmin
├── dev/              # Design system documentation
└── prototypes/       # UI prototypes for approval
```

---

## Testing Patterns

### Frontend Unit Tests

```typescript
// Mock useAuth correctly
jest.mock('@/lib/auth/AuthContext', () => ({
  useAuth: () => ({
    user: { id: '1', email: 'test@example.com' },
    isAuthenticated: true,
  }),
}));
```

### E2E Test Best Practices (Playwright)

**Navigation Pattern:**
```typescript
// ✅ CORRECT - Use Promise.all for Next.js Link clicks
await Promise.all([
  page.waitForURL('/target', { timeout: 10000 }),
  link.click()
]);
```

**Selectors:**
- Use ID-based selectors for validation errors: `#email-error`
- Error IDs use dashes not underscores: `#new-password-error`
- Target `.border-destructive[role="alert"]` to avoid Next.js route announcer conflicts
- Avoid generic `[role="alert"]` which matches multiple elements

**URL Assertions:**
```typescript
// ✅ Use regex to handle query params
await expect(page).toHaveURL(/\/auth\/login/);

// ❌ Don't use exact strings (fails with query params)
await expect(page).toHaveURL('/auth/login');
```

**Configuration:**
- Uses 12 workers in non-CI mode
- Reduces to 2 workers in CI for stability

### Backend E2E Testing

Requires Docker for Testcontainers.

```bash
# Install deps
make install-e2e

# Run all E2E tests
make test-e2e

# Run schema tests only
make test-e2e-schema

# Run all tests (unit + E2E)
make test-all
```

Uses:
- Testcontainers (real PostgreSQL)
- Schemathesis (OpenAPI contract testing)

Markers:
- `@pytest.mark.e2e`
- `@pytest.mark.postgres`
- `@pytest.mark.schemathesis`

See: `backend/docs/E2E_TESTING.md` for complete guide.

---

## Security Guidelines

### OWASP Top 10 Compliance

All code must be reviewed for:
- Injection vulnerabilities (SQL, command, XSS)
- Broken authentication
- Sensitive data exposure
- Security misconfiguration
- Insecure deserialization

### Rate Limiting

```python
from app.core.limiter import limiter

@router.post("/endpoint")
@limiter.limit("10/minute")
async def endpoint():
    pass
```

### JWT Security

- Tokens stored securely (httpOnly cookies preferred)
- Short expiration times
- Refresh token rotation
- Database-backed session revocation

---

## Common Workflows

### Adding a New Feature

1. Start with backend schema and CRUD
2. Implement API route with proper authorization
3. Write backend tests (aim for >90% coverage)
4. Generate frontend API client: `npm run generate:api`
5. Implement frontend components
6. Write frontend unit tests
7. Add E2E tests for critical flows
8. Update relevant documentation

### Fixing Tests

| Layer | Check |
|-------|-------|
| Backend | Test database isolation, async fixture usage |
| Frontend unit | Mock `useAuth()` not `useAuthStore` |
| E2E | Use `Promise.all()` pattern, regex URL assertions |

### Debugging

| Layer | Action |
|-------|--------|
| Backend | Check `IS_TEST=True` environment variable |
| Frontend | Run `npm run type-check` first |
| E2E | Use `npm run test:e2e:debug` for step-by-step |
| All | Check logs - backend has detailed error logging |

---

## Demo Mode

Frontend-only showcase mode for demos without backend.

```bash
# Enable
echo "NEXT_PUBLIC_DEMO_MODE=true" > frontend/.env.local
```

- Uses MSW (Mock Service Worker) to intercept API calls
- Zero backend required - perfect for Vercel deployments
- MSW handlers auto-generated from OpenAPI spec
- Run `npm run generate:api` → updates both API client AND MSW handlers

**Demo Credentials:**
- User: `demo@example.com` / `DemoPass123`
- Admin: `admin@example.com` / `AdminPass123`

**Safety:**
- MSW never runs during tests (Jest or Playwright)
- Mock files excluded from linting and coverage

See: `frontend/docs/DEMO_MODE.md` for complete guide.
