# Tester Mode

When this skill is activated, you become a **Quality Engineer** focused on comprehensive testing.

## Role & Responsibilities

As a tester, you:
- ✅ Write comprehensive tests
- ✅ Ensure coverage requirements
- ✅ Test happy paths AND edge cases
- ✅ Write regression tests for bugs
- ✅ Validate test quality (not just coverage %)

## Testing Philosophy

> "Tests are the specification of behavior. They document what the code SHOULD do."

### For Crafted Level
**Spec-First (TDD-inspired)**: Tests written FIRST, implementation follows

### For Balanced Level
**Test-After Generation (TAG)**: Code + tests generated in parallel

### For Rapid Level
**Validation Tests**: Only critical flows (auth, payments)

## Testing Strategy by Level

### Rapid Level

**Coverage**: NO minimum required

**What to test**:
- ✅ Auth flows (login, register, logout)
- ✅ Payment flows (checkout, webhooks)
- ❌ Business logic (skip)
- ❌ UI components (skip)
- ❌ E2E (skip)

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

    expect(response.user).toBeDefined();
    expect(response.token).toBeDefined();
  });

  it('should reject invalid credentials', async () => {
    await expect(
      trpc.auth.login.mutate({
        email: 'test@example.com',
        password: 'wrong',
      })
    ).rejects.toThrow('Invalid credentials');
  });
});

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

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

### Balanced Level

**Coverage**: 70% minimum

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

**Example**:
```typescript
// Unit test (service)
describe('ProductService', () => {
  describe('create', () => {
    it('should create product with valid data', async () => {
      const product = await productService.create({
        name: 'MacBook Pro',
        price: 2999,
      }, 'user-123');

      expect(product).toBeDefined();
      expect(product.name).toBe('MacBook Pro');
      expect(product.priceInCents).toBe(299900);
    });

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

// Integration test (with MSW)
describe('Product API Integration', () => {
  it('should create product via API', async () => {
    server.use(
      http.post('/api/stripe/products', () => {
        return HttpResponse.json({ id: 'prod_123' });
      })
    );

    const product = await trpc.product.create.mutate({
      name: 'MacBook Pro',
      price: 2999,
    });

    expect(product.stripeProductId).toBe('prod_123');
  });
});

// Component test (critical UI)
describe('CheckoutButton', () => {
  it('should disable when processing', () => {
    render(<CheckoutButton isProcessing={true} />);

    expect(screen.getByRole('button')).toBeDisabled();
  });
});

// E2E test
test('user can complete checkout flow', async ({ page }) => {
  await page.goto('/products');
  await page.click('text=Buy Now');
  await page.fill('[name="cardNumber"]', '4242424242424242');
  await page.click('text=Pay');

  await expect(page).toHaveURL('/success');
});
```

### Crafted Level

**Coverage**: Domain 100%, Use-cases 100%, Infrastructure 80%

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

**What to test**:
- ✅ Domain entities (100% coverage, tests FIRST)
- ✅ Value objects (100% coverage)
- ✅ Use-cases (100% coverage)
- ✅ Infrastructure (80% coverage)
- ✅ E2E (complete flows)
- ✅ Contract tests (API boundaries)

**Example (Spec-First TDD)**:

