# D3 — Testing

## สิ่งที่ต้องส่ง D3

### 1️. เอกสารแผนการทดสอบ (Test Plan Document)

**ไฟล์:** `docs/D3_Test_Plan.md`

สิ่งที่ต้องมี:

```markdown
# Test Plan Document

## 1. Introduction

- โปรเจกต์: [ชื่อโปรเจกต์]
- เวอร์ชัน: 1.0
- วันที่: [วันที่]
- ผู้เขียน: [ชื่อ]

## 2. Testing Scope

- ฟีเจอร์ที่ทดสอบ: [ระบุ 3-5 features หลัก]
- ฟีเจอร์ที่ไม่ทดสอบ: [ระบุ]

## 3. Testing Strategy

### Unit Testing

- ทดสอบ: ฟังก์ชัน, methods, logic
- ขอบเขต: >= 15 test cases
- Coverage: >= 80%
- Framework: Jest

### Integration Testing

- ทดสอบ: interaction between modules
- ขอบเขต: >= 5 test suites
- สิ่งที่ทดสอบ:
  1. Authentication flow
  2. API endpoints
  3. Database operations
  4. Payment processing
  5. Email notifications

### System Testing (e2e)

- ทดสอบ: end-to-end workflows
- สถานการณ์: 5+ scenarios

### UAT (User Acceptance Testing)

- ทดสอบกับผู้ใช้จริง
- สถานการณ์: 3-5 real-world scenarios

## 4. Test Tools & Environment

- Unit Testing: Jest
- Integration Testing: Supertest / Postman
- e2e Testing: Playwright
```

### ตัวอย่าง Test Plan สำหรับ E-Commerce:

```markdown
# Test Plan - E-Commerce Application v1.0

## Testing Scope

### Features to Test:

1. User Registration & Login
2. Product Search & Filtering
3. Shopping Cart
4. Order Checkout
5. Admin Dashboard

### NOT Testing:

- Third-party payment gateway (mock only)
- Email service (mock only)

## Testing Strategy

### Unit Tests (54 cases total)

**Authentication Service (15 cases)**

- test_register_withValidEmail_returnsUser
- test_register_withDuplicateEmail_throwsError
- test_login_withCorrectPassword_returnsToken
- test_login_withWrongPassword_throwsError
- test_validateToken_withExpiredToken_returnsNull
- ... (10 more)

**Product Service (15 cases)**

- test_getProducts_returnsList
- test_searchProducts_withKeyword_returnsMatches
- test_filterByPrice_returnsBetweenRange
- ... (12 more)

**Order Service (12 cases)**

- test_createOrder_withValidItems_succeeds
- test_calculateTotal_withTaxAndDiscount_isCorrect
- ... (10 more)

**Utility Functions (12 cases)**

- test_calculateDiscount_correctPercentage
- test_formatDate_correctFormat
- ... (10 more)

### Integration Tests (12 cases)

1. **Auth Flow**
   - Register → Login → Access Protected Route

2. **Product Search**
   - GET /api/products → Filter → Sort

3. **Order Workflow**
   - Create Order → Add Items → Calculate Total → Checkout

4. **Database Operations**
   - Create User → Query → Update → Delete

5. **API Error Handling**
   - 400 Bad Request → 401 Unauthorized → 404 Not Found

### System Testing (End-to-End)

Scenario 1: User Journey

- Register → Verify Email → Login → Browse → Cart → Checkout → Confirm

Scenario 2: Admin Dashboard

- Login as Admin → View Orders → Update Status → Generate Report

Scenario 3: Error Handling

- Invalid Input → Network Error → Server Error → Recovery

## Test Metrics

- Coverage Target: >= 80%
- Success Rate: 100% (all tests pass)
- Failed Tests Allowed: 0 critical
- Test Execution Time: < 2 minutes
```

---

### 2️. Unit Tests (Jest Test Cases)

**โฟลเดอร์:** `tests/unit/`

ต้องมี >= 15 test cases ที่ cover logic หลัก:

