# Coverage - Test Coverage Validation

This skill validates test coverage requirements using the vitest MCP tool.

## Purpose

Ensure test coverage meets level-specific requirements:
- **Rapid**: No minimum (but critical flows must be tested)
- **Balanced**: 70% minimum
- **Crafted**: Domain 100%, Use-cases 100%, Infrastructure 80%

## Usage

### Auto-detect level
```bash
/coverage
```

The skill will:
1. Detect project level using `get_architecture_info`
2. Run tests with coverage
3. Validate against level requirements

### Specify level
```bash
/coverage rapid
/coverage balanced
/coverage crafted
```

### Check specific package
```bash
/coverage crafted packages/domain
/coverage balanced packages/services
```

## Coverage Requirements by Level

### Rapid Level

**Requirement**: NO minimum coverage

**What MUST be tested**:
- ✅ Auth flows (login, register, logout)
- ✅ Payment flows (checkout, webhooks)

**What can be skipped**:
- ⏭️ Business logic
- ⏭️ UI components
- ⏭️ E2E tests

**Example**:
```typescript
// Minimal tests for Rapid
describe('Auth', () => {
  it('should login with valid credentials', async () => {
    const result = await trpc.auth.login.mutate({
      email: 'test@example.com',
      password: 'Password123!',
    });

    expect(result.user).toBeDefined();
  });
});

describe('Payments', () => {
  it('should create checkout session', async () => {
    const result = await trpc.payment.checkout.mutate({
      productId: 'prod_123',
    });

    expect(result.checkoutUrl).toContain('stripe.com');
  });
});
```

### Balanced Level

**Requirement**: 70% minimum

**What MUST be tested**:
- ✅ Unit tests (100% public methods in services)
- ✅ Integration tests (router → service → repository)
- ✅ Component tests (critical UI)
- ✅ E2E tests (auth + checkout flows)

**Example**:
```typescript
// Service unit tests
describe('ProductService', () => {
  describe('create', () => {
    it('should create product', async () => {
      const product = await productService.create(validInput, userId);
      expect(product).toBeDefined();
    });

    it('should reject negative price', async () => {
      await expect(
        productService.create({ ...validInput, price: -100 }, userId)
      ).rejects.toThrow('Price must be positive');
    });
  });

  describe('update', () => {
    it('should update product', async () => {
      const updated = await productService.update(id, updates);
      expect(updated.name).toBe(updates.name);
    });
  });

  describe('delete', () => {
    it('should delete product', async () => {
      await productService.delete(id);
      const product = await productRepository.findById(id);
      expect(product).toBeNull();
    });
  });
});

// Integration tests
describe('Product API Integration', () => {
  it('should create product via API', async () => {
    const product = await trpc.product.create.mutate(validInput);
    expect(product).toBeDefined();
  });
});
```

### Crafted Level

**Requirement**:
- Domain: 100%
- Use-cases: 100%
- Infrastructure: 80%

**Test approach**: Spec-First (tests written FIRST)

**Example**:
```typescript
// Domain entity tests (WRITTEN FIRST)
describe('Product Entity', () => {
  describe('creation', () => {
    it('should create with valid data', () => {
      const result = Product.create(validProps);
      expect(result.isSuccess).toBe(true);
    });

    it('should reject empty name', () => {
      const result = Product.create({ ...validProps, name: '' });
      expect(result.isFailure).toBe(true);
      expect(result.error).toBe('Product name is required');
    });

    it('should reject negative price', () => {
      const result = Product.create({
        ...validProps,
        price: Money.fromCents(0),
      });
      expect(result.isFailure).toBe(true);
      expect(result.error).toBe('Price must be positive');
    });
  });

  describe('price update', () => {
    it('should allow price increase', () => {
      const product = Product.create(validProps).value;
      const result = product.updatePrice(Money.fromDollars(3299));
      expect(result.isSuccess).toBe(true);
    });

    it('should reject price decrease > 20%', () => {
      const product = Product.create(validProps).value;
      const result = product.updatePrice(Money.fromDollars(2099));
      expect(result.isFailure).toBe(true);
    });
  });

  describe('publishing', () => {
    it('should publish unpublished product', () => {
      const product = Product.create(validProps).value;
      const result = product.publish();
      expect(result.isSuccess).toBe(true);
      expect(product.isPublished).toBe(true);
    });

    it('should reject publishing already published product', () => {
      const product = Product.create(validProps).value;
      product.publish();
      const result = product.publish();
      expect(result.isFailure).toBe(true);
    });
  });
});

// Use-case tests (100% coverage)
describe('CreateProductUseCase', () => {
  it('should create valid product', async () => {
    const result = await useCase.execute(validInput);
    expect(result.isSuccess).toBe(true);
  });

  it('should fail with invalid input', async () => {
    const result = await useCase.execute(invalidInput);
    expect(result.isFailure).toBe(true);
  });
});
```

