13 KiB
13 KiB
Phase 7: Testing - Checklist
📋 Overview
This phase covers comprehensive testing of the refactored CRM backend system, including unit tests, integration tests, and manual API testing.
✅ Completed Tasks
None yet - Phase 7 is the final phase and has not been started.
🧪 Testing Strategy
Testing Pyramid
/\
/ \ E2E Tests (Manual/API)
/____\
/ \ Integration Tests
/________\
/ \ Unit Tests
/____________\
Test Categories
- Unit Tests - Test individual functions in isolation
- Integration Tests - Test service layer with database
- API Tests - Test endpoints with requests/responses
- Manual Tests - Postman/curl testing
📝 Unit Tests
Customer Service Tests
-
generateCrmCustomerCode()- Generates unique code per branch
- Increments correctly
- Handles empty branch
-
getCustomersByBranch()- Returns only branch customers
- Filters by status correctly
- Includes contacts
- Handles empty results
-
getCustomerById()- Returns correct customer
- Enforces branch access
- Returns null for non-existent
- Returns null for wrong branch
-
createCustomer()- Creates customer with correct branch
- Auto-generates CRM code
- Validates required fields
- Sets createdBy and updatedBy
-
updateCustomer()- Updates customer correctly
- Enforces branch access
- Updates erpCustomerCode
- Sets updatedBy
-
deleteCustomer()- Soft deletes customer
- Enforces branch access
- Returns error if already deleted
Contact Service Tests
-
getVisibleContactsForCustomer()- Returns only user's contacts (createdBy == userId)
- Returns public contacts (isPublic == true)
- Enforces branch access
- Filters by customerId
-
createContact()- Creates contact with correct branch
- Sets createdBy to current user
- Sets isPublic to false by default
- Validates required fields
-
updateContact()- Updates contact correctly
- Only allows creator to update
- Can update isPublic flag
- Enforces branch access
-
shareContact()- Sets isPublic to true
- Only allows creator to share
- Returns error for non-existent contact
-
unshareContact()- Sets isPublic to false
- Only allows creator to unshare
- Returns error for non-existent contact
-
deleteContact()- Deletes contact correctly
- Only allows creator to delete
- Enforces branch access
Quotation Service Tests
-
generateQuotationCode()- Generates unique code
- Follows pattern (Q-YYYY-XXXXX)
-
calculateBaseCurrencyAmount()- Converts to THB correctly
- Handles THB (exchangeRate = 1)
- Handles different currencies
- Returns null for invalid currency
-
validateQuotationStatus()- Allows editing DRAFT
- Prevents editing SENT
- Validates status transitions
-
createQuotation()- Creates quotation with correct branch
- Validates currency code
- Validates exchange rate
- Calculates baseCurrencyAmount
- Validates customer has visible contacts
- Sets revisionNo to null (initial)
- Sets parentQuotationId to null (initial)
-
updateQuotation()- Updates quotation correctly
- Enforces branch access
- Validates status (only DRAFT)
- Sets updatedBy
-
createRevision()- Clones quotation correctly
- Increments revisionNo
- Sets parentQuotationId
- Sets status to DRAFT
- Preserves currency and exchange rate
-
getQuotationVersions()- Returns all versions by code
- Includes different currencies
- Orders by revisionNo
🔗 Integration Tests
Database Integration
- Test database connection
- Test transaction rollback
- Test concurrent access
- Test foreign key constraints
Service Integration
- Test customer creation with contacts
- Test quotation creation with items
- Test revision creation
- Test contact visibility rules
- Test branch scoping
API Integration
- Test authentication flow
- Test branch context injection
- Test error handling
- Test response formats
🌐 Manual API Tests
Customer Endpoints
Get All Customers
GET /api/customers
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Query Params (optional):
status: active | inactive | pending
Expected Response:
{
"success": true,
"data": [...],
"count": 10,
"message": "Found 10 customer(s)"
}
Get Customer by ID
GET /api/customers/:id
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Expected Response:
{
"success": true,
"data": {
"id": "...",
"branchId": "...",
"name": "...",
"crmCustomerCode": "...",
...
}
}
Create Customer
POST /api/customers
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Content-Type: application/json
Body:
{
"name": "Test Customer",
"email": "test@example.com",
"phone": "1234567890",
"company": "Test Company",
"address": "123 Test St",
"customerStatus": "active",
"customerType": "corporate",
"taxId": "1234567890123"
}
Expected Response:
{
"success": true,
"data": {
"id": "...",
"crmCustomerCode": "CUST-001", // Auto-generated
...
},
"message": "Customer created successfully"
}
Update Customer
PUT /api/customers/:id
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Content-Type: application/json
Body:
{
"name": "Updated Customer",
"erpCustomerCode": "ERP-001" // Optional
}
Expected Response:
{
"success": true,
"data": {...},
"message": "Customer updated successfully"
}
Delete Customer
DELETE /api/customers/:id
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Expected Response:
{
"success": true,
"data": {
"id": "...",
"deletedAt": "2026-04-24T..."
},
"message": "Customer deleted successfully"
}
Contact Endpoints
Get Visible Contacts
GET /api/customers/:customerId/contacts
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Expected Response:
{
"success": true,
"data": [
{
"id": "...",
"name": "John Doe",
"email": "john@example.com",
"isPublic": false,
...
}
],
"count": 5,
"message": "Found 5 contact(s)"
}
Create Contact
POST /api/customers/:customerId/contacts
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Content-Type: application/json
Body:
{
"name": "John Doe",
"position": "Manager",
"phone": "9876543210",
"email": "john@example.com",
"isPrimary": true
}
Expected Response:
{
"success": true,
"data": {
"id": "...",
"name": "John Doe",
"isPublic": false, // Default
...
},
"message": "Contact created successfully"
}
Share Contact
POST /api/contacts/:contactId/share
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Expected Response:
{
"success": true,
"data": {
"id": "...",
"name": "John Doe",
"isPublic": true, // Now shared
...
},
"message": "Contact shared successfully"
}
Quotation Endpoints
Create Quotation
POST /api/quotations
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Content-Type: application/json
Body:
{
"customerId": "customer-uuid",
"quotationDate": "2026-04-24T10:00:00Z",
"validUntil": "2026-05-24T10:00:00Z",
"currencyCode": "USD",
"exchangeRate": 35.5,
"subtotal": "1000.00",
"discount": "50.00",
"taxRate": 7.0,
"taxAmount": "66.50",
"totalAmount": "1016.50",
"notes": "Test quotation"
}
Expected Response:
{
"success": true,
"data": {
"id": "...",
"code": "Q-2026-00001", // Auto-generated
"baseCurrencyAmount": "36085.75", // THB equivalent
"status": "new_job_draft",
"revisionNo": null,
...
},
"message": "Quotation created successfully"
}
Update Quotation (Draft Only)
PUT /api/quotations/:id
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Content-Type: application/json
Body:
{
"subtotal": "1200.00",
"totalAmount": "1219.80"
}
Expected Response (Success):
{
"success": true,
"data": {...},
"message": "Quotation updated successfully"
}
Expected Response (Error if SENT):
{
"success": false,
"error": "Quotation cannot be edited. Create a revision instead."
}
Create Revision
POST /api/quotations/:id/revision
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Expected Response:
{
"success": true,
"data": {
"id": "...",
"code": "Q-2026-00001", // Same code
"revisionNo": 1, // Incremented
"parentQuotationId": "original-quotation-id",
"status": "new_job_draft", // Reset to draft
...
},
"message": "Revision created successfully"
}
Get Quotation Versions
GET /api/quotations/code/:code/versions
Headers:
Authorization: Bearer <token>
x-branch-id: <branch-uuid>
Expected Response:
{
"success": true,
"data": [
{
"id": "...",
"code": "Q-2026-00001",
"revisionNo": 0,
"currencyCode": "THB",
"totalAmount": "1000.00",
...
},
{
"id": "...",
"code": "Q-2026-00001",
"revisionNo": 1,
"currencyCode": "THB",
"totalAmount": "1200.00",
...
},
{
"id": "...",
"code": "Q-2026-00001",
"revisionNo": 0, // Different currency version
"currencyCode": "USD",
"totalAmount": "30.00",
...
}
],
"count": 3
}
🐛 Error Scenarios
Authentication Errors
- Missing Authorization header
- Invalid token
- Expired token
- Missing x-branch-id header
- Invalid branch ID
- User has no access to branch
Validation Errors
- Missing required fields
- Invalid email format
- Invalid currency code
- Negative exchange rate
- Invalid status transition
- Creating quotation without visible contacts
Permission Errors
- Accessing customer from wrong branch
- Updating contact not owned by user
- Sharing contact not owned by user
- Deleting contact not owned by user
- Editing sent quotation (without revision)
- Accessing quotation from wrong branch
Business Logic Errors
- Duplicate CRM customer code
- Duplicate ERP customer code
- Quotation with no items
- Invalid discount amount
- Invalid tax calculation
- Revision of revision (not allowed)
📊 Test Coverage Goals
Minimum Coverage Targets
- Unit Tests: 80% code coverage
- Integration Tests: 60% code coverage
- API Tests: 100% endpoint coverage
Critical Path Coverage
- Customer CRUD flow
- Contact visibility flow
- Quotation creation flow
- Revision creation flow
- Multi-currency conversion
- Branch scoping enforcement
🛠️ Testing Tools
Recommended Tools
- Unit Tests: Jest, Vitest
- Integration Tests: Supertest, @elysiajs/testing
- API Testing: Postman, Insomnia, curl
- Database Testing: testcontainers, docker-compose
Test Data
Create test data fixtures:
- Sample customers
- Sample contacts
- Sample quotations
- Sample quotation items
- Test users with different branch access
📝 Test Execution
Run All Tests
npm test
Run Unit Tests Only
npm run test:unit
Run Integration Tests Only
npm run test:integration
Run with Coverage
npm run test:coverage
Run Specific Test File
npm test customers.service.test.ts
🎯 Success Criteria
Phase 7 is complete when:
- All unit tests pass (80% coverage)
- All integration tests pass (60% coverage)
- All API endpoints tested manually
- All error scenarios tested
- Critical path scenarios tested
- Test documentation complete
- CI/CD pipeline configured
📋 Remaining Tasks
- Set up test framework (Jest/Vitest)
- Write unit tests for customer service
- Write unit tests for quotation service
- Write integration tests
- Create Postman collection
- Execute manual API tests
- Document test results
- Fix any bugs found
🔄 Next Steps
After Phase 7:
- Create final project documentation
- Deploy to staging environment
- Conduct user acceptance testing (UAT)
- Deploy to production
- Monitor and maintain
Phase 7 Status: 🚧 NOT STARTED
Overall Project Status: 85% Complete (Phases 1-6 Done)