```javascript
// tests/unit/authService.test.js

const AuthService = require("../../src/services/authService");
const UserRepository = require("../../src/repositories/userRepository");

describe("AuthService", () => {
  let authService;
  let userRepository;

  beforeEach(() => {
    userRepository = new UserRepository();
    authService = new AuthService(userRepository);
  });

  describe("register", () => {
    test("should create user with valid data", async () => {
      // Arrange
      const userData = {
        email: "user@example.com",
        password: "SecurePass123!",
        firstName: "John",
      };

      // Act
      const result = await authService.register(userData);

      // Assert
      expect(result).toBeDefined();
      expect(result.email).toBe("user@example.com");
      expect(result.id).toBeDefined();
    });

    test("should throw error with duplicate email", async () => {
      // Arrange
      const userData = {
        email: "existing@example.com",
        password: "SecurePass123!",
      };
      jest.spyOn(userRepository, "findByEmail").mockResolvedValue({});

      // Act & Assert
      await expect(authService.register(userData)).rejects.toThrow(
        "Email already exists",
      );
    });

    test("should hash password before saving", async () => {
      // Arrange
      const userData = {
        email: "user@example.com",
        password: "SecurePass123!",
      };

      // Act
      const result = await authService.register(userData);

      // Assert
      expect(result.password).not.toBe("SecurePass123!");
      expect(result.password).toMatch(/^\$2[aby]$/); // bcrypt hash
    });

    test("should throw error with weak password", async () => {
      // Arrange
      const userData = {
        email: "user@example.com",
        password: "123",
      };

      // Act & Assert
      await expect(authService.register(userData)).rejects.toThrow(
        "Password must be at least 8 characters",
      );
    });
  });

  describe("login", () => {
    test("should return token with correct credentials", async () => {
      // Arrange
      const credentials = {
        email: "user@example.com",
        password: "SecurePass123!",
      };
      const user = {
        id: 1,
        email: "user@example.com",
        password: "$2b$10$...", // hashed
      };
      jest.spyOn(userRepository, "findByEmail").mockResolvedValue(user);

      // Act
      const result = await authService.login(credentials);

      // Assert
      expect(result.token).toBeDefined();
      expect(result.user.id).toBe(1);
    });

    test("should throw error with wrong password", async () => {
      // Arrange
      const credentials = {
        email: "user@example.com",
        password: "WrongPassword",
      };

      // Act & Assert
      await expect(authService.login(credentials)).rejects.toThrow(
        "Invalid credentials",
      );
    });
  });

  describe("validateToken", () => {
    test("should return user data with valid token", () => {
      // Arrange
      const validToken = "eyJhbGciOiJIUzI1NiIs...";

      // Act
      const result = authService.validateToken(validToken);

      // Assert
      expect(result).toBeDefined();
      expect(result.userId).toBeDefined();
    });

    test("should return null with expired token", () => {
      // Arrange
      const expiredToken = "eyJhbGciOiJIUzI1NiIs...(expired)";

      // Act
      const result = authService.validateToken(expiredToken);

      // Assert
      expect(result).toBeNull();
    });
  });
});
```

**ตัวอย่างเพิ่มเติม:**

```javascript
// tests/unit/productService.test.js
describe("ProductService", () => {
  test("should get all products", async () => {
    const products = await productService.getAllProducts();
    expect(Array.isArray(products)).toBe(true);
    expect(products.length).toBeGreaterThan(0);
  });

  test("should search products by name", async () => {
    const results = await productService.search("laptop");
    expect(results.every((p) => p.name.toLowerCase().includes("laptop"))).toBe(
      true,
    );
  });

  test("should filter by price range", async () => {
    const results = await productService.filterByPrice(100, 500);
    expect(results.every((p) => p.price >= 100 && p.price <= 500)).toBe(true);
  });

  test("should calculate discount correctly", () => {
    const price = 100;
    const discount = productService.calculateDiscount(price, 10);
    expect(discount).toBe(90);
  });

  test("should get product by ID", async () => {
    const product = await productService.getById(1);
    expect(product.id).toBe(1);
    expect(product.name).toBeDefined();
  });
});

// tests/unit/orderService.test.js
describe("OrderService", () => {
  test("should create order with valid items", async () => {
    const orderData = {
      userId: 1,
      items: [
        { productId: 1, quantity: 2 },
        { productId: 2, quantity: 1 },
      ],
    };
    const order = await orderService.create(orderData);
    expect(order.id).toBeDefined();
    expect(order.status).toBe("PENDING");
  });

  test("should calculate order total with tax", async () => {
    const items = [
      { price: 100, quantity: 2 },
      { price: 50, quantity: 1 },
    ];
    const total = orderService.calculateTotal(items, 0.07); // 7% tax
    expect(total).toBe(267.5);
  });

  test("should not create order with empty items", async () => {
    const orderData = {
      userId: 1,
      items: [],
    };
    await expect(orderService.create(orderData)).rejects.toThrow(
      "Order must have at least 1 item",
    );
  });

  test("should apply coupon discount", async () => {
    const total = 100;
    const discounted = orderService.applyCoupon(total, "SAVE10");
    expect(discounted).toBe(90);
  });
});
```