## MCP Tools Used

```typescript
// Detect level
mcp__crafted-context__get_architecture_info()

// Run tests with coverage
mcp__vitest__check_coverage({
  threshold: 70 // Balanced
})

mcp__vitest__check_coverage({
  threshold: 100 // Crafted domain
})

// Check specific test
mcp__vitest__run_specific_test({
  testFile: "packages/domain/entities/Product.test.ts"
})

// Run all tests
mcp__vitest__run_tests()

// Validate coverage per level
mcp__rules-validator__check_test_coverage({
  testPath: "packages/domain",
  level: "crafted"
})
```

## Example Output

### Rapid (Pass)
```
✅ Test coverage OK for RAPID level

Critical flows tested:
✅ Auth (login, register, logout)
✅ Payments (checkout, webhook)

No minimum coverage required for Rapid.
Current coverage: 45% (informational only)

Ready to ship!
```

### Balanced (Pass)
```
✅ Test coverage OK for BALANCED level

Coverage: 75% (required: 70%)

Breakdown:
✅ Services: 82%
✅ Repositories: 68%
✅ Routers: 75%

All public methods in services are tested ✅
Integration tests present ✅
E2E tests (auth + checkout) passing ✅

Great job!
```

### Balanced (Fail)
```
❌ Test coverage BELOW requirement for BALANCED level

Coverage: 62% (required: 70%)

Missing coverage:
❌ ProductService.update() - NO TESTS
❌ ProductService.delete() - NO TESTS
❌ OrderService.cancel() - Partial coverage (50%)

Untested files:
❌ packages/services/notification.service.ts - 0%
❌ packages/repositories/audit.repository.ts - 0%

Please add tests to reach 70% coverage.
```

### Crafted (Fail)
```
❌ Test coverage BELOW requirement for CRAFTED level

Domain coverage: 85% (required: 100%)
Use-case coverage: 90% (required: 100%)
Infrastructure coverage: 75% (required: 80%)

Domain - Missing tests:
❌ Product.updatePrice() - NO TESTS
❌ Product.publish() - Partial (50%)
❌ Order.cancel() - NO TESTS

Use-cases - Missing tests:
❌ UpdateProductUseCase - Partial (80%)
❌ CancelOrderUseCase - NO TESTS

Infrastructure - Missing tests:
❌ PrismaProductRepository.findByCategory() - NO TESTS

CRITICAL: Domain must have 100% coverage for Crafted level.
```

## Coverage by Package (Crafted)

For Crafted level, check coverage per package:

```bash
# Domain (must be 100%)
/coverage crafted packages/domain

# Use-cases (must be 100%)
/coverage crafted packages/domain/use-cases

# Infrastructure (must be 80%)
/coverage crafted packages/infrastructure
```

