# Testing Documentation for django-wallet-utils

## Table of Contents

1. [Overview](#overview)
2. [Test Structure](#test-structure)
3. [Test Coverage by Component](#test-coverage-by-component)
4. [Test Categories](#test-categories)
5. [Test Models](#test-models)
6. [Running Tests](#running-tests)
7. [Coverage Goals](#coverage-goals)
8. [CI/CD Setup](#cicd-setup)
9. [Test Maintenance](#test-maintenance)

---

## Overview

The `django-wallet-utils` package includes a comprehensive test suite with **121+ test methods** covering all functionality documented in the usage guide. Tests are organized into 5 main test files:

- `test_wallet_service.py` - Main service tests (70+ tests)
- `test_wallet_repository.py` - Repository implementation tests (18 tests)
- `test_wallet_models.py` - Model tests (15 tests)
- `test_transaction_types.py` - Transaction type constants tests (18 tests)
- `test_exceptions.py` - Exception tests (10 tests)

All tests follow Django's testing best practices and use `TestCase` for standard tests and `TransactionTestCase` for tests involving database transactions and race conditions.

---

## Test Structure

```
django-wallet-utils/
├── tests/
│   ├── __init__.py
│   ├── settings.py              # Django test settings
│   ├── models.py                 # Test models (TestUser, TestWallet, etc.)
│   ├── migrations/               # Test model migrations
│   ├── test_wallet_service.py    # Main service tests
│   ├── test_wallet_repository.py # Repository tests
│   ├── test_wallet_models.py     # Model tests
│   ├── test_transaction_types.py # Transaction type tests
│   └── test_exceptions.py        # Exception tests
├── manage.py                     # Django test runner
└── pyproject.toml                # Package config with test deps
```

---

## Test Coverage by Component

### 1. WalletService Tests (`test_wallet_service.py`)

#### 1.1 Add Point Tests (`WalletServiceAddPointTests`)

**Standard Cases:**
- `test_add_point_standard` - Basic add point operation
- `test_add_point_with_transaction_type` - Add with transaction type constant
- `test_add_point_with_iid` - Add with initiator ID (user-initiated)
- `test_add_point_system_initiated` - Add with system initiator (default iid=-100)
- `test_add_point_with_extra_data` - Add with extra data dictionary
- `test_add_point_multiple_point_types` - Add to different point types
- `test_add_point_decimal_precision` - Decimal precision handling (2, 0, 8 decimal places)
- `test_add_point_accumulation` - Multiple additions accumulate correctly

**Edge Cases:**
- `test_add_point_zero_amount` - Adding zero amount
- `test_add_point_very_small_amount` - Very small amounts (0.01)
- `test_add_point_very_large_amount` - Very large amounts (9999999999999.99)
- `test_add_point_nonexistent_user` - Non-existent user raises error

**Error Cases:**
- `test_add_point_invalid_point_type` - Invalid point type raises `InvalidPointTypeError`
- `test_add_point_invalid_params_not_dict` - Extra data not a dict raises error
- `test_add_point_invalid_params_data_not_dict` - Nested data not a dict raises error
- `test_add_point_reserved_field_in_extra_data` - Reserved fields in extra_data raises error

#### 1.2 Deduct Point Tests (`WalletServiceDeductPointTests`)

**Standard Cases:**
- `test_deduct_point_standard` - Basic deduct point operation
- `test_deduct_point_with_transaction_type` - Deduct with transaction type constant
- `test_deduct_point_exact_balance` - Deducting exact balance amount
- `test_deduct_point_decimal_precision` - Decimal precision handling

**Edge Cases:**
- `test_deduct_point_zero_amount` - Deducting zero amount (no change)
- `test_deduct_point_multiple_deductions` - Multiple deductions work correctly
- `test_deduct_point_allow_negative` - Allow negative balance when enabled

**Error Cases:**
- `test_deduct_point_insufficient_balance` - Insufficient balance raises `InsufficientBalanceError`

**Race Condition Tests:**
- `test_deduct_point_race_condition_prevention` - Atomic SQL prevents race conditions
- `test_atomic_deduction_prevents_race_condition` - Concurrent deductions handled correctly

#### 1.3 Transaction History Tests (`WalletServiceTransactionHistoryTests`)

**Filter Tests:**
- `test_get_transaction_history_by_user_id` - Filter by user ID
- `test_get_transaction_history_by_point_type` - Filter by point type
- `test_get_transaction_history_by_trans_type` - Filter by transaction type (integer constant)
- `test_get_transaction_history_by_transaction_type_credit` - Filter by transaction type ('c')
- `test_get_transaction_history_by_transaction_type_debit` - Filter by transaction type ('d')
- `test_get_transaction_history_by_iid` - Filter by initiator ID
- `test_get_transaction_history_date_range` - Filter by date range (datetime objects)
- `test_get_transaction_history_date_range_string` - Filter by date range (string dates)
- `test_get_transaction_history_combined_filters` - Multiple filters combined

**Pagination Tests:**
- `test_get_transaction_history_pagination_limit` - Limit results
- `test_get_transaction_history_pagination_offset` - Offset results
- `test_get_transaction_history_ordering` - Ordering (newest first)

**Single Transaction Tests:**
- `test_get_transaction_by_id` - Get transaction by ID
- `test_get_transaction_by_id_not_found` - Non-existent ID returns None
- `test_count_transactions` - Count transactions matching filters

**Error Cases:**
- `test_get_transaction_history_invalid_point_type` - Invalid point type raises error
- `test_get_transaction_history_invalid_transaction_type` - Invalid transaction type raises error

#### 1.4 Separate Wallet Model Tests (`WalletServiceSeparateWalletModelTests`)

- `test_add_point_separate_wallet_model` - Add point using separate wallet model
- `test_deduct_point_separate_wallet_model` - Deduct point using separate wallet model
- `test_get_user_balance_separate_wallet_model` - Get balance from separate wallet model

#### 1.5 Custom Field Name Tests (`WalletServiceCustomFieldTests`)

- `test_add_point_custom_field_name` - Add point with custom field name mapping
- `test_add_point_custom_user_id_field` - Add point with custom user_id field name

#### 1.6 Transaction Type Tests (`WalletServiceTransactionTypeTests`)

- `test_transaction_type_constants` - Transaction type constants available
- `test_get_transaction_type` - Get transaction type by name
- `test_get_transaction_type_name` - Get transaction type name by constant
- `test_transaction_type_in_transaction_record` - Transaction type stored correctly

#### 1.7 Edge Cases and Outliers (`WalletServiceEdgeCasesTests`)

- `test_system_user_id` - System user ID (-100) works correctly
- `test_very_long_remarks` - Very long remarks handled correctly
- `test_empty_remarks` - Empty remarks handled correctly
- `test_none_transaction_type` - None transaction type handled correctly
- `test_empty_extra_data` - Empty extra_data dictionary handled correctly
- `test_no_params` - No params in extra_data handled correctly
- `test_multiple_concurrent_operations` - Multiple concurrent operations work correctly
- `test_balance_accuracy_after_multiple_operations` - Balance accuracy after many operations

---

### 2. WalletRepository Tests (`test_wallet_repository.py`)

#### 2.1 Initialization Tests (`WalletRepositoryInitializationTests`)

- `test_init_with_user_model` - Initialize with User model only
- `test_init_with_separate_wallet_model` - Initialize with separate wallet model
- `test_init_with_custom_field_map` - Initialize with custom field name mapping
- `test_init_with_custom_user_id_field` - Initialize with custom user_id field name

#### 2.2 Balance Operation Tests (`WalletRepositoryBalanceOperationTests`)

**Update Balance:**
- `test_update_balance_add` - Update balance (add operation)
- `test_update_balance_negative_not_allowed` - Negative balance not allowed (default)
- `test_update_balance_negative_allowed` - Negative balance allowed when enabled

**Deduct Balance Atomic:**
- `test_deduct_balance_atomic_success` - Atomic deduction succeeds
- `test_deduct_balance_atomic_insufficient` - Atomic deduction fails with insufficient balance
- `test_deduct_balance_atomic_exact_balance` - Atomic deduction with exact balance
- `test_deduct_balance_atomic_allow_negative` - Atomic deduction allows negative when enabled

**Get User Balance:**
- `test_get_user_balance` - Get user balance for point type

#### 2.3 Separate Model Tests (`WalletRepositorySeparateModelTests`)

- `test_update_balance_separate_model` - Update balance with separate model
- `test_deduct_balance_atomic_separate_model` - Atomic deduction with separate model
- `test_get_user_balance_separate_model` - Get balance from separate model

#### 2.4 Transaction Record Tests (`WalletRepositoryTransactionRecordTests`)

- `test_create_transaction_record` - Create transaction record with string date
- `test_create_transaction_record_with_datetime` - Create transaction record with datetime

---

### 3. Model Tests (`test_wallet_models.py`)

#### 3.1 WalletTransaction Model Tests (`WalletTransactionModelTests`)

**Field Tests:**
- `test_create_transaction` - Create transaction with all fields
- `test_transaction_type_choices` - Transaction type field ('c' or 'd')
- `test_transaction_extra_data` - Extra data JSONField works correctly
- `test_transaction_extra_data_default` - Extra data defaults to empty dict
- `test_transaction_descr_default` - Description defaults to empty string
- `test_transaction_trans_type_nullable` - Transaction type can be None

**Ordering and Representation:**
- `test_transaction_ordering` - Transactions ordered by creation date (newest first)
- `test_transaction_string_representation` - String representation works correctly

**Precision and Edge Cases:**
- `test_transaction_decimal_precision` - Decimal precision (2 decimal places)
- `test_transaction_system_user_id` - System user ID (-100) works correctly

**Index Tests:**
- `test_transaction_indexes` - Single field indexes exist
- `test_transaction_composite_indexes` - Composite indexes exist

#### 3.2 AbstractWalletTransaction Tests (`AbstractWalletTransactionTests`)

- `test_abstract_model_not_creates_table` - Abstract model doesn't create table
- `test_custom_model_extends_abstract` - Custom model extends abstract correctly
- `test_custom_model_custom_decimal_places` - Custom model with custom decimal places

---

### 4. Transaction Types Tests (`test_transaction_types.py`)

#### 4.1 Constant Tests (`TransactionTypeConstantsTests`)

**Category Tests:**
- `test_wallet_operations_constants` - Wallet operation constants (1000-1007)
- `test_package_operations_constants` - Package operation constants (2000-2006)
- `test_commission_rewards_constants` - Commission & rewards constants (3000-3501)
- `test_product_operations_constants` - Product operation constants (4000-4006)
- `test_system_operations_constants` - System operation constants (5000-5004)

**Range Tests:**
- `test_constant_ranges` - Constants are in expected ranges

#### 4.2 Helper Function Tests (`TransactionTypeHelperFunctionsTests`)

- `test_get_transaction_type_valid` - Get transaction type by name (valid)
- `test_get_transaction_type_invalid` - Get transaction type by name (invalid)
- `test_get_transaction_type_name_valid` - Get transaction type name by constant (valid)
- `test_get_transaction_type_name_invalid` - Get transaction type name by constant (invalid)
- `test_transaction_types_dictionary` - TRANSACTION_TYPES dictionary works
- `test_transaction_type_names_dictionary` - TRANSACTION_TYPE_NAMES dictionary works
- `test_dictionary_consistency` - Dictionaries are consistent with each other
- `test_all_constants_in_dictionary` - All constants are in dictionaries

---

### 5. Exception Tests (`test_exceptions.py`)

#### 5.1 WalletOperationError Tests (`WalletOperationErrorTests`)

- `test_wallet_operation_error` - Base exception can be raised
- `test_wallet_operation_error_inheritance` - Inherits from Exception

#### 5.2 InsufficientBalanceError Tests (`InsufficientBalanceErrorTests`)

- `test_insufficient_balance_error` - Error with available and requested amounts
- `test_insufficient_balance_error_inheritance` - Inherits from WalletOperationError
- `test_insufficient_balance_error_none_values` - Error with None values

#### 5.3 InvalidPointTypeError Tests (`InvalidPointTypeErrorTests`)

- `test_invalid_point_type_error` - Error with point_type
- `test_invalid_point_type_error_inheritance` - Inherits from WalletOperationError
- `test_invalid_point_type_error_none_value` - Error with None point_type

#### 5.4 InvalidParamsError Tests (`InvalidParamsErrorTests`)

- `test_invalid_params_error` - Error with method name
- `test_invalid_params_error_inheritance` - Inherits from WalletOperationError
- `test_invalid_params_error_none_value` - Error with None method

---

## Test Categories

### Standard Cases

Tests that verify normal, expected behavior with valid inputs:

- Basic add/deduct operations
- Transaction type constants
- Initiator ID handling
- Multiple point types
- Decimal precision
- Balance accumulation
- Transaction history filtering
- Pagination and ordering

### Edge Cases

Tests that verify behavior with boundary values and unusual but valid inputs:

- Zero amounts
- Very small amounts (0.01)
- Very large amounts (9999999999999.99)
- Exact balance deductions
- Empty strings/None values
- Very long remarks
- System user IDs (-100)
- Empty extra_data dictionaries

### Error Cases

Tests that verify proper error handling and validation:

- Invalid point types
- Invalid parameters
- Insufficient balance
- Reserved fields in extra_data
- Non-existent users
- Invalid transaction types
- Invalid date formats

### Outlier Cases

Tests that verify behavior under extreme or unusual conditions:

- Concurrent operations
- Race condition prevention
- Multiple rapid operations
- Balance accuracy after many operations
- Custom field mappings
- Separate wallet models
- Custom user_id field names

---

## Test Models

The test suite uses three test models to simulate real-world scenarios:

### TestUser

```python
class TestUser(models.Model):
    username = models.CharField(max_length=150, unique=True)
    email = models.EmailField(blank=True)
    
    # Wallet balance fields
    credit_balance = models.DecimalField(max_digits=20, decimal_places=2, default=0)
    reward_points = models.DecimalField(max_digits=20, decimal_places=0, default=0)
    crypto_balance = models.DecimalField(max_digits=20, decimal_places=8, default=0)
    
    # Custom field name example
    points = models.DecimalField(max_digits=20, decimal_places=0, default=0)
```

**Purpose:** Tests wallet operations on the user model directly (default behavior).

### TestWallet

```python
class TestWallet(models.Model):
    user_id = models.BigIntegerField(unique=True, db_index=True)
    
    # Wallet balance fields
    credit_balance = models.DecimalField(max_digits=20, decimal_places=2, default=0)
    reward_points = models.DecimalField(max_digits=20, decimal_places=0, default=0)
    crypto_balance = models.DecimalField(max_digits=20, decimal_places=8, default=0)
```

**Purpose:** Tests wallet operations using a separate wallet model (separate balance storage).

### TestWalletCustomField

```python
class TestWalletCustomField(models.Model):
    owner_id = models.BigIntegerField(unique=True, db_index=True)
    
    credit_balance = models.DecimalField(max_digits=20, decimal_places=2, default=0)
```

**Purpose:** Tests wallet operations with custom user_id field name (`owner_id` instead of `user_id`).

---

## Running Tests

### Prerequisites

1. Install the package in editable mode with test dependencies:
   ```bash
   cd /home/cursorai/projects/django-wallet-utils
   python -m venv .venv
   source .venv/bin/activate  # On Windows: .venv\Scripts\activate
   pip install -e ".[test]"
   ```

2. Run migrations:
   ```bash
   python manage.py migrate
   ```

### Running All Tests

```bash
# From django-wallet-utils directory
python manage.py test
```

### Running Specific Test Files

```bash
# Run service tests
python manage.py test tests.test_wallet_service

# Run repository tests
python manage.py test tests.test_wallet_repository

# Run model tests
python manage.py test tests.test_wallet_models

# Run transaction type tests
python manage.py test tests.test_transaction_types

# Run exception tests
python manage.py test tests.test_exceptions
```

### Running Specific Test Classes

```bash
# Run add point tests only
python manage.py test tests.test_wallet_service.WalletServiceAddPointTests

# Run deduct point tests only
python manage.py test tests.test_wallet_service.WalletServiceDeductPointTests

# Run transaction history tests only
python manage.py test tests.test_wallet_service.WalletServiceTransactionHistoryTests
```

### Running Specific Test Methods

```bash
# Run a single test
python manage.py test tests.test_wallet_service.WalletServiceAddPointTests.test_add_point_standard

# Run multiple specific tests
python manage.py test tests.test_wallet_service.WalletServiceAddPointTests.test_add_point_standard tests.test_wallet_service.WalletServiceAddPointTests.test_add_point_with_transaction_type
```

### Running Tests with Verbose Output

```bash
# Verbose output
python manage.py test --verbosity=2

# Extra verbose output
python manage.py test --verbosity=3
```

### Running Tests with Coverage

```bash
# Install coverage (if not already installed)
pip install coverage

# Run tests with coverage
coverage run --source='src/wallet_utils' manage.py test

# Generate coverage report
coverage report

# Generate HTML coverage report
coverage html
```

---

## Coverage Goals

### Current Coverage

The test suite aims for **100% code coverage** of:

- ✅ All public methods in `WalletService`
- ✅ All methods in `WalletRepository`
- ✅ All model fields and methods in `WalletTransaction` and `AbstractWalletTransaction`
- ✅ All transaction type constants and helper functions
- ✅ All exception classes

### Coverage Targets

- **Service Layer**: 100% (all methods, all branches)
- **Repository Layer**: 100% (all methods, all branches)
- **Model Layer**: 100% (all fields, all methods)
- **Transaction Types**: 100% (all constants, all helpers)
- **Exceptions**: 100% (all exception classes)

### Areas Covered

- ✅ Standard operations (add, deduct, get balance)
- ✅ Transaction history retrieval (all filters, pagination)
- ✅ Error handling (all exception types)
- ✅ Edge cases (zero amounts, very large amounts, etc.)
- ✅ Race conditions (atomic operations)
- ✅ Custom configurations (separate models, custom fields)
- ✅ Decimal precision (2, 0, 8 decimal places)
- ✅ Transaction types (all constants)

---

## CI/CD Setup

### GitHub Actions Example

Create `.github/workflows/test.yml`:

```yaml
name: Tests

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    strategy:
      matrix:
        python-version: ["3.9", "3.10", "3.11", "3.12"]
        django-version: ["4.2", "5.0", "5.1", "5.2"]
    
    steps:
    - uses: actions/checkout@v3
    
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -e ".[test]"
        pip install django==${{ matrix.django-version }}
    
    - name: Run migrations
      run: python manage.py migrate
    
    - name: Run tests
      run: python manage.py test --verbosity=2
    
    - name: Generate coverage report
      run: |
        pip install coverage
        coverage run --source='src/wallet_utils' manage.py test
        coverage report
        coverage xml
```

### Running Tests in CI

The CI setup should:

1. Install Python and Django dependencies
2. Run migrations
3. Run all tests
4. Generate coverage reports
5. Fail if any test fails

---

## Test Maintenance

### Adding New Tests

When adding new functionality:

1. **Add tests first** (TDD approach)
2. **Follow naming conventions**: `test_<method>_<scenario>`
3. **Use descriptive docstrings**: Explain what the test verifies
4. **Group related tests**: Use test classes for related functionality
5. **Test all branches**: Cover both success and error paths
6. **Test edge cases**: Zero, None, empty, very large values
7. **Test error cases**: Invalid inputs, missing data, etc.

### Test Organization

- **One test class per component**: `WalletServiceAddPointTests`, `WalletServiceDeductPointTests`, etc.
- **One test method per scenario**: Each test verifies one specific behavior
- **Use setUp() for common setup**: Create test fixtures in `setUp()`
- **Use tearDown() for cleanup**: Clean up resources if needed

### Best Practices

1. **Isolation**: Each test should be independent and can run in any order
2. **Deterministic**: Tests should produce the same results every time
3. **Fast**: Tests should run quickly (use `TestCase` for most tests)
4. **Clear**: Test names and docstrings should clearly describe what's being tested
5. **Complete**: Tests should verify both the operation and its side effects

### Troubleshooting

**Common Issues:**

1. **Migration errors**: Run `python manage.py migrate` before tests
2. **Import errors**: Ensure package is installed with `pip install -e ".[test]"`
3. **Database errors**: Tests use SQLite by default; ensure database file is writable
4. **Race condition tests failing**: Use `TransactionTestCase` for tests involving concurrent operations

**Debugging:**

```bash
# Run tests with debug output
python manage.py test --verbosity=3

# Run specific failing test
python manage.py test tests.test_wallet_service.WalletServiceAddPointTests.test_add_point_standard --verbosity=3

# Use Django shell to debug
python manage.py shell
```

---

## Summary

The `django-wallet-utils` test suite provides comprehensive coverage of all package functionality:

- **121+ test methods** across 5 test files
- **Standard, edge, error, and outlier cases** all covered
- **100% code coverage** target for all components
- **Easy to run** with Django's test runner
- **CI/CD ready** with GitHub Actions example

The tests ensure the package works correctly in all scenarios and help prevent regressions when making changes.