---

### 3️. Integration Tests

**โฟลเดอร์:** `tests/integration/`

ต้องมี >= 5 test suites:

```javascript
// tests/integration/auth.integration.test.js

const request = require("supertest");
const app = require("../../src/app");
const db = require("../../src/config/database");

describe("Authentication Integration", () => {
  beforeAll(async () => {
    await db.connect();
  });

  afterAll(async () => {
    await db.disconnect();
  });

  beforeEach(async () => {
    await db.clearUsers();
  });

  describe("POST /api/auth/register", () => {
    test("should register user and return token", async () => {
      const response = await request(app).post("/api/auth/register").send({
        email: "newuser@example.com",
        password: "SecurePass123!",
        firstName: "John",
        lastName: "Doe",
      });

      expect(response.status).toBe(201);
      expect(response.body.user.id).toBeDefined();
      expect(response.body.token).toBeDefined();
    });

    test("should return 400 with duplicate email", async () => {
      // First registration
      await request(app).post("/api/auth/register").send({
        email: "user@example.com",
        password: "SecurePass123!",
      });

      // Second with same email
      const response = await request(app).post("/api/auth/register").send({
        email: "user@example.com",
        password: "AnotherPass123!",
      });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain("already exists");
    });

    test("should validate email format", async () => {
      const response = await request(app).post("/api/auth/register").send({
        email: "invalid-email",
        password: "SecurePass123!",
      });

      expect(response.status).toBe(400);
      expect(response.body.error).toContain("email");
    });
  });

  describe("POST /api/auth/login", () => {
    test("should login and return token", async () => {
      // Register first
      await request(app).post("/api/auth/register").send({
        email: "user@example.com",
        password: "SecurePass123!",
      });

      // Login
      const response = await request(app).post("/api/auth/login").send({
        email: "user@example.com",
        password: "SecurePass123!",
      });

      expect(response.status).toBe(200);
      expect(response.body.token).toBeDefined();
      expect(response.body.user.email).toBe("user@example.com");
    });

    test("should reject wrong password", async () => {
      await request(app).post("/api/auth/register").send({
        email: "user@example.com",
        password: "SecurePass123!",
      });

      const response = await request(app).post("/api/auth/login").send({
        email: "user@example.com",
        password: "WrongPassword",
      });

      expect(response.status).toBe(401);
    });
  });

  describe("Protected Routes", () => {
    test("should access protected route with valid token", async () => {
      const registerRes = await request(app).post("/api/auth/register").send({
        email: "user@example.com",
        password: "SecurePass123!",
      });

      const token = registerRes.body.token;

      const response = await request(app)
        .get("/api/users/profile")
        .set("Authorization", `Bearer ${token}`);

      expect(response.status).toBe(200);
      expect(response.body.email).toBe("user@example.com");
    });

    test("should reject request without token", async () => {
      const response = await request(app).get("/api/users/profile");

      expect(response.status).toBe(401);
    });

    test("should reject request with invalid token", async () => {
      const response = await request(app)
        .get("/api/users/profile")
        .set("Authorization", "Bearer invalid_token");

      expect(response.status).toBe(401);
    });
  });
});

// tests/integration/orders.integration.test.js
describe("Order Integration", () => {
  test("should create order from cart items", async () => {
    // Setup: login user
    // Setup: add products to cart
    // Act: create order
    // Assert: order created with correct total
  });

  test("should apply discount code", async () => {
    // Setup: create order
    // Act: apply valid coupon code
    // Assert: total updated correctly
  });

  test("should process payment", async () => {
    // Setup: create order
    // Act: call payment API
    // Assert: order status changed to CONFIRMED
  });

  test("should update order status", async () => {
    // Setup: create and pay order
    // Act: admin updates status
    // Assert: status changes from CONFIRMED to SHIPPED
  });

  test("should handle shipping address", async () => {
    // Setup: create order
    // Act: add shipping address
    // Assert: address saved correctly
  });
});
```

---

### 4️. Test Cases Spreadsheet

**ไฟล์:** `docs/D3_Test_Cases.xlsx` หรือ `docs/D3_Test_Cases.md`

