Loyalty System Test Suite
Created: 2025-11-28 Status: Completed Coverage: 98.84% overall
Overview
Comprehensive test suite for the loyalty system utilities covering points calculation, rewards redemption, and tier management functionality.
Test Summary
Total Test Count: 92 tests
- Points Tests: 26 tests
- Rewards Tests: 29 tests
- Tiers Tests: 37 tests
Coverage Metrics
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 98.84 | 94.11 | 100 | 98.84 |
points.ts | 100 | 97.72 | 100 | 100 | 94
rewards.ts | 99.28 | 97.14 | 100 | 99.28 | 116-117
tiers.ts | 97.32 | 90.54 | 100 | 97.32 | 80-81,188-194
------------|---------|----------|---------|---------|-------------------
Files Created
Utility Files
src/lib/loyalty/points.ts- Points calculation and earning logicsrc/lib/loyalty/rewards.ts- Rewards redemption logicsrc/lib/loyalty/tiers.ts- Tier management logicsrc/lib/loyalty/index.ts- Main export file
Test Files
src/lib/loyalty/__tests__/points.test.ts- 26 tests for points functionalitysrc/lib/loyalty/__tests__/rewards.test.ts- 29 tests for rewards functionalitysrc/lib/loyalty/__tests__/tiers.test.ts- 37 tests for tier functionality
Test Coverage Details
Points Tests (26 tests)
calculatePointsForPurchase (7 tests)
- Calculates base points without program
- Calculates base points with program
- Applies tier multiplier correctly
- Applies bonus rules correctly - multiplier type
- Applies bonus rules correctly - fixed type
- Skips bonus rules when minimum purchase not met
- Rounds down fractional points
earnPoints (3 tests)
- Creates loyalty record if not exists
- Awards points and creates transaction
- Uses default description if not provided
expirePoints (3 tests)
- Returns 0 when no loyalty record exists
- Returns 0 when no expired transactions
- Expires points and creates expiration transactions
adjustPoints (5 tests)
- Creates loyalty record if not exists
- Adjusts points positively
- Adjusts points negatively
- Adjusts lifetime points when specified
- Does not adjust lifetime for negative adjustments
getPointsValue (4 tests)
- Returns value based on program redemption rate
- Uses default rate when no program exists
- Rounds to 2 decimal places
- Handles zero points
Error handling (4 tests)
- Handles database errors in calculatePointsForPurchase
- Handles database errors in earnPoints
- Handles database errors in expirePoints
- Handles database errors in adjustPoints
Rewards Tests (29 tests)
validateRedemption (7 tests)
- Rejects zero or negative points
- Rejects negative points
- Rejects when no loyalty account exists
- Rejects when insufficient points
- Rejects when no active program
- Approves valid redemption
- Approves exact point balance redemption
redeemPoints (5 tests)
- Throws error for invalid redemption
- Successfully redeems points
- Creates transaction with custom description
- Creates transaction with default description
- Handles redemption with different redemption rate
calculateMinimumPointsForDiscount (4 tests)
- Calculates minimum points needed
- Rounds up fractional points
- Throws error when no active program
- Handles different redemption rates
calculateMaximumDiscount (4 tests)
- Calculates maximum discount available
- Returns 0 when no loyalty account
- Returns 0 when no active program
- Rounds to 2 decimal places
awardBonusPoints (2 tests)
- Creates loyalty record if not exists
- Awards bonus points successfully
checkRewardEligibility (4 tests)
- Returns true when user has enough points
- Returns true when user has exact points
- Returns false when user has insufficient points
- Returns false when no loyalty account
Error handling (3 tests)
- Handles database errors in validateRedemption
- Handles database errors in redeemPoints
- Handles database errors in awardBonusPoints
Tiers Tests (37 tests)
getActiveTiers (3 tests)
- Returns tiers from active program
- Returns empty array when no active program
- Returns empty array when program has no tiers
calculateCurrentTier (6 tests)
- Returns null when no loyalty record
- Returns Bronze tier for 0 points
- Returns Silver tier for 1000 points
- Returns Gold tier for 5000+ points
- Returns highest qualifying tier
- Returns null when no tiers available
getTierStatus (5 tests)
- Returns empty status when no loyalty record
- Returns status for Bronze tier user
- Returns status for Silver tier user
- Returns status for Gold tier user (highest tier)
- Calculates progress correctly near tier threshold
- Returns 0 progress for new user
checkTierUpgrade (6 tests)
- Returns no upgrade when no loyalty record
- Upgrades from Bronze to Silver
- Upgrades from Silver to Gold
- Does not upgrade when already at correct tier
- Upgrades from null tier to Bronze
- Skips multiple tiers if points allow
- Does not downgrade tiers
getTierPerks (3 tests)
- Returns perks for a tier
- Returns empty object when tier not found
- Returns empty object when tier has no perks
hasTierPerk (4 tests)
- Returns true when user has the perk
- Returns false when user does not have the perk
- Returns false when no current tier
- Returns false when tier has no perks
getUsersInTier (2 tests)
- Returns user IDs in a tier
- Returns empty array when no users in tier
getTierDistribution (3 tests)
- Returns distribution of users across tiers
- Excludes 'No Tier' when count is 0
- Returns empty array when no tiers
Error handling (3 tests)
- Handles database errors in calculateCurrentTier
- Handles database errors in checkTierUpgrade
- Handles database errors in getTierStatus
Test Patterns Used
Mocking Strategy
- Prisma Client: Mocked using
jest-mock-extendedfor type-safe mocking - Logger: Mocked to prevent console output during tests
- Database Transactions: Mocked with callback pattern to simulate Prisma transactions
Test Organization
- Each utility file has its own test file
- Tests grouped by function using
describeblocks - Error handling tests separated into their own section
- Edge cases and boundary conditions thoroughly tested
Coverage Goals Achieved
- Statement Coverage: 98.84%
- Branch Coverage: 94.11%
- Function Coverage: 100%
- Line Coverage: 98.84%
Running the Tests
Run all loyalty tests
npm test src/lib/loyalty/__tests__
Run specific test file
npm test src/lib/loyalty/__tests__/points.test.ts
npm test src/lib/loyalty/__tests__/rewards.test.ts
npm test src/lib/loyalty/__tests__/tiers.test.ts
Run with coverage
npm test -- --coverage --collectCoverageFrom='src/lib/loyalty/**/*.ts' src/lib/loyalty/__tests__
Key Test Scenarios Covered
Points Calculation
- Base points calculation with and without loyalty programs
- Tier multipliers application
- Bonus rules (multiplier and fixed types)
- Points expiration
- Manual points adjustments
- Points value conversion to dollars
Rewards Redemption
- Validation of redemption requests
- Insufficient points handling
- Points-to-discount conversion
- Minimum points calculation for discounts
- Maximum discount calculation
- Bonus points awarding
- Reward eligibility checking
Tier Management
- Current tier calculation based on lifetime points
- Tier progression and upgrades
- Progress calculation to next tier
- Tier perks management
- User distribution across tiers
- No tier downgrading (based on lifetime points)
Edge Cases Tested
- Zero values: Zero points, zero discount, zero users
- Null/undefined: Missing loyalty records, missing programs, missing tiers
- Exact matches: Exact point requirements, exact tier thresholds
- Rounding: Fractional points, decimal currency values
- Boundaries: Minimum/maximum values, tier transitions
- Error conditions: Database errors, invalid inputs
Mock Data Examples
Mock Loyalty Program
{
id: 1,
name: "Test Program",
pointsPerDollar: 10,
redemptionRate: 0.01,
isActive: true,
tiers: [...],
bonusRules: [...]
}
Mock Tiers
[
{ id: 1, name: "Bronze", minPoints: 0, pointsMultiplier: 1 },
{ id: 2, name: "Silver", minPoints: 1000, pointsMultiplier: 1.5 },
{ id: 3, name: "Gold", minPoints: 5000, pointsMultiplier: 2 }
]
Mock Customer Loyalty
{
id: 1,
userId: 1,
totalPoints: 1000,
lifetimePoints: 5000,
currentTierId: 2
}
Notes
- All tests pass successfully
- No flaky tests detected
- Tests are independent and can run in any order
- Comprehensive error handling coverage
- Type-safe mocking ensures tests match actual implementation
- Tests cover both success and failure scenarios
Future Enhancements
Potential areas for additional testing:
- Integration tests with real database
- Performance tests for bulk operations
- Concurrent transaction handling
- Points expiration scheduling tests
- Complex bonus rule combinations