Compare commits
4 Commits
c5c602d599
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b9b38ca208 | |||
| 34dd87d0f7 | |||
| e76a7567b6 | |||
| f9cbb1b596 |
@ -20,7 +20,9 @@
|
|||||||
"Bash(docker-compose ps:*)",
|
"Bash(docker-compose ps:*)",
|
||||||
"Bash(tree:*)",
|
"Bash(tree:*)",
|
||||||
"Bash(docker-compose:*)",
|
"Bash(docker-compose:*)",
|
||||||
"Bash(chmod:*)"
|
"Bash(chmod:*)",
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(cat:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
400
DOCKER_COMPOSE_GUIDE.md
Normal file
400
DOCKER_COMPOSE_GUIDE.md
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
# Docker Compose Patterns - Quick Guide
|
||||||
|
|
||||||
|
This guide provides a focused reference for all Docker Compose patterns used in this project.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This project uses **three Docker Compose files** for different purposes:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-compose.yml ← Start application server
|
||||||
|
docker-compose.test.yml ← Run unit tests
|
||||||
|
docker-compose.coverage.yml ← Generate coverage reports
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern 1: Application Server
|
||||||
|
|
||||||
|
**File**: `docker-compose.yml`
|
||||||
|
**Purpose**: Deploy JAX-WS application to Tomcat
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start server
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Start with build
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Stop server
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Restart
|
||||||
|
docker-compose restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### What Happens
|
||||||
|
|
||||||
|
1. Builds Docker image (3 stages):
|
||||||
|
- Stage 1: Run unit tests
|
||||||
|
- Stage 2: Build WAR file
|
||||||
|
- Stage 3: Deploy to Tomcat
|
||||||
|
2. Starts Tomcat on port 8080
|
||||||
|
3. Creates/persists SQLite database
|
||||||
|
|
||||||
|
### Access
|
||||||
|
|
||||||
|
- Hello World: http://localhost:8080/jaxws-hello-world/hello?wsdl
|
||||||
|
- Loan Service: http://localhost:8080/jaxws-hello-world/loan?wsdl
|
||||||
|
- Tomcat Manager: http://localhost:8080/manager (admin/admin123)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern 2: Run Unit Tests
|
||||||
|
|
||||||
|
**File**: `docker-compose.test.yml`
|
||||||
|
**Purpose**: Execute JUnit tests
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
docker-compose -f docker-compose.test.yml up --build
|
||||||
|
|
||||||
|
# View test output
|
||||||
|
docker-compose -f docker-compose.test.yml logs
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
docker-compose -f docker-compose.test.yml down
|
||||||
|
|
||||||
|
# One-liner (run and clean)
|
||||||
|
docker-compose -f docker-compose.test.yml up --build && docker-compose -f docker-compose.test.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Convenience Scripts
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```cmd
|
||||||
|
run-tests.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
./run-tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### What Happens
|
||||||
|
|
||||||
|
1. Builds test environment
|
||||||
|
2. Runs `mvn test`
|
||||||
|
3. Executes all *Test.java files
|
||||||
|
4. Exports results to `./target/surefire-reports/`
|
||||||
|
5. Generates JaCoCo coverage data
|
||||||
|
|
||||||
|
### Output
|
||||||
|
|
||||||
|
**Console:**
|
||||||
|
```
|
||||||
|
Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
|
||||||
|
BUILD SUCCESS
|
||||||
|
```
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `./target/surefire-reports/` - Test reports (text & XML)
|
||||||
|
- `./target/jacoco.exec` - Coverage data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern 3: Generate Code Coverage
|
||||||
|
|
||||||
|
**File**: `docker-compose.coverage.yml`
|
||||||
|
**Purpose**: Generate JaCoCo coverage reports
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate coverage
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker-compose -f docker-compose.coverage.yml logs
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
docker-compose -f docker-compose.coverage.yml down
|
||||||
|
|
||||||
|
# One-liner (generate and clean)
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build && docker-compose -f docker-compose.coverage.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Convenience Scripts
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```cmd
|
||||||
|
run-coverage.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
./run-coverage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### What Happens
|
||||||
|
|
||||||
|
1. Builds test environment
|
||||||
|
2. Runs `mvn test` with JaCoCo agent
|
||||||
|
3. Generates coverage reports (HTML, XML, CSV)
|
||||||
|
4. Exports to `./target/site/jacoco/`
|
||||||
|
|
||||||
|
### Output
|
||||||
|
|
||||||
|
**Console:**
|
||||||
|
```
|
||||||
|
=================================================================================
|
||||||
|
Coverage report generated successfully!
|
||||||
|
=================================================================================
|
||||||
|
HTML Report: ./target/site/jacoco/index.html
|
||||||
|
XML Report: ./target/site/jacoco/jacoco.xml
|
||||||
|
CSV Report: ./target/site/jacoco/jacoco.csv
|
||||||
|
=================================================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `./target/site/jacoco/index.html` ← **Open this in browser**
|
||||||
|
- `./target/site/jacoco/jacoco.xml` (for CI/CD)
|
||||||
|
- `./target/site/jacoco/jacoco.csv` (for spreadsheets)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Workflow
|
||||||
|
|
||||||
|
### Typical Development Cycle
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Write code
|
||||||
|
vim src/main/java/com/example/service/MyService.java
|
||||||
|
|
||||||
|
# 2. Run tests
|
||||||
|
docker-compose -f docker-compose.test.yml up --build
|
||||||
|
docker-compose -f docker-compose.test.yml down
|
||||||
|
|
||||||
|
# 3. Check coverage
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
docker-compose -f docker-compose.coverage.yml down
|
||||||
|
|
||||||
|
# 4. View coverage report
|
||||||
|
# Open target/site/jacoco/index.html in browser
|
||||||
|
|
||||||
|
# 5. Deploy if tests pass
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# 6. Verify
|
||||||
|
curl http://localhost:8080/jaxws-hello-world/hello?wsdl
|
||||||
|
|
||||||
|
# 7. View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# 8. Stop when done
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Convenience Scripts (Faster)
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```cmd
|
||||||
|
REM 1. Write code
|
||||||
|
notepad src\main\java\com\example\service\MyService.java
|
||||||
|
|
||||||
|
REM 2. Run tests
|
||||||
|
run-tests.bat
|
||||||
|
|
||||||
|
REM 3. Generate coverage
|
||||||
|
run-coverage.bat
|
||||||
|
|
||||||
|
REM 4. View coverage
|
||||||
|
start target\site\jacoco\index.html
|
||||||
|
|
||||||
|
REM 5. Deploy
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
# 1. Write code
|
||||||
|
vim src/main/java/com/example/service/MyService.java
|
||||||
|
|
||||||
|
# 2. Run tests
|
||||||
|
./run-tests.sh
|
||||||
|
|
||||||
|
# 3. Generate coverage
|
||||||
|
./run-coverage.sh
|
||||||
|
|
||||||
|
# 4. View coverage (Linux)
|
||||||
|
xdg-open target/site/jacoco/index.html
|
||||||
|
|
||||||
|
# 5. Deploy
|
||||||
|
docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference Table
|
||||||
|
|
||||||
|
| Task | Docker Compose Command | Script Alternative |
|
||||||
|
|------|----------------------|-------------------|
|
||||||
|
| **Run Tests** | `docker-compose -f docker-compose.test.yml up --build` | `run-tests.bat` / `./run-tests.sh` |
|
||||||
|
| **Generate Coverage** | `docker-compose -f docker-compose.coverage.yml up --build` | `run-coverage.bat` / `./run-coverage.sh` |
|
||||||
|
| **Start Server** | `docker-compose up -d` | N/A |
|
||||||
|
| **Stop Server** | `docker-compose down` | N/A |
|
||||||
|
| **View Logs** | `docker-compose logs -f` | N/A |
|
||||||
|
| **Rebuild Server** | `docker-compose up -d --build` | N/A |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Pattern: Test-Driven Development (TDD)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Watch-test loop (manual)
|
||||||
|
while true; do
|
||||||
|
docker-compose -f docker-compose.test.yml up --build
|
||||||
|
docker-compose -f docker-compose.test.yml down
|
||||||
|
read -p "Press Enter to run again, Ctrl+C to exit..."
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern: CI/CD Pipeline
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# GitHub Actions example
|
||||||
|
- name: Run Tests
|
||||||
|
run: docker-compose -f docker-compose.test.yml up --build
|
||||||
|
|
||||||
|
- name: Generate Coverage
|
||||||
|
run: docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
|
||||||
|
- name: Upload Coverage
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
files: ./target/site/jacoco/jacoco.xml
|
||||||
|
|
||||||
|
- name: Deploy (if tests pass)
|
||||||
|
run: docker-compose up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern: Pre-commit Hook
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# .git/hooks/pre-commit
|
||||||
|
|
||||||
|
echo "Running tests before commit..."
|
||||||
|
docker-compose -f docker-compose.test.yml up --build
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Tests failed! Commit aborted."
|
||||||
|
docker-compose -f docker-compose.test.yml down
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker-compose -f docker-compose.test.yml down
|
||||||
|
echo "Tests passed! Proceeding with commit."
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Tests Failing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View detailed test output
|
||||||
|
docker-compose -f docker-compose.test.yml up
|
||||||
|
|
||||||
|
# Check test container logs
|
||||||
|
docker-compose -f docker-compose.test.yml logs
|
||||||
|
|
||||||
|
# Access test container
|
||||||
|
docker-compose -f docker-compose.test.yml run --rm test bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### Coverage Report Empty
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure tests are running
|
||||||
|
docker-compose -f docker-compose.test.yml up --build
|
||||||
|
|
||||||
|
# Check if target directory is mounted
|
||||||
|
docker-compose -f docker-compose.coverage.yml config | grep target
|
||||||
|
|
||||||
|
# Manually verify mount
|
||||||
|
ls -la target/site/jacoco/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Won't Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if port 8080 is in use
|
||||||
|
netstat -ano | findstr :8080 # Windows
|
||||||
|
lsof -i :8080 # Linux/Mac
|
||||||
|
|
||||||
|
# View server logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Rebuild from scratch
|
||||||
|
docker-compose down
|
||||||
|
docker-compose build --no-cache
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Usage
|
||||||
|
|
||||||
|
### Run Specific Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Override command to run specific test
|
||||||
|
docker-compose -f docker-compose.test.yml run --rm test \
|
||||||
|
mvn test -Dtest=HelloWorldServiceImplTest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Memory Settings
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Override environment variable
|
||||||
|
docker-compose -f docker-compose.test.yml run --rm \
|
||||||
|
-e MAVEN_OPTS="-Xmx1024m" test mvn test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Debug Mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests with Maven debug output
|
||||||
|
docker-compose -f docker-compose.test.yml run --rm test \
|
||||||
|
mvn test -X
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- **3 Docker Compose files** = 3 different purposes
|
||||||
|
- **Test first** before deploying
|
||||||
|
- **Check coverage** to ensure quality
|
||||||
|
- **Use scripts** for convenience
|
||||||
|
- **All outputs** go to `./target/`
|
||||||
|
|
||||||
|
**Quick commands to remember:**
|
||||||
|
```bash
|
||||||
|
run-tests.bat # or ./run-tests.sh
|
||||||
|
run-coverage.bat # or ./run-coverage.sh
|
||||||
|
docker-compose up -d # start server
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! You now have everything you need to use Docker Compose patterns in this project.
|
||||||
30
Dockerfile.coverage
Normal file
30
Dockerfile.coverage
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Multi-stage build for code coverage
|
||||||
|
FROM maven:3.8.6-openjdk-8 AS builder
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy pom.xml and src
|
||||||
|
COPY pom.xml .
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
# Build with tests
|
||||||
|
RUN mvn clean package -DskipTests=false
|
||||||
|
|
||||||
|
# Create a separate stage for running tests with coverage
|
||||||
|
FROM maven:3.8.6-openjdk-8
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the built project
|
||||||
|
COPY --from=builder /app/target/*.war /app/app.war
|
||||||
|
|
||||||
|
# Install dependencies for coverage reporting
|
||||||
|
RUN apt-get update && apt-get install -y wget unzip
|
||||||
|
|
||||||
|
# Create a script to run tests with coverage
|
||||||
|
COPY scripts/ .
|
||||||
|
|
||||||
|
# Run tests with coverage
|
||||||
|
CMD ["mvn", "test"]
|
||||||
16
Dockerfile.coverage.simple
Normal file
16
Dockerfile.coverage.simple
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM maven:3.8.6-openjdk-8
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy pom.xml and source files
|
||||||
|
COPY pom.xml .
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
# Run tests with coverage
|
||||||
|
RUN mvn clean test
|
||||||
|
|
||||||
|
# Generate coverage report
|
||||||
|
RUN mvn jacoco:report
|
||||||
|
|
||||||
|
# Show the coverage report location
|
||||||
|
RUN echo "Coverage report is available in target/site/jacoco/"
|
||||||
584
README.md
584
README.md
@ -2,6 +2,25 @@
|
|||||||
|
|
||||||
A complete JAX-WS SOAP web services application for loan processing, featuring customer registration, credit score evaluation, and loan approval workflows with SQLite database persistence.
|
A complete JAX-WS SOAP web services application for loan processing, featuring customer registration, credit score evaluation, and loan approval workflows with SQLite database persistence.
|
||||||
|
|
||||||
|
## Quick Reference Card
|
||||||
|
|
||||||
|
### 🚀 Three Ways to Use This Project
|
||||||
|
|
||||||
|
| Task | Command | Result |
|
||||||
|
|------|---------|--------|
|
||||||
|
| **Run Tests** | `docker-compose -f docker-compose.test.yml up --build` | Tests run, results in `./target/surefire-reports/` |
|
||||||
|
| **Generate Coverage** | `docker-compose -f docker-compose.coverage.yml up --build` | Coverage report in `./target/site/jacoco/index.html` |
|
||||||
|
| **Start Server** | `docker-compose up -d` | Server running at http://localhost:8080 |
|
||||||
|
|
||||||
|
### 📝 Convenience Scripts
|
||||||
|
|
||||||
|
| Platform | Run Tests | Generate Coverage |
|
||||||
|
|----------|-----------|-------------------|
|
||||||
|
| **Windows** | `run-tests.bat` | `run-coverage.bat` |
|
||||||
|
| **Linux/Mac** | `./run-tests.sh` | `./run-coverage.sh` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Project Details
|
## Project Details
|
||||||
|
|
||||||
### Overview
|
### Overview
|
||||||
@ -23,7 +42,8 @@ This project demonstrates a production-ready JAX-WS web service implementation u
|
|||||||
- **Application Server**: Apache Tomcat 9+
|
- **Application Server**: Apache Tomcat 9+
|
||||||
- **Database**: SQLite 3.43.0
|
- **Database**: SQLite 3.43.0
|
||||||
- **Containerization**: Docker & Docker Compose
|
- **Containerization**: Docker & Docker Compose
|
||||||
- **Testing**: Python 3 with zeep library
|
- **Unit Testing**: JUnit 5 (Jupiter), Mockito, JaCoCo
|
||||||
|
- **Integration Testing**: Python 3 with requests library
|
||||||
|
|
||||||
### Key Features
|
### Key Features
|
||||||
|
|
||||||
@ -32,7 +52,8 @@ This project demonstrates a production-ready JAX-WS web service implementation u
|
|||||||
- **Credit Risk Assessment**: Tiered approval logic based on credit scores
|
- **Credit Risk Assessment**: Tiered approval logic based on credit scores
|
||||||
- **Blacklist Management**: Customer screening and rejection system
|
- **Blacklist Management**: Customer screening and rejection system
|
||||||
- **Docker Support**: Fully containerized with one-command deployment
|
- **Docker Support**: Fully containerized with one-command deployment
|
||||||
- **Comprehensive Testing**: Python test clients for all services
|
- **Unit Testing**: JUnit 5 tests with JaCoCo code coverage
|
||||||
|
- **Integration Testing**: Python test clients for all services
|
||||||
- **Production-Ready**: Singleton patterns, prepared statements, error handling
|
- **Production-Ready**: Singleton patterns, prepared statements, error handling
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -46,6 +67,75 @@ This project demonstrates a production-ready JAX-WS web service implementation u
|
|||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
||||||
|
#### Three Docker Compose Configurations
|
||||||
|
|
||||||
|
This project provides three Docker Compose files for different purposes:
|
||||||
|
|
||||||
|
| Purpose | File | Command | Output Location |
|
||||||
|
|---------|------|---------|----------------|
|
||||||
|
| **🚀 Start Server** | `docker-compose.yml` | `docker-compose up -d` | http://localhost:8080 |
|
||||||
|
| **✅ Run Tests** | `docker-compose.test.yml` | `docker-compose -f docker-compose.test.yml up --build` | `./target/surefire-reports/` |
|
||||||
|
| **📊 Coverage Report** | `docker-compose.coverage.yml` | `docker-compose -f docker-compose.coverage.yml up --build` | `./target/site/jacoco/index.html` |
|
||||||
|
|
||||||
|
**Visual Workflow:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Development Workflow │
|
||||||
|
└─────────────────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
1. Write Code
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────┐
|
||||||
|
│ Run Unit Tests │ ──► docker-compose -f docker-compose.test.yml up --build
|
||||||
|
│ (docker-compose.test) │
|
||||||
|
└──────────┬──────────────┘
|
||||||
|
│ Tests Pass ✓
|
||||||
|
▼
|
||||||
|
┌─────────────────────────┐
|
||||||
|
│ Generate Coverage │ ──► docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
│ (docker-compose.coverage│
|
||||||
|
└──────────┬──────────────┘ Open: ./target/site/jacoco/index.html
|
||||||
|
│ Coverage OK ✓
|
||||||
|
▼
|
||||||
|
┌─────────────────────────┐
|
||||||
|
│ Deploy Application │ ──► docker-compose up -d
|
||||||
|
│ (docker-compose.yml) │
|
||||||
|
└──────────┬──────────────┘ Access: http://localhost:8080
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Production Ready!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Quick Commands:**
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
docker-compose -f docker-compose.test.yml up --build && docker-compose -f docker-compose.test.yml down
|
||||||
|
|
||||||
|
# Generate coverage report
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build && docker-compose -f docker-compose.coverage.yml down
|
||||||
|
|
||||||
|
# Start application server
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or use convenience scripts:**
|
||||||
|
```cmd
|
||||||
|
REM Windows
|
||||||
|
run-tests.bat
|
||||||
|
run-coverage.bat
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
./run-tests.sh
|
||||||
|
./run-coverage.sh
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
#### 1. Start the Application
|
#### 1. Start the Application
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -88,6 +178,190 @@ docker-compose logs jaxws-service
|
|||||||
docker-compose down
|
docker-compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Docker Compose Patterns
|
||||||
|
|
||||||
|
This project includes three Docker Compose configurations for different purposes.
|
||||||
|
|
||||||
|
> **📖 For detailed patterns, workflows, and troubleshooting, see [DOCKER_COMPOSE_GUIDE.md](DOCKER_COMPOSE_GUIDE.md)**
|
||||||
|
|
||||||
|
#### Pattern 1: Start Application Server
|
||||||
|
|
||||||
|
**File**: `docker-compose.yml`
|
||||||
|
**Purpose**: Deploy and run the JAX-WS application on Tomcat
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start the server in detached mode
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Start with build (after code changes)
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# View logs in real-time
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Stop the server
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
1. Runs unit tests (Stage 1: tester)
|
||||||
|
2. Builds WAR file (Stage 2: builder)
|
||||||
|
3. Deploys to Tomcat (Stage 3)
|
||||||
|
4. Exposes port 8080
|
||||||
|
5. Creates/persists SQLite database
|
||||||
|
|
||||||
|
**Access points:**
|
||||||
|
- Hello World Service: http://localhost:8080/jaxws-hello-world/hello?wsdl
|
||||||
|
- Loan Service: http://localhost:8080/jaxws-hello-world/loan?wsdl
|
||||||
|
- Tomcat Manager: http://localhost:8080/manager (admin/admin123)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Pattern 2: Run Unit Tests
|
||||||
|
|
||||||
|
**File**: `docker-compose.test.yml`
|
||||||
|
**Purpose**: Execute JUnit tests and export results to `./target`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
docker-compose -f docker-compose.test.yml up --build
|
||||||
|
|
||||||
|
# Run tests in background
|
||||||
|
docker-compose -f docker-compose.test.yml up -d
|
||||||
|
|
||||||
|
# View test output
|
||||||
|
docker-compose -f docker-compose.test.yml logs
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
docker-compose -f docker-compose.test.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
1. Builds test environment (tester stage)
|
||||||
|
2. Runs `mvn test`
|
||||||
|
3. Executes all *Test.java files
|
||||||
|
4. Exports test reports to `./target/surefire-reports/`
|
||||||
|
5. Exports coverage data to `./target/jacoco.exec`
|
||||||
|
|
||||||
|
**Test results location:**
|
||||||
|
- Console output: Test summary with pass/fail counts
|
||||||
|
- Reports: `./target/surefire-reports/` (text and XML)
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
```
|
||||||
|
Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
|
||||||
|
BUILD SUCCESS
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Pattern 3: Generate Code Coverage Report
|
||||||
|
|
||||||
|
**File**: `docker-compose.coverage.yml`
|
||||||
|
**Purpose**: Run tests and generate JaCoCo coverage reports in `./target/site/jacoco/`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate coverage reports
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
|
||||||
|
# Generate coverage in background
|
||||||
|
docker-compose -f docker-compose.coverage.yml up -d
|
||||||
|
|
||||||
|
# View coverage generation logs
|
||||||
|
docker-compose -f docker-compose.coverage.yml logs
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
docker-compose -f docker-compose.coverage.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
1. Builds test environment
|
||||||
|
2. Runs `mvn test` with JaCoCo agent
|
||||||
|
3. Generates coverage reports in multiple formats
|
||||||
|
4. Exports to `./target/site/jacoco/`
|
||||||
|
|
||||||
|
**Coverage reports location:**
|
||||||
|
- **HTML Report**: `./target/site/jacoco/index.html` ← **Open this in browser**
|
||||||
|
- **XML Report**: `./target/site/jacoco/jacoco.xml` (for CI/CD)
|
||||||
|
- **CSV Report**: `./target/site/jacoco/jacoco.csv` (for spreadsheets)
|
||||||
|
|
||||||
|
**Example output:**
|
||||||
|
```
|
||||||
|
=================================================================================
|
||||||
|
Coverage report generated successfully!
|
||||||
|
=================================================================================
|
||||||
|
HTML Report: ./target/site/jacoco/index.html
|
||||||
|
XML Report: ./target/site/jacoco/jacoco.xml
|
||||||
|
CSV Report: ./target/site/jacoco/jacoco.csv
|
||||||
|
=================================================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Complete Workflow Example
|
||||||
|
|
||||||
|
Here's a typical development workflow using all three patterns:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Run tests to ensure code works
|
||||||
|
docker-compose -f docker-compose.test.yml up --build
|
||||||
|
docker-compose -f docker-compose.test.yml down
|
||||||
|
|
||||||
|
# 2. Generate coverage report to check test quality
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
docker-compose -f docker-compose.coverage.yml down
|
||||||
|
|
||||||
|
# 3. View coverage report
|
||||||
|
# Open target/site/jacoco/index.html in browser
|
||||||
|
|
||||||
|
# 4. Deploy application if tests pass
|
||||||
|
docker-compose up -d --build
|
||||||
|
|
||||||
|
# 5. Verify deployment
|
||||||
|
curl http://localhost:8080/jaxws-hello-world/hello?wsdl
|
||||||
|
|
||||||
|
# 6. View application logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# 7. Stop when done
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Quick Reference Scripts
|
||||||
|
|
||||||
|
For convenience, use the provided scripts instead of typing full commands:
|
||||||
|
|
||||||
|
| Task | Windows | Linux/Mac | Docker Compose Equivalent |
|
||||||
|
|------|---------|-----------|--------------------------|
|
||||||
|
| Run Tests | `run-tests.bat` | `./run-tests.sh` | `docker-compose -f docker-compose.test.yml up --build` |
|
||||||
|
| Generate Coverage | `run-coverage.bat` | `./run-coverage.sh` | `docker-compose -f docker-compose.coverage.yml up --build` |
|
||||||
|
| Start Server | N/A | N/A | `docker-compose up -d` |
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```cmd
|
||||||
|
REM Windows
|
||||||
|
run-tests.bat
|
||||||
|
run-coverage.bat
|
||||||
|
|
||||||
|
REM Then start server
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
./run-tests.sh
|
||||||
|
./run-coverage.sh
|
||||||
|
|
||||||
|
# Then start server
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Rebuild After Code Changes
|
### Rebuild After Code Changes
|
||||||
|
|
||||||
When you modify the source code, rebuild and restart:
|
When you modify the source code, rebuild and restart:
|
||||||
@ -103,15 +377,33 @@ docker-compose up -d
|
|||||||
|
|
||||||
### Docker Commands Reference
|
### Docker Commands Reference
|
||||||
|
|
||||||
|
#### Application Server Commands
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
|---------|-------------|
|
|---------|-------------|
|
||||||
| `docker-compose up -d` | Start services in detached mode |
|
| `docker-compose up -d` | Start application server in detached mode |
|
||||||
| `docker-compose up -d --build` | Rebuild and start services |
|
| `docker-compose up -d --build` | Rebuild and start application server |
|
||||||
| `docker-compose down` | Stop and remove containers |
|
| `docker-compose down` | Stop and remove application containers |
|
||||||
| `docker-compose logs -f` | Follow logs output |
|
| `docker-compose logs -f` | Follow application logs in real-time |
|
||||||
| `docker-compose ps` | List running containers |
|
| `docker-compose ps` | List running containers |
|
||||||
| `docker-compose restart` | Restart services |
|
| `docker-compose restart` | Restart application services |
|
||||||
| `docker-compose exec jaxws-service bash` | Access container shell |
|
| `docker-compose exec jaxws-service bash` | Access application container shell |
|
||||||
|
|
||||||
|
#### Test Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `docker-compose -f docker-compose.test.yml up --build` | Run unit tests |
|
||||||
|
| `docker-compose -f docker-compose.test.yml logs` | View test output |
|
||||||
|
| `docker-compose -f docker-compose.test.yml down` | Clean up test containers |
|
||||||
|
|
||||||
|
#### Coverage Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `docker-compose -f docker-compose.coverage.yml up --build` | Generate coverage reports |
|
||||||
|
| `docker-compose -f docker-compose.coverage.yml logs` | View coverage generation logs |
|
||||||
|
| `docker-compose -f docker-compose.coverage.yml down` | Clean up coverage containers |
|
||||||
|
|
||||||
### Accessing the Database
|
### Accessing the Database
|
||||||
|
|
||||||
@ -145,38 +437,45 @@ The `docker-compose.yml` includes these configurations:
|
|||||||
jaxws-hello-world/
|
jaxws-hello-world/
|
||||||
│
|
│
|
||||||
├── src/
|
├── src/
|
||||||
│ └── main/
|
│ ├── main/
|
||||||
│ ├── java/
|
│ │ ├── java/
|
||||||
│ │ └── com/
|
│ │ │ └── com/
|
||||||
│ │ └── example/
|
│ │ │ └── example/
|
||||||
│ │ ├── listener/
|
│ │ │ ├── listener/
|
||||||
│ │ │ └── DatabaseInitializationListener.java # Application startup initialization
|
│ │ │ │ └── DatabaseInitializationListener.java # Application startup initialization
|
||||||
│ │ │
|
│ │ │ │
|
||||||
│ │ ├── model/ # Data models (POJOs)
|
│ │ │ ├── model/ # Data models (POJOs)
|
||||||
│ │ │ ├── CustomerRegistrationRequest.java # Customer registration request
|
│ │ │ │ ├── CustomerRegistrationRequest.java # Customer registration request
|
||||||
│ │ │ ├── LoanRequest.java # Loan application request
|
│ │ │ │ ├── LoanRequest.java # Loan application request
|
||||||
│ │ │ └── LoanResponse.java # Loan application response
|
│ │ │ │ └── LoanResponse.java # Loan application response
|
||||||
│ │ │
|
│ │ │ │
|
||||||
│ │ ├── repository/ # Data access layer
|
│ │ │ ├── repository/ # Data access layer
|
||||||
│ │ │ ├── CustomerRepository.java # Customer DAO interface
|
│ │ │ │ ├── CustomerRepository.java # Customer DAO interface
|
||||||
│ │ │ ├── LoanHistoryRepository.java # Loan history DAO interface
|
│ │ │ │ ├── LoanHistoryRepository.java # Loan history DAO interface
|
||||||
│ │ │ └── impl/
|
│ │ │ │ └── impl/
|
||||||
│ │ │ ├── CustomerRepositoryImpl.java # Customer DAO implementation
|
│ │ │ │ ├── CustomerRepositoryImpl.java # Customer DAO implementation
|
||||||
│ │ │ └── LoanHistoryRepositoryImpl.java # Loan history DAO implementation
|
│ │ │ │ └── LoanHistoryRepositoryImpl.java # Loan history DAO implementation
|
||||||
│ │ │
|
│ │ │ │
|
||||||
│ │ ├── service/ # Web service layer
|
│ │ │ ├── service/ # Web service layer
|
||||||
│ │ │ ├── HelloWorldService.java # Hello World interface
|
│ │ │ │ ├── HelloWorldService.java # Hello World interface
|
||||||
│ │ │ ├── HelloWorldServiceImpl.java # Hello World implementation
|
│ │ │ │ ├── HelloWorldServiceImpl.java # Hello World implementation
|
||||||
│ │ │ ├── LoanApprovalService.java # Loan service (interface + impl)
|
│ │ │ │ ├── LoanApprovalService.java # Loan service (interface + impl)
|
||||||
│ │ │ └── CreditScoreService.java # Credit score evaluation
|
│ │ │ │ └── CreditScoreService.java # Credit score evaluation
|
||||||
│ │ │
|
│ │ │ │
|
||||||
│ │ └── util/
|
│ │ │ └── util/
|
||||||
│ │ └── DatabaseManager.java # Singleton DB connection manager
|
│ │ │ └── DatabaseManager.java # Singleton DB connection manager
|
||||||
│ │
|
│ │ │
|
||||||
│ └── webapp/
|
│ │ └── webapp/
|
||||||
│ └── WEB-INF/
|
│ │ └── WEB-INF/
|
||||||
│ ├── web.xml # Web application descriptor
|
│ │ ├── web.xml # Web application descriptor
|
||||||
│ └── sun-jaxws.xml # JAX-WS endpoint configuration
|
│ │ └── sun-jaxws.xml # JAX-WS endpoint configuration
|
||||||
|
│ │
|
||||||
|
│ └── test/
|
||||||
|
│ └── java/
|
||||||
|
│ └── com/
|
||||||
|
│ └── example/
|
||||||
|
│ └── service/
|
||||||
|
│ └── HelloWorldServiceImplTest.java # Unit tests for Hello World service
|
||||||
│
|
│
|
||||||
├── scripts/ # Python test clients
|
├── scripts/ # Python test clients
|
||||||
│ ├── test_all_services.py # Comprehensive test suite
|
│ ├── test_all_services.py # Comprehensive test suite
|
||||||
@ -186,13 +485,27 @@ jaxws-hello-world/
|
|||||||
│ └── README.md # Test scripts documentation
|
│ └── README.md # Test scripts documentation
|
||||||
│
|
│
|
||||||
├── logs/ # Tomcat logs (Docker volume)
|
├── logs/ # Tomcat logs (Docker volume)
|
||||||
|
├── target/ # Build output (generated)
|
||||||
|
│ ├── site/jacoco/ # Code coverage reports
|
||||||
|
│ │ ├── index.html # HTML coverage report
|
||||||
|
│ │ ├── jacoco.xml # XML coverage report
|
||||||
|
│ │ └── jacoco.csv # CSV coverage report
|
||||||
|
│ └── surefire-reports/ # Test execution reports
|
||||||
│
|
│
|
||||||
├── pom.xml # Maven project configuration
|
├── pom.xml # Maven project configuration
|
||||||
├── docker-compose.yml # Docker Compose orchestration
|
├── docker-compose.yml # Docker Compose orchestration
|
||||||
|
├── docker-compose.test.yml # Docker Compose for unit tests
|
||||||
|
├── docker-compose.coverage.yml # Docker Compose for coverage
|
||||||
├── Dockerfile # Multi-stage Docker build
|
├── Dockerfile # Multi-stage Docker build
|
||||||
|
├── Dockerfile.coverage.simple # Dockerfile for coverage
|
||||||
├── tomcat-users.xml # Tomcat manager users
|
├── tomcat-users.xml # Tomcat manager users
|
||||||
├── loan_app.db # SQLite database (created on first run)
|
├── loan_app.db # SQLite database (created on first run)
|
||||||
|
├── run-tests.bat # Windows test runner script
|
||||||
|
├── run-tests.sh # Linux/Mac test runner script
|
||||||
|
├── run-coverage.bat # Windows coverage script
|
||||||
|
├── run-coverage.sh # Linux/Mac coverage script
|
||||||
├── README.md # This file
|
├── README.md # This file
|
||||||
|
├── DOCKER_COMPOSE_GUIDE.md # Docker Compose patterns reference
|
||||||
├── apidoc.md # Complete API documentation
|
├── apidoc.md # Complete API documentation
|
||||||
├── requirements.md # Project requirements specification
|
├── requirements.md # Project requirements specification
|
||||||
├── .dockerignore # Docker build exclusions
|
├── .dockerignore # Docker build exclusions
|
||||||
@ -777,6 +1090,197 @@ See [scripts/README.md](scripts/README.md)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Unit Tests and Code Coverage
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
This project includes comprehensive unit testing with JUnit 5 and code coverage analysis with JaCoCo. All test results and coverage reports are automatically exported to the `./target` directory for easy access.
|
||||||
|
|
||||||
|
### Quick Start
|
||||||
|
|
||||||
|
#### Run Unit Tests
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```cmd
|
||||||
|
run-tests.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
./run-tests.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Docker Compose:**
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.test.yml up --build
|
||||||
|
docker-compose -f docker-compose.test.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Generate Code Coverage Reports
|
||||||
|
|
||||||
|
**Windows:**
|
||||||
|
```cmd
|
||||||
|
run-coverage.bat
|
||||||
|
```
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
./run-coverage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Docker Compose:**
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
docker-compose -f docker-compose.coverage.yml down
|
||||||
|
```
|
||||||
|
|
||||||
|
### Coverage Reports
|
||||||
|
|
||||||
|
All coverage reports are generated in `./target/site/jacoco/`:
|
||||||
|
|
||||||
|
- **HTML Report**: `target/site/jacoco/index.html` (open in browser)
|
||||||
|
- **XML Report**: `target/site/jacoco/jacoco.xml` (for CI/CD tools)
|
||||||
|
- **CSV Report**: `target/site/jacoco/jacoco.csv` (for spreadsheets)
|
||||||
|
|
||||||
|
### Test Framework
|
||||||
|
|
||||||
|
The project uses:
|
||||||
|
|
||||||
|
- **JUnit 5 (Jupiter)** - Modern testing framework with annotations
|
||||||
|
- **Mockito** - Mocking framework for complex test scenarios
|
||||||
|
- **JaCoCo** - Code coverage analysis and reporting
|
||||||
|
- **Maven Surefire** - Test execution plugin
|
||||||
|
|
||||||
|
### Current Test Coverage
|
||||||
|
|
||||||
|
| Service | Test Class | Test Cases | Status |
|
||||||
|
|---------|-----------|------------|--------|
|
||||||
|
| Hello World Service | HelloWorldServiceImplTest | 10 | ✅ Passing |
|
||||||
|
|
||||||
|
### Understanding Coverage Reports
|
||||||
|
|
||||||
|
**Coverage Metrics:**
|
||||||
|
- **Line Coverage**: Percentage of executable lines tested
|
||||||
|
- **Branch Coverage**: Percentage of decision points (if/else) tested
|
||||||
|
- **Method Coverage**: Percentage of methods invoked during tests
|
||||||
|
- **Class Coverage**: Percentage of classes with at least one test
|
||||||
|
|
||||||
|
**Coverage Goals:**
|
||||||
|
- Line Coverage: > 80%
|
||||||
|
- Branch Coverage: > 70%
|
||||||
|
- Method Coverage: > 85%
|
||||||
|
|
||||||
|
### Running Tests with Maven (Local)
|
||||||
|
|
||||||
|
If you have Maven installed locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests only
|
||||||
|
mvn test
|
||||||
|
|
||||||
|
# Run tests with coverage
|
||||||
|
mvn clean test
|
||||||
|
|
||||||
|
# View coverage report
|
||||||
|
# Open target/site/jacoco/index.html in your browser
|
||||||
|
```
|
||||||
|
|
||||||
|
### CI/CD Integration
|
||||||
|
|
||||||
|
#### GitHub Actions Example
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Run tests with coverage
|
||||||
|
run: mvn clean test
|
||||||
|
|
||||||
|
- name: Upload coverage to Codecov
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
files: ./target/site/jacoco/jacoco.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Jenkins Example
|
||||||
|
|
||||||
|
```groovy
|
||||||
|
stage('Test') {
|
||||||
|
steps {
|
||||||
|
sh 'mvn clean test'
|
||||||
|
jacoco(
|
||||||
|
execPattern: 'target/jacoco.exec',
|
||||||
|
classPattern: 'target/classes',
|
||||||
|
sourcePattern: 'src/main/java'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Writing New Tests
|
||||||
|
|
||||||
|
Create test classes in `src/test/java/` following this pattern:
|
||||||
|
|
||||||
|
```java
|
||||||
|
package com.example.service;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@DisplayName("Your Service Tests")
|
||||||
|
class YourServiceTest {
|
||||||
|
|
||||||
|
private YourService service;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
service = new YourServiceImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should do something")
|
||||||
|
void testSomething() {
|
||||||
|
// Arrange
|
||||||
|
String input = "test";
|
||||||
|
String expected = "result";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String result = service.doSomething(input);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals(expected, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
#### Tests Not Running
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure test files end with Test.java
|
||||||
|
# Verify directory: src/test/java/
|
||||||
|
# Check pom.xml includes maven-surefire-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Coverage Reports Not Generated
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure JaCoCo plugin is in pom.xml
|
||||||
|
mvn clean test
|
||||||
|
|
||||||
|
# Or use Docker
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Permission Issues (Linux/Mac)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x run-tests.sh
|
||||||
|
chmod +x run-coverage.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Service Endpoints
|
## Service Endpoints
|
||||||
|
|
||||||
Once running, the following endpoints are available:
|
Once running, the following endpoints are available:
|
||||||
|
|||||||
346
TEST_GUIDE.md
346
TEST_GUIDE.md
@ -1,346 +0,0 @@
|
|||||||
# Unit Testing Guide
|
|
||||||
|
|
||||||
This guide explains how to run and write unit tests for the JAX-WS Hello World Service project.
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Run Tests with Docker (Recommended)
|
|
||||||
|
|
||||||
#### Windows
|
|
||||||
```cmd
|
|
||||||
run-tests.bat
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Linux/Mac
|
|
||||||
```bash
|
|
||||||
./run-tests.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run Tests with Maven (Local)
|
|
||||||
|
|
||||||
If you have Maven installed locally:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mvn test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test Results Summary
|
|
||||||
|
|
||||||
### Current Test Coverage
|
|
||||||
|
|
||||||
| Service | Test Class | Test Cases | Status |
|
|
||||||
|---------|-----------|------------|--------|
|
|
||||||
| Hello World Service | HelloWorldServiceImplTest | 10 | ✅ Passing |
|
|
||||||
|
|
||||||
### Test Case Details
|
|
||||||
|
|
||||||
The `HelloWorldServiceImplTest` includes the following test cases:
|
|
||||||
|
|
||||||
1. **testGetHelloWorld_WithValidName** - Verifies greeting with valid name
|
|
||||||
2. **testGetHelloWorld_WithEmptyString** - Handles empty string input
|
|
||||||
3. **testGetHelloWorld_WithNull** - Handles null input gracefully
|
|
||||||
4. **testGetHelloWorld_WithSpecialCharacters** - Handles special characters
|
|
||||||
5. **testGetHelloWorld_WithSpaces** - Handles names with spaces
|
|
||||||
6. **testGetHelloWorld_WithLongName** - Handles long names
|
|
||||||
7. **testGetHelloWorld_ReturnsNonNull** - Ensures non-null response
|
|
||||||
8. **testGetHelloWorld_StartsWithHelloWorld** - Verifies message format
|
|
||||||
9. **testGetHelloWorld_EndsWithExclamation** - Verifies message ending
|
|
||||||
10. **testGetHelloWorld_ContainsName** - Verifies name inclusion
|
|
||||||
|
|
||||||
## Docker Test Configuration
|
|
||||||
|
|
||||||
### Test Architecture
|
|
||||||
|
|
||||||
The project uses a multi-stage Dockerfile:
|
|
||||||
|
|
||||||
- **Stage 1 (tester)**: Runs unit tests
|
|
||||||
- **Stage 2 (builder)**: Builds the WAR file
|
|
||||||
- **Stage 3**: Deploys to Tomcat
|
|
||||||
|
|
||||||
### Test Docker Compose
|
|
||||||
|
|
||||||
The `docker-compose.test.yml` file is configured to:
|
|
||||||
- Target the `tester` stage of the Dockerfile
|
|
||||||
- Mount source code for live testing
|
|
||||||
- Cache Maven dependencies for faster subsequent runs
|
|
||||||
- Set appropriate memory limits
|
|
||||||
|
|
||||||
### Run Tests Manually
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build and run tests
|
|
||||||
docker-compose -f docker-compose.test.yml up --build
|
|
||||||
|
|
||||||
# Clean up after tests
|
|
||||||
docker-compose -f docker-compose.test.yml down
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Framework
|
|
||||||
|
|
||||||
### Dependencies
|
|
||||||
|
|
||||||
The project uses:
|
|
||||||
|
|
||||||
- **JUnit 5 (Jupiter)** - Testing framework
|
|
||||||
- `junit-jupiter-api:5.9.3` - Test annotations and assertions
|
|
||||||
- `junit-jupiter-engine:5.9.3` - Test execution engine
|
|
||||||
|
|
||||||
- **Mockito** - Mocking framework (for future complex tests)
|
|
||||||
- `mockito-core:5.3.1` - Core mocking functionality
|
|
||||||
- `mockito-junit-jupiter:5.3.1` - JUnit 5 integration
|
|
||||||
|
|
||||||
- **Maven Surefire** - Test runner plugin
|
|
||||||
- Configured to run all `*Test.java` files
|
|
||||||
|
|
||||||
## Writing New Tests
|
|
||||||
|
|
||||||
### Test Structure
|
|
||||||
|
|
||||||
Tests follow the standard JUnit 5 structure:
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.example.service;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.DisplayName;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
@DisplayName("Service Tests")
|
|
||||||
class YourServiceTest {
|
|
||||||
|
|
||||||
private YourService service;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
service = new YourServiceImpl();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("Should do something")
|
|
||||||
void testSomething() {
|
|
||||||
// Arrange
|
|
||||||
String input = "test";
|
|
||||||
String expected = "expected result";
|
|
||||||
|
|
||||||
// Act
|
|
||||||
String result = service.doSomething(input);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assertEquals(expected, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Test Best Practices
|
|
||||||
|
|
||||||
1. **Use Descriptive Names**: Use `@DisplayName` for readable test descriptions
|
|
||||||
2. **Follow AAA Pattern**: Arrange, Act, Assert
|
|
||||||
3. **Test Edge Cases**: Include null, empty, and boundary conditions
|
|
||||||
4. **Keep Tests Isolated**: Each test should be independent
|
|
||||||
5. **Test One Thing**: Each test should verify one specific behavior
|
|
||||||
|
|
||||||
### Adding Tests for New Services
|
|
||||||
|
|
||||||
To add tests for other services (e.g., LoanApprovalService):
|
|
||||||
|
|
||||||
1. Create test class in `src/test/java/com/example/service/`
|
|
||||||
2. Follow the naming convention: `{ServiceName}Test.java`
|
|
||||||
3. Run tests to verify
|
|
||||||
|
|
||||||
Example structure for LoanApprovalService tests:
|
|
||||||
|
|
||||||
```java
|
|
||||||
package com.example.service;
|
|
||||||
|
|
||||||
import com.example.model.*;
|
|
||||||
import com.example.repository.*;
|
|
||||||
import org.junit.jupiter.api.*;
|
|
||||||
import org.mockito.*;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
import static org.mockito.Mockito.*;
|
|
||||||
|
|
||||||
@DisplayName("LoanApprovalService Tests")
|
|
||||||
class LoanApprovalServiceTest {
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private CustomerRepository customerRepository;
|
|
||||||
|
|
||||||
@Mock
|
|
||||||
private CreditScoreService creditScoreService;
|
|
||||||
|
|
||||||
private LoanApprovalService service;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void setUp() {
|
|
||||||
MockitoAnnotations.openMocks(this);
|
|
||||||
service = new LoanApprovalService();
|
|
||||||
// Inject mocks if needed
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@DisplayName("Should approve loan for high credit score")
|
|
||||||
void testLoanApproval_HighCreditScore() {
|
|
||||||
// Test implementation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
src/
|
|
||||||
├── main/
|
|
||||||
│ └── java/
|
|
||||||
│ └── com/
|
|
||||||
│ └── example/
|
|
||||||
│ └── service/
|
|
||||||
│ ├── HelloWorldService.java
|
|
||||||
│ └── HelloWorldServiceImpl.java
|
|
||||||
└── test/
|
|
||||||
└── java/
|
|
||||||
└── com/
|
|
||||||
└── example/
|
|
||||||
└── service/
|
|
||||||
└── HelloWorldServiceImplTest.java
|
|
||||||
```
|
|
||||||
|
|
||||||
## Continuous Integration
|
|
||||||
|
|
||||||
### Running Tests in CI/CD
|
|
||||||
|
|
||||||
The Dockerfile includes testing as the first stage, which means:
|
|
||||||
|
|
||||||
- Tests run automatically during Docker build
|
|
||||||
- Build fails if tests fail
|
|
||||||
- Ensures code quality before deployment
|
|
||||||
|
|
||||||
To run tests in CI/CD pipelines:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Option 1: Run full build (includes tests)
|
|
||||||
docker build -t jaxws-app .
|
|
||||||
|
|
||||||
# Option 2: Run tests only
|
|
||||||
docker build --target tester -t jaxws-test .
|
|
||||||
|
|
||||||
# Option 3: Use docker-compose
|
|
||||||
docker-compose -f docker-compose.test.yml up --abort-on-container-exit
|
|
||||||
```
|
|
||||||
|
|
||||||
## Viewing Test Reports
|
|
||||||
|
|
||||||
### Console Output
|
|
||||||
|
|
||||||
Test results are displayed in the console with summary:
|
|
||||||
|
|
||||||
```
|
|
||||||
-------------------------------------------------------
|
|
||||||
T E S T S
|
|
||||||
-------------------------------------------------------
|
|
||||||
Running com.example.service.HelloWorldServiceImplTest
|
|
||||||
Tests run: 10, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.024 s
|
|
||||||
|
|
||||||
Results:
|
|
||||||
|
|
||||||
Tests run: 10, Failures: 0, Errors: 0, Skipped: 0
|
|
||||||
|
|
||||||
BUILD SUCCESS
|
|
||||||
```
|
|
||||||
|
|
||||||
### Surefire Reports
|
|
||||||
|
|
||||||
Maven Surefire generates detailed reports in:
|
|
||||||
- `target/surefire-reports/` (text and XML formats)
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Tests Not Running
|
|
||||||
|
|
||||||
**Problem**: Maven doesn't find tests
|
|
||||||
|
|
||||||
**Solution**:
|
|
||||||
- Ensure test files end with `Test.java`
|
|
||||||
- Verify test directory structure: `src/test/java/`
|
|
||||||
- Check pom.xml includes maven-surefire-plugin
|
|
||||||
|
|
||||||
### Docker Build Fails on Tests
|
|
||||||
|
|
||||||
**Problem**: Docker build fails at test stage
|
|
||||||
|
|
||||||
**Solution**:
|
|
||||||
```bash
|
|
||||||
# View detailed test output
|
|
||||||
docker build --target tester -t jaxws-test .
|
|
||||||
|
|
||||||
# Check test container logs
|
|
||||||
docker logs <container-id>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Dependency Issues
|
|
||||||
|
|
||||||
**Problem**: Cannot resolve JUnit dependencies
|
|
||||||
|
|
||||||
**Solution**:
|
|
||||||
```bash
|
|
||||||
# Clear Maven cache and rebuild
|
|
||||||
docker-compose -f docker-compose.test.yml build --no-cache
|
|
||||||
```
|
|
||||||
|
|
||||||
### Permission Denied on run-tests.sh
|
|
||||||
|
|
||||||
**Problem**: Cannot execute run-tests.sh on Linux/Mac
|
|
||||||
|
|
||||||
**Solution**:
|
|
||||||
```bash
|
|
||||||
chmod +x run-tests.sh
|
|
||||||
./run-tests.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
Consider adding tests for:
|
|
||||||
|
|
||||||
1. **LoanApprovalService**
|
|
||||||
- Customer registration workflows
|
|
||||||
- Loan approval logic
|
|
||||||
- Credit score evaluation
|
|
||||||
- Blacklist handling
|
|
||||||
|
|
||||||
2. **Repository Layer**
|
|
||||||
- Database operations
|
|
||||||
- CRUD functionality
|
|
||||||
- Error handling
|
|
||||||
|
|
||||||
3. **Integration Tests**
|
|
||||||
- End-to-end SOAP request/response
|
|
||||||
- Database integration
|
|
||||||
- Multi-service workflows
|
|
||||||
|
|
||||||
4. **Performance Tests**
|
|
||||||
- Load testing
|
|
||||||
- Stress testing
|
|
||||||
- Response time validation
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [JUnit 5 User Guide](https://junit.org/junit5/docs/current/user-guide/)
|
|
||||||
- [Mockito Documentation](https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html)
|
|
||||||
- [Maven Surefire Plugin](https://maven.apache.org/surefire/maven-surefire-plugin/)
|
|
||||||
- [Test-Driven Development Best Practices](https://martinfowler.com/bliki/TestDrivenDevelopment.html)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
This project now has a complete unit testing setup with:
|
|
||||||
|
|
||||||
- ✅ JUnit 5 testing framework
|
|
||||||
- ✅ 10 passing test cases for HelloWorldService
|
|
||||||
- ✅ Docker-based test execution
|
|
||||||
- ✅ Convenient test runner scripts
|
|
||||||
- ✅ Multi-stage Dockerfile with test stage
|
|
||||||
- ✅ Maven Surefire configuration
|
|
||||||
- ✅ Mockito for future mocking needs
|
|
||||||
|
|
||||||
To add more tests, simply create new test classes following the established patterns and run them using the provided scripts.
|
|
||||||
32
docker-compose.coverage.yml
Normal file
32
docker-compose.coverage.yml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
coverage:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.coverage.simple
|
||||||
|
image: jaxws-coverage:latest
|
||||||
|
container_name: jaxws-coverage-report
|
||||||
|
volumes:
|
||||||
|
# Mount source code
|
||||||
|
- ./src:/app/src:ro
|
||||||
|
- ./pom.xml:/app/pom.xml:ro
|
||||||
|
# Mount target directory to export coverage reports to host
|
||||||
|
- ./target:/app/target
|
||||||
|
# Mount Maven cache to speed up builds
|
||||||
|
- maven-cache:/root/.m2
|
||||||
|
environment:
|
||||||
|
- MAVEN_OPTS=-Xmx512m
|
||||||
|
command: >
|
||||||
|
sh -c "mvn test &&
|
||||||
|
echo '=================================================================================' &&
|
||||||
|
echo 'Coverage report generated successfully!' &&
|
||||||
|
echo '=================================================================================' &&
|
||||||
|
echo 'HTML Report: ./target/site/jacoco/index.html' &&
|
||||||
|
echo 'XML Report: ./target/site/jacoco/jacoco.xml' &&
|
||||||
|
echo 'CSV Report: ./target/site/jacoco/jacoco.csv' &&
|
||||||
|
echo '================================================================================'"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
maven-cache:
|
||||||
|
driver: local
|
||||||
@ -12,6 +12,8 @@ services:
|
|||||||
# Mount source for live testing during development
|
# Mount source for live testing during development
|
||||||
- ./src:/app/src:ro
|
- ./src:/app/src:ro
|
||||||
- ./pom.xml:/app/pom.xml:ro
|
- ./pom.xml:/app/pom.xml:ro
|
||||||
|
# Mount target directory to access test reports and coverage on host
|
||||||
|
- ./target:/app/target
|
||||||
# Mount Maven cache to speed up subsequent test runs
|
# Mount Maven cache to speed up subsequent test runs
|
||||||
- maven-cache:/root/.m2
|
- maven-cache:/root/.m2
|
||||||
command: mvn test
|
command: mvn test
|
||||||
|
|||||||
25
pom.xml
25
pom.xml
@ -67,13 +67,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-core</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
<version>5.3.1</version>
|
<version>3.12.4</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-junit-jupiter</artifactId>
|
<artifactId>mockito-junit-jupiter</artifactId>
|
||||||
<version>5.3.1</version>
|
<version>3.12.4</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@ -108,6 +108,27 @@
|
|||||||
</includes>
|
</includes>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<!-- JaCoCo Plugin for code coverage -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jacoco</groupId>
|
||||||
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
<version>0.8.11</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>pre-test</id>
|
||||||
|
<goals>
|
||||||
|
<goal>prepare-agent</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>post-test</id>
|
||||||
|
<phase>test</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>report</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
44
run-coverage.bat
Normal file
44
run-coverage.bat
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
@echo off
|
||||||
|
REM Script to generate code coverage reports using Docker
|
||||||
|
|
||||||
|
echo ================================================================================
|
||||||
|
echo Generating Code Coverage Reports
|
||||||
|
echo ================================================================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Clean previous reports
|
||||||
|
if exist target\site\jacoco (
|
||||||
|
echo Cleaning previous coverage reports...
|
||||||
|
rmdir /s /q target\site\jacoco
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Run tests with coverage
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ================================================================================
|
||||||
|
echo Coverage Report Generated
|
||||||
|
echo ================================================================================
|
||||||
|
echo.
|
||||||
|
echo HTML Report: target\site\jacoco\index.html
|
||||||
|
echo XML Report: target\site\jacoco\jacoco.xml
|
||||||
|
echo CSV Report: target\site\jacoco\jacoco.csv
|
||||||
|
echo.
|
||||||
|
echo Open the HTML report in your browser to view coverage details.
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM Clean up the coverage container
|
||||||
|
docker-compose -f docker-compose.coverage.yml down
|
||||||
|
|
||||||
|
REM Ask user if they want to open the report
|
||||||
|
set /p OPEN_REPORT="Would you like to open the coverage report now? (y/n): "
|
||||||
|
if /i "%OPEN_REPORT%"=="y" (
|
||||||
|
if exist target\site\jacoco\index.html (
|
||||||
|
start target\site\jacoco\index.html
|
||||||
|
) else (
|
||||||
|
echo Error: Coverage report not found!
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
pause
|
||||||
50
run-coverage.sh
Normal file
50
run-coverage.sh
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Script to generate code coverage reports using Docker
|
||||||
|
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "Generating Code Coverage Reports"
|
||||||
|
echo "================================================================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Clean previous reports
|
||||||
|
if [ -d "target/site/jacoco" ]; then
|
||||||
|
echo "Cleaning previous coverage reports..."
|
||||||
|
rm -rf target/site/jacoco
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run tests with coverage
|
||||||
|
docker-compose -f docker-compose.coverage.yml up --build
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "================================================================================"
|
||||||
|
echo "Coverage Report Generated"
|
||||||
|
echo "================================================================================"
|
||||||
|
echo ""
|
||||||
|
echo "HTML Report: target/site/jacoco/index.html"
|
||||||
|
echo "XML Report: target/site/jacoco/jacoco.xml"
|
||||||
|
echo "CSV Report: target/site/jacoco/jacoco.csv"
|
||||||
|
echo ""
|
||||||
|
echo "Open the HTML report in your browser to view coverage details."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Clean up the coverage container
|
||||||
|
docker-compose -f docker-compose.coverage.yml down
|
||||||
|
|
||||||
|
# Ask user if they want to open the report (for systems with xdg-open)
|
||||||
|
read -p "Would you like to open the coverage report now? (y/n): " OPEN_REPORT
|
||||||
|
if [ "$OPEN_REPORT" = "y" ] || [ "$OPEN_REPORT" = "Y" ]; then
|
||||||
|
if [ -f "target/site/jacoco/index.html" ]; then
|
||||||
|
# Try to open with default browser
|
||||||
|
if command -v xdg-open > /dev/null; then
|
||||||
|
xdg-open target/site/jacoco/index.html
|
||||||
|
elif command -v open > /dev/null; then
|
||||||
|
open target/site/jacoco/index.html
|
||||||
|
else
|
||||||
|
echo "Please open target/site/jacoco/index.html manually in your browser."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Error: Coverage report not found!"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
4
scripts/run_coverage.sh
Normal file
4
scripts/run_coverage.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo "Running tests with code coverage..."
|
||||||
|
mvn clean test
|
||||||
|
echo "Coverage report generated in target/site/jacoco/"
|
||||||
@ -0,0 +1,313 @@
|
|||||||
|
package com.example.listener;
|
||||||
|
|
||||||
|
import com.example.util.DatabaseManager;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
|
import javax.servlet.ServletContextEvent;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for DatabaseInitializationListener
|
||||||
|
*/
|
||||||
|
@DisplayName("DatabaseInitializationListener Tests")
|
||||||
|
class DatabaseInitializationListenerTest {
|
||||||
|
|
||||||
|
private DatabaseInitializationListener listener;
|
||||||
|
private ServletContextEvent mockEvent;
|
||||||
|
private DatabaseManager dbManager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
listener = new DatabaseInitializationListener();
|
||||||
|
mockEvent = mock(ServletContextEvent.class);
|
||||||
|
dbManager = DatabaseManager.getInstance();
|
||||||
|
// Reset database before each test
|
||||||
|
dbManager.resetDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should initialize database on context initialization")
|
||||||
|
void testContextInitialized() throws SQLException {
|
||||||
|
// Call the listener
|
||||||
|
assertDoesNotThrow(() -> listener.contextInitialized(mockEvent));
|
||||||
|
|
||||||
|
// Verify database was initialized
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='customers'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next(), "Customers table should exist after initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='loan_history'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next(), "Loan history table should exist after initialization");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should not throw exception on successful initialization")
|
||||||
|
void testSuccessfulInitialization() {
|
||||||
|
assertDoesNotThrow(() -> listener.contextInitialized(mockEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle context destroyed event")
|
||||||
|
void testContextDestroyed() {
|
||||||
|
assertDoesNotThrow(() -> listener.contextDestroyed(mockEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should initialize database even if already initialized")
|
||||||
|
void testReinitializeDatabase() throws SQLException {
|
||||||
|
// First initialization
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
|
||||||
|
// Add some data
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.execute("INSERT INTO customers (customer_name, is_blacklisted, registered_at) " +
|
||||||
|
"VALUES ('Test User', 0, '2025-01-01 10:00:00')");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second initialization should not fail
|
||||||
|
assertDoesNotThrow(() -> listener.contextInitialized(mockEvent));
|
||||||
|
|
||||||
|
// Data should still exist (tables are IF NOT EXISTS)
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM customers")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(1, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should test database connection before initialization")
|
||||||
|
void testConnectionTestBeforeInitialization() {
|
||||||
|
// The listener tests connection first
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
|
||||||
|
// Verify database is accessible
|
||||||
|
assertTrue(dbManager.testConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle multiple context initialization calls")
|
||||||
|
void testMultipleContextInitializations() {
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle multiple context destroyed calls")
|
||||||
|
void testMultipleContextDestroyedCalls() {
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
listener.contextDestroyed(mockEvent);
|
||||||
|
listener.contextDestroyed(mockEvent);
|
||||||
|
listener.contextDestroyed(mockEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should initialize and destroy in sequence")
|
||||||
|
void testInitializeAndDestroySequence() {
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
listener.contextDestroyed(mockEvent);
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
listener.contextDestroyed(mockEvent);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should verify ServletContextEvent is used")
|
||||||
|
void testServletContextEventUsage() {
|
||||||
|
ServletContextEvent event = mock(ServletContextEvent.class);
|
||||||
|
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
listener.contextInitialized(event);
|
||||||
|
listener.contextDestroyed(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify the event object is passed (though not used in implementation)
|
||||||
|
assertNotNull(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle null ServletContextEvent gracefully in contextDestroyed")
|
||||||
|
void testNullEventInContextDestroyed() {
|
||||||
|
// contextDestroyed doesn't use the event, so null should be fine
|
||||||
|
assertDoesNotThrow(() -> listener.contextDestroyed(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should initialize database tables with correct schema")
|
||||||
|
void testDatabaseSchemaAfterInitialization() throws SQLException {
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
|
||||||
|
// Test customers table schema
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
|
||||||
|
stmt.execute("INSERT INTO customers (customer_name, is_blacklisted, registered_at) " +
|
||||||
|
"VALUES ('Schema Test', 1, '2025-01-01 10:00:00')");
|
||||||
|
|
||||||
|
try (ResultSet rs = stmt.executeQuery("SELECT * FROM customers WHERE customer_name = 'Schema Test'")) {
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("Schema Test", rs.getString("customer_name"));
|
||||||
|
assertEquals(1, rs.getInt("is_blacklisted"));
|
||||||
|
assertEquals("2025-01-01 10:00:00", rs.getString("registered_at"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test loan_history table schema
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
|
||||||
|
stmt.execute("INSERT INTO loan_history " +
|
||||||
|
"(applicant_name, requested_amount, approved, approved_rate, rejection_reason, processed_at) " +
|
||||||
|
"VALUES ('Schema Test', 50000, 1, 5.5, NULL, '2025-01-01 10:00:00')");
|
||||||
|
|
||||||
|
try (ResultSet rs = stmt.executeQuery("SELECT * FROM loan_history WHERE applicant_name = 'Schema Test'")) {
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("Schema Test", rs.getString("applicant_name"));
|
||||||
|
assertEquals(50000, rs.getInt("requested_amount"));
|
||||||
|
assertEquals(1, rs.getInt("approved"));
|
||||||
|
assertEquals(5.5, rs.getDouble("approved_rate"), 0.001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should complete initialization within reasonable time")
|
||||||
|
void testInitializationPerformance() {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
|
||||||
|
long endTime = System.currentTimeMillis();
|
||||||
|
long duration = endTime - startTime;
|
||||||
|
|
||||||
|
assertTrue(duration < 5000, "Initialization should complete within 5 seconds, took: " + duration + "ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle rapid initialization and destruction")
|
||||||
|
void testRapidInitAndDestroy() {
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
listener.contextDestroyed(mockEvent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should maintain database state across listener calls")
|
||||||
|
void testDatabaseStatePersistence() throws SQLException {
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
|
||||||
|
// Add data
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.execute("INSERT INTO customers (customer_name, is_blacklisted, registered_at) " +
|
||||||
|
"VALUES ('Persistent User', 0, '2025-01-01 10:00:00')");
|
||||||
|
}
|
||||||
|
|
||||||
|
listener.contextDestroyed(mockEvent);
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
|
||||||
|
// Verify data persists
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT * FROM customers WHERE customer_name = 'Persistent User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next(), "Data should persist across listener lifecycle");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create new listener instance successfully")
|
||||||
|
void testListenerInstantiation() {
|
||||||
|
DatabaseInitializationListener newListener = new DatabaseInitializationListener();
|
||||||
|
|
||||||
|
assertNotNull(newListener);
|
||||||
|
assertDoesNotThrow(() -> newListener.contextInitialized(mockEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle concurrent context initialization")
|
||||||
|
void testConcurrentInitialization() throws InterruptedException {
|
||||||
|
int threadCount = 5;
|
||||||
|
Thread[] threads = new Thread[threadCount];
|
||||||
|
|
||||||
|
for (int i = 0; i < threadCount; i++) {
|
||||||
|
threads[i] = new Thread(() -> {
|
||||||
|
assertDoesNotThrow(() -> listener.contextInitialized(mockEvent));
|
||||||
|
});
|
||||||
|
threads[i].start();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Thread thread : threads) {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify database is still in good state
|
||||||
|
assertTrue(dbManager.testConnection());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should verify database connection is working after initialization")
|
||||||
|
void testDatabaseConnectionAfterInit() {
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
|
||||||
|
assertTrue(dbManager.testConnection(), "Database connection should be working after initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should initialize database with empty tables")
|
||||||
|
void testEmptyTablesAfterInitialization() throws SQLException {
|
||||||
|
listener.contextInitialized(mockEvent);
|
||||||
|
|
||||||
|
// Verify customers table is empty
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM customers")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(0, rs.getInt("count"), "Customers table should be empty after initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify loan_history table is empty
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM loan_history")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(0, rs.getInt("count"), "Loan history table should be empty after initialization");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should not throw exception when destroying uninitialized context")
|
||||||
|
void testDestroyUninitializedContext() {
|
||||||
|
// Call destroy without init
|
||||||
|
assertDoesNotThrow(() -> listener.contextDestroyed(mockEvent));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,172 @@
|
|||||||
|
package com.example.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for CustomerRegistrationRequest model class
|
||||||
|
*/
|
||||||
|
@DisplayName("CustomerRegistrationRequest Model Tests")
|
||||||
|
class CustomerRegistrationRequestTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create CustomerRegistrationRequest with default constructor")
|
||||||
|
void testDefaultConstructor() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest();
|
||||||
|
|
||||||
|
assertNotNull(request);
|
||||||
|
assertNull(request.getCustomerName());
|
||||||
|
assertFalse(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create CustomerRegistrationRequest with parameterized constructor")
|
||||||
|
void testParameterizedConstructor() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest("John Doe", true);
|
||||||
|
|
||||||
|
assertEquals("John Doe", request.getCustomerName());
|
||||||
|
assertTrue(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get customerName")
|
||||||
|
void testSetAndGetCustomerName() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest();
|
||||||
|
request.setCustomerName("Jane Smith");
|
||||||
|
|
||||||
|
assertEquals("Jane Smith", request.getCustomerName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get blacklisted status")
|
||||||
|
void testSetAndGetBlacklisted() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest();
|
||||||
|
request.setBlacklisted(true);
|
||||||
|
|
||||||
|
assertTrue(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle null customerName")
|
||||||
|
void testNullCustomerName() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest(null, false);
|
||||||
|
|
||||||
|
assertNull(request.getCustomerName());
|
||||||
|
assertFalse(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle empty string customerName")
|
||||||
|
void testEmptyCustomerName() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest("", true);
|
||||||
|
|
||||||
|
assertEquals("", request.getCustomerName());
|
||||||
|
assertTrue(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create non-blacklisted customer by default")
|
||||||
|
void testNonBlacklistedByDefault() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest("Test User", false);
|
||||||
|
|
||||||
|
assertFalse(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create blacklisted customer")
|
||||||
|
void testBlacklistedCustomer() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest("Blacklisted User", true);
|
||||||
|
|
||||||
|
assertTrue(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should generate correct toString output")
|
||||||
|
void testToString() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest("Alice Brown", true);
|
||||||
|
String result = request.toString();
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("CustomerRegistrationRequest{"));
|
||||||
|
assertTrue(result.contains("customerName='Alice Brown'"));
|
||||||
|
assertTrue(result.contains("blacklisted=true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should generate toString with null customerName")
|
||||||
|
void testToStringWithNull() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest(null, false);
|
||||||
|
String result = request.toString();
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("customerName='null'") || result.contains("customerName=null"));
|
||||||
|
assertTrue(result.contains("blacklisted=false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle special characters in customerName")
|
||||||
|
void testSpecialCharactersInCustomerName() {
|
||||||
|
String specialName = "José O'Reilly-Smith";
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest(specialName, false);
|
||||||
|
|
||||||
|
assertEquals(specialName, request.getCustomerName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should update values multiple times")
|
||||||
|
void testMultipleUpdates() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest("Initial Name", false);
|
||||||
|
|
||||||
|
request.setCustomerName("Updated Name");
|
||||||
|
request.setBlacklisted(true);
|
||||||
|
|
||||||
|
assertEquals("Updated Name", request.getCustomerName());
|
||||||
|
assertTrue(request.isBlacklisted());
|
||||||
|
|
||||||
|
request.setCustomerName("Final Name");
|
||||||
|
request.setBlacklisted(false);
|
||||||
|
|
||||||
|
assertEquals("Final Name", request.getCustomerName());
|
||||||
|
assertFalse(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should toggle blacklisted status")
|
||||||
|
void testToggleBlacklistedStatus() {
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest("Toggle User", false);
|
||||||
|
|
||||||
|
assertFalse(request.isBlacklisted());
|
||||||
|
|
||||||
|
request.setBlacklisted(true);
|
||||||
|
assertTrue(request.isBlacklisted());
|
||||||
|
|
||||||
|
request.setBlacklisted(false);
|
||||||
|
assertFalse(request.isBlacklisted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle very long customerName")
|
||||||
|
void testLongCustomerName() {
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
builder.append("A");
|
||||||
|
}
|
||||||
|
String longName = builder.toString();
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest(longName, true);
|
||||||
|
|
||||||
|
assertEquals(longName, request.getCustomerName());
|
||||||
|
assertEquals(1000, request.getCustomerName().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle whitespace in customerName")
|
||||||
|
void testWhitespaceInCustomerName() {
|
||||||
|
CustomerRegistrationRequest request1 = new CustomerRegistrationRequest(" ", false);
|
||||||
|
assertEquals(" ", request1.getCustomerName());
|
||||||
|
|
||||||
|
CustomerRegistrationRequest request2 = new CustomerRegistrationRequest("John Doe", false);
|
||||||
|
assertEquals("John Doe", request2.getCustomerName());
|
||||||
|
}
|
||||||
|
}
|
||||||
169
src/test/java/com/example/model/LoanRequestTest.java
Normal file
169
src/test/java/com/example/model/LoanRequestTest.java
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package com.example.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for LoanRequest model class
|
||||||
|
*/
|
||||||
|
@DisplayName("LoanRequest Model Tests")
|
||||||
|
class LoanRequestTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create LoanRequest with default constructor")
|
||||||
|
void testDefaultConstructor() {
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
|
||||||
|
assertNotNull(request);
|
||||||
|
assertNull(request.getApplicantName());
|
||||||
|
assertEquals(0, request.getRequestedAmount());
|
||||||
|
assertEquals(0, request.getCreditScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create LoanRequest with parameterized constructor")
|
||||||
|
void testParameterizedConstructor() {
|
||||||
|
LoanRequest request = new LoanRequest("John Doe", 50000, 750);
|
||||||
|
|
||||||
|
assertEquals("John Doe", request.getApplicantName());
|
||||||
|
assertEquals(50000, request.getRequestedAmount());
|
||||||
|
assertEquals(750, request.getCreditScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get applicantName")
|
||||||
|
void testSetAndGetApplicantName() {
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Jane Smith");
|
||||||
|
|
||||||
|
assertEquals("Jane Smith", request.getApplicantName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get requestedAmount")
|
||||||
|
void testSetAndGetRequestedAmount() {
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setRequestedAmount(100000);
|
||||||
|
|
||||||
|
assertEquals(100000, request.getRequestedAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get creditScore")
|
||||||
|
void testSetAndGetCreditScore() {
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setCreditScore(800);
|
||||||
|
|
||||||
|
assertEquals(800, request.getCreditScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle null applicantName")
|
||||||
|
void testNullApplicantName() {
|
||||||
|
LoanRequest request = new LoanRequest(null, 50000, 700);
|
||||||
|
|
||||||
|
assertNull(request.getApplicantName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle negative requestedAmount")
|
||||||
|
void testNegativeRequestedAmount() {
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setRequestedAmount(-1000);
|
||||||
|
|
||||||
|
assertEquals(-1000, request.getRequestedAmount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle negative creditScore")
|
||||||
|
void testNegativeCreditScore() {
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setCreditScore(-100);
|
||||||
|
|
||||||
|
assertEquals(-100, request.getCreditScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle zero values")
|
||||||
|
void testZeroValues() {
|
||||||
|
LoanRequest request = new LoanRequest("Test User", 0, 0);
|
||||||
|
|
||||||
|
assertEquals("Test User", request.getApplicantName());
|
||||||
|
assertEquals(0, request.getRequestedAmount());
|
||||||
|
assertEquals(0, request.getCreditScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle maximum integer values")
|
||||||
|
void testMaximumValues() {
|
||||||
|
LoanRequest request = new LoanRequest("Test User", Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
|
||||||
|
assertEquals(Integer.MAX_VALUE, request.getRequestedAmount());
|
||||||
|
assertEquals(Integer.MAX_VALUE, request.getCreditScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should generate correct toString output")
|
||||||
|
void testToString() {
|
||||||
|
LoanRequest request = new LoanRequest("Alice Brown", 75000, 720);
|
||||||
|
String result = request.toString();
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("LoanRequest{"));
|
||||||
|
assertTrue(result.contains("applicantName='Alice Brown'"));
|
||||||
|
assertTrue(result.contains("requestedAmount=75000"));
|
||||||
|
assertTrue(result.contains("creditScore=720"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should generate toString with null applicantName")
|
||||||
|
void testToStringWithNull() {
|
||||||
|
LoanRequest request = new LoanRequest(null, 50000, 700);
|
||||||
|
String result = request.toString();
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("applicantName='null'") || result.contains("applicantName=null"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle empty string applicantName")
|
||||||
|
void testEmptyApplicantName() {
|
||||||
|
LoanRequest request = new LoanRequest("", 50000, 700);
|
||||||
|
|
||||||
|
assertEquals("", request.getApplicantName());
|
||||||
|
assertTrue(request.toString().contains("applicantName=''"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle special characters in applicantName")
|
||||||
|
void testSpecialCharactersInApplicantName() {
|
||||||
|
String specialName = "José O'Reilly-Smith";
|
||||||
|
LoanRequest request = new LoanRequest(specialName, 50000, 700);
|
||||||
|
|
||||||
|
assertEquals(specialName, request.getApplicantName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should update values multiple times")
|
||||||
|
void testMultipleUpdates() {
|
||||||
|
LoanRequest request = new LoanRequest("Initial Name", 10000, 600);
|
||||||
|
|
||||||
|
request.setApplicantName("Updated Name");
|
||||||
|
request.setRequestedAmount(20000);
|
||||||
|
request.setCreditScore(650);
|
||||||
|
|
||||||
|
assertEquals("Updated Name", request.getApplicantName());
|
||||||
|
assertEquals(20000, request.getRequestedAmount());
|
||||||
|
assertEquals(650, request.getCreditScore());
|
||||||
|
|
||||||
|
request.setApplicantName("Final Name");
|
||||||
|
request.setRequestedAmount(30000);
|
||||||
|
request.setCreditScore(700);
|
||||||
|
|
||||||
|
assertEquals("Final Name", request.getApplicantName());
|
||||||
|
assertEquals(30000, request.getRequestedAmount());
|
||||||
|
assertEquals(700, request.getCreditScore());
|
||||||
|
}
|
||||||
|
}
|
||||||
274
src/test/java/com/example/model/LoanResponseTest.java
Normal file
274
src/test/java/com/example/model/LoanResponseTest.java
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
package com.example.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for LoanResponse model class
|
||||||
|
*/
|
||||||
|
@DisplayName("LoanResponse Model Tests")
|
||||||
|
class LoanResponseTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create LoanResponse with default constructor")
|
||||||
|
void testDefaultConstructor() {
|
||||||
|
LoanResponse response = new LoanResponse();
|
||||||
|
|
||||||
|
assertNotNull(response);
|
||||||
|
assertFalse(response.isApproved());
|
||||||
|
assertEquals(0.0, response.getApprovedRate(), 0.001);
|
||||||
|
assertNull(response.getRejectionReason());
|
||||||
|
assertNull(response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create LoanResponse with parameterized constructor")
|
||||||
|
void testParameterizedConstructor() {
|
||||||
|
LoanResponse response = new LoanResponse(true, 5.5, null, "Loan approved");
|
||||||
|
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(5.5, response.getApprovedRate(), 0.001);
|
||||||
|
assertNull(response.getRejectionReason());
|
||||||
|
assertEquals("Loan approved", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create approved loan response")
|
||||||
|
void testApprovedLoanResponse() {
|
||||||
|
LoanResponse response = new LoanResponse(true, 4.5, null, "Congratulations! Your loan has been approved.");
|
||||||
|
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(4.5, response.getApprovedRate(), 0.001);
|
||||||
|
assertNull(response.getRejectionReason());
|
||||||
|
assertNotNull(response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should create rejected loan response")
|
||||||
|
void testRejectedLoanResponse() {
|
||||||
|
LoanResponse response = new LoanResponse(false, 0.0, "Poor credit score", "Loan application rejected");
|
||||||
|
|
||||||
|
assertFalse(response.isApproved());
|
||||||
|
assertEquals(0.0, response.getApprovedRate(), 0.001);
|
||||||
|
assertEquals("Poor credit score", response.getRejectionReason());
|
||||||
|
assertEquals("Loan application rejected", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get approved status")
|
||||||
|
void testSetAndGetApproved() {
|
||||||
|
LoanResponse response = new LoanResponse();
|
||||||
|
response.setApproved(true);
|
||||||
|
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get approvedRate")
|
||||||
|
void testSetAndGetApprovedRate() {
|
||||||
|
LoanResponse response = new LoanResponse();
|
||||||
|
response.setApprovedRate(6.25);
|
||||||
|
|
||||||
|
assertEquals(6.25, response.getApprovedRate(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get rejectionReason")
|
||||||
|
void testSetAndGetRejectionReason() {
|
||||||
|
LoanResponse response = new LoanResponse();
|
||||||
|
response.setRejectionReason("Insufficient income");
|
||||||
|
|
||||||
|
assertEquals("Insufficient income", response.getRejectionReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should set and get message")
|
||||||
|
void testSetAndGetMessage() {
|
||||||
|
LoanResponse response = new LoanResponse();
|
||||||
|
response.setMessage("Processing complete");
|
||||||
|
|
||||||
|
assertEquals("Processing complete", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle null rejectionReason")
|
||||||
|
void testNullRejectionReason() {
|
||||||
|
LoanResponse response = new LoanResponse(true, 5.0, null, "Approved");
|
||||||
|
|
||||||
|
assertNull(response.getRejectionReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle null message")
|
||||||
|
void testNullMessage() {
|
||||||
|
LoanResponse response = new LoanResponse(false, 0.0, "Rejected", null);
|
||||||
|
|
||||||
|
assertNull(response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle zero approvedRate")
|
||||||
|
void testZeroApprovedRate() {
|
||||||
|
LoanResponse response = new LoanResponse(false, 0.0, "Not approved", "Rejected");
|
||||||
|
|
||||||
|
assertEquals(0.0, response.getApprovedRate(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle negative approvedRate")
|
||||||
|
void testNegativeApprovedRate() {
|
||||||
|
LoanResponse response = new LoanResponse();
|
||||||
|
response.setApprovedRate(-1.5);
|
||||||
|
|
||||||
|
assertEquals(-1.5, response.getApprovedRate(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle very high approvedRate")
|
||||||
|
void testHighApprovedRate() {
|
||||||
|
LoanResponse response = new LoanResponse();
|
||||||
|
response.setApprovedRate(99.99);
|
||||||
|
|
||||||
|
assertEquals(99.99, response.getApprovedRate(), 0.001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle decimal precision in approvedRate")
|
||||||
|
void testDecimalPrecision() {
|
||||||
|
LoanResponse response = new LoanResponse(true, 5.125, null, "Approved");
|
||||||
|
|
||||||
|
assertEquals(5.125, response.getApprovedRate(), 0.0001);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should generate correct toString output")
|
||||||
|
void testToString() {
|
||||||
|
LoanResponse response = new LoanResponse(true, 4.75, null, "Loan approved successfully");
|
||||||
|
String result = response.toString();
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("LoanResponse{"));
|
||||||
|
assertTrue(result.contains("approved=true"));
|
||||||
|
assertTrue(result.contains("approvedRate=4.75"));
|
||||||
|
assertTrue(result.contains("rejectionReason='null'") || result.contains("rejectionReason=null"));
|
||||||
|
assertTrue(result.contains("message='Loan approved successfully'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should generate toString with rejection")
|
||||||
|
void testToStringWithRejection() {
|
||||||
|
LoanResponse response = new LoanResponse(false, 0.0, "Credit too low", "Application denied");
|
||||||
|
String result = response.toString();
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertTrue(result.contains("approved=false"));
|
||||||
|
assertTrue(result.contains("approvedRate=0.0"));
|
||||||
|
assertTrue(result.contains("rejectionReason='Credit too low'"));
|
||||||
|
assertTrue(result.contains("message='Application denied'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should update values multiple times")
|
||||||
|
void testMultipleUpdates() {
|
||||||
|
LoanResponse response = new LoanResponse(false, 0.0, "Initial rejection", "Initial message");
|
||||||
|
|
||||||
|
response.setApproved(true);
|
||||||
|
response.setApprovedRate(5.5);
|
||||||
|
response.setRejectionReason(null);
|
||||||
|
response.setMessage("Updated to approved");
|
||||||
|
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(5.5, response.getApprovedRate(), 0.001);
|
||||||
|
assertNull(response.getRejectionReason());
|
||||||
|
assertEquals("Updated to approved", response.getMessage());
|
||||||
|
|
||||||
|
response.setApproved(false);
|
||||||
|
response.setApprovedRate(0.0);
|
||||||
|
response.setRejectionReason("Final rejection");
|
||||||
|
response.setMessage("Final message");
|
||||||
|
|
||||||
|
assertFalse(response.isApproved());
|
||||||
|
assertEquals(0.0, response.getApprovedRate(), 0.001);
|
||||||
|
assertEquals("Final rejection", response.getRejectionReason());
|
||||||
|
assertEquals("Final message", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle empty strings")
|
||||||
|
void testEmptyStrings() {
|
||||||
|
LoanResponse response = new LoanResponse(false, 0.0, "", "");
|
||||||
|
|
||||||
|
assertEquals("", response.getRejectionReason());
|
||||||
|
assertEquals("", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle special characters in strings")
|
||||||
|
void testSpecialCharacters() {
|
||||||
|
LoanResponse response = new LoanResponse(
|
||||||
|
false,
|
||||||
|
0.0,
|
||||||
|
"Reason: Credit < 500 & income > $50k",
|
||||||
|
"Message: Contact us @ info@bank.com"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(response.getRejectionReason().contains("<"));
|
||||||
|
assertTrue(response.getRejectionReason().contains("&"));
|
||||||
|
assertTrue(response.getMessage().contains("@"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle very long strings")
|
||||||
|
void testLongStrings() {
|
||||||
|
StringBuilder reasonBuilder = new StringBuilder();
|
||||||
|
StringBuilder messageBuilder = new StringBuilder();
|
||||||
|
for (int i = 0; i < 500; i++) {
|
||||||
|
reasonBuilder.append("A");
|
||||||
|
messageBuilder.append("B");
|
||||||
|
}
|
||||||
|
String longReason = reasonBuilder.toString();
|
||||||
|
String longMessage = messageBuilder.toString();
|
||||||
|
|
||||||
|
LoanResponse response = new LoanResponse(false, 0.0, longReason, longMessage);
|
||||||
|
|
||||||
|
assertEquals(500, response.getRejectionReason().length());
|
||||||
|
assertEquals(500, response.getMessage().length());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle approved loan with various rates")
|
||||||
|
void testVariousApprovedRates() {
|
||||||
|
double[] rates = {3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 7.0, 8.0, 9.0, 10.0};
|
||||||
|
|
||||||
|
for (double rate : rates) {
|
||||||
|
LoanResponse response = new LoanResponse(true, rate, null, "Approved at " + rate + "%");
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(rate, response.getApprovedRate(), 0.001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should toggle approved status")
|
||||||
|
void testToggleApprovedStatus() {
|
||||||
|
LoanResponse response = new LoanResponse(false, 0.0, "Rejected", "Not approved");
|
||||||
|
|
||||||
|
assertFalse(response.isApproved());
|
||||||
|
|
||||||
|
response.setApproved(true);
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
|
||||||
|
response.setApproved(false);
|
||||||
|
assertFalse(response.isApproved());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle Double.MAX_VALUE as approvedRate")
|
||||||
|
void testMaxDoubleValue() {
|
||||||
|
LoanResponse response = new LoanResponse();
|
||||||
|
response.setApprovedRate(Double.MAX_VALUE);
|
||||||
|
|
||||||
|
assertEquals(Double.MAX_VALUE, response.getApprovedRate(), 0.001);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,268 @@
|
|||||||
|
package com.example.repository.impl;
|
||||||
|
|
||||||
|
import com.example.util.DatabaseManager;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for CustomerRepositoryImpl
|
||||||
|
*/
|
||||||
|
@DisplayName("CustomerRepositoryImpl Tests")
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
class CustomerRepositoryImplTest {
|
||||||
|
|
||||||
|
private CustomerRepositoryImpl repository;
|
||||||
|
private DatabaseManager dbManager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
dbManager = DatabaseManager.getInstance();
|
||||||
|
dbManager.resetDatabase();
|
||||||
|
repository = new CustomerRepositoryImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
@DisplayName("Should register new customer successfully")
|
||||||
|
void testRegisterCustomer() {
|
||||||
|
boolean result = repository.registerCustomer("John Doe", false);
|
||||||
|
|
||||||
|
assertTrue(result, "Should successfully register new customer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
@DisplayName("Should register blacklisted customer")
|
||||||
|
void testRegisterBlacklistedCustomer() {
|
||||||
|
boolean result = repository.registerCustomer("Blacklisted User", true);
|
||||||
|
|
||||||
|
assertTrue(result);
|
||||||
|
assertTrue(repository.isBlacklisted("Blacklisted User"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
@DisplayName("Should not register duplicate customer")
|
||||||
|
void testRegisterDuplicateCustomer() {
|
||||||
|
repository.registerCustomer("John Doe", false);
|
||||||
|
boolean result = repository.registerCustomer("John Doe", false);
|
||||||
|
|
||||||
|
assertFalse(result, "Should not register duplicate customer");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
@DisplayName("Should throw exception for null customer name")
|
||||||
|
void testRegisterCustomerWithNullName() {
|
||||||
|
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
repository.registerCustomer(null, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("cannot be null or empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
@DisplayName("Should throw exception for empty customer name")
|
||||||
|
void testRegisterCustomerWithEmptyName() {
|
||||||
|
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
repository.registerCustomer("", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("cannot be null or empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
@DisplayName("Should throw exception for whitespace-only customer name")
|
||||||
|
void testRegisterCustomerWithWhitespaceName() {
|
||||||
|
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
|
||||||
|
repository.registerCustomer(" ", false);
|
||||||
|
});
|
||||||
|
|
||||||
|
assertTrue(exception.getMessage().contains("cannot be null or empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
@DisplayName("Should check blacklist status for existing customer")
|
||||||
|
void testIsBlacklistedForExistingCustomer() {
|
||||||
|
repository.registerCustomer("Normal User", false);
|
||||||
|
repository.registerCustomer("Bad User", true);
|
||||||
|
|
||||||
|
assertFalse(repository.isBlacklisted("Normal User"));
|
||||||
|
assertTrue(repository.isBlacklisted("Bad User"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(8)
|
||||||
|
@DisplayName("Should return false for non-existent customer blacklist check")
|
||||||
|
void testIsBlacklistedForNonExistentCustomer() {
|
||||||
|
boolean result = repository.isBlacklisted("Non Existent User");
|
||||||
|
|
||||||
|
assertFalse(result, "Non-existent customer should not be blacklisted");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(9)
|
||||||
|
@DisplayName("Should handle multiple customer registrations")
|
||||||
|
void testMultipleCustomerRegistrations() {
|
||||||
|
assertTrue(repository.registerCustomer("User1", false));
|
||||||
|
assertTrue(repository.registerCustomer("User2", true));
|
||||||
|
assertTrue(repository.registerCustomer("User3", false));
|
||||||
|
assertTrue(repository.registerCustomer("User4", true));
|
||||||
|
|
||||||
|
assertFalse(repository.isBlacklisted("User1"));
|
||||||
|
assertTrue(repository.isBlacklisted("User2"));
|
||||||
|
assertFalse(repository.isBlacklisted("User3"));
|
||||||
|
assertTrue(repository.isBlacklisted("User4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(10)
|
||||||
|
@DisplayName("Should handle customer names with special characters")
|
||||||
|
void testCustomerNamesWithSpecialCharacters() {
|
||||||
|
assertTrue(repository.registerCustomer("O'Brien", false));
|
||||||
|
assertTrue(repository.registerCustomer("Jean-Claude", false));
|
||||||
|
assertTrue(repository.registerCustomer("José García", false));
|
||||||
|
|
||||||
|
assertFalse(repository.isBlacklisted("O'Brien"));
|
||||||
|
assertFalse(repository.isBlacklisted("Jean-Claude"));
|
||||||
|
assertFalse(repository.isBlacklisted("José García"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(11)
|
||||||
|
@DisplayName("Should handle very long customer names")
|
||||||
|
void testVeryLongCustomerName() {
|
||||||
|
StringBuilder longName = new StringBuilder();
|
||||||
|
for (int i = 0; i < 200; i++) {
|
||||||
|
longName.append("A");
|
||||||
|
}
|
||||||
|
String customerName = longName.toString();
|
||||||
|
|
||||||
|
assertTrue(repository.registerCustomer(customerName, false));
|
||||||
|
assertFalse(repository.isBlacklisted(customerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(12)
|
||||||
|
@DisplayName("Should handle case-sensitive customer names")
|
||||||
|
void testCaseSensitiveCustomerNames() {
|
||||||
|
assertTrue(repository.registerCustomer("john doe", false));
|
||||||
|
assertTrue(repository.registerCustomer("John Doe", false));
|
||||||
|
assertTrue(repository.registerCustomer("JOHN DOE", false));
|
||||||
|
|
||||||
|
// All should be registered as separate customers
|
||||||
|
assertFalse(repository.isBlacklisted("john doe"));
|
||||||
|
assertFalse(repository.isBlacklisted("John Doe"));
|
||||||
|
assertFalse(repository.isBlacklisted("JOHN DOE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(13)
|
||||||
|
@DisplayName("Should handle customer names with numbers")
|
||||||
|
void testCustomerNamesWithNumbers() {
|
||||||
|
assertTrue(repository.registerCustomer("User123", false));
|
||||||
|
assertTrue(repository.registerCustomer("123User", false));
|
||||||
|
assertTrue(repository.registerCustomer("User 456", false));
|
||||||
|
|
||||||
|
assertFalse(repository.isBlacklisted("User123"));
|
||||||
|
assertFalse(repository.isBlacklisted("123User"));
|
||||||
|
assertFalse(repository.isBlacklisted("User 456"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(14)
|
||||||
|
@DisplayName("Should handle rapid successive registrations")
|
||||||
|
void testRapidSuccessiveRegistrations() {
|
||||||
|
for (int i = 0; i < 50; i++) {
|
||||||
|
assertTrue(repository.registerCustomer("RapidUser" + i, i % 2 == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify some registrations (i%2==0 means blacklisted=true)
|
||||||
|
assertTrue(repository.isBlacklisted("RapidUser0")); // i=0: even, blacklisted
|
||||||
|
assertFalse(repository.isBlacklisted("RapidUser1")); // i=1: odd, not blacklisted
|
||||||
|
assertTrue(repository.isBlacklisted("RapidUser10")); // i=10: even, blacklisted
|
||||||
|
assertFalse(repository.isBlacklisted("RapidUser11")); // i=11: odd, not blacklisted
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(15)
|
||||||
|
@DisplayName("Should maintain blacklist status consistency")
|
||||||
|
void testBlacklistStatusConsistency() {
|
||||||
|
repository.registerCustomer("Test User", true);
|
||||||
|
|
||||||
|
// Check multiple times
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assertTrue(repository.isBlacklisted("Test User"),
|
||||||
|
"Blacklist status should be consistent across multiple checks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(16)
|
||||||
|
@DisplayName("Should handle customer names with unicode characters")
|
||||||
|
void testCustomerNamesWithUnicodeCharacters() {
|
||||||
|
assertTrue(repository.registerCustomer("用户名", false));
|
||||||
|
assertTrue(repository.registerCustomer("Имя пользователя", false));
|
||||||
|
assertTrue(repository.registerCustomer("اسم المستخدم", false));
|
||||||
|
|
||||||
|
assertFalse(repository.isBlacklisted("用户名"));
|
||||||
|
assertFalse(repository.isBlacklisted("Имя пользователя"));
|
||||||
|
assertFalse(repository.isBlacklisted("اسم المستخدم"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(17)
|
||||||
|
@DisplayName("Should handle customer registration with leading/trailing spaces")
|
||||||
|
void testCustomerNamesWithSpaces() {
|
||||||
|
// The repository trims spaces in validation, so these should work
|
||||||
|
assertTrue(repository.registerCustomer("User With Spaces", false));
|
||||||
|
assertFalse(repository.isBlacklisted("User With Spaces"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(18)
|
||||||
|
@DisplayName("Should handle alternating blacklist registrations")
|
||||||
|
void testAlternatingBlacklistRegistrations() {
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
boolean isBlacklisted = (i % 3 == 0);
|
||||||
|
repository.registerCustomer("AlternateUser" + i, isBlacklisted);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue(repository.isBlacklisted("AlternateUser0"));
|
||||||
|
assertFalse(repository.isBlacklisted("AlternateUser1"));
|
||||||
|
assertFalse(repository.isBlacklisted("AlternateUser2"));
|
||||||
|
assertTrue(repository.isBlacklisted("AlternateUser3"));
|
||||||
|
assertFalse(repository.isBlacklisted("AlternateUser4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(19)
|
||||||
|
@DisplayName("Should handle registration attempt after failed registration")
|
||||||
|
void testRegistrationAfterFailedAttempt() {
|
||||||
|
repository.registerCustomer("TestUser", false);
|
||||||
|
|
||||||
|
// First duplicate attempt fails
|
||||||
|
assertFalse(repository.registerCustomer("TestUser", true));
|
||||||
|
|
||||||
|
// Second duplicate attempt also fails
|
||||||
|
assertFalse(repository.registerCustomer("TestUser", false));
|
||||||
|
|
||||||
|
// Original registration should still be valid
|
||||||
|
assertFalse(repository.isBlacklisted("TestUser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(20)
|
||||||
|
@DisplayName("Should correctly identify non-blacklisted status")
|
||||||
|
void testNonBlacklistedStatus() {
|
||||||
|
repository.registerCustomer("Good Customer", false);
|
||||||
|
|
||||||
|
boolean isBlacklisted = repository.isBlacklisted("Good Customer");
|
||||||
|
|
||||||
|
assertFalse(isBlacklisted, "Good customer should not be blacklisted");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,410 @@
|
|||||||
|
package com.example.repository.impl;
|
||||||
|
|
||||||
|
import com.example.util.DatabaseManager;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for LoanHistoryRepositoryImpl
|
||||||
|
*/
|
||||||
|
@DisplayName("LoanHistoryRepositoryImpl Tests")
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
class LoanHistoryRepositoryImplTest {
|
||||||
|
|
||||||
|
private LoanHistoryRepositoryImpl repository;
|
||||||
|
private DatabaseManager dbManager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
dbManager = DatabaseManager.getInstance();
|
||||||
|
dbManager.resetDatabase();
|
||||||
|
repository = new LoanHistoryRepositoryImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
@DisplayName("Should save approved loan history")
|
||||||
|
void testSaveApprovedLoanHistory() throws SQLException {
|
||||||
|
repository.saveHistory("John Doe", 50000, true, 5.5, null);
|
||||||
|
|
||||||
|
// Verify data was saved
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT * FROM loan_history WHERE applicant_name = 'John Doe'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("John Doe", rs.getString("applicant_name"));
|
||||||
|
assertEquals(50000, rs.getInt("requested_amount"));
|
||||||
|
assertEquals(1, rs.getInt("approved"));
|
||||||
|
assertEquals(5.5, rs.getDouble("approved_rate"), 0.001);
|
||||||
|
assertNull(rs.getString("rejection_reason"));
|
||||||
|
assertNotNull(rs.getString("processed_at"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
@DisplayName("Should save rejected loan history")
|
||||||
|
void testSaveRejectedLoanHistory() throws SQLException {
|
||||||
|
repository.saveHistory("Jane Smith", 100000, false, 0.0, "Credit score too low");
|
||||||
|
|
||||||
|
// Verify data was saved
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT * FROM loan_history WHERE applicant_name = 'Jane Smith'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("Jane Smith", rs.getString("applicant_name"));
|
||||||
|
assertEquals(100000, rs.getInt("requested_amount"));
|
||||||
|
assertEquals(0, rs.getInt("approved"));
|
||||||
|
assertEquals(0.0, rs.getDouble("approved_rate"), 0.001);
|
||||||
|
assertEquals("Credit score too low", rs.getString("rejection_reason"));
|
||||||
|
assertNotNull(rs.getString("processed_at"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
@DisplayName("Should save multiple loan histories")
|
||||||
|
void testSaveMultipleLoanHistories() throws SQLException {
|
||||||
|
repository.saveHistory("User1", 30000, true, 4.5, null);
|
||||||
|
repository.saveHistory("User2", 60000, false, 0.0, "Insufficient income");
|
||||||
|
repository.saveHistory("User3", 45000, true, 6.0, null);
|
||||||
|
|
||||||
|
// Verify count
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM loan_history")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(3, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
@DisplayName("Should save loan history with zero amount")
|
||||||
|
void testSaveLoanHistoryWithZeroAmount() throws SQLException {
|
||||||
|
repository.saveHistory("Zero User", 0, false, 0.0, "Invalid amount");
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT * FROM loan_history WHERE applicant_name = 'Zero User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(0, rs.getInt("requested_amount"));
|
||||||
|
assertFalse(rs.getBoolean("approved"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
@DisplayName("Should save loan history with very large amount")
|
||||||
|
void testSaveLoanHistoryWithLargeAmount() throws SQLException {
|
||||||
|
repository.saveHistory("Rich User", 10000000, true, 3.5, null);
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT * FROM loan_history WHERE applicant_name = 'Rich User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(10000000, rs.getInt("requested_amount"));
|
||||||
|
assertEquals(3.5, rs.getDouble("approved_rate"), 0.001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
@DisplayName("Should save loan history with special characters in name")
|
||||||
|
void testSaveLoanHistoryWithSpecialCharacters() throws SQLException {
|
||||||
|
repository.saveHistory("O'Brien", 50000, true, 5.0, null);
|
||||||
|
repository.saveHistory("Jean-Claude", 60000, false, 0.0, "Reason: Bad history");
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT COUNT(*) as count FROM loan_history WHERE applicant_name LIKE '%-%' OR applicant_name LIKE '%''%'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(2, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
@DisplayName("Should save loan history with different approval rates")
|
||||||
|
void testSaveLoanHistoryWithDifferentRates() throws SQLException {
|
||||||
|
double[] rates = {3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 7.0, 8.0};
|
||||||
|
|
||||||
|
for (int i = 0; i < rates.length; i++) {
|
||||||
|
repository.saveHistory("User" + i, 50000, true, rates[i], null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify all rates were saved correctly
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT approved_rate FROM loan_history WHERE approved = 1 ORDER BY approved_rate")) {
|
||||||
|
|
||||||
|
for (double rate : rates) {
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(rate, rs.getDouble("approved_rate"), 0.001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(8)
|
||||||
|
@DisplayName("Should save loan history with long rejection reason")
|
||||||
|
void testSaveLoanHistoryWithLongRejectionReason() throws SQLException {
|
||||||
|
StringBuilder longReason = new StringBuilder();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
longReason.append("Reason part ").append(i).append(". ");
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.saveHistory("Test User", 50000, false, 0.0, longReason.toString());
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT rejection_reason FROM loan_history WHERE applicant_name = 'Test User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
String savedReason = rs.getString("rejection_reason");
|
||||||
|
assertNotNull(savedReason);
|
||||||
|
assertTrue(savedReason.length() > 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(9)
|
||||||
|
@DisplayName("Should save multiple histories for same applicant")
|
||||||
|
void testSaveMultipleHistoriesForSameApplicant() throws SQLException {
|
||||||
|
repository.saveHistory("Repeat User", 30000, false, 0.0, "First rejection");
|
||||||
|
repository.saveHistory("Repeat User", 40000, false, 0.0, "Second rejection");
|
||||||
|
repository.saveHistory("Repeat User", 50000, true, 5.5, null);
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT COUNT(*) as count FROM loan_history WHERE applicant_name = 'Repeat User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(3, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(10)
|
||||||
|
@DisplayName("Should save loan history with null rejection reason for approved loan")
|
||||||
|
void testSaveLoanHistoryWithNullRejectionReason() throws SQLException {
|
||||||
|
repository.saveHistory("Approved User", 50000, true, 5.5, null);
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT rejection_reason FROM loan_history WHERE applicant_name = 'Approved User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertNull(rs.getString("rejection_reason"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(11)
|
||||||
|
@DisplayName("Should handle rapid successive history saves")
|
||||||
|
void testRapidSuccessiveHistorySaves() throws SQLException {
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
repository.saveHistory("Rapid" + i, 50000, i % 2 == 0, 5.5, i % 2 == 0 ? null : "Rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM loan_history")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(100, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(12)
|
||||||
|
@DisplayName("Should save loan history with unicode characters in name")
|
||||||
|
void testSaveLoanHistoryWithUnicodeCharacters() throws SQLException {
|
||||||
|
repository.saveHistory("用户", 50000, true, 5.0, null);
|
||||||
|
repository.saveHistory("Пользователь", 60000, false, 0.0, "Отклонено");
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM loan_history")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(2, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(13)
|
||||||
|
@DisplayName("Should save loan history with decimal approval rates")
|
||||||
|
void testSaveLoanHistoryWithDecimalRates() throws SQLException {
|
||||||
|
repository.saveHistory("Decimal User", 50000, true, 5.125, null);
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT approved_rate FROM loan_history WHERE applicant_name = 'Decimal User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(5.125, rs.getDouble("approved_rate"), 0.0001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(14)
|
||||||
|
@DisplayName("Should save loan history with empty rejection reason")
|
||||||
|
void testSaveLoanHistoryWithEmptyRejectionReason() throws SQLException {
|
||||||
|
repository.saveHistory("Empty Reason", 50000, false, 0.0, "");
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT rejection_reason FROM loan_history WHERE applicant_name = 'Empty Reason'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("", rs.getString("rejection_reason"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(15)
|
||||||
|
@DisplayName("Should auto-generate ID for loan history records")
|
||||||
|
void testAutoGenerateId() throws SQLException {
|
||||||
|
repository.saveHistory("User1", 50000, true, 5.5, null);
|
||||||
|
repository.saveHistory("User2", 60000, true, 6.0, null);
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT id FROM loan_history ORDER BY id")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
int id1 = rs.getInt("id");
|
||||||
|
assertTrue(rs.next());
|
||||||
|
int id2 = rs.getInt("id");
|
||||||
|
|
||||||
|
assertTrue(id2 > id1, "IDs should be auto-incrementing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(16)
|
||||||
|
@DisplayName("Should save processed_at timestamp")
|
||||||
|
void testSaveProcessedAtTimestamp() throws SQLException {
|
||||||
|
repository.saveHistory("Timestamp User", 50000, true, 5.5, null);
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT processed_at FROM loan_history WHERE applicant_name = 'Timestamp User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
String processedAt = rs.getString("processed_at");
|
||||||
|
assertNotNull(processedAt);
|
||||||
|
// Verify format: yyyy-MM-dd HH:mm:ss
|
||||||
|
assertTrue(processedAt.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(17)
|
||||||
|
@DisplayName("Should handle different rejection reasons")
|
||||||
|
void testDifferentRejectionReasons() throws SQLException {
|
||||||
|
String[] reasons = {
|
||||||
|
"Credit score too low",
|
||||||
|
"Insufficient income",
|
||||||
|
"Poor credit history",
|
||||||
|
"High debt-to-income ratio",
|
||||||
|
"Blacklisted customer",
|
||||||
|
"Invalid application"
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < reasons.length; i++) {
|
||||||
|
repository.saveHistory("RejectedUser" + i, 50000, false, 0.0, reasons[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT COUNT(DISTINCT rejection_reason) as count FROM loan_history WHERE approved = 0")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(reasons.length, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(18)
|
||||||
|
@DisplayName("Should save loan history with negative amount")
|
||||||
|
void testSaveLoanHistoryWithNegativeAmount() throws SQLException {
|
||||||
|
repository.saveHistory("Negative User", -1000, false, 0.0, "Invalid amount");
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT requested_amount FROM loan_history WHERE applicant_name = 'Negative User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(-1000, rs.getInt("requested_amount"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(19)
|
||||||
|
@DisplayName("Should maintain history order by processed_at")
|
||||||
|
void testHistoryOrder() throws SQLException {
|
||||||
|
// Save multiple histories with slight delay
|
||||||
|
repository.saveHistory("First", 50000, true, 5.5, null);
|
||||||
|
try { Thread.sleep(10); } catch (InterruptedException e) { }
|
||||||
|
repository.saveHistory("Second", 60000, true, 6.0, null);
|
||||||
|
try { Thread.sleep(10); } catch (InterruptedException e) { }
|
||||||
|
repository.saveHistory("Third", 70000, true, 7.0, null);
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT applicant_name FROM loan_history ORDER BY processed_at")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("First", rs.getString("applicant_name"));
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("Second", rs.getString("applicant_name"));
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("Third", rs.getString("applicant_name"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(20)
|
||||||
|
@DisplayName("Should save loan history with maximum double value for rate")
|
||||||
|
void testSaveLoanHistoryWithMaxRate() throws SQLException {
|
||||||
|
repository.saveHistory("Max Rate User", 50000, true, Double.MAX_VALUE, null);
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT approved_rate FROM loan_history WHERE applicant_name = 'Max Rate User'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(Double.MAX_VALUE, rs.getDouble("approved_rate"), 0.001);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
561
src/test/java/com/example/service/LoanApprovalServiceTest.java
Normal file
561
src/test/java/com/example/service/LoanApprovalServiceTest.java
Normal file
@ -0,0 +1,561 @@
|
|||||||
|
package com.example.service;
|
||||||
|
|
||||||
|
import com.example.model.CustomerRegistrationRequest;
|
||||||
|
import com.example.model.LoanRequest;
|
||||||
|
import com.example.model.LoanResponse;
|
||||||
|
import com.example.repository.CustomerRepository;
|
||||||
|
import com.example.repository.LoanHistoryRepository;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
import javax.xml.ws.soap.SOAPFaultException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for LoanApprovalService
|
||||||
|
* Tests customer registration and loan approval logic
|
||||||
|
*/
|
||||||
|
@DisplayName("LoanApprovalService Tests")
|
||||||
|
class LoanApprovalServiceTest {
|
||||||
|
|
||||||
|
private LoanApprovalService service;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CustomerRepository customerRepository;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanHistoryRepository loanHistoryRepository;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CreditScoreService creditScoreService;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
MockitoAnnotations.openMocks(this);
|
||||||
|
service = new LoanApprovalService();
|
||||||
|
service.setCustomerRepository(customerRepository);
|
||||||
|
service.setLoanHistoryRepository(loanHistoryRepository);
|
||||||
|
service.setCreditScoreService(creditScoreService);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Customer Registration Tests
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should register new customer successfully")
|
||||||
|
void testRegisterNewCustomer_Success() {
|
||||||
|
// Arrange
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest();
|
||||||
|
request.setCustomerName("John Doe");
|
||||||
|
request.setBlacklisted(false);
|
||||||
|
|
||||||
|
when(customerRepository.registerCustomer("John Doe", false)).thenReturn(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String result = service.registerNewCustomer(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals("Registration Successful", result);
|
||||||
|
verify(customerRepository).registerCustomer("John Doe", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should register blacklisted customer successfully")
|
||||||
|
void testRegisterNewCustomer_Blacklisted() {
|
||||||
|
// Arrange
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest();
|
||||||
|
request.setCustomerName("Bad Actor");
|
||||||
|
request.setBlacklisted(true);
|
||||||
|
|
||||||
|
when(customerRepository.registerCustomer("Bad Actor", true)).thenReturn(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String result = service.registerNewCustomer(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals("Registration Successful", result);
|
||||||
|
verify(customerRepository).registerCustomer("Bad Actor", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should fail registration for duplicate customer")
|
||||||
|
void testRegisterNewCustomer_Duplicate() {
|
||||||
|
// Arrange
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest();
|
||||||
|
request.setCustomerName("Existing Customer");
|
||||||
|
request.setBlacklisted(false);
|
||||||
|
|
||||||
|
when(customerRepository.registerCustomer("Existing Customer", false)).thenReturn(false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
String result = service.registerNewCustomer(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertEquals("Error: Customer already exists", result);
|
||||||
|
verify(customerRepository).registerCustomer("Existing Customer", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should throw SOAP fault for null registration request")
|
||||||
|
void testRegisterNewCustomer_NullRequest() {
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(SOAPFaultException.class, () -> {
|
||||||
|
service.registerNewCustomer(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(customerRepository, never()).registerCustomer(anyString(), anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should throw SOAP fault for null customer name")
|
||||||
|
void testRegisterNewCustomer_NullName() {
|
||||||
|
// Arrange
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest();
|
||||||
|
request.setCustomerName(null);
|
||||||
|
request.setBlacklisted(false);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(SOAPFaultException.class, () -> {
|
||||||
|
service.registerNewCustomer(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(customerRepository, never()).registerCustomer(anyString(), anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should throw SOAP fault for empty customer name")
|
||||||
|
void testRegisterNewCustomer_EmptyName() {
|
||||||
|
// Arrange
|
||||||
|
CustomerRegistrationRequest request = new CustomerRegistrationRequest();
|
||||||
|
request.setCustomerName(" ");
|
||||||
|
request.setBlacklisted(false);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(SOAPFaultException.class, () -> {
|
||||||
|
service.registerNewCustomer(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(customerRepository, never()).registerCustomer(anyString(), anyBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Loan Approval Tests - Excellent Credit (>= 700)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should approve loan with 3.5% rate for credit score 750")
|
||||||
|
void testLoanApproval_ExcellentCredit() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Alice");
|
||||||
|
request.setRequestedAmount(50000);
|
||||||
|
request.setCreditScore(750);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Alice")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Alice")).thenReturn(750);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(3.5, response.getApprovedRate());
|
||||||
|
assertNull(response.getRejectionReason());
|
||||||
|
assertEquals("Loan approved with excellent rate", response.getMessage());
|
||||||
|
|
||||||
|
verify(loanHistoryRepository).saveHistory("Alice", 50000, true, 3.5, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should approve loan with 3.5% rate for credit score exactly 700")
|
||||||
|
void testLoanApproval_CreditScore700() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Bob");
|
||||||
|
request.setRequestedAmount(30000);
|
||||||
|
request.setCreditScore(700);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Bob")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Bob")).thenReturn(700);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(3.5, response.getApprovedRate());
|
||||||
|
assertEquals("Loan approved with excellent rate", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Loan Approval Tests - Good Credit (600-699)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should approve loan with 5.5% rate for credit score 650")
|
||||||
|
void testLoanApproval_GoodCredit() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Charlie");
|
||||||
|
request.setRequestedAmount(25000);
|
||||||
|
request.setCreditScore(650);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Charlie")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Charlie")).thenReturn(650);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(5.5, response.getApprovedRate());
|
||||||
|
assertNull(response.getRejectionReason());
|
||||||
|
assertEquals("Loan approved with standard rate", response.getMessage());
|
||||||
|
|
||||||
|
verify(loanHistoryRepository).saveHistory("Charlie", 25000, true, 5.5, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should approve loan with 5.5% rate for credit score exactly 600")
|
||||||
|
void testLoanApproval_CreditScore600() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Diana");
|
||||||
|
request.setRequestedAmount(20000);
|
||||||
|
request.setCreditScore(600);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Diana")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Diana")).thenReturn(600);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(5.5, response.getApprovedRate());
|
||||||
|
assertEquals("Loan approved with standard rate", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should approve loan with 5.5% rate for credit score 699")
|
||||||
|
void testLoanApproval_CreditScore699() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Eve");
|
||||||
|
request.setRequestedAmount(15000);
|
||||||
|
request.setCreditScore(699);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Eve")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Eve")).thenReturn(699);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(5.5, response.getApprovedRate());
|
||||||
|
assertEquals("Loan approved with standard rate", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Loan Approval Tests - Fair Credit (500-599)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should approve loan with 8.5% rate for credit score 550")
|
||||||
|
void testLoanApproval_FairCredit() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Frank");
|
||||||
|
request.setRequestedAmount(10000);
|
||||||
|
request.setCreditScore(550);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Frank")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Frank")).thenReturn(550);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(8.5, response.getApprovedRate());
|
||||||
|
assertNull(response.getRejectionReason());
|
||||||
|
assertEquals("Loan approved with high risk rate", response.getMessage());
|
||||||
|
|
||||||
|
verify(loanHistoryRepository).saveHistory("Frank", 10000, true, 8.5, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should approve loan with 8.5% rate for credit score exactly 500")
|
||||||
|
void testLoanApproval_CreditScore500() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Grace");
|
||||||
|
request.setRequestedAmount(5000);
|
||||||
|
request.setCreditScore(500);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Grace")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Grace")).thenReturn(500);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(8.5, response.getApprovedRate());
|
||||||
|
assertEquals("Loan approved with high risk rate", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should approve loan with 8.5% rate for credit score 599")
|
||||||
|
void testLoanApproval_CreditScore599() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Henry");
|
||||||
|
request.setRequestedAmount(8000);
|
||||||
|
request.setCreditScore(599);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Henry")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Henry")).thenReturn(599);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(8.5, response.getApprovedRate());
|
||||||
|
assertEquals("Loan approved with high risk rate", response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Loan Rejection Tests - Poor Credit (< 500)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should reject loan for credit score below 500")
|
||||||
|
void testLoanRejection_PoorCredit() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Ivan");
|
||||||
|
request.setRequestedAmount(5000);
|
||||||
|
request.setCreditScore(450);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Ivan")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Ivan")).thenReturn(450);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertFalse(response.isApproved());
|
||||||
|
assertEquals(0.0, response.getApprovedRate());
|
||||||
|
assertEquals("Credit score too low", response.getRejectionReason());
|
||||||
|
assertEquals("Loan application rejected", response.getMessage());
|
||||||
|
|
||||||
|
verify(loanHistoryRepository).saveHistory("Ivan", 5000, false, 0.0, "Credit score too low");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should reject loan for credit score 499")
|
||||||
|
void testLoanRejection_CreditScore499() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Jane");
|
||||||
|
request.setRequestedAmount(3000);
|
||||||
|
request.setCreditScore(499);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Jane")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Jane")).thenReturn(499);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertFalse(response.isApproved());
|
||||||
|
assertEquals("Credit score too low", response.getRejectionReason());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Blacklist Tests
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should reject loan for blacklisted applicant")
|
||||||
|
void testLoanRejection_Blacklisted() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Blacklisted Person");
|
||||||
|
request.setRequestedAmount(10000);
|
||||||
|
request.setCreditScore(750);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Blacklisted Person")).thenReturn(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertFalse(response.isApproved());
|
||||||
|
assertEquals(0.0, response.getApprovedRate());
|
||||||
|
assertEquals("Applicant is blacklisted", response.getRejectionReason());
|
||||||
|
assertEquals("Loan application rejected", response.getMessage());
|
||||||
|
|
||||||
|
verify(loanHistoryRepository).saveHistory("Blacklisted Person", 10000, false, 0.0, "Applicant is blacklisted");
|
||||||
|
verify(creditScoreService, never()).getCreditScore(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Input Validation Tests
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should throw SOAP fault for null loan request")
|
||||||
|
void testLoanApplication_NullRequest() {
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(SOAPFaultException.class, () -> {
|
||||||
|
service.processLoanApplication(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(customerRepository, never()).isBlacklisted(anyString());
|
||||||
|
verify(loanHistoryRepository, never()).saveHistory(anyString(), anyInt(), anyBoolean(), anyDouble(), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should throw SOAP fault for null applicant name")
|
||||||
|
void testLoanApplication_NullApplicantName() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName(null);
|
||||||
|
request.setRequestedAmount(10000);
|
||||||
|
request.setCreditScore(700);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(SOAPFaultException.class, () -> {
|
||||||
|
service.processLoanApplication(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(customerRepository, never()).isBlacklisted(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should throw SOAP fault for empty applicant name")
|
||||||
|
void testLoanApplication_EmptyApplicantName() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName(" ");
|
||||||
|
request.setRequestedAmount(10000);
|
||||||
|
request.setCreditScore(700);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(SOAPFaultException.class, () -> {
|
||||||
|
service.processLoanApplication(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(customerRepository, never()).isBlacklisted(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should throw SOAP fault for zero loan amount")
|
||||||
|
void testLoanApplication_ZeroAmount() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Test User");
|
||||||
|
request.setRequestedAmount(0);
|
||||||
|
request.setCreditScore(700);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(SOAPFaultException.class, () -> {
|
||||||
|
service.processLoanApplication(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(customerRepository, never()).isBlacklisted(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should throw SOAP fault for negative loan amount")
|
||||||
|
void testLoanApplication_NegativeAmount() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Test User");
|
||||||
|
request.setRequestedAmount(-1000);
|
||||||
|
request.setCreditScore(700);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
assertThrows(SOAPFaultException.class, () -> {
|
||||||
|
service.processLoanApplication(request);
|
||||||
|
});
|
||||||
|
|
||||||
|
verify(customerRepository, never()).isBlacklisted(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Edge Case Tests
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle very large loan amount")
|
||||||
|
void testLoanApplication_LargeLoanAmount() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Rich Person");
|
||||||
|
request.setRequestedAmount(Integer.MAX_VALUE);
|
||||||
|
request.setCreditScore(800);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Rich Person")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Rich Person")).thenReturn(800);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(3.5, response.getApprovedRate());
|
||||||
|
|
||||||
|
verify(loanHistoryRepository).saveHistory("Rich Person", Integer.MAX_VALUE, true, 3.5, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle applicant names with special characters")
|
||||||
|
void testLoanApplication_SpecialCharactersInName() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("O'Brien-Smith");
|
||||||
|
request.setRequestedAmount(20000);
|
||||||
|
request.setCreditScore(720);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("O'Brien-Smith")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("O'Brien-Smith")).thenReturn(720);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
assertEquals(3.5, response.getApprovedRate());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("Should handle minimum valid loan amount")
|
||||||
|
void testLoanApplication_MinimumAmount() {
|
||||||
|
// Arrange
|
||||||
|
LoanRequest request = new LoanRequest();
|
||||||
|
request.setApplicantName("Small Borrower");
|
||||||
|
request.setRequestedAmount(1);
|
||||||
|
request.setCreditScore(700);
|
||||||
|
|
||||||
|
when(customerRepository.isBlacklisted("Small Borrower")).thenReturn(false);
|
||||||
|
when(creditScoreService.getCreditScore("Small Borrower")).thenReturn(700);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
LoanResponse response = service.processLoanApplication(request);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(response.isApproved());
|
||||||
|
verify(loanHistoryRepository).saveHistory("Small Borrower", 1, true, 3.5, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
315
src/test/java/com/example/util/DatabaseManagerTest.java
Normal file
315
src/test/java/com/example/util/DatabaseManagerTest.java
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
package com.example.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for DatabaseManager utility class
|
||||||
|
*/
|
||||||
|
@DisplayName("DatabaseManager Tests")
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
class DatabaseManagerTest {
|
||||||
|
|
||||||
|
private DatabaseManager dbManager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
dbManager = DatabaseManager.getInstance();
|
||||||
|
// Reset database before each test
|
||||||
|
dbManager.resetDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
@DisplayName("Should return singleton instance")
|
||||||
|
void testGetInstance() {
|
||||||
|
DatabaseManager instance1 = DatabaseManager.getInstance();
|
||||||
|
DatabaseManager instance2 = DatabaseManager.getInstance();
|
||||||
|
|
||||||
|
assertNotNull(instance1);
|
||||||
|
assertNotNull(instance2);
|
||||||
|
assertSame(instance1, instance2, "Should return same instance");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
|
@DisplayName("Should get database connection")
|
||||||
|
void testGetConnection() throws SQLException {
|
||||||
|
Connection conn = dbManager.getConnection();
|
||||||
|
|
||||||
|
assertNotNull(conn);
|
||||||
|
assertFalse(conn.isClosed());
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
assertTrue(conn.isClosed());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
@DisplayName("Should initialize database schema")
|
||||||
|
void testInitializeSchema() throws SQLException {
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
|
||||||
|
// Verify customers table exists
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='customers'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next(), "Customers table should exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify loan_history table exists
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='loan_history'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next(), "Loan history table should exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(4)
|
||||||
|
@DisplayName("Should create customers table with correct schema")
|
||||||
|
void testCustomersTableSchema() throws SQLException {
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
|
||||||
|
// Insert test data
|
||||||
|
stmt.execute("INSERT INTO customers (customer_name, is_blacklisted, registered_at) " +
|
||||||
|
"VALUES ('Test User', 0, '2025-01-01 10:00:00')");
|
||||||
|
|
||||||
|
// Query test data
|
||||||
|
try (ResultSet rs = stmt.executeQuery("SELECT * FROM customers WHERE customer_name = 'Test User'")) {
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("Test User", rs.getString("customer_name"));
|
||||||
|
assertEquals(0, rs.getInt("is_blacklisted"));
|
||||||
|
assertEquals("2025-01-01 10:00:00", rs.getString("registered_at"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(5)
|
||||||
|
@DisplayName("Should create loan_history table with correct schema")
|
||||||
|
void testLoanHistoryTableSchema() throws SQLException {
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
|
||||||
|
// Insert test data
|
||||||
|
stmt.execute("INSERT INTO loan_history " +
|
||||||
|
"(applicant_name, requested_amount, approved, approved_rate, rejection_reason, processed_at) " +
|
||||||
|
"VALUES ('Test Applicant', 50000, 1, 5.5, NULL, '2025-01-01 10:00:00')");
|
||||||
|
|
||||||
|
// Query test data
|
||||||
|
try (ResultSet rs = stmt.executeQuery("SELECT * FROM loan_history WHERE applicant_name = 'Test Applicant'")) {
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("Test Applicant", rs.getString("applicant_name"));
|
||||||
|
assertEquals(50000, rs.getInt("requested_amount"));
|
||||||
|
assertEquals(1, rs.getInt("approved"));
|
||||||
|
assertEquals(5.5, rs.getDouble("approved_rate"), 0.001);
|
||||||
|
assertNull(rs.getString("rejection_reason"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(6)
|
||||||
|
@DisplayName("Should reset database successfully")
|
||||||
|
void testResetDatabase() throws SQLException {
|
||||||
|
// First, initialize and add some data
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
|
||||||
|
stmt.execute("INSERT INTO customers (customer_name, is_blacklisted, registered_at) " +
|
||||||
|
"VALUES ('User1', 0, '2025-01-01 10:00:00')");
|
||||||
|
|
||||||
|
// Verify data exists
|
||||||
|
try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM customers")) {
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(1, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset database
|
||||||
|
dbManager.resetDatabase();
|
||||||
|
|
||||||
|
// Verify tables are empty
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM customers")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(0, rs.getInt("count"), "Customers table should be empty after reset");
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM loan_history")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(0, rs.getInt("count"), "Loan history table should be empty after reset");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(7)
|
||||||
|
@DisplayName("Should test database connection successfully")
|
||||||
|
void testTestConnection() {
|
||||||
|
boolean result = dbManager.testConnection();
|
||||||
|
|
||||||
|
assertTrue(result, "Test connection should return true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(8)
|
||||||
|
@DisplayName("Should handle multiple connections")
|
||||||
|
void testMultipleConnections() throws SQLException {
|
||||||
|
Connection conn1 = dbManager.getConnection();
|
||||||
|
Connection conn2 = dbManager.getConnection();
|
||||||
|
Connection conn3 = dbManager.getConnection();
|
||||||
|
|
||||||
|
assertNotNull(conn1);
|
||||||
|
assertNotNull(conn2);
|
||||||
|
assertNotNull(conn3);
|
||||||
|
|
||||||
|
assertNotSame(conn1, conn2, "Should create different connection objects");
|
||||||
|
assertNotSame(conn2, conn3, "Should create different connection objects");
|
||||||
|
|
||||||
|
conn1.close();
|
||||||
|
conn2.close();
|
||||||
|
conn3.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(9)
|
||||||
|
@DisplayName("Should close connection safely with non-null connection")
|
||||||
|
void testCloseConnectionWithValidConnection() throws SQLException {
|
||||||
|
Connection conn = dbManager.getConnection();
|
||||||
|
assertFalse(conn.isClosed());
|
||||||
|
|
||||||
|
DatabaseManager.closeConnection(conn);
|
||||||
|
|
||||||
|
assertTrue(conn.isClosed());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(10)
|
||||||
|
@DisplayName("Should handle closing null connection gracefully")
|
||||||
|
void testCloseConnectionWithNullConnection() {
|
||||||
|
// Should not throw exception
|
||||||
|
assertDoesNotThrow(() -> DatabaseManager.closeConnection(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(11)
|
||||||
|
@DisplayName("Should handle closing already closed connection")
|
||||||
|
void testCloseConnectionWithAlreadyClosedConnection() throws SQLException {
|
||||||
|
Connection conn = dbManager.getConnection();
|
||||||
|
conn.close();
|
||||||
|
|
||||||
|
// Should not throw exception
|
||||||
|
assertDoesNotThrow(() -> DatabaseManager.closeConnection(conn));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(12)
|
||||||
|
@DisplayName("Should handle schema initialization multiple times")
|
||||||
|
void testInitializeSchemMultipleTimes() {
|
||||||
|
// Initialize multiple times - should not fail
|
||||||
|
assertDoesNotThrow(() -> {
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(13)
|
||||||
|
@DisplayName("Should maintain data consistency across connections")
|
||||||
|
void testDataConsistencyAcrossConnections() throws SQLException {
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
|
||||||
|
// Insert data with one connection
|
||||||
|
try (Connection conn1 = dbManager.getConnection();
|
||||||
|
Statement stmt = conn1.createStatement()) {
|
||||||
|
|
||||||
|
stmt.execute("INSERT INTO customers (customer_name, is_blacklisted, registered_at) " +
|
||||||
|
"VALUES ('Consistency Test', 1, '2025-01-01 10:00:00')");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data with different connection
|
||||||
|
try (Connection conn2 = dbManager.getConnection();
|
||||||
|
Statement stmt = conn2.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT * FROM customers WHERE customer_name = 'Consistency Test'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals("Consistency Test", rs.getString("customer_name"));
|
||||||
|
assertEquals(1, rs.getInt("is_blacklisted"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(14)
|
||||||
|
@DisplayName("Should support transactions")
|
||||||
|
void testTransactionSupport() throws SQLException {
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection()) {
|
||||||
|
conn.setAutoCommit(false);
|
||||||
|
|
||||||
|
try (Statement stmt = conn.createStatement()) {
|
||||||
|
stmt.execute("INSERT INTO customers (customer_name, is_blacklisted, registered_at) " +
|
||||||
|
"VALUES ('Transaction Test', 0, '2025-01-01 10:00:00')");
|
||||||
|
|
||||||
|
// Rollback
|
||||||
|
conn.rollback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify data was not committed
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM customers WHERE customer_name = 'Transaction Test'")) {
|
||||||
|
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(0, rs.getInt("count"), "Data should not exist after rollback");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(15)
|
||||||
|
@DisplayName("Should handle large data insertions")
|
||||||
|
void testLargeDataInsertion() throws SQLException {
|
||||||
|
dbManager.initializeSchema();
|
||||||
|
|
||||||
|
try (Connection conn = dbManager.getConnection();
|
||||||
|
Statement stmt = conn.createStatement()) {
|
||||||
|
|
||||||
|
// Insert 100 customers
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
stmt.execute("INSERT INTO customers (customer_name, is_blacklisted, registered_at) " +
|
||||||
|
"VALUES ('Customer" + i + "', " + (i % 2) + ", '2025-01-01 10:00:00')");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify count
|
||||||
|
try (ResultSet rs = stmt.executeQuery("SELECT COUNT(*) as count FROM customers")) {
|
||||||
|
assertTrue(rs.next());
|
||||||
|
assertEquals(100, rs.getInt("count"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user