ตาราง: Test Case ID, Description, Input, Expected Output, Status

```markdown
# Test Cases

| Test ID | Feature  | Description                   | Input             | Expected Output              | Status | Notes |
| ------- | -------- | ----------------------------- | ----------------- | ---------------------------- | ------ | ----- |
| TC-001  | Auth     | Register with valid email     | email, password   | User created, token returned | PASS   | ✅    |
| TC-002  | Auth     | Register with duplicate email | email (existing)  | Error 400                    | PASS   | ✅    |
| TC-003  | Auth     | Login with correct password   | email, password   | Token returned               | PASS   | ✅    |
| TC-004  | Auth     | Login with wrong password     | email, wrong_pass | Error 401                    | PASS   | ✅    |
| TC-005  | Auth     | Validate token                | valid_token       | User data returned           | PASS   | ✅    |
| TC-006  | Auth     | Validate expired token        | expired_token     | null returned                | PASS   | ✅    |
| TC-007  | Products | Search products               | keyword='laptop'  | [matching products]          | PASS   | ✅    |
| TC-008  | Products | Filter by price               | min=100, max=500  | [products in range]          | PASS   | ✅    |
| TC-009  | Products | Get product details           | productId=1       | Product object               | PASS   | ✅    |
| TC-010  | Orders   | Create order                  | userId, items[]   | Order created                | PASS   | ✅    |
| TC-011  | Orders   | Calculate total with tax      | items[], tax=0.07 | Correct total                | PASS   | ✅    |
| TC-012  | Orders   | Apply coupon                  | code='SAVE10'     | Discounted total             | PASS   | ✅    |
| TC-013  | Orders   | Get order history             | userId=1          | [orders list]                | PASS   | ✅    |
| TC-014  | Admin    | View all orders               | admin_token       | [all orders]                 | PASS   | ✅    |
| TC-015  | Admin    | Update order status           | orderId, status   | Status updated               | PASS   | ✅    |
| ...     | ...      | ...                           | ...               | ...                          | ...    | ...   |

**Summary:** 54/54 tests passed (100%)
```

---

### 5️. Test Coverage Report

**ไฟล์:** `coverage/coverage-report.html` หรือ Screenshot

ต้องมี:

- Coverage >= 80%
- Breakdown by file
- Lines covered / uncovered

```markdown
# Coverage Report

## Summary

- **Line Coverage:** 86.4% (Target: >= 80%)
- **Function Coverage:** 88.2% ✅
- **Branch Coverage:** 84.1% ✅
- **Statement Coverage:** 86.4% ✅
```

## By File

```
| File | Lines | Functions | Branches | Statements |
|------|-------|-----------|----------|------------|
| src/services/authService.js | 95% | 100% | 90% | 95% |
| src/services/productService.js | 88% | 90% | 85% | 88% |
| src/services/orderService.js | 82% | 85% | 80% | 82% |
| src/repositories/userRepository.js | 80% | 85% | 75% | 80% |
| src/controllers/authController.js | 75% | 80% | 70% | 75% |
```

## Uncovered Lines

```
src/controllers/authController.js:45-48 (3 lines) - Error handling edge case
src/services/productService.js:120-125 (6 lines) - Cache invalidation

**Status:** PASS (All targets met)
```

---

### 6. UAT Scenarios

**ไฟล์:** `docs/D3_UAT_Scenarios.md`