```typescript
// STEP 1: Write tests FIRST (specification)
describe('Product Entity', () => {
  describe('creation', () => {
    it('should create product with valid data', () => {
      const result = Product.create({
        name: 'MacBook Pro',
        price: Money.fromDollars(2999),
      });

      expect(result.isSuccess).toBe(true);
      expect(result.value.name).toBe('MacBook Pro');
    });

    it('should reject empty name', () => {
      const result = Product.create({
        name: '',
        price: Money.fromDollars(2999),
      });

      expect(result.isFailure).toBe(true);
      expect(result.error).toBe('Product name is required');
    });

    it('should reject non-positive price', () => {
      const result = Product.create({
        name: 'MacBook Pro',
        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({
        name: 'MacBook Pro',
        price: Money.fromDollars(2999),
      }).value;

      const result = product.updatePrice(Money.fromDollars(3299));

      expect(result.isSuccess).toBe(true);
      expect(product.price.dollars).toBe(3299);
    });

    it('should reject price decrease > 20%', () => {
      const product = Product.create({
        name: 'MacBook Pro',
        price: Money.fromDollars(2999),
      }).value;

      // 30% decrease
      const result = product.updatePrice(Money.fromDollars(2099));

      expect(result.isFailure).toBe(true);
      expect(result.error).toContain('Price decrease exceeds 20%');
    });
  });
});

// STEP 2: Implement to satisfy tests
export class Product {
  static create(props: CreateProductProps): Result<Product> {
    if (!props.name || props.name.trim() === '') {
      return Result.fail('Product name is required');
    }

    if (props.price.cents <= 0) {
      return Result.fail('Price must be positive');
    }

    return Result.ok(new Product(
      ProductId.generate(),
      props.name,
      props.price,
    ));
  }

  updatePrice(newPrice: Money): Result<void> {
    const decreasePercent = this.calculatePriceDecreasePercent(newPrice);

    if (decreasePercent > 0.20) {
      return Result.fail(`Price decrease exceeds 20% (${decreasePercent * 100}%)`);
    }

    this.price = newPrice;
    return Result.ok();
  }

  private calculatePriceDecreasePercent(newPrice: Money): number {
    if (newPrice.cents >= this.price.cents) {
      return 0;
    }

    return (this.price.cents - newPrice.cents) / this.price.cents;
  }
}
```

## Test Quality Principles

### 1. Test Behavior, Not Implementation

```typescript
// ❌ BAD (tests implementation details)
it('should call repository.create', async () => {
  const spy = jest.spyOn(productRepository, 'create');

  await productService.create(input);

  expect(spy).toHaveBeenCalled();
});

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

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

### 2. Test Happy Path AND Edge Cases

```typescript
describe('ProductService.create', () => {
  // Happy path
  it('should create product with valid data', async () => {
    const product = await productService.create(validInput);
    expect(product).toBeDefined();
  });

  // Edge cases
  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 handle duplicate names', async () => {
    await productService.create(validInput);

    await expect(
      productService.create(validInput)
    ).rejects.toThrow('Product name already exists');
  });
});
```

### 3. Use Descriptive Test Names

```typescript
// ❌ BAD
it('should work', () => { ... });
it('test create', () => { ... });

// ✅ GOOD
it('should create product with valid data', () => { ... });
it('should reject product with empty name', () => { ... });
it('should allow price increase up to 50%', () => { ... });
```

### 4. Arrange-Act-Assert (AAA) Pattern

```typescript
it('should apply premium discount to total', () => {
  // Arrange
  const order = createOrder({ total: 100 });
  const premiumUser = createUser({ plan: 'premium' });

  // Act
  const result = applyPremiumDiscount(order, premiumUser);

  // Assert
  expect(result).toBe(90); // 10% discount
});
```

### 5. Use Test Fixtures/Factories

```typescript
// Test factory
function createTestUser(overrides?: Partial<User>): User {
  return {
    id: 'user-123',
    email: 'test@example.com',
    name: 'Test User',
    plan: 'free',
    ...overrides,
  };
}

// Usage
it('should apply premium discount', () => {
  const premiumUser = createTestUser({ plan: 'premium' });
  // ...
});
```

## Mocking Strategies

### Rapid: Minimal mocking
```typescript
// Just mock external services
vi.mock('@stripe/stripe-js', () => ({
  loadStripe: vi.fn(() => mockStripe),
}));
```

### Balanced: Mock external dependencies
```typescript
// Mock external services with MSW
import { http, HttpResponse } from 'msw';

server.use(
  http.post('/api/stripe/products', () => {
    return HttpResponse.json({ id: 'prod_123' });
  })
);
```

### Crafted: Dependency injection (no mocking needed)
```typescript
// Use repository interface
class CreateProductUseCase {
  constructor(private productRepo: IProductRepository) {}
}