**Example output**:
```
✅ Domain coverage: 100%

Files:
✅ Product.ts - 100%
✅ Order.ts - 100%
✅ User.ts - 100%
✅ Money.ts - 100%
✅ Email.ts - 100%

All domain entities have complete test coverage!

✅ Use-case coverage: 100%

Files:
✅ CreateProduct.ts - 100%
✅ UpdateProduct.ts - 100%
✅ DeleteProduct.ts - 100%
✅ CreateOrder.ts - 100%

All use-cases have complete test coverage!

⚠️ Infrastructure coverage: 75% (required: 80%)

Files:
✅ PrismaProductRepository.ts - 85%
✅ PrismaOrderRepository.ts - 90%
❌ PrismaUserRepository.ts - 60%

Please improve infrastructure coverage to 80%.
```

## Coverage vs Quality

**Important**: Coverage % is NOT the only metric!

```typescript
// ❌ BAD - 100% coverage but poor quality
it('should work', () => {
  productService.create(input);
  // No assertions! Just calls the function
});

// ✅ GOOD - Meaningful test
it('should create product with valid data', async () => {
  const product = await productService.create(input);

  expect(product).toBeDefined();
  expect(product.name).toBe(input.name);
  expect(product.price).toBe(input.price);
});

// ✅ BETTER - Tests behavior and edge cases
describe('ProductService.create', () => {
  it('should create product with valid data', async () => {
    const product = await productService.create(validInput);
    expect(product.name).toBe(validInput.name);
  });

  it('should reject empty name', async () => {
    await expect(
      productService.create({ ...validInput, name: '' })
    ).rejects.toThrow('Product name is required');
  });

  it('should reject negative price', async () => {
    await expect(
      productService.create({ ...validInput, price: -100 })
    ).rejects.toThrow('Price must be positive');
  });

  it('should save product to database', async () => {
    const product = await productService.create(validInput);
    const saved = await productRepository.findById(product.id);
    expect(saved).toBeDefined();
  });
});
```

## Common Coverage Issues

### 1. Missing Edge Case Tests

```typescript
// Only testing happy path
it('should create product', async () => {
  const product = await productService.create(validInput);
  expect(product).toBeDefined();
});

// Add edge cases
it('should reject empty name', async () => { ... });
it('should reject negative price', async () => { ... });
it('should handle duplicate names', async () => { ... });
```

### 2. Untested Error Paths

```typescript
// Function has error handling
function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}

// But no test for error case!
it('should divide numbers', () => {
  expect(divide(10, 2)).toBe(5);
});

// Add error test
it('should reject division by zero', () => {
  expect(() => divide(10, 0)).toThrow('Division by zero');
});
```

### 3. Untested Private Methods (Indirectly)

```typescript
// Private method
class Product {
  private calculateDiscount(price: number): number {
    return price * 0.1;
  }

  public getTotalPrice(): number {
    const discount = this.calculateDiscount(this.price);
    return this.price - discount;
  }
}

// Test public method (which tests private method indirectly)
it('should calculate total price with discount', () => {
  const product = new Product('Item', 100);
  expect(product.getTotalPrice()).toBe(90); // Tests calculateDiscount indirectly
});
```

## Integration with Workflows

This skill is automatically invoked by:
- `/feature` workflow (Step 3: Testing, validates coverage)
- `/bugfix` workflow (Step 4: Testing, ensures coverage maintained)
- `/reviewer` workflow (Step 1: Checks coverage requirements)

## Tips

1. **For Balanced**: Aim for 75-80%, not just 70%
2. **For Crafted**: Write tests FIRST (Spec-First TDD)
3. **Quality > Coverage %** - Meaningful tests matter more
4. **Test behavior, not implementation** - Don't test private methods directly
5. **Test edge cases** - Not just happy paths

## Commands

```bash
# Auto-detect and check coverage
/coverage

# Check for specific level
/coverage rapid
/coverage balanced
/coverage crafted

# Check specific package
/coverage crafted packages/domain

# Run tests with coverage report
pnpm test --coverage

# Check coverage with specific threshold
pnpm test --coverage --threshold=70
```

## Exit Codes

- `0` - Coverage meets requirements ✅
- `1` - Coverage below requirement (fails CI for Balanced/Crafted) ❌