```markdown
# UAT (User Acceptance Testing) Scenarios

## Scenario 1: User Registration & Login

**Objective:** New user can register and login successfully

**Steps:**

1. Open application homepage
2. Click "Sign Up" button
3. Fill in registration form:
   - Email: user@example.com
   - Password: SecurePass123!
   - Name: John Doe
4. Click "Create Account"
5. Check email for verification link
6. Click verification link
7. Login with registered credentials

**Expected Outcome:**
✓ Account created successfully
✓ Verification email received
✓ Can login with new account
✓ Redirected to home page

**Test Date:** 30/01/2025
**Tester:** Product Owner
**Result:** PASS
**Sign:** **\_\_\_**

---

## Scenario 2: Product Search & Filter

**Objective:** User can search and filter products effectively

**Steps:**

1. Go to Products page
2. Search for "Laptop"
3. Apply filters:
   - Price: $500 - $1500
   - Brand: Apple
4. Sort by: Price (Low to High)

**Expected Outcome:**
✓ Results show matching products
✓ Filters applied correctly
✓ Sorting works
✓ Results update in real-time

**Test Date:** 30/01/2025
**Tester:** Product Owner
**Result:** PASS

---

## Scenario 3: Shopping Cart & Checkout

**Objective:** User can add items and complete purchase

**Steps:**

1. Add 2 laptops to cart
2. View cart
3. Verify quantities and prices
4. Apply coupon code: "SAVE10"
5. Proceed to checkout
6. Enter shipping address
7. Select payment method
8. Complete payment

**Expected Outcome:**
✓ Items added to cart
✓ Coupon applied (10% discount)
✓ Shipping address saved
✓ Payment processed
✓ Order confirmation received

**Test Date:** 31/01/2025
**Tester:** Product Owner
**Result:** PASS

---

## Scenario 4: Order Tracking

**Objective:** User can track order status

**Steps:**

1. Login to account
2. Go to "My Orders"
3. Click on recent order
4. View order details and status
5. Track shipment

**Expected Outcome:**
✓ Order list displayed
✓ Order details accurate
✓ Status updates in real-time
✓ Tracking info available

**Test Date:** 01/02/2025
**Tester:** Product Owner
**Result:** PASS

---

## Scenario 5: Admin Dashboard

**Objective:** Admin can manage orders and products

**Steps:**

1. Login as admin
2. View dashboard
3. Check pending orders
4. Update order status to "Shipped"
5. Add new product
6. Generate sales report

**Expected Outcome:**
✓ Dashboard loads correctly
✓ All orders visible
✓ Status updates work
✓ New product added
✓ Report generated

**Test Date:** 02/02/2025
**Tester:** QA Lead
**Result:** PASS

---

## UAT Summary
```

| Scenario             | Result | Issues | Approval |
| -------------------- | ------ | ------ | -------- |
| Registration & Login | PASS   | 0      | ✓        |
| Product Search       | PASS   | 0      | ✓        |
| Cart & Checkout      | PASS   | 0      | ✓        |
| Order Tracking       | PASS   | 0      | ✓        |
| Admin Dashboard      | PASS   | 0      | ✓        |

```
**Overall Status:** All scenarios passed
**Approved by:** Product Owner (Signature: _______) Date: 02/02/2025
```

---

## โครงสร้างโปรเจกต์ D3 Complete

```
GitHub Repository:
├── tests/
│   ├── unit/
│   │   ├── authService.test.js
│   │   ├── productService.test.js
│   │   ├── orderService.test.js
│   │   └── ...
│   └── integration/
│       ├── auth.integration.test.js
│       ├── orders.integration.test.js
│       └── ...
├── coverage/
│   ├── coverage-summary.json
│   ├── lcov.info
│   └── coverage.html
├── docs/
│   ├── D3_Test_Plan.md
│   ├── D3_Test_Cases.md (หรือ .xlsx)
│   ├── D3_UAT_Scenarios.md
│   ├── Performance_Test_Report.md
│   └── D3_Status_Report.md
├── src/
│   ├── [code skeleton from D2]
│   ├── tests configuration
│   └── ...
├── package.json (with test scripts)
├── jest.config.js
└── README.md
```

---

## เกณฑ์การประเมิน D3

- **Unit Tests:** Coverage >= 80%, tests pass (2 pts)
- **Integration Tests:** Cover major workflows, >= 5 test suites (2 pt)
- **Test Documentation:** ครบครัน Test Plan + Test Cases + UAT (1 pt)

**รวม: 5 คะแนน**

---

## Submission Checklist D3

```markdown
[ ] Unit Tests: >= 15 test cases, >= 80% coverage
[ ] Integration Tests: >= 5 test suites
[ ] Test Plan Document: ครบถ้วน
[ ] Test Cases: ทุกข้อมี Expected Output
[ ] UAT Scenarios: 3-5 scenarios, ลงชื่อ
[ ] Coverage Report: >= 80%, HTML report
[ ] Email: ส่งถูกต้อง, subject ชัดเจน
```

---

## Tips สำหรับนิสิต

1. **Unit Tests ต้องรัน** - `npm test` ต้องสำเร็จ
2. **Coverage >= 80%** - ใช้ `jest --coverage` เพื่อตรวจสอบ
3. **Integration Tests จำเป็น** - ต้องทดสอบ API จริง ไม่ใช่ mock ทั้งหมด
4. **UAT ต้องลงชื่อ** - ต้องมีลายเซ็นจาก Product Owner จริง

---
