# Debugging Guide

Common debugging strategies and troubleshooting patterns for Flyer Crawler.

## Table of Contents

- [Quick Debugging Checklist](#quick-debugging-checklist)
- [Container Issues](#container-issues)
- [Database Issues](#database-issues)
- [Test Failures](#test-failures)
- [API Errors](#api-errors)
- [Authentication Problems](#authentication-problems)
- [Background Job Issues](#background-job-issues)
- [SSL Certificate Issues](#ssl-certificate-issues)
- [Frontend Issues](#frontend-issues)
- [Performance Problems](#performance-problems)
- [Debugging Tools](#debugging-tools)

---

## Quick Debugging Checklist

When something breaks, check these first:

1. **Are containers running?**

   ```bash
   podman ps
   ```

2. **Is the database accessible?**

   ```bash
   podman exec flyer-crawler-postgres pg_isready -U postgres
   ```

3. **Are environment variables set?**

   ```bash
   # Check .env.local exists
   cat .env.local
   ```

4. **Are there recent errors in logs?**

   ```bash
   # PM2 logs (dev container)
   podman exec -it flyer-crawler-dev pm2 logs

   # Container logs
   podman logs -f flyer-crawler-dev

   # PM2 logs (production)
   pm2 logs flyer-crawler-api
   ```

5. **Are PM2 processes running?**

   ```bash
   podman exec -it flyer-crawler-dev pm2 status
   ```

6. **Is Redis accessible?**

   ```bash
   podman exec flyer-crawler-redis redis-cli ping
   ```

---

## Container Issues

### Container Won't Start

**Symptom**: `podman start` fails or container exits immediately

**Debug**:

```bash
# Check container status
podman ps -a

# View container logs
podman logs flyer-crawler-postgres
podman logs flyer-crawler-redis
podman logs flyer-crawler-dev

# Inspect container
podman inspect flyer-crawler-dev
```

**Common Causes**:

- Port already in use
- Insufficient resources
- Configuration error

**Solutions**:

```bash
# Check port usage
netstat -an | findstr "5432"
netstat -an | findstr "6379"

# Remove and recreate container
podman stop flyer-crawler-postgres
podman rm flyer-crawler-postgres
# ... recreate with podman run ...
```

### "Unable to connect to Podman socket"

**Symptom**: `Error: unable to connect to Podman socket`

**Solution**:

```bash
# Start Podman machine
podman machine start

# Verify it's running
podman machine list
```

### Port Already in Use

**Symptom**: `Error: port 5432 is already allocated`

**Solutions**:

**Option 1**: Stop conflicting service

```bash
# Find process using port
netstat -ano | findstr "5432"

# Stop the service or kill process
```

**Option 2**: Use different port

```bash
# Run container on different host port
podman run -d --name flyer-crawler-postgres -p 5433:5432 ...

# Update .env.local
DB_PORT=5433
```

---

## Database Issues

### Connection Refused

**Symptom**: `Error: connect ECONNREFUSED 127.0.0.1:5432`

**Debug**:

```bash
# 1. Check if PostgreSQL container is running
podman ps | grep postgres

# 2. Check if PostgreSQL is ready
podman exec flyer-crawler-postgres pg_isready -U postgres

# 3. Test connection
podman exec flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev -c "SELECT 1;"
```

**Common Causes**:

- Container not running
- PostgreSQL still initializing
- Wrong credentials in `.env.local`

**Solutions**:

```bash
# Start container
podman start flyer-crawler-postgres

# Wait for initialization (check logs)
podman logs -f flyer-crawler-postgres

# Verify credentials match .env.local
cat .env.local | grep DB_
```

### Schema Out of Sync

**Symptom**: Tests fail with missing column or table errors

**Cause**: `master_schema_rollup.sql` not in sync with migrations

**Solution**:

```bash
# Reset database with current schema
podman exec -i flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev < sql/drop_tables.sql
podman exec -i flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev < sql/master_schema_rollup.sql

# Verify schema
podman exec flyer-crawler-postgres psql -U postgres -d flyer_crawler_dev -c "\dt"
```

### Query Performance Issues

**Debug**:

```sql
-- Enable query logging
ALTER DATABASE flyer_crawler_dev SET log_statement = 'all';

-- Check slow queries
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
WHERE mean_exec_time > 100
ORDER BY mean_exec_time DESC
LIMIT 10;

-- Analyze query plan
EXPLAIN ANALYZE
SELECT * FROM flyers WHERE store_id = 1;
```

**Solutions**:

- Add missing indexes
- Optimize WHERE clauses
- Use connection pooling
- See [ADR-034](../adr/0034-repository-pattern-standards.md)

---

## Test Failures

### Tests Pass on Windows, Fail in Container

**Cause**: Platform-specific behavior ([ADR-014](../adr/0014-containerization-and-deployment-strategy.md))

**Rule**: Container results are authoritative. Windows results are unreliable.

**Solution**:

```bash
# Always run tests in container
podman exec -it flyer-crawler-dev npm test

# For specific test
podman exec -it flyer-crawler-dev npm test -- --run src/path/to/test.test.ts
```

### Integration Tests Fail

**Common Issues**:

**1. Vitest globalSetup Context Isolation**

**Symptom**: Mocks or spies don't work in integration tests

**Cause**: `globalSetup` runs in separate Node.js context

**Solutions**:

- Mark test as `.todo()` and document limitation
- Create test-only API endpoints
- Use Redis-based mock flags

See [CLAUDE.md#integration-test-issues](../../CLAUDE.md#integration-test-issues) for details.

**2. Cache Stale After Direct SQL**

**Symptom**: Test reads stale data after direct database insert

**Cause**: Cache not invalidated

**Solution**:

```typescript
// After direct SQL insert
await cacheService.invalidateFlyers();
```

**3. Queue Interference**

**Symptom**: Cleanup worker processes test data before assertions

**Solution**:

```typescript
const { cleanupQueue } = await import('../../services/queues.server');
await cleanupQueue.drain();
await cleanupQueue.pause();
// ... test ...
await cleanupQueue.resume();
```

### Type Check Failures

**Symptom**: `npm run type-check` fails

**Debug**:

```bash
# Run type check in container
podman exec -it flyer-crawler-dev npm run type-check

# Check specific file
podman exec -it flyer-crawler-dev npx tsc --noEmit src/path/to/file.ts
```

**Common Causes**:

- Missing type definitions
- Incorrect imports
- Type mismatch in function calls

---

## API Errors

### 404 Not Found

**Debug**:

```bash
# Check route registration
grep -r "router.get" src/routes/

# Check route path matches request
# Verify middleware order
```

**Common Causes**:

- Route not registered in `server.ts`
- Typo in route path
- Middleware blocking request

### 500 Internal Server Error

**Debug**:

```bash
# Check application logs
podman logs -f flyer-crawler-dev

# Check Bugsink for errors
# Visit: http://localhost:8443 (dev) or https://bugsink.projectium.com (prod)
```

**Common Causes**:

- Unhandled exception
- Database error
- Missing environment variable

**Solution Pattern**:

```typescript
// Always wrap route handlers
app.get('/api/endpoint', async (req, res) => {
  try {
    const result = await service.doSomething();
    return sendSuccess(res, result);
  } catch (error) {
    return sendError(res, error); // Handles error types automatically
  }
});
```

### 401 Unauthorized

**Debug**:

```bash
# Check JWT token in request
# Verify token is valid and not expired

# Test token decoding
node -e "console.log(require('jsonwebtoken').decode('YOUR_TOKEN_HERE'))"
```

**Common Causes**:

- Token expired
- Invalid token format
- Missing Authorization header
- Wrong JWT_SECRET

---

## Authentication Problems

### OAuth Not Working

**Debug**:

```bash
# 1. Verify OAuth credentials
cat .env.local | grep GOOGLE_CLIENT

# 2. Check OAuth routes are registered
grep -r "passport.authenticate" src/routes/

# 3. Verify redirect URI matches Google Console
# Should be: http://localhost:3001/api/auth/google/callback
```

**Common Issues**:

- Redirect URI mismatch in Google Console
- OAuth not enabled (commented out in config)
- Wrong client ID/secret

See [AUTHENTICATION.md](../architecture/AUTHENTICATION.md) for setup.

### JWT Token Invalid

**Debug**:

```typescript
// Decode token to inspect
import jwt from 'jsonwebtoken';

const decoded = jwt.decode(token);
console.log('Token payload:', decoded);
console.log('Expired:', decoded.exp < Date.now() / 1000);
```

**Solutions**:

- Regenerate token
- Check JWT_SECRET matches between environments
- Verify token hasn't expired

---

## PM2 Process Issues (Dev Container)

### PM2 Process Not Starting

**Symptom**: `pm2 status` shows process as "errored" or "stopped"

**Debug**:

```bash
# Check process status
podman exec -it flyer-crawler-dev pm2 status

# View process logs
podman exec -it flyer-crawler-dev pm2 logs flyer-crawler-api-dev --lines 50

# Show detailed process info
podman exec -it flyer-crawler-dev pm2 show flyer-crawler-api-dev

# Check if port is in use
podman exec -it flyer-crawler-dev netstat -tlnp | grep 3001
```

**Common Causes**:

- Port already in use by another process
- Missing environment variables
- Syntax error in code
- Database connection failure

**Solutions**:

```bash
# Restart specific process
podman exec -it flyer-crawler-dev pm2 restart flyer-crawler-api-dev

# Restart all processes
podman exec -it flyer-crawler-dev pm2 restart all

# Delete and recreate processes
podman exec -it flyer-crawler-dev pm2 delete all
podman exec -it flyer-crawler-dev pm2 start /app/ecosystem.dev.config.cjs
```

### PM2 Log Access

**View logs in real-time**:

```bash
# All processes
podman exec -it flyer-crawler-dev pm2 logs

# Specific process
podman exec -it flyer-crawler-dev pm2 logs flyer-crawler-api-dev

# Last 100 lines
podman exec -it flyer-crawler-dev pm2 logs --lines 100
```

**View log files directly**:

```bash
# API logs
MSYS_NO_PATHCONV=1 podman exec flyer-crawler-dev cat /var/log/pm2/api-out.log
MSYS_NO_PATHCONV=1 podman exec flyer-crawler-dev cat /var/log/pm2/api-error.log

# Worker logs
MSYS_NO_PATHCONV=1 podman exec flyer-crawler-dev cat /var/log/pm2/worker-out.log

# Vite logs
MSYS_NO_PATHCONV=1 podman exec flyer-crawler-dev cat /var/log/pm2/vite-out.log
```

### Redis Log Access

**View Redis logs**:

```bash
# View Redis log file
MSYS_NO_PATHCONV=1 podman exec flyer-crawler-redis cat /var/log/redis/redis-server.log

# View from dev container (shared volume)
MSYS_NO_PATHCONV=1 podman exec flyer-crawler-dev cat /var/log/redis/redis-server.log
```

---

## Background Job Issues

### Jobs Not Processing

**Debug**:

```bash
# Check if worker is running (dev container)
podman exec -it flyer-crawler-dev pm2 status

# Check worker logs (dev container)
podman exec -it flyer-crawler-dev pm2 logs flyer-crawler-worker-dev

# Check if worker is running (production)
pm2 list

# Check worker logs (production)
pm2 logs flyer-crawler-worker

# Check Redis connection
podman exec flyer-crawler-redis redis-cli ping

# Check queue status
node -e "
const { flyerProcessingQueue } = require('./dist/services/queues.server.js');
flyerProcessingQueue.getJobCounts().then(console.log);
"
```

**Common Causes**:

- Worker not running
- Redis connection lost
- Queue paused
- Job stuck in failed state

**Solutions**:

```bash
# Restart worker
pm2 restart flyer-crawler-worker

# Clear failed jobs
node -e "
const { flyerProcessingQueue } = require('./dist/services/queues.server.js');
flyerProcessingQueue.clean(0, 1000, 'failed');
"
```

### Jobs Failing

**Debug**:

```bash
# Check failed jobs
node -e "
const { flyerProcessingQueue } = require('./dist/services/queues.server.js');
flyerProcessingQueue.getFailed().then(jobs => {
  jobs.forEach(job => console.log(job.failedReason));
});
"

# Check worker logs for stack traces
pm2 logs flyer-crawler-worker --lines 100
```

**Common Causes**:

- Gemini API errors
- Database errors
- Invalid job data

---

## SSL Certificate Issues

### Images Not Loading (ERR_CERT_AUTHORITY_INVALID)

**Symptom**: Flyer images fail to load with `ERR_CERT_AUTHORITY_INVALID` in browser console

**Cause**: Mixed hostname origins - user accesses via `localhost` but images use `127.0.0.1` (or vice versa)

**Debug**:

```bash
# Check which hostname images are using
podman exec flyer-crawler-dev psql -U postgres -d flyer_crawler_dev \
  -c "SELECT image_url FROM flyers LIMIT 1;"

# Verify certificate includes both hostnames
podman exec flyer-crawler-dev openssl x509 -in /app/certs/localhost.crt -text -noout | grep -A1 "Subject Alternative Name"

# Check NGINX accepts both hostnames
podman exec flyer-crawler-dev grep "server_name" /etc/nginx/sites-available/default
```

**Solution**: The dev container is configured to handle both hostnames:

1. Certificate includes SANs for `localhost`, `127.0.0.1`, and `::1`
2. NGINX `server_name` directive includes both `localhost` and `127.0.0.1`

If you still see errors:

```bash
# Rebuild container to regenerate certificate
podman-compose down
podman-compose build --no-cache flyer-crawler-dev
podman-compose up -d
```

See [FLYER-URL-CONFIGURATION.md](../FLYER-URL-CONFIGURATION.md#ssl-certificate-configuration-dev-container) for full details.

### Self-Signed Certificate Not Trusted

**Symptom**: Browser shows security warning for `https://localhost`

**Temporary Workaround**: Accept the warning by clicking "Advanced" > "Proceed to localhost"

**Permanent Fix (Recommended)**: Install the mkcert CA certificate to eliminate all SSL warnings.

The CA certificate is located at `certs/mkcert-ca.crt` in the project root. See [`certs/README.md`](../../certs/README.md) for platform-specific installation instructions (Windows, macOS, Linux, Firefox).

After installation:

- Your browser will trust all mkcert certificates without warnings
- Both `https://localhost/` and `https://127.0.0.1/` will work without SSL errors
- Flyer images will load without `ERR_CERT_AUTHORITY_INVALID` errors

### NGINX SSL Configuration Test

**Debug**:

```bash
# Test NGINX configuration
podman exec flyer-crawler-dev nginx -t

# Check if NGINX is listening on 443
podman exec flyer-crawler-dev netstat -tlnp | grep 443

# Verify certificate files exist
podman exec flyer-crawler-dev ls -la /app/certs/
```

---

## Frontend Issues

### Hot Reload Not Working

**Debug**:

```bash
# Check Vite is running
curl http://localhost:5173

# Check for port conflicts
netstat -an | findstr "5173"
```

**Solution**:

```bash
# Restart dev server
npm run dev
```

### API Calls Failing (CORS)

**Symptom**: `CORS policy: No 'Access-Control-Allow-Origin' header`

**Debug**:

```typescript
// Check CORS configuration in server.ts
import cors from 'cors';

app.use(
  cors({
    origin: env.FRONTEND_URL, // Should match http://localhost:5173 in dev
    credentials: true,
  }),
);
```

**Solution**: Verify `FRONTEND_URL` in `.env.local` matches the frontend URL

---

## Performance Problems

### Slow API Responses

**Debug**:

```typescript
// Add timing logs
const start = Date.now();
const result = await slowOperation();
console.log(`Operation took ${Date.now() - start}ms`);
```

**Common Causes**:

- N+1 query problem
- Missing database indexes
- Large payload size
- No caching

**Solutions**:

- Use JOINs instead of multiple queries
- Add indexes: `CREATE INDEX idx_name ON table(column);`
- Implement pagination
- Add Redis caching

### High Memory Usage

**Debug**:

```bash
# Check PM2 memory usage
pm2 monit

# Check container memory
podman stats flyer-crawler-dev
```

**Common Causes**:

- Memory leak
- Large in-memory cache
- Unbounded array growth

---

## Debugging Tools

### VS Code Debugger

**Launch Configuration** (`.vscode/launch.json`):

```json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug Tests",
      "program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
      "args": ["--run", "${file}"],
      "console": "integratedTerminal",
      "internalConsoleOptions": "neverOpen"
    }
  ]
}
```

### Logging

```typescript
import { logger } from './utils/logger';

// Structured logging
logger.info('Processing flyer', { flyerId, userId });
logger.error('Failed to process', { error, context });
logger.debug('Cache hit', { key, ttl });
```

### Database Query Logging

```typescript
// In development, log all queries
if (env.NODE_ENV === 'development') {
  pool.on('connect', () => {
    console.log('Database connected');
  });

  // Log slow queries
  const originalQuery = pool.query.bind(pool);
  pool.query = async (...args) => {
    const start = Date.now();
    const result = await originalQuery(...args);
    const duration = Date.now() - start;
    if (duration > 100) {
      console.log(`Slow query (${duration}ms):`, args[0]);
    }
    return result;
  };
}
```

### Redis Debugging

```bash
# Monitor Redis commands
podman exec -it flyer-crawler-redis redis-cli monitor

# Check keys
podman exec flyer-crawler-redis redis-cli keys "*"

# Get key value
podman exec flyer-crawler-redis redis-cli get "flyer:123"

# Check cache stats
podman exec flyer-crawler-redis redis-cli info stats
```

---

## See Also

- [DEV-CONTAINER.md](DEV-CONTAINER.md) - Dev container guide (PM2, Logstash)
- [TESTING.md](TESTING.md) - Testing strategies
- [CODE-PATTERNS.md](CODE-PATTERNS.md) - Common patterns
- [MONITORING.md](../operations/MONITORING.md) - Production monitoring
- [LOGSTASH-QUICK-REF.md](../operations/LOGSTASH-QUICK-REF.md) - Log aggregation
- [Bugsink Setup](../tools/BUGSINK-SETUP.md) - Error tracking
- [DevOps Guide](../subagents/DEVOPS-GUIDE.md) - Container debugging