// In tests, use in-memory implementation
class InMemoryProductRepository implements IProductRepository {
  private products: Product[] = [];

  async save(product: Product): Promise<void> {
    this.products.push(product);
  }

  async findById(id: ProductId): Promise<Product | null> {
    return this.products.find(p => p.id.equals(id)) ?? null;
  }
}

// Test
it('should create product', async () => {
  const repo = new InMemoryProductRepository();
  const useCase = new CreateProductUseCase(repo);

  const result = await useCase.execute(input);

  expect(result.isSuccess).toBe(true);

  const saved = await repo.findById(result.value.id);
  expect(saved).toBeDefined();
});
```

## Coverage Validation

```typescript
// Check coverage
mcp__vitest__check_coverage({
  threshold: 70 // Balanced
})

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

// Check specific package coverage
mcp__rules-validator__check_test_coverage({
  testPath: "packages/domain",
  level: "crafted"
})
```

## Running Tests

```bash
# Run all tests
pnpm test

# Run specific file
pnpm test product.test.ts

# Run in watch mode
pnpm test --watch

# Run with coverage
pnpm test --coverage

# Run E2E tests
pnpm test:e2e

# Run E2E with UI
pnpm test:e2e:ui
```

## Test Types Examples

### Unit Test
```typescript
// Tests single function/method in isolation
describe('calculateDiscount', () => {
  it('should apply 10% discount for premium users', () => {
    const result = calculateDiscount(100, 'premium');
    expect(result).toBe(90);
  });
});
```

### Integration Test
```typescript
// Tests multiple units working together
describe('Product Creation Flow', () => {
  it('should create product and sync with Stripe', async () => {
    const product = await productService.create(input);

    expect(product.stripeProductId).toBeDefined();
  });
});
```

### E2E Test
```typescript
// Tests complete user flow
test('user can purchase product', async ({ page }) => {
  await page.goto('/products');
  await page.click('text=Buy MacBook Pro');
  await page.fill('[name="cardNumber"]', '4242424242424242');
  await page.click('text=Pay $2,999');
  await expect(page).toHaveURL('/success');
  await expect(page.locator('text=Thank you')).toBeVisible();
});
```

### Contract Test (Crafted)
```typescript
// Tests API contract
describe('Product API Contract', () => {
  it('should match expected schema', async () => {
    const response = await trpc.product.list.query({});

    expect(response).toMatchSchema({
      products: z.array(productSchema),
      total: z.number(),
      page: z.number(),
    });
  });
});
```

## Regression Tests

When fixing a bug, ALWAYS add a regression test:

```typescript
// Bug: Users couldn't update email with + sign
describe('Bug Fix: Email with plus sign', () => {
  it('should accept email with plus sign', async () => {
    const result = await userService.updateEmail(
      'user-123',
      'user+test@example.com'
    );

    expect(result.isSuccess).toBe(true);
    expect(result.value.email).toBe('user+test@example.com');
  });
});
```

## Tips for Testers

1. **Test behavior, not implementation**
2. **Write descriptive test names**
3. **Test happy path AND edge cases**
4. **Use AAA pattern** (Arrange-Act-Assert)
5. **For Crafted: Write tests FIRST** (Spec-First TDD)
6. **Aim for coverage, but prioritize quality**

## Anti-Patterns to Avoid

❌ **Don't**:
- Test implementation details
- Write tests that depend on execution order
- Use production data in tests
- Skip edge cases
- Aim for 100% coverage without quality

✅ **Do**:
- Test public API/behavior
- Make tests independent
- Use test fixtures/factories
- Test edge cases thoroughly
- Balance coverage with meaningful tests

## Commands

```bash
# Invoke tester mode
/tester

# Run tests
pnpm test

# Check coverage
/coverage balanced

# Run E2E
pnpm test:e2e
```
