diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c15ce1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Testing +.pytest_cache/ +.coverage +htmlcov/ +test-results.xml +test-report.html +coverage.xml + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..1ec02f4 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,94 @@ +pipeline { + agent any + + environment { + PYTHON_VERSION = '3.9' + } + + stages { + stage('Checkout') { + steps { + echo 'Checking out code...' + checkout scm + } + } + + stage('Setup Python Environment') { + steps { + echo 'Setting up Python environment...' + bat ''' + python --version + python -m pip install --upgrade pip + ''' + } + } + + stage('Install Dependencies') { + steps { + echo 'Installing dependencies...' + bat ''' + pip install -r requirements.txt + ''' + } + } + + stage('Run Tests') { + steps { + echo 'Running payment tests...' + bat ''' + pytest tests/ -v --junitxml=test-results.xml --html=test-report.html --self-contained-html + ''' + } + } + + stage('Code Coverage') { + steps { + echo 'Generating code coverage report...' + bat ''' + pytest tests/ --cov=src --cov-report=html --cov-report=xml + ''' + } + } + + stage('Publish Results') { + steps { + echo 'Publishing test results...' + junit 'test-results.xml' + + publishHTML(target: [ + allowMissing: false, + alwaysLinkToLastBuild: true, + keepAll: true, + reportDir: 'htmlcov', + reportFiles: 'index.html', + reportName: 'Coverage Report' + ]) + + publishHTML(target: [ + allowMissing: false, + alwaysLinkToLastBuild: true, + keepAll: true, + reportDir: '.', + reportFiles: 'test-report.html', + reportName: 'Test Report' + ]) + } + } + } + + post { + always { + echo 'Cleaning up...' + cleanWs(patterns: [ + [pattern: '**/__pycache__', type: 'INCLUDE'], + [pattern: '**/*.pyc', type: 'INCLUDE'] + ]) + } + success { + echo 'Pipeline completed successfully!' + } + failure { + echo 'Pipeline failed!' + } + } +} diff --git a/README b/README index 980a0d5..81251ac 100644 --- a/README +++ b/README @@ -1 +1,55 @@ -Hello World! +# Hello World - Payment Test Suite + +This repository contains a dummy payment service with comprehensive test cases designed to run through Jenkins CI/CD pipeline. + +## Quick Start + +### Run Tests Locally +```bash +pip install -r requirements.txt +pytest tests/ -v +``` + +### View Coverage +```bash +pytest tests/ --cov=src --cov-report=html +``` + +## What's Included + +- **Payment Service**: Dummy payment processing module +- **21 Test Cases**: Comprehensive test coverage including positive, negative, and edge cases +- **Jenkins Pipeline**: Automated CI/CD configuration +- **Coverage Reports**: HTML and XML coverage reporting + +## Documentation + +See [SETUP_GUIDE.md](SETUP_GUIDE.md) for complete setup instructions and Jenkins configuration. + +## Test Results + +The test suite includes: +- ✅ 4 Positive test cases +- ❌ 8 Negative test cases +- 🔍 9 Edge case tests + +Total: **21 automated tests** + +## Jenkins Integration + +This project includes a complete Jenkinsfile that: +1. Checks out code +2. Sets up Python environment +3. Installs dependencies +4. Runs all tests +5. Generates coverage reports +6. Publishes results + +## Project Structure +``` +Hello-World/ +├── src/ # Payment service source code +├── tests/ # Test cases +├── Jenkinsfile # Jenkins pipeline +└── requirements.txt # Dependencies +``` diff --git a/SETUP_GUIDE.md b/SETUP_GUIDE.md new file mode 100644 index 0000000..d1e40ca --- /dev/null +++ b/SETUP_GUIDE.md @@ -0,0 +1,271 @@ +# Payment Test Suite - Jenkins Setup Guide + +## Project Structure + +``` +Hello-World/ +├── src/ +│ ├── __init__.py +│ └── payment_service.py # Dummy payment service +├── tests/ +│ ├── __init__.py +│ └── test_payment.py # Payment test cases (21 tests) +├── Jenkinsfile # Jenkins pipeline configuration +├── requirements.txt # Python dependencies +├── pytest.ini # Pytest configuration +├── .gitignore # Git ignore patterns +├── SETUP_GUIDE.md # This file +└── README # Original readme + +``` + +## Test Cases Included + +The test suite includes **21 comprehensive test cases**: + +### Positive Tests +1. ✅ Successful payment processing +2. ✅ Multiple payments handling +3. ✅ Transaction retrieval +4. ✅ Successful refund processing + +### Negative Tests +5. ❌ Invalid amount (zero) +6. ❌ Invalid amount (negative) +7. ❌ Invalid card number (too short) +8. ❌ Invalid card number (empty) +9. ❌ Invalid CVV (too short) +10. ❌ Invalid CVV (empty) +11. ❌ Refund non-existent transaction +12. ❌ Duplicate refund + +### Edge Cases +13. 🔍 Large amount payment +14. 🔍 Small amount payment +15. 🔍 Get all transactions (empty) +16. 🔍 Get non-existent transaction +17. 🔍 Card number masking + +## Prerequisites + +### Local Setup +1. **Python 3.9+** installed +2. **Git** installed +3. **pip** package manager + +### Jenkins Setup +1. **Jenkins** server installed and running +2. **Python plugin** for Jenkins +3. **HTML Publisher plugin** for Jenkins +4. **JUnit plugin** for Jenkins (usually pre-installed) + +## Step-by-Step Procedure + +### Part 1: Local Testing (Optional but Recommended) + +1. **Clone/Navigate to your repository:** + ```bash + cd d:\Hello-World + ``` + +2. **Install dependencies:** + ```bash + pip install -r requirements.txt + ``` + +3. **Run tests locally:** + ```bash + # Run all tests + pytest tests/ -v + + # Run with coverage + pytest tests/ --cov=src --cov-report=html + + # Run specific test + pytest tests/test_payment.py::TestPaymentService::test_successful_payment -v + ``` + +4. **View coverage report:** + ```bash + # Open htmlcov/index.html in your browser + start htmlcov/index.html + ``` + +### Part 2: Jenkins Setup + +#### Option A: Jenkins Pipeline (Recommended) + +1. **Install Required Jenkins Plugins:** + - Go to Jenkins → Manage Jenkins → Manage Plugins + - Install: + - Pipeline + - Git plugin + - HTML Publisher plugin + - JUnit plugin + +2. **Create New Jenkins Job:** + - Click "New Item" + - Enter job name: `Payment-Test-Suite` + - Select "Pipeline" + - Click OK + +3. **Configure Pipeline:** + - Scroll to "Pipeline" section + - Definition: Select "Pipeline script from SCM" + - SCM: Select "Git" + - Repository URL: Enter your Git repository URL + - Branch: `*/main` (or your default branch) + - Script Path: `Jenkinsfile` + - Click "Save" + +4. **Run the Pipeline:** + - Click "Build Now" + - Monitor the build in the console output + +#### Option B: Freestyle Project + +1. **Create New Jenkins Job:** + - Click "New Item" + - Enter job name: `Payment-Test-Suite-Freestyle` + - Select "Freestyle project" + - Click OK + +2. **Source Code Management:** + - Select "Git" + - Repository URL: Enter your repository URL + - Branch: `*/main` + +3. **Build Steps:** + - Add build step → Execute Windows batch command + ```batch + python -m pip install --upgrade pip + pip install -r requirements.txt + pytest tests/ -v --junitxml=test-results.xml --html=test-report.html --self-contained-html + pytest tests/ --cov=src --cov-report=html --cov-report=xml + ``` + +4. **Post-build Actions:** + - Add "Publish JUnit test result report" + - Test report XMLs: `test-results.xml` + - Add "Publish HTML reports" + - HTML directory to archive: `htmlcov` + - Index page: `index.html` + - Report title: `Coverage Report` + +5. **Save and Build:** + - Click "Save" + - Click "Build Now" + +### Part 3: GitHub/Git Integration + +1. **Push your code to Git:** + ```bash + git add . + git commit -m "Add payment test suite and Jenkins pipeline" + git push origin main + ``` + +2. **Configure Webhook (Optional - for automatic builds):** + - In GitHub: Settings → Webhooks → Add webhook + - Payload URL: `http://your-jenkins-url/github-webhook/` + - Content type: `application/json` + - Select: "Just the push event" + - Active: ✓ + +3. **In Jenkins Job Configuration:** + - Build Triggers → Check "GitHub hook trigger for GITScm polling" + +### Part 4: Viewing Results + +After a successful build, you can view: + +1. **Test Results:** + - Click on build number → Test Results + - Shows all 21 test cases with pass/fail status + +2. **Coverage Report:** + - Click on build number → Coverage Report + - Shows code coverage percentage + +3. **HTML Test Report:** + - Click on build number → Test Report + - Detailed HTML report with test execution details + +4. **Console Output:** + - Click on build number → Console Output + - View complete build log + +## Jenkins Pipeline Stages + +The Jenkinsfile includes these stages: + +1. **Checkout** - Pulls code from repository +2. **Setup Python Environment** - Verifies Python installation +3. **Install Dependencies** - Installs required packages +4. **Run Tests** - Executes all test cases +5. **Code Coverage** - Generates coverage report +6. **Publish Results** - Publishes test and coverage reports + +## Troubleshooting + +### Common Issues + +1. **Python not found:** + - Add Python to system PATH + - In Jenkins: Manage Jenkins → Global Tool Configuration → Add Python + +2. **Module not found:** + - Ensure `requirements.txt` is installed + - Check virtual environment activation + +3. **Tests not discovered:** + - Verify pytest.ini configuration + - Check test file naming (test_*.py) + +4. **HTML reports not showing:** + - Install HTML Publisher plugin + - Configure Content Security Policy: + ``` + Manage Jenkins → Script Console → Run: + System.setProperty("hudson.model.DirectoryBrowserSupport.CSP", "") + ``` + +## Running Specific Test Categories + +```bash +# Run only successful payment tests +pytest tests/test_payment.py -k "successful" -v + +# Run only negative tests +pytest tests/test_payment.py -k "invalid" -v + +# Run with markers (if configured) +pytest tests/ -m "unit" -v +``` + +## CI/CD Best Practices + +1. **Run tests on every commit** +2. **Maintain >80% code coverage** +3. **Review failed tests immediately** +4. **Keep test execution time < 5 minutes** +5. **Archive test reports for compliance** + +## Next Steps + +1. Add more test cases for edge scenarios +2. Integrate with Slack/Email for notifications +3. Add performance testing +4. Implement test data management +5. Add security scanning stages + +## Support + +For issues or questions: +- Check Jenkins console output +- Review test logs in test-report.html +- Verify Python and dependency versions + +--- + +**Last Updated:** May 2026 diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..25c3eb7 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,23 @@ +[pytest] +# Pytest configuration file + +# Test discovery patterns +python_files = test_*.py +python_classes = Test* +python_functions = test_* + +# Test paths +testpaths = tests + +# Output options +addopts = + -v + --strict-markers + --tb=short + --disable-warnings + +# Markers +markers = + slow: marks tests as slow + integration: marks tests as integration tests + unit: marks tests as unit tests diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8371baf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +# Testing Dependencies +pytest==7.4.3 +pytest-html==4.1.1 +pytest-cov==4.1.0 + +# Code Quality +flake8==6.1.0 diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..375a132 --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +# Payment Service Package diff --git a/src/payment_service.py b/src/payment_service.py new file mode 100644 index 0000000..677ad89 --- /dev/null +++ b/src/payment_service.py @@ -0,0 +1,117 @@ +""" +Dummy Payment Service for Testing +""" + +class PaymentService: + """Simple payment service for demonstration""" + + def __init__(self): + self.transactions = [] + + def process_payment(self, amount, card_number, cvv): + """ + Process a payment transaction + + Args: + amount (float): Payment amount + card_number (str): Card number + cvv (str): CVV code + + Returns: + dict: Transaction result + """ + if amount <= 0: + return { + "status": "failed", + "message": "Invalid amount", + "transaction_id": None + } + + if not card_number or len(card_number) != 16: + return { + "status": "failed", + "message": "Invalid card number", + "transaction_id": None + } + + if not cvv or len(cvv) != 3: + return { + "status": "failed", + "message": "Invalid CVV", + "transaction_id": None + } + + transaction_id = f"TXN{len(self.transactions) + 1:06d}" + transaction = { + "transaction_id": transaction_id, + "amount": amount, + "card_number": f"****{card_number[-4:]}", + "status": "success" + } + + self.transactions.append(transaction) + + return { + "status": "success", + "message": "Payment processed successfully", + "transaction_id": transaction_id + } + + def refund_payment(self, transaction_id): + """ + Refund a payment + + Args: + transaction_id (str): Transaction ID to refund + + Returns: + dict: Refund result + """ + transaction = next( + (t for t in self.transactions if t["transaction_id"] == transaction_id), + None + ) + + if not transaction: + return { + "status": "failed", + "message": "Transaction not found" + } + + if transaction.get("refunded"): + return { + "status": "failed", + "message": "Transaction already refunded" + } + + transaction["refunded"] = True + + return { + "status": "success", + "message": "Refund processed successfully", + "transaction_id": transaction_id + } + + def get_transaction(self, transaction_id): + """ + Get transaction details + + Args: + transaction_id (str): Transaction ID + + Returns: + dict: Transaction details or None + """ + return next( + (t for t in self.transactions if t["transaction_id"] == transaction_id), + None + ) + + def get_all_transactions(self): + """ + Get all transactions + + Returns: + list: All transactions + """ + return self.transactions.copy() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..eb3c09d --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1 @@ +# Test Package diff --git a/tests/test_payment.py b/tests/test_payment.py new file mode 100644 index 0000000..0fef8e3 --- /dev/null +++ b/tests/test_payment.py @@ -0,0 +1,168 @@ +""" +Test cases for Payment Service +""" +import pytest +import sys +import os + +# Add src directory to path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'src'))) + +from payment_service import PaymentService + + +class TestPaymentService: + """Test suite for PaymentService""" + + @pytest.fixture + def payment_service(self): + """Create a fresh payment service instance for each test""" + return PaymentService() + + # Positive Test Cases + + def test_successful_payment(self, payment_service): + """Test successful payment processing""" + result = payment_service.process_payment( + amount=100.50, + card_number="1234567890123456", + cvv="123" + ) + + assert result["status"] == "success" + assert result["transaction_id"] is not None + assert result["message"] == "Payment processed successfully" + + def test_multiple_payments(self, payment_service): + """Test processing multiple payments""" + result1 = payment_service.process_payment(100.00, "1234567890123456", "123") + result2 = payment_service.process_payment(200.00, "9876543210987654", "456") + + assert result1["status"] == "success" + assert result2["status"] == "success" + assert result1["transaction_id"] != result2["transaction_id"] + assert len(payment_service.get_all_transactions()) == 2 + + def test_get_transaction(self, payment_service): + """Test retrieving transaction details""" + result = payment_service.process_payment(150.00, "1234567890123456", "123") + transaction_id = result["transaction_id"] + + transaction = payment_service.get_transaction(transaction_id) + + assert transaction is not None + assert transaction["transaction_id"] == transaction_id + assert transaction["amount"] == 150.00 + + def test_successful_refund(self, payment_service): + """Test successful refund processing""" + # First make a payment + payment_result = payment_service.process_payment(100.00, "1234567890123456", "123") + transaction_id = payment_result["transaction_id"] + + # Then refund it + refund_result = payment_service.refund_payment(transaction_id) + + assert refund_result["status"] == "success" + assert refund_result["message"] == "Refund processed successfully" + + # Negative Test Cases + + def test_invalid_amount_zero(self, payment_service): + """Test payment with zero amount""" + result = payment_service.process_payment(0, "1234567890123456", "123") + + assert result["status"] == "failed" + assert result["message"] == "Invalid amount" + assert result["transaction_id"] is None + + def test_invalid_amount_negative(self, payment_service): + """Test payment with negative amount""" + result = payment_service.process_payment(-50.00, "1234567890123456", "123") + + assert result["status"] == "failed" + assert result["message"] == "Invalid amount" + + def test_invalid_card_number_short(self, payment_service): + """Test payment with short card number""" + result = payment_service.process_payment(100.00, "123456", "123") + + assert result["status"] == "failed" + assert result["message"] == "Invalid card number" + + def test_invalid_card_number_empty(self, payment_service): + """Test payment with empty card number""" + result = payment_service.process_payment(100.00, "", "123") + + assert result["status"] == "failed" + assert result["message"] == "Invalid card number" + + def test_invalid_cvv_short(self, payment_service): + """Test payment with short CVV""" + result = payment_service.process_payment(100.00, "1234567890123456", "12") + + assert result["status"] == "failed" + assert result["message"] == "Invalid CVV" + + def test_invalid_cvv_empty(self, payment_service): + """Test payment with empty CVV""" + result = payment_service.process_payment(100.00, "1234567890123456", "") + + assert result["status"] == "failed" + assert result["message"] == "Invalid CVV" + + def test_refund_nonexistent_transaction(self, payment_service): + """Test refunding a transaction that doesn't exist""" + result = payment_service.refund_payment("TXN999999") + + assert result["status"] == "failed" + assert result["message"] == "Transaction not found" + + def test_duplicate_refund(self, payment_service): + """Test refunding the same transaction twice""" + # Make a payment + payment_result = payment_service.process_payment(100.00, "1234567890123456", "123") + transaction_id = payment_result["transaction_id"] + + # First refund should succeed + refund1 = payment_service.refund_payment(transaction_id) + assert refund1["status"] == "success" + + # Second refund should fail + refund2 = payment_service.refund_payment(transaction_id) + assert refund2["status"] == "failed" + assert refund2["message"] == "Transaction already refunded" + + # Edge Cases + + def test_large_amount_payment(self, payment_service): + """Test payment with large amount""" + result = payment_service.process_payment(999999.99, "1234567890123456", "123") + + assert result["status"] == "success" + + def test_small_amount_payment(self, payment_service): + """Test payment with small valid amount""" + result = payment_service.process_payment(0.01, "1234567890123456", "123") + + assert result["status"] == "success" + + def test_get_all_transactions_empty(self, payment_service): + """Test getting all transactions when none exist""" + transactions = payment_service.get_all_transactions() + + assert transactions == [] + + def test_get_nonexistent_transaction(self, payment_service): + """Test getting a transaction that doesn't exist""" + transaction = payment_service.get_transaction("TXN999999") + + assert transaction is None + + def test_card_number_masking(self, payment_service): + """Test that card numbers are properly masked in transactions""" + payment_service.process_payment(100.00, "1234567890123456", "123") + transaction = payment_service.get_all_transactions()[0] + + assert transaction["card_number"] == "****3456" + assert "1234567890123456" not in str(transaction)