From a5ec70e0af7d9b84af7c96b4efd7357388ad61ed Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 11 Sep 2025 18:44:09 +0300 Subject: [PATCH] Add test data schedule configuration methods to ConfigManager --- README.md | 376 +++++++++++++ pom.xml | 162 ++++++ .../financial/api/config/ConfigManager.java | 245 +++++++++ .../api/models/credit/CreditApplication.java | 459 ++++++++++++++++ .../api/models/credit/CreditScore.java | 320 +++++++++++ .../api/models/wallet/Transaction.java | 330 ++++++++++++ .../financial/api/models/wallet/Wallet.java | 237 +++++++++ .../financial/api/services/BaseApiClient.java | 259 +++++++++ .../financial/api/services/CreditService.java | 312 +++++++++++ .../financial/api/services/WalletService.java | 279 ++++++++++ .../api/utils/AnnotationTransformer.java | 271 ++++++++++ .../financial/api/utils/CouchbaseUtil.java | 484 +++++++++++++++++ .../com/financial/api/utils/DatabaseUtil.java | 274 ++++++++++ .../api/utils/ElasticsearchUtil.java | 395 ++++++++++++++ .../financial/api/utils/TestDataFactory.java | 445 ++++++++++++++++ .../com/financial/api/utils/TestListener.java | 207 ++++++++ src/main/resources/config.properties | 44 ++ src/main/resources/log4j2.xml | 85 +++ .../financial/api/tests/CreditApiTest.java | 486 +++++++++++++++++ .../api/tests/CreditPerformanceTest.java | 469 +++++++++++++++++ .../api/tests/CreditSecurityTest.java | 496 ++++++++++++++++++ .../financial/api/tests/WalletApiTest.java | 420 +++++++++++++++ .../api/tests/WalletPerformanceTest.java | 472 +++++++++++++++++ .../api/tests/WalletSecurityTest.java | 359 +++++++++++++ src/test/resources/testng.xml | 193 +++++++ 25 files changed, 8079 insertions(+) create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/com/financial/api/config/ConfigManager.java create mode 100644 src/main/java/com/financial/api/models/credit/CreditApplication.java create mode 100644 src/main/java/com/financial/api/models/credit/CreditScore.java create mode 100644 src/main/java/com/financial/api/models/wallet/Transaction.java create mode 100644 src/main/java/com/financial/api/models/wallet/Wallet.java create mode 100644 src/main/java/com/financial/api/services/BaseApiClient.java create mode 100644 src/main/java/com/financial/api/services/CreditService.java create mode 100644 src/main/java/com/financial/api/services/WalletService.java create mode 100644 src/main/java/com/financial/api/utils/AnnotationTransformer.java create mode 100644 src/main/java/com/financial/api/utils/CouchbaseUtil.java create mode 100644 src/main/java/com/financial/api/utils/DatabaseUtil.java create mode 100644 src/main/java/com/financial/api/utils/ElasticsearchUtil.java create mode 100644 src/main/java/com/financial/api/utils/TestDataFactory.java create mode 100644 src/main/java/com/financial/api/utils/TestListener.java create mode 100644 src/main/resources/config.properties create mode 100644 src/main/resources/log4j2.xml create mode 100644 src/test/java/com/financial/api/tests/CreditApiTest.java create mode 100644 src/test/java/com/financial/api/tests/CreditPerformanceTest.java create mode 100644 src/test/java/com/financial/api/tests/CreditSecurityTest.java create mode 100644 src/test/java/com/financial/api/tests/WalletApiTest.java create mode 100644 src/test/java/com/financial/api/tests/WalletPerformanceTest.java create mode 100644 src/test/java/com/financial/api/tests/WalletSecurityTest.java create mode 100644 src/test/resources/testng.xml diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f81fe4 --- /dev/null +++ b/README.md @@ -0,0 +1,376 @@ +# Dracarys - Financial API Automation Framework + +A comprehensive test automation framework for testing financial APIs, including wallet and credit scoring services. + +## Table of Contents + +- [Features](#features) +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Configuration](#configuration) +- [Project Structure](#project-structure) +- [Running Tests](#running-tests) +- [Test Categories](#test-categories) +- [Reporting](#reporting) +- [Extending the Framework](#extending-the-framework) +- [Best Practices](#best-practices) + +## Features + +- **API Testing**: Comprehensive testing of REST APIs +- **Performance Testing**: Load and stress testing capabilities +- **Security Testing**: Security vulnerability testing +- **Data-Driven Testing**: Support for data-driven tests +- **Parallel Execution**: Parallel test execution for faster results +- **Detailed Reporting**: Comprehensive test reports with metrics +- **Database Validation**: Ability to validate data in databases +- **Environment Configuration**: Support for multiple environments + +## Prerequisites + +- Java 8 or higher +- Maven 3.6 or higher +- TestNG 7.4 or higher +- RestAssured 4.3 or higher +- MySQL or PostgreSQL (for database validation) + +## Installation + +1. Clone the repository: + ```bash + git clone https://github.com/iwasforcedtobehere/Dracarys.git + cd Dracarys + ``` + +2. Install dependencies: + ```bash + mvn clean install + ``` + +## Configuration + +### Environment Configuration + +The framework supports multiple environments (dev, test, staging, prod). Configuration files are located in `src/main/resources/`. + +1. **config.properties**: + ```properties + # Base URI for API + baseURI=https://api.example.com + + # API version + apiVersion=v1 + + # Environment + environment=test + + # Database configuration + db.driver=com.mysql.jdbc.Driver + db.url=jdbc:mysql://localhost:3306/test_db + db.username=test_user + db.password=test_password + + # Authentication + auth.type=bearer + auth.token=your_auth_token + ``` + +2. **log4j2.xml**: + Configure logging levels and appenders as needed. + +### Test Configuration + +Test configurations are defined in `src/test/resources/testng.xml`. You can create different test suites for different purposes. + +## Project Structure + +``` +Dracarys/ +├── src/ +│ ├── main/ +│ │ ├── java/ +│ │ │ └── com/financial/api/ +│ │ │ ├── config/ # Configuration classes +│ │ │ ├── models/ # Data models +│ │ │ │ ├── wallet/ # Wallet-related models +│ │ │ │ └── credit/ # Credit-related models +│ │ │ ├── services/ # API service classes +│ │ │ └── utils/ # Utility classes +│ │ └── resources/ +│ │ ├── config.properties # Configuration file +│ │ └── log4j2.xml # Logging configuration +│ └── test/ +│ ├── java/ +│ │ └── com/financial/api/ +│ │ └── tests/ # Test classes +│ │ ├── WalletApiTest.java +│ │ ├── CreditApiTest.java +│ │ ├── WalletPerformanceTest.java +│ │ ├── CreditPerformanceTest.java +│ │ ├── WalletSecurityTest.java +│ │ └── CreditSecurityTest.java +│ └── resources/ +│ └── testng.xml # Test configuration +├── pom.xml # Maven dependencies +└── README.md # This file +``` + +## Running Tests + +### Running All Tests + +```bash +mvn clean test +``` + +### Running Specific Test Suite + +```bash +mvn clean test -Dsuite=src/test/resources/testng.xml +``` + +### Running Tests with Specific Groups + +```bash +mvn clean test -Dgroups=smoke +mvn clean test -Dgroups=regression +mvn clean test -Dgroups=performance +mvn clean test -Dgroups=security +``` + +### Running Tests in Parallel + +```bash +mvn clean test -Dparallel=methods -DthreadCount=5 +``` + +## Test Categories + +The framework includes several categories of tests: + +### 1. Functional Tests + +- **WalletApiTest**: Tests for wallet operations + - Create wallet + - Get wallet + - Update wallet + - Get wallet balance + - Create transaction + - Get transaction + - Update transaction status + - Deposit funds + - Withdraw funds + +- **CreditApiTest**: Tests for credit operations + - Submit credit application + - Get credit application + - Update credit application + - Get applications by status + - Calculate credit score + - Get credit score + - Get credit score history + - Approve application + - Reject application + - Get credit products + +### 2. Performance Tests + +- **WalletPerformanceTest**: Performance tests for wallet operations + - Create multiple wallets + - Get multiple wallets + - Create multiple transactions + - Deposit funds performance + - Get wallet balance performance + - Get transactions performance + - Transfer funds performance + +- **CreditPerformanceTest**: Performance tests for credit operations + - Submit multiple applications + - Get multiple applications + - Calculate multiple credit scores + - Get multiple credit scores + - Get credit score histories + - Get applications by status performance + - Get credit products performance + +### 3. Security Tests + +- **WalletSecurityTest**: Security tests for wallet operations + - SQL injection in wallet creation + - XSS in wallet creation + - Authentication bypass in wallet retrieval + - Authorization bypass in wallet retrieval + - IDOR in wallet retrieval + - SQL injection in transaction creation + - XSS in transaction creation + - Negative amount in transaction creation + - Extremely large amount in transaction creation + - Authentication bypass in transaction retrieval + - Authorization bypass in transaction retrieval + - CSRF protection in wallet update + - Rate limiting in wallet creation + - Input validation in wallet creation + - Sensitive data exposure in wallet response + +- **CreditSecurityTest**: Security tests for credit operations + - SQL injection in credit application submission + - XSS in credit application submission + - Authentication bypass in credit application retrieval + - Authorization bypass in credit application retrieval + - IDOR in credit application retrieval + - SQL injection in credit score calculation + - XSS in credit score calculation + - Negative loan amount in credit application submission + - Extremely large loan amount in credit application submission + - Negative loan term in credit application submission + - Extremely large loan term in credit application submission + - Authentication bypass in credit score retrieval + - Authorization bypass in credit score retrieval + - CSRF protection in credit application update + - Rate limiting in credit application submission + - Input validation in credit application submission + - Sensitive data exposure in credit application response + - Sensitive data exposure in credit score response + +## Reporting + +The framework generates detailed reports for test execution: + +### Console Reports + +- Real-time test execution status +- Test pass/fail statistics +- Test execution time metrics +- Error logs and stack traces + +### HTML Reports + +- Comprehensive test reports with charts +- Test execution summary +- Detailed test results +- Performance metrics +- Security vulnerability findings + +### Log Files + +- Detailed execution logs +- Error logs +- Debug information + +## Extending the Framework + +### Adding New API Tests + +1. Create a new test class in `src/test/java/com/financial/api/tests/` +2. Extend the appropriate base class if available +3. Add test methods with appropriate annotations +4. Use the existing service classes or create new ones + +### Adding New API Services + +1. Create a new service class in `src/main/java/com/financial/api/services/` +2. Extend the `BaseService` class +3. Implement methods for API operations +4. Use RestAssured for HTTP requests + +### Adding New Data Models + +1. Create a new model class in `src/main/java/com/financial/api/models/` +2. Define fields with appropriate data types +3. Add getters and setters +4. Add validation annotations if needed + +### Adding New Utilities + +1. Create a new utility class in `src/main/java/com/financial/api/utils/` +2. Implement utility methods +3. Make methods static if appropriate +4. Add Javadoc comments + +## Best Practices + +### Test Design + +- Follow the Arrange-Act-Assert pattern +- Keep tests independent and isolated +- Use descriptive test names +- Add proper test documentation +- Use assertions effectively + +### Code Quality + +- Follow Java coding standards +- Use meaningful variable and method names +- Add proper comments and documentation +- Keep methods small and focused +- Handle exceptions properly + +### Performance Testing + +- Define realistic performance thresholds +- Use appropriate load levels +- Monitor system resources during tests +- Analyze performance metrics +- Identify and address bottlenecks + +### Security Testing + +- Test for common security vulnerabilities +- Use both positive and negative test cases +- Validate input sanitization +- Test authentication and authorization +- Check for sensitive data exposure + +### Maintenance + +- Regularly update dependencies +- Refactor code as needed +- Add new tests for new features +- Fix failing tests promptly +- Keep documentation up to date + +## Troubleshooting + +### Common Issues + +1. **Test Failures Due to Environment Changes** + - Update configuration files + - Check API endpoints + - Verify authentication tokens + +2. **Database Connection Issues** + - Check database credentials + - Verify database URL + - Ensure database is running + +3. **Performance Test Failures** + - Check system resources + - Verify test data + - Adjust performance thresholds + +4. **Security Test Failures** + - Check API security implementations + - Verify input validation + - Review authentication and authorization + +### Getting Help + +- Check the project documentation +- Review test code examples +- Contact the framework maintainers +- Submit issues on GitHub + +## Contributing + +We welcome contributions to the framework! Please follow these guidelines: + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests for new functionality +5. Ensure all tests pass +6. Submit a pull request + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9ace69d --- /dev/null +++ b/pom.xml @@ -0,0 +1,162 @@ + + + 4.0.0 + + com.financial.api + dracarys + 1.0.0 + jar + + Dracarys + Financial API Automation Framework + https://github.com/iwasforcedtobehere/Dracarys + + + 1.8 + 1.8 + UTF-8 + + + 7.4.0 + 4.3.3 + 2.12.3 + 2.14.1 + 8.0.25 + 42.2.23 + 1.18.20 + 3.12.0 + 5.0.8 + 1.1.1 + + + + + + org.testng + testng + ${testng.version} + test + + + + + io.rest-assured + rest-assured + ${rest-assured.version} + test + + + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.apache.logging.log4j + log4j-api + ${log4j.version} + + + + + mysql + mysql-connector-java + ${mysql-connector.version} + test + + + + + org.postgresql + postgresql + ${postgresql.version} + test + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + + + com.aventstack + extentreports + ${extentreports.version} + + + + + com.googlecode.json-simple + json-simple + ${json-simple.version} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + src/test/resources/testng.xml + + + ${baseURI} + ${apiVersion} + ${environment} + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.0.0-M5 + + + + integration-test + verify + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/financial/api/config/ConfigManager.java b/src/main/java/com/financial/api/config/ConfigManager.java new file mode 100644 index 0000000..c24e165 --- /dev/null +++ b/src/main/java/com/financial/api/config/ConfigManager.java @@ -0,0 +1,245 @@ +package com.financial.api.config; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Configuration Manager class to handle loading and accessing configuration properties. + */ +public class ConfigManager { + private static final Logger logger = LogManager.getLogger(ConfigManager.class); + private static ConfigManager instance; + private Properties properties; + + private ConfigManager() { + properties = new Properties(); + loadProperties(); + } + + /** + * Get the singleton instance of ConfigManager + * @return ConfigManager instance + */ + public static synchronized ConfigManager getInstance() { + if (instance == null) { + instance = new ConfigManager(); + } + return instance; + } + + /** + * Load properties from the configuration file + */ + private void loadProperties() { + try (InputStream input = getClass().getClassLoader().getResourceAsStream("config.properties")) { + if (input == null) { + logger.error("Unable to find config.properties"); + return; + } + properties.load(input); + logger.info("Configuration loaded successfully"); + } catch (IOException e) { + logger.error("Error loading configuration: {}", e.getMessage(), e); + } + } + + /** + * Get property value as String + * @param key Property key + * @return Property value as String + */ + public String getProperty(String key) { + return properties.getProperty(key); + } + + /** + * Get property value as String with default value + * @param key Property key + * @param defaultValue Default value if property not found + * @return Property value as String or default value + */ + public String getProperty(String key, String defaultValue) { + return properties.getProperty(key, defaultValue); + } + + /** + * Get property value as Integer + * @param key Property key + * @return Property value as Integer + */ + public int getIntProperty(String key) { + try { + return Integer.parseInt(properties.getProperty(key)); + } catch (NumberFormatException e) { + logger.error("Error parsing integer property for key {}: {}", key, e.getMessage(), e); + throw new RuntimeException("Invalid integer property for key: " + key, e); + } + } + + /** + * Get property value as Integer with default value + * @param key Property key + * @param defaultValue Default value if property not found or invalid + * @return Property value as Integer or default value + */ + public int getIntProperty(String key, int defaultValue) { + try { + return Integer.parseInt(properties.getProperty(key, String.valueOf(defaultValue))); + } catch (NumberFormatException e) { + logger.warn("Error parsing integer property for key {}, using default value {}: {}", + key, defaultValue, e.getMessage(), e); + return defaultValue; + } + } + + /** + * Get property value as Boolean + * @param key Property key + * @return Property value as Boolean + */ + public boolean getBooleanProperty(String key) { + return Boolean.parseBoolean(properties.getProperty(key)); + } + + /** + * Get property value as Boolean with default value + * @param key Property key + * @param defaultValue Default value if property not found + * @return Property value as Boolean or default value + */ + public boolean getBooleanProperty(String key, boolean defaultValue) { + return Boolean.parseBoolean(properties.getProperty(key, String.valueOf(defaultValue))); + } + + /** + * Get API base URL + * @return API base URL + */ + public String getApiBaseUrl() { + return getProperty("api.base.url"); + } + + /** + * Get API version + * @return API version + */ + public String getApiVersion() { + return getProperty("api.version"); + } + + /** + * Get API timeout in milliseconds + * @return API timeout + */ + public int getApiTimeout() { + return getIntProperty("api.timeout", 10000); + } + + /** + * Get database URL + * @return Database URL + */ + public String getDbUrl() { + return getProperty("db.url"); + } + + /** + * Get database username + * @return Database username + */ + public String getDbUsername() { + return getProperty("db.username"); + } + + /** + * Get database password + * @return Database password + */ + public String getDbPassword() { + return getProperty("db.password"); + } + + /** + * Get Elasticsearch host + * @return Elasticsearch host + */ + public String getElasticsearchHost() { + return getProperty("elasticsearch.host"); + } + + /** + * Get Elasticsearch port + * @return Elasticsearch port + */ + public int getElasticsearchPort() { + return getIntProperty("elasticsearch.port", 9200); + } + + /** + * Get Couchbase host + * @return Couchbase host + */ + public String getCouchbaseHost() { + return getProperty("couchbase.host"); + } + + /** + * Get Couchbase username + * @return Couchbase username + */ + public String getCouchbaseUsername() { + return getProperty("couchbase.username"); + } + + /** + * Get Couchbase password + * @return Couchbase password + */ + public String getCouchbasePassword() { + return getProperty("couchbase.password"); + } + + /** + * Get Couchbase bucket name + * @return Couchbase bucket name + */ + public String getCouchbaseBucketName() { + return getProperty("couchbase.bucket.name"); + } + + /** + * Get test data directory + * @return Test data directory + */ + public String getTestDataDir() { + return getProperty("test.data.dir", "src/test/resources/testdata"); + } + + /** + * Get test report directory + * @return Test report directory + */ + public String getTestReportDir() { + return getProperty("test.report.dir", "target/test-reports"); + } + + /** + * Get log directory + * @return Log directory + */ + public String getLogDir() { + return getProperty("log.dir", "target/logs"); + } + + /** + * Get current environment + * @return Current environment (dev, staging, prod) + */ + public String getEnvironment() { + return getProperty("env", "dev"); + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/models/credit/CreditApplication.java b/src/main/java/com/financial/api/models/credit/CreditApplication.java new file mode 100644 index 0000000..0a35854 --- /dev/null +++ b/src/main/java/com/financial/api/models/credit/CreditApplication.java @@ -0,0 +1,459 @@ +package com.financial.api.models.credit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.time.LocalDate; +import java.util.UUID; + +/** + * CreditApplication model representing a credit application in the financial system. + */ +public class CreditApplication { + private String id; + private String customerId; + private String applicantName; + private String applicantEmail; + private String applicantPhone; + private LocalDate dateOfBirth; + private BigDecimal monthlyIncome; + private BigDecimal loanAmount; + private int loanTermMonths; + private String loanPurpose; + private EmploymentStatus employmentStatus; + private CreditApplicationStatus status; + private Integer creditScore; + private BigDecimal approvedAmount; + private BigDecimal interestRate; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private LocalDateTime reviewedAt; + private String reviewedBy; + private String rejectionReason; + + public enum EmploymentStatus { + @JsonProperty("EMPLOYED_FULL_TIME") + EMPLOYED_FULL_TIME, + + @JsonProperty("EMPLOYED_PART_TIME") + EMPLOYED_PART_TIME, + + @JsonProperty("SELF_EMPLOYED") + SELF_EMPLOYED, + + @JsonProperty("UNEMPLOYED") + UNEMPLOYED, + + @JsonProperty("RETIRED") + RETIRED, + + @JsonProperty("STUDENT") + STUDENT + } + + public enum CreditApplicationStatus { + @JsonProperty("DRAFT") + DRAFT, + + @JsonProperty("SUBMITTED") + SUBMITTED, + + @JsonProperty("UNDER_REVIEW") + UNDER_REVIEW, + + @JsonProperty("APPROVED") + APPROVED, + + @JsonProperty("REJECTED") + REJECTED, + + @JsonProperty("CANCELLED") + CANCELLED + } + + /** + * Default constructor + */ + public CreditApplication() { + this.id = UUID.randomUUID().toString(); + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + this.status = CreditApplicationStatus.DRAFT; + } + + /** + * Parameterized constructor + * @param customerId Customer ID + * @param applicantName Applicant name + * @param applicantEmail Applicant email + * @param applicantPhone Applicant phone + * @param dateOfBirth Date of birth + * @param monthlyIncome Monthly income + * @param loanAmount Loan amount + * @param loanTermMonths Loan term in months + * @param loanPurpose Loan purpose + * @param employmentStatus Employment status + */ + public CreditApplication(String customerId, String applicantName, String applicantEmail, + String applicantPhone, LocalDate dateOfBirth, BigDecimal monthlyIncome, + BigDecimal loanAmount, int loanTermMonths, String loanPurpose, + EmploymentStatus employmentStatus) { + this(); + this.customerId = customerId; + this.applicantName = applicantName; + this.applicantEmail = applicantEmail; + this.applicantPhone = applicantPhone; + this.dateOfBirth = dateOfBirth; + this.monthlyIncome = monthlyIncome; + this.loanAmount = loanAmount; + this.loanTermMonths = loanTermMonths; + this.loanPurpose = loanPurpose; + this.employmentStatus = employmentStatus; + } + + /** + * Get application ID + * @return Application ID + */ + public String getId() { + return id; + } + + /** + * Set application ID + * @param id Application ID + */ + public void setId(String id) { + this.id = id; + } + + /** + * Get customer ID + * @return Customer ID + */ + public String getCustomerId() { + return customerId; + } + + /** + * Set customer ID + * @param customerId Customer ID + */ + public void setCustomerId(String customerId) { + this.customerId = customerId; + } + + /** + * Get applicant name + * @return Applicant name + */ + public String getApplicantName() { + return applicantName; + } + + /** + * Set applicant name + * @param applicantName Applicant name + */ + public void setApplicantName(String applicantName) { + this.applicantName = applicantName; + } + + /** + * Get applicant email + * @return Applicant email + */ + public String getApplicantEmail() { + return applicantEmail; + } + + /** + * Set applicant email + * @param applicantEmail Applicant email + */ + public void setApplicantEmail(String applicantEmail) { + this.applicantEmail = applicantEmail; + } + + /** + * Get applicant phone + * @return Applicant phone + */ + public String getApplicantPhone() { + return applicantPhone; + } + + /** + * Set applicant phone + * @param applicantPhone Applicant phone + */ + public void setApplicantPhone(String applicantPhone) { + this.applicantPhone = applicantPhone; + } + + /** + * Get date of birth + * @return Date of birth + */ + public LocalDate getDateOfBirth() { + return dateOfBirth; + } + + /** + * Set date of birth + * @param dateOfBirth Date of birth + */ + public void setDateOfBirth(LocalDate dateOfBirth) { + this.dateOfBirth = dateOfBirth; + } + + /** + * Get monthly income + * @return Monthly income + */ + public BigDecimal getMonthlyIncome() { + return monthlyIncome; + } + + /** + * Set monthly income + * @param monthlyIncome Monthly income + */ + public void setMonthlyIncome(BigDecimal monthlyIncome) { + this.monthlyIncome = monthlyIncome; + } + + /** + * Get loan amount + * @return Loan amount + */ + public BigDecimal getLoanAmount() { + return loanAmount; + } + + /** + * Set loan amount + * @param loanAmount Loan amount + */ + public void setLoanAmount(BigDecimal loanAmount) { + this.loanAmount = loanAmount; + } + + /** + * Get loan term in months + * @return Loan term in months + */ + public int getLoanTermMonths() { + return loanTermMonths; + } + + /** + * Set loan term in months + * @param loanTermMonths Loan term in months + */ + public void setLoanTermMonths(int loanTermMonths) { + this.loanTermMonths = loanTermMonths; + } + + /** + * Get loan purpose + * @return Loan purpose + */ + public String getLoanPurpose() { + return loanPurpose; + } + + /** + * Set loan purpose + * @param loanPurpose Loan purpose + */ + public void setLoanPurpose(String loanPurpose) { + this.loanPurpose = loanPurpose; + } + + /** + * Get employment status + * @return Employment status + */ + public EmploymentStatus getEmploymentStatus() { + return employmentStatus; + } + + /** + * Set employment status + * @param employmentStatus Employment status + */ + public void setEmploymentStatus(EmploymentStatus employmentStatus) { + this.employmentStatus = employmentStatus; + } + + /** + * Get application status + * @return Application status + */ + public CreditApplicationStatus getStatus() { + return status; + } + + /** + * Set application status + * @param status Application status + */ + public void setStatus(CreditApplicationStatus status) { + this.status = status; + } + + /** + * Get credit score + * @return Credit score + */ + public Integer getCreditScore() { + return creditScore; + } + + /** + * Set credit score + * @param creditScore Credit score + */ + public void setCreditScore(Integer creditScore) { + this.creditScore = creditScore; + } + + /** + * Get approved amount + * @return Approved amount + */ + public BigDecimal getApprovedAmount() { + return approvedAmount; + } + + /** + * Set approved amount + * @param approvedAmount Approved amount + */ + public void setApprovedAmount(BigDecimal approvedAmount) { + this.approvedAmount = approvedAmount; + } + + /** + * Get interest rate + * @return Interest rate + */ + public BigDecimal getInterestRate() { + return interestRate; + } + + /** + * Set interest rate + * @param interestRate Interest rate + */ + public void setInterestRate(BigDecimal interestRate) { + this.interestRate = interestRate; + } + + /** + * Get creation timestamp + * @return Creation timestamp + */ + public LocalDateTime getCreatedAt() { + return createdAt; + } + + /** + * Set creation timestamp + * @param createdAt Creation timestamp + */ + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + /** + * Get last update timestamp + * @return Last update timestamp + */ + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + /** + * Set last update timestamp + * @param updatedAt Last update timestamp + */ + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + /** + * Get review timestamp + * @return Review timestamp + */ + public LocalDateTime getReviewedAt() { + return reviewedAt; + } + + /** + * Set review timestamp + * @param reviewedAt Review timestamp + */ + public void setReviewedAt(LocalDateTime reviewedAt) { + this.reviewedAt = reviewedAt; + } + + /** + * Get reviewer ID + * @return Reviewer ID + */ + public String getReviewedBy() { + return reviewedBy; + } + + /** + * Set reviewer ID + * @param reviewedBy Reviewer ID + */ + public void setReviewedBy(String reviewedBy) { + this.reviewedBy = reviewedBy; + } + + /** + * Get rejection reason + * @return Rejection reason + */ + public String getRejectionReason() { + return rejectionReason; + } + + /** + * Set rejection reason + * @param rejectionReason Rejection reason + */ + public void setRejectionReason(String rejectionReason) { + this.rejectionReason = rejectionReason; + } + + @Override + public String toString() { + return "CreditApplication{" + + "id='" + id + '\'' + + ", customerId='" + customerId + '\'' + + ", applicantName='" + applicantName + '\'' + + ", applicantEmail='" + applicantEmail + '\'' + + ", applicantPhone='" + applicantPhone + '\'' + + ", dateOfBirth=" + dateOfBirth + + ", monthlyIncome=" + monthlyIncome + + ", loanAmount=" + loanAmount + + ", loanTermMonths=" + loanTermMonths + + ", loanPurpose='" + loanPurpose + '\'' + + ", employmentStatus=" + employmentStatus + + ", status=" + status + + ", creditScore=" + creditScore + + ", approvedAmount=" + approvedAmount + + ", interestRate=" + interestRate + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + ", reviewedAt=" + reviewedAt + + ", reviewedBy='" + reviewedBy + '\'' + + ", rejectionReason='" + rejectionReason + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/models/credit/CreditScore.java b/src/main/java/com/financial/api/models/credit/CreditScore.java new file mode 100644 index 0000000..2ccc356 --- /dev/null +++ b/src/main/java/com/financial/api/models/credit/CreditScore.java @@ -0,0 +1,320 @@ +package com.financial.api.models.credit; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * CreditScore model representing a credit score calculation in the financial system. + */ +public class CreditScore { + private String id; + private String applicationId; + private String customerId; + private int score; + private String scoreBand; + private CreditRating rating; + private LocalDateTime calculatedAt; + private LocalDateTime expiresAt; + private String calculationMethod; + private String factors; + private String recommendation; + + public enum CreditRating { + @JsonProperty("EXCELLENT") + EXCELLENT, // 800-850 + + @JsonProperty("VERY_GOOD") + VERY_GOOD, // 740-799 + + @JsonProperty("GOOD") + GOOD, // 670-739 + + @JsonProperty("FAIR") + FAIR, // 580-669 + + @JsonProperty("POOR") + POOR, // 300-579 + + @JsonProperty("NO_SCORE") + NO_SCORE // No credit history + } + + /** + * Default constructor + */ + public CreditScore() { + this.id = UUID.randomUUID().toString(); + this.calculatedAt = LocalDateTime.now(); + } + + /** + * Parameterized constructor + * @param applicationId Application ID + * @param customerId Customer ID + * @param score Credit score value + * @param calculationMethod Calculation method used + */ + public CreditScore(String applicationId, String customerId, int score, String calculationMethod) { + this(); + this.applicationId = applicationId; + this.customerId = customerId; + this.score = score; + this.calculationMethod = calculationMethod; + this.scoreBand = determineScoreBand(score); + this.rating = determineRating(score); + this.recommendation = generateRecommendation(score); + + // Set expiration to 30 days from calculation + this.expiresAt = calculatedAt.plusDays(30); + } + + /** + * Determine score band based on score value + * @param score Credit score value + * @return Score band description + */ + private String determineScoreBand(int score) { + if (score >= 800) return "800-850"; + if (score >= 740) return "740-799"; + if (score >= 670) return "670-739"; + if (score >= 580) return "580-669"; + if (score >= 300) return "300-579"; + return "No Score"; + } + + /** + * Determine credit rating based on score value + * @param score Credit score value + * @return Credit rating + */ + private CreditRating determineRating(int score) { + if (score >= 800) return CreditRating.EXCELLENT; + if (score >= 740) return CreditRating.VERY_GOOD; + if (score >= 670) return CreditRating.GOOD; + if (score >= 580) return CreditRating.FAIR; + if (score >= 300) return CreditRating.POOR; + return CreditRating.NO_SCORE; + } + + /** + * Generate recommendation based on credit score + * @param score Credit score value + * @return Recommendation text + */ + private String generateRecommendation(int score) { + if (score >= 740) { + return "Excellent credit. Eligible for best loan terms and interest rates."; + } else if (score >= 670) { + return "Good credit. Eligible for competitive loan terms."; + } else if (score >= 580) { + return "Fair credit. May qualify for loans but with less favorable terms."; + } else { + return "Poor credit. Consider improving credit score before applying for loans."; + } + } + + /** + * Get credit score ID + * @return Credit score ID + */ + public String getId() { + return id; + } + + /** + * Set credit score ID + * @param id Credit score ID + */ + public void setId(String id) { + this.id = id; + } + + /** + * Get application ID + * @return Application ID + */ + public String getApplicationId() { + return applicationId; + } + + /** + * Set application ID + * @param applicationId Application ID + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + /** + * Get customer ID + * @return Customer ID + */ + public String getCustomerId() { + return customerId; + } + + /** + * Set customer ID + * @param customerId Customer ID + */ + public void setCustomerId(String customerId) { + this.customerId = customerId; + } + + /** + * Get credit score value + * @return Credit score value + */ + public int getScore() { + return score; + } + + /** + * Set credit score value + * @param score Credit score value + */ + public void setScore(int score) { + this.score = score; + this.scoreBand = determineScoreBand(score); + this.rating = determineRating(score); + this.recommendation = generateRecommendation(score); + } + + /** + * Get score band + * @return Score band + */ + public String getScoreBand() { + return scoreBand; + } + + /** + * Set score band + * @param scoreBand Score band + */ + public void setScoreBand(String scoreBand) { + this.scoreBand = scoreBand; + } + + /** + * Get credit rating + * @return Credit rating + */ + public CreditRating getRating() { + return rating; + } + + /** + * Set credit rating + * @param rating Credit rating + */ + public void setRating(CreditRating rating) { + this.rating = rating; + } + + /** + * Get calculation timestamp + * @return Calculation timestamp + */ + public LocalDateTime getCalculatedAt() { + return calculatedAt; + } + + /** + * Set calculation timestamp + * @param calculatedAt Calculation timestamp + */ + public void setCalculatedAt(LocalDateTime calculatedAt) { + this.calculatedAt = calculatedAt; + } + + /** + * Get expiration timestamp + * @return Expiration timestamp + */ + public LocalDateTime getExpiresAt() { + return expiresAt; + } + + /** + * Set expiration timestamp + * @param expiresAt Expiration timestamp + */ + public void setExpiresAt(LocalDateTime expiresAt) { + this.expiresAt = expiresAt; + } + + /** + * Get calculation method + * @return Calculation method + */ + public String getCalculationMethod() { + return calculationMethod; + } + + /** + * Set calculation method + * @param calculationMethod Calculation method + */ + public void setCalculationMethod(String calculationMethod) { + this.calculationMethod = calculationMethod; + } + + /** + * Get factors affecting the score + * @return Factors affecting the score + */ + public String getFactors() { + return factors; + } + + /** + * Set factors affecting the score + * @param factors Factors affecting the score + */ + public void setFactors(String factors) { + this.factors = factors; + } + + /** + * Get recommendation + * @return Recommendation + */ + public String getRecommendation() { + return recommendation; + } + + /** + * Set recommendation + * @param recommendation Recommendation + */ + public void setRecommendation(String recommendation) { + this.recommendation = recommendation; + } + + /** + * Check if credit score is expired + * @return true if expired, false otherwise + */ + public boolean isExpired() { + return LocalDateTime.now().isAfter(expiresAt); + } + + @Override + public String toString() { + return "CreditScore{" + + "id='" + id + '\'' + + ", applicationId='" + applicationId + '\'' + + ", customerId='" + customerId + '\'' + + ", score=" + score + + ", scoreBand='" + scoreBand + '\'' + + ", rating=" + rating + + ", calculatedAt=" + calculatedAt + + ", expiresAt=" + expiresAt + + ", calculationMethod='" + calculationMethod + '\'' + + ", factors='" + factors + '\'' + + ", recommendation='" + recommendation + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/models/wallet/Transaction.java b/src/main/java/com/financial/api/models/wallet/Transaction.java new file mode 100644 index 0000000..3ec588c --- /dev/null +++ b/src/main/java/com/financial/api/models/wallet/Transaction.java @@ -0,0 +1,330 @@ +package com.financial.api.models.wallet; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * Transaction model representing a financial transaction in the wallet system. + */ +public class Transaction { + private String id; + private String walletId; + private String referenceId; + private TransactionType type; + private BigDecimal amount; + private String currency; + private String description; + private TransactionStatus status; + private String counterpartyAccount; + private String counterpartyName; + private LocalDateTime createdAt; + private LocalDateTime processedAt; + private String createdBy; + private String processedBy; + + public enum TransactionType { + @JsonProperty("DEPOSIT") + DEPOSIT, + + @JsonProperty("WITHDRAWAL") + WITHDRAWAL, + + @JsonProperty("TRANSFER") + TRANSFER, + + @JsonProperty("PAYMENT") + PAYMENT, + + @JsonProperty("REFUND") + REFUND + } + + public enum TransactionStatus { + @JsonProperty("PENDING") + PENDING, + + @JsonProperty("PROCESSING") + PROCESSING, + + @JsonProperty("COMPLETED") + COMPLETED, + + @JsonProperty("FAILED") + FAILED, + + @JsonProperty("CANCELLED") + CANCELLED + } + + /** + * Default constructor + */ + public Transaction() { + this.id = UUID.randomUUID().toString(); + this.createdAt = LocalDateTime.now(); + this.status = TransactionStatus.PENDING; + } + + /** + * Parameterized constructor + * @param walletId Wallet ID + * @param type Transaction type + * @param amount Transaction amount + * @param currency Currency code + * @param description Transaction description + */ + public Transaction(String walletId, TransactionType type, BigDecimal amount, String currency, String description) { + this(); + this.walletId = walletId; + this.type = type; + this.amount = amount; + this.currency = currency; + this.description = description; + } + + /** + * Get transaction ID + * @return Transaction ID + */ + public String getId() { + return id; + } + + /** + * Set transaction ID + * @param id Transaction ID + */ + public void setId(String id) { + this.id = id; + } + + /** + * Get wallet ID + * @return Wallet ID + */ + public String getWalletId() { + return walletId; + } + + /** + * Set wallet ID + * @param walletId Wallet ID + */ + public void setWalletId(String walletId) { + this.walletId = walletId; + } + + /** + * Get reference ID + * @return Reference ID + */ + public String getReferenceId() { + return referenceId; + } + + /** + * Set reference ID + * @param referenceId Reference ID + */ + public void setReferenceId(String referenceId) { + this.referenceId = referenceId; + } + + /** + * Get transaction type + * @return Transaction type + */ + public TransactionType getType() { + return type; + } + + /** + * Set transaction type + * @param type Transaction type + */ + public void setType(TransactionType type) { + this.type = type; + } + + /** + * Get transaction amount + * @return Transaction amount + */ + public BigDecimal getAmount() { + return amount; + } + + /** + * Set transaction amount + * @param amount Transaction amount + */ + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + /** + * Get currency code + * @return Currency code + */ + public String getCurrency() { + return currency; + } + + /** + * Set currency code + * @param currency Currency code + */ + public void setCurrency(String currency) { + this.currency = currency; + } + + /** + * Get transaction description + * @return Transaction description + */ + public String getDescription() { + return description; + } + + /** + * Set transaction description + * @param description Transaction description + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Get transaction status + * @return Transaction status + */ + public TransactionStatus getStatus() { + return status; + } + + /** + * Set transaction status + * @param status Transaction status + */ + public void setStatus(TransactionStatus status) { + this.status = status; + } + + /** + * Get counterparty account number + * @return Counterparty account number + */ + public String getCounterpartyAccount() { + return counterpartyAccount; + } + + /** + * Set counterparty account number + * @param counterpartyAccount Counterparty account number + */ + public void setCounterpartyAccount(String counterpartyAccount) { + this.counterpartyAccount = counterpartyAccount; + } + + /** + * Get counterparty name + * @return Counterparty name + */ + public String getCounterpartyName() { + return counterpartyName; + } + + /** + * Set counterparty name + * @param counterpartyName Counterparty name + */ + public void setCounterpartyName(String counterpartyName) { + this.counterpartyName = counterpartyName; + } + + /** + * Get creation timestamp + * @return Creation timestamp + */ + public LocalDateTime getCreatedAt() { + return createdAt; + } + + /** + * Set creation timestamp + * @param createdAt Creation timestamp + */ + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + /** + * Get processing timestamp + * @return Processing timestamp + */ + public LocalDateTime getProcessedAt() { + return processedAt; + } + + /** + * Set processing timestamp + * @param processedAt Processing timestamp + */ + public void setProcessedAt(LocalDateTime processedAt) { + this.processedAt = processedAt; + } + + /** + * Get creator user ID + * @return Creator user ID + */ + public String getCreatedBy() { + return createdBy; + } + + /** + * Set creator user ID + * @param createdBy Creator user ID + */ + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + /** + * Get processor user ID + * @return Processor user ID + */ + public String getProcessedBy() { + return processedBy; + } + + /** + * Set processor user ID + * @param processedBy Processor user ID + */ + public void setProcessedBy(String processedBy) { + this.processedBy = processedBy; + } + + @Override + public String toString() { + return "Transaction{" + + "id='" + id + '\'' + + ", walletId='" + walletId + '\'' + + ", referenceId='" + referenceId + '\'' + + ", type=" + type + + ", amount=" + amount + + ", currency='" + currency + '\'' + + ", description='" + description + '\'' + + ", status=" + status + + ", counterpartyAccount='" + counterpartyAccount + '\'' + + ", counterpartyName='" + counterpartyName + '\'' + + ", createdAt=" + createdAt + + ", processedAt=" + processedAt + + ", createdBy='" + createdBy + '\'' + + ", processedBy='" + processedBy + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/models/wallet/Wallet.java b/src/main/java/com/financial/api/models/wallet/Wallet.java new file mode 100644 index 0000000..e6c7de0 --- /dev/null +++ b/src/main/java/com/financial/api/models/wallet/Wallet.java @@ -0,0 +1,237 @@ +package com.financial.api.models.wallet; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * Wallet model representing a digital wallet in the financial system. + */ +public class Wallet { + private String id; + private String customerId; + private String accountNumber; + private BigDecimal balance; + private String currency; + private WalletStatus status; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private String createdBy; + private String updatedBy; + + public enum WalletStatus { + @JsonProperty("ACTIVE") + ACTIVE, + + @JsonProperty("INACTIVE") + INACTIVE, + + @JsonProperty("SUSPENDED") + SUSPENDED, + + @JsonProperty("CLOSED") + CLOSED + } + + /** + * Default constructor + */ + public Wallet() { + this.id = UUID.randomUUID().toString(); + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + this.status = WalletStatus.ACTIVE; + } + + /** + * Parameterized constructor + * @param customerId Customer ID + * @param accountNumber Account number + * @param initialBalance Initial balance + * @param currency Currency code + */ + public Wallet(String customerId, String accountNumber, BigDecimal initialBalance, String currency) { + this(); + this.customerId = customerId; + this.accountNumber = accountNumber; + this.balance = initialBalance; + this.currency = currency; + } + + /** + * Get wallet ID + * @return Wallet ID + */ + public String getId() { + return id; + } + + /** + * Set wallet ID + * @param id Wallet ID + */ + public void setId(String id) { + this.id = id; + } + + /** + * Get customer ID + * @return Customer ID + */ + public String getCustomerId() { + return customerId; + } + + /** + * Set customer ID + * @param customerId Customer ID + */ + public void setCustomerId(String customerId) { + this.customerId = customerId; + } + + /** + * Get account number + * @return Account number + */ + public String getAccountNumber() { + return accountNumber; + } + + /** + * Set account number + * @param accountNumber Account number + */ + public void setAccountNumber(String accountNumber) { + this.accountNumber = accountNumber; + } + + /** + * Get wallet balance + * @return Wallet balance + */ + public BigDecimal getBalance() { + return balance; + } + + /** + * Set wallet balance + * @param balance Wallet balance + */ + public void setBalance(BigDecimal balance) { + this.balance = balance; + } + + /** + * Get currency code + * @return Currency code + */ + public String getCurrency() { + return currency; + } + + /** + * Set currency code + * @param currency Currency code + */ + public void setCurrency(String currency) { + this.currency = currency; + } + + /** + * Get wallet status + * @return Wallet status + */ + public WalletStatus getStatus() { + return status; + } + + /** + * Set wallet status + * @param status Wallet status + */ + public void setStatus(WalletStatus status) { + this.status = status; + } + + /** + * Get creation timestamp + * @return Creation timestamp + */ + public LocalDateTime getCreatedAt() { + return createdAt; + } + + /** + * Set creation timestamp + * @param createdAt Creation timestamp + */ + public void setCreatedAt(LocalDateTime createdAt) { + this.createdAt = createdAt; + } + + /** + * Get last update timestamp + * @return Last update timestamp + */ + public LocalDateTime getUpdatedAt() { + return updatedAt; + } + + /** + * Set last update timestamp + * @param updatedAt Last update timestamp + */ + public void setUpdatedAt(LocalDateTime updatedAt) { + this.updatedAt = updatedAt; + } + + /** + * Get creator user ID + * @return Creator user ID + */ + public String getCreatedBy() { + return createdBy; + } + + /** + * Set creator user ID + * @param createdBy Creator user ID + */ + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + /** + * Get updater user ID + * @return Updater user ID + */ + public String getUpdatedBy() { + return updatedBy; + } + + /** + * Set updater user ID + * @param updatedBy Updater user ID + */ + public void setUpdatedBy(String updatedBy) { + this.updatedBy = updatedBy; + } + + @Override + public String toString() { + return "Wallet{" + + "id='" + id + '\'' + + ", customerId='" + customerId + '\'' + + ", accountNumber='" + accountNumber + '\'' + + ", balance=" + balance + + ", currency='" + currency + '\'' + + ", status=" + status + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + ", createdBy='" + createdBy + '\'' + + ", updatedBy='" + updatedBy + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/services/BaseApiClient.java b/src/main/java/com/financial/api/services/BaseApiClient.java new file mode 100644 index 0000000..94e8685 --- /dev/null +++ b/src/main/java/com/financial/api/services/BaseApiClient.java @@ -0,0 +1,259 @@ +package com.financial.api.services; + +import com.financial.api.config.ConfigManager; +import io.restassured.RestAssured; +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.filter.log.ResponseLoggingFilter; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Map; + +/** + * Base API client class that provides common functionality for API testing. + */ +public class BaseApiClient { + private static final Logger logger = LogManager.getLogger(BaseApiClient.class); + protected static ConfigManager configManager; + protected RequestSpecification requestSpec; + + /** + * Initialize the API client with default configuration + */ + public BaseApiClient() { + configManager = ConfigManager.getInstance(); + setupRestAssured(); + } + + /** + * Set up RestAssured configuration + */ + private void setupRestAssured() { + String baseUrl = configManager.getApiBaseUrl(); + String apiVersion = configManager.getApiVersion(); + int timeout = configManager.getApiTimeout(); + + RestAssured.baseURI = baseUrl; + RestAssured.basePath = "/" + apiVersion; + + requestSpec = new RequestSpecBuilder() + .setContentType(ContentType.JSON) + .setAccept(ContentType.JSON) + .addFilter(new RequestLoggingFilter()) + .addFilter(new ResponseLoggingFilter()) + .build(); + } + + /** + * Send a GET request + * @param endpoint API endpoint + * @return Response object + */ + protected Response get(String endpoint) { + logger.info("Sending GET request to: {}", endpoint); + return RestAssured.given() + .spec(requestSpec) + .when() + .get(endpoint); + } + + /** + * Send a GET request with path parameters + * @param endpoint API endpoint with path parameters + * @param pathParams Path parameters + * @return Response object + */ + protected Response get(String endpoint, Map pathParams) { + logger.info("Sending GET request to: {} with path parameters: {}", endpoint, pathParams); + return RestAssured.given() + .spec(requestSpec) + .pathParams(pathParams) + .when() + .get(endpoint); + } + + /** + * Send a GET request with query parameters + * @param endpoint API endpoint + * @param queryParams Query parameters + * @return Response object + */ + protected Response getWithQueryParams(String endpoint, Map queryParams) { + logger.info("Sending GET request to: {} with query parameters: {}", endpoint, queryParams); + return RestAssured.given() + .spec(requestSpec) + .queryParams(queryParams) + .when() + .get(endpoint); + } + + /** + * Send a POST request + * @param endpoint API endpoint + * @param body Request body + * @return Response object + */ + protected Response post(String endpoint, Object body) { + logger.info("Sending POST request to: {} with body: {}", endpoint, body); + return RestAssured.given() + .spec(requestSpec) + .body(body) + .when() + .post(endpoint); + } + + /** + * Send a POST request with path parameters + * @param endpoint API endpoint with path parameters + * @param pathParams Path parameters + * @param body Request body + * @return Response object + */ + protected Response post(String endpoint, Map pathParams, Object body) { + logger.info("Sending POST request to: {} with path parameters: {} and body: {}", endpoint, pathParams, body); + return RestAssured.given() + .spec(requestSpec) + .pathParams(pathParams) + .body(body) + .when() + .post(endpoint); + } + + /** + * Send a PUT request + * @param endpoint API endpoint + * @param body Request body + * @return Response object + */ + protected Response put(String endpoint, Object body) { + logger.info("Sending PUT request to: {} with body: {}", endpoint, body); + return RestAssured.given() + .spec(requestSpec) + .body(body) + .when() + .put(endpoint); + } + + /** + * Send a PUT request with path parameters + * @param endpoint API endpoint with path parameters + * @param pathParams Path parameters + * @param body Request body + * @return Response object + */ + protected Response put(String endpoint, Map pathParams, Object body) { + logger.info("Sending PUT request to: {} with path parameters: {} and body: {}", endpoint, pathParams, body); + return RestAssured.given() + .spec(requestSpec) + .pathParams(pathParams) + .body(body) + .when() + .put(endpoint); + } + + /** + * Send a PATCH request + * @param endpoint API endpoint + * @param body Request body + * @return Response object + */ + protected Response patch(String endpoint, Object body) { + logger.info("Sending PATCH request to: {} with body: {}", endpoint, body); + return RestAssured.given() + .spec(requestSpec) + .body(body) + .when() + .patch(endpoint); + } + + /** + * Send a PATCH request with path parameters + * @param endpoint API endpoint with path parameters + * @param pathParams Path parameters + * @param body Request body + * @return Response object + */ + protected Response patch(String endpoint, Map pathParams, Object body) { + logger.info("Sending PATCH request to: {} with path parameters: {} and body: {}", endpoint, pathParams, body); + return RestAssured.given() + .spec(requestSpec) + .pathParams(pathParams) + .body(body) + .when() + .patch(endpoint); + } + + /** + * Send a DELETE request + * @param endpoint API endpoint + * @return Response object + */ + protected Response delete(String endpoint) { + logger.info("Sending DELETE request to: {}", endpoint); + return RestAssured.given() + .spec(requestSpec) + .when() + .delete(endpoint); + } + + /** + * Send a DELETE request with path parameters + * @param endpoint API endpoint with path parameters + * @param pathParams Path parameters + * @return Response object + */ + protected Response delete(String endpoint, Map pathParams) { + logger.info("Sending DELETE request to: {} with path parameters: {}", endpoint, pathParams); + return RestAssured.given() + .spec(requestSpec) + .pathParams(pathParams) + .when() + .delete(endpoint); + } + + /** + * Add headers to the request specification + * @param headers Map of headers + */ + protected void addHeaders(Map headers) { + if (headers != null && !headers.isEmpty()) { + requestSpec.headers(headers); + } + } + + /** + * Add authentication header to the request specification + * @param authToken Authentication token + */ + protected void addAuthToken(String authToken) { + requestSpec.header("Authorization", "Bearer " + authToken); + } + + /** + * Set base URL for the API client + * @param baseUrl Base URL + */ + protected void setBaseUrl(String baseUrl) { + RestAssured.baseURI = baseUrl; + } + + /** + * Set base path for the API client + * @param basePath Base path + */ + protected void setBasePath(String basePath) { + RestAssured.basePath = basePath; + } + + /** + * Reset RestAssured configuration + */ + protected void resetRestAssured() { + RestAssured.reset(); + setupRestAssured(); + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/services/CreditService.java b/src/main/java/com/financial/api/services/CreditService.java new file mode 100644 index 0000000..ad6de30 --- /dev/null +++ b/src/main/java/com/financial/api/services/CreditService.java @@ -0,0 +1,312 @@ +package com.financial.api.services; + +import com.financial.api.models.credit.CreditApplication; +import com.financial.api.models.credit.CreditScore; +import io.restassured.response.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; + +/** + * Service class for Credit API operations + */ +public class CreditService extends BaseApiClient { + private static final Logger logger = LogManager.getLogger(CreditService.class); + + /** + * Submit a new credit application + * @param application CreditApplication object + * @return Response object + */ + public Response submitApplication(CreditApplication application) { + logger.info("Submitting credit application for customer: {}", application.getCustomerId()); + return post("/credit/applications", application); + } + + /** + * Get credit application by ID + * @param applicationId Application ID + * @return Response object + */ + public Response getApplication(String applicationId) { + logger.info("Getting credit application with ID: {}", applicationId); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + return get("/credit/applications/{applicationId}", pathParams); + } + + /** + * Get all credit applications for a customer + * @param customerId Customer ID + * @return Response object + */ + public Response getCustomerApplications(String customerId) { + logger.info("Getting credit applications for customer: {}", customerId); + Map queryParams = new HashMap<>(); + queryParams.put("customerId", customerId); + return getWithQueryParams("/credit/applications", queryParams); + } + + /** + * Get credit applications by status + * @param status Application status + * @return Response object + */ + public Response getApplicationsByStatus(CreditApplication.CreditApplicationStatus status) { + logger.info("Getting credit applications with status: {}", status); + Map queryParams = new HashMap<>(); + queryParams.put("status", status); + return getWithQueryParams("/credit/applications", queryParams); + } + + /** + * Update credit application + * @param applicationId Application ID + * @param application Updated application object + * @return Response object + */ + public Response updateApplication(String applicationId, CreditApplication application) { + logger.info("Updating credit application with ID: {}", applicationId); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + return put("/credit/applications/{applicationId}", pathParams, application); + } + + /** + * Update credit application status + * @param applicationId Application ID + * @param status New status + * @param reviewedBy Reviewer ID + * @param rejectionReason Rejection reason (if applicable) + * @return Response object + */ + public Response updateApplicationStatus(String applicationId, CreditApplication.CreditApplicationStatus status, + String reviewedBy, String rejectionReason) { + logger.info("Updating credit application status for application ID: {} to {}", applicationId, status); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + + Map body = new HashMap<>(); + body.put("status", status); + body.put("reviewedBy", reviewedBy); + + if (rejectionReason != null && !rejectionReason.isEmpty()) { + body.put("rejectionReason", rejectionReason); + } + + return patch("/credit/applications/{applicationId}/status", pathParams, body); + } + + /** + * Delete credit application + * @param applicationId Application ID + * @return Response object + */ + public Response deleteApplication(String applicationId) { + logger.info("Deleting credit application with ID: {}", applicationId); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + return delete("/credit/applications/{applicationId}", pathParams); + } + + /** + * Get credit score for an application + * @param applicationId Application ID + * @return Response object + */ + public Response getCreditScore(String applicationId) { + logger.info("Getting credit score for application ID: {}", applicationId); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + return get("/credit/applications/{applicationId}/score", pathParams); + } + + /** + * Get credit score history for an application + * @param applicationId Application ID + * @return Response object + */ + public Response getCreditScoreHistory(String applicationId) { + logger.info("Getting credit score history for application ID: {}", applicationId); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + return get("/credit/applications/{applicationId}/score/history", pathParams); + } + + /** + * Request credit score calculation + * @param applicationId Application ID + * @param calculationMethod Calculation method + * @return Response object + */ + public Response calculateCreditScore(String applicationId, String calculationMethod) { + logger.info("Requesting credit score calculation for application ID: {}", applicationId); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + + Map body = new HashMap<>(); + body.put("calculationMethod", calculationMethod); + + return post("/credit/applications/{applicationId}/score/calculate", pathParams, body); + } + + /** + * Approve credit application + * @param applicationId Application ID + * @param approvedAmount Approved amount + * @param interestRate Interest rate + * @param reviewedBy Reviewer ID + * @return Response object + */ + public Response approveApplication(String applicationId, double approvedAmount, double interestRate, String reviewedBy) { + logger.info("Approving credit application with ID: {}", applicationId); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + + Map body = new HashMap<>(); + body.put("status", CreditApplication.CreditApplicationStatus.APPROVED); + body.put("approvedAmount", approvedAmount); + body.put("interestRate", interestRate); + body.put("reviewedBy", reviewedBy); + + return patch("/credit/applications/{applicationId}/status", pathParams, body); + } + + /** + * Reject credit application + * @param applicationId Application ID + * @param rejectionReason Rejection reason + * @param reviewedBy Reviewer ID + * @return Response object + */ + public Response rejectApplication(String applicationId, String rejectionReason, String reviewedBy) { + logger.info("Rejecting credit application with ID: {}", applicationId); + Map pathParams = new HashMap<>(); + pathParams.put("applicationId", applicationId); + + Map body = new HashMap<>(); + body.put("status", CreditApplication.CreditApplicationStatus.REJECTED); + body.put("rejectionReason", rejectionReason); + body.put("reviewedBy", reviewedBy); + + return patch("/credit/applications/{applicationId}/status", pathParams, body); + } + + /** + * Get customer credit score + * @param customerId Customer ID + * @return Response object + */ + public Response getCustomerCreditScore(String customerId) { + logger.info("Getting credit score for customer: {}", customerId); + Map pathParams = new HashMap<>(); + pathParams.put("customerId", customerId); + return get("/credit/customers/{customerId}/score", pathParams); + } + + /** + * Get customer credit score history + * @param customerId Customer ID + * @return Response object + */ + public Response getCustomerCreditScoreHistory(String customerId) { + logger.info("Getting credit score history for customer: {}", customerId); + Map pathParams = new HashMap<>(); + pathParams.put("customerId", customerId); + return get("/credit/customers/{customerId}/score/history", pathParams); + } + + /** + * Request customer credit score calculation + * @param customerId Customer ID + * @param calculationMethod Calculation method + * @return Response object + */ + public Response calculateCustomerCreditScore(String customerId, String calculationMethod) { + logger.info("Requesting credit score calculation for customer: {}", customerId); + Map pathParams = new HashMap<>(); + pathParams.put("customerId", customerId); + + Map body = new HashMap<>(); + body.put("calculationMethod", calculationMethod); + + return post("/credit/customers/{customerId}/score/calculate", pathParams, body); + } + + /** + * Get credit products + * @return Response object + */ + public Response getCreditProducts() { + logger.info("Getting available credit products"); + return get("/credit/products"); + } + + /** + * Get credit product by ID + * @param productId Product ID + * @return Response object + */ + public Response getCreditProduct(String productId) { + logger.info("Getting credit product with ID: {}", productId); + Map pathParams = new HashMap<>(); + pathParams.put("productId", productId); + return get("/credit/products/{productId}", pathParams); + } + + /** + * Get credit eligibility + * @param customerId Customer ID + * @param productId Product ID + * @return Response object + */ + public Response getCreditEligibility(String customerId, String productId) { + logger.info("Getting credit eligibility for customer: {} and product: {}", customerId, productId); + Map pathParams = new HashMap<>(); + pathParams.put("customerId", customerId); + pathParams.put("productId", productId); + return get("/credit/customers/{customerId}/eligibility/{productId}", pathParams); + } + + /** + * Get credit offers for a customer + * @param customerId Customer ID + * @return Response object + */ + public Response getCreditOffers(String customerId) { + logger.info("Getting credit offers for customer: {}", customerId); + Map pathParams = new HashMap<>(); + pathParams.put("customerId", customerId); + return get("/credit/customers/{customerId}/offers", pathParams); + } + + /** + * Accept credit offer + * @param customerId Customer ID + * @param offerId Offer ID + * @return Response object + */ + public Response acceptCreditOffer(String customerId, String offerId) { + logger.info("Accepting credit offer with ID: {} for customer: {}", offerId, customerId); + Map pathParams = new HashMap<>(); + pathParams.put("customerId", customerId); + pathParams.put("offerId", offerId); + return post("/credit/customers/{customerId}/offers/{offerId}/accept", pathParams, null); + } + + /** + * Reject credit offer + * @param customerId Customer ID + * @param offerId Offer ID + * @return Response object + */ + public Response rejectCreditOffer(String customerId, String offerId) { + logger.info("Rejecting credit offer with ID: {} for customer: {}", offerId, customerId); + Map pathParams = new HashMap<>(); + pathParams.put("customerId", customerId); + pathParams.put("offerId", offerId); + return post("/credit/customers/{customerId}/offers/{offerId}/reject", pathParams, null); + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/services/WalletService.java b/src/main/java/com/financial/api/services/WalletService.java new file mode 100644 index 0000000..0e847e9 --- /dev/null +++ b/src/main/java/com/financial/api/services/WalletService.java @@ -0,0 +1,279 @@ +package com.financial.api.services; + +import com.financial.api.models.wallet.Transaction; +import com.financial.api.models.wallet.Wallet; +import io.restassured.response.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Service class for Wallet API operations + */ +public class WalletService extends BaseApiClient { + private static final Logger logger = LogManager.getLogger(WalletService.class); + + /** + * Create a new wallet + * @param wallet Wallet object to create + * @return Response object + */ + public Response createWallet(Wallet wallet) { + logger.info("Creating wallet for customer: {}", wallet.getCustomerId()); + return post("/wallets", wallet); + } + + /** + * Get wallet by ID + * @param walletId Wallet ID + * @return Response object + */ + public Response getWallet(String walletId) { + logger.info("Getting wallet with ID: {}", walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + return get("/wallets/{walletId}", pathParams); + } + + /** + * Get wallet by account number + * @param accountNumber Account number + * @return Response object + */ + public Response getWalletByAccountNumber(String accountNumber) { + logger.info("Getting wallet with account number: {}", accountNumber); + Map queryParams = new HashMap<>(); + queryParams.put("accountNumber", accountNumber); + return getWithQueryParams("/wallets", queryParams); + } + + /** + * Get all wallets for a customer + * @param customerId Customer ID + * @return Response object + */ + public Response getCustomerWallets(String customerId) { + logger.info("Getting wallets for customer: {}", customerId); + Map queryParams = new HashMap<>(); + queryParams.put("customerId", customerId); + return getWithQueryParams("/wallets", queryParams); + } + + /** + * Update wallet information + * @param walletId Wallet ID + * @param wallet Updated wallet object + * @return Response object + */ + public Response updateWallet(String walletId, Wallet wallet) { + logger.info("Updating wallet with ID: {}", walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + return put("/wallets/{walletId}", pathParams, wallet); + } + + /** + * Update wallet status + * @param walletId Wallet ID + * @param status New status + * @return Response object + */ + public Response updateWalletStatus(String walletId, Wallet.WalletStatus status) { + logger.info("Updating wallet status for wallet ID: {} to {}", walletId, status); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + + Map body = new HashMap<>(); + body.put("status", status); + + return patch("/wallets/{walletId}/status", pathParams, body); + } + + /** + * Delete wallet + * @param walletId Wallet ID + * @return Response object + */ + public Response deleteWallet(String walletId) { + logger.info("Deleting wallet with ID: {}", walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + return delete("/wallets/{walletId}", pathParams); + } + + /** + * Create a transaction + * @param walletId Wallet ID + * @param transaction Transaction object + * @return Response object + */ + public Response createTransaction(String walletId, Transaction transaction) { + logger.info("Creating transaction for wallet ID: {}", walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + return post("/wallets/{walletId}/transactions", pathParams, transaction); + } + + /** + * Get transaction by ID + * @param walletId Wallet ID + * @param transactionId Transaction ID + * @return Response object + */ + public Response getTransaction(String walletId, String transactionId) { + logger.info("Getting transaction with ID: {} for wallet ID: {}", transactionId, walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + pathParams.put("transactionId", transactionId); + return get("/wallets/{walletId}/transactions/{transactionId}", pathParams); + } + + /** + * Get all transactions for a wallet + * @param walletId Wallet ID + * @return Response object + */ + public Response getTransactions(String walletId) { + logger.info("Getting transactions for wallet ID: {}", walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + return get("/wallets/{walletId}/transactions", pathParams); + } + + /** + * Get transactions for a wallet with filters + * @param walletId Wallet ID + * @param transactionType Transaction type filter + * @param status Transaction status filter + * @param startDate Start date filter + * @param endDate End date filter + * @param limit Maximum number of transactions to return + * @param offset Offset for pagination + * @return Response object + */ + public Response getTransactionsWithFilters(String walletId, Transaction.TransactionType transactionType, + Transaction.TransactionStatus status, String startDate, + String endDate, Integer limit, Integer offset) { + logger.info("Getting filtered transactions for wallet ID: {}", walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + + Map queryParams = new HashMap<>(); + if (transactionType != null) { + queryParams.put("type", transactionType); + } + if (status != null) { + queryParams.put("status", status); + } + if (startDate != null) { + queryParams.put("startDate", startDate); + } + if (endDate != null) { + queryParams.put("endDate", endDate); + } + if (limit != null) { + queryParams.put("limit", limit); + } + if (offset != null) { + queryParams.put("offset", offset); + } + + return getWithQueryParams("/wallets/{walletId}/transactions", queryParams); + } + + /** + * Update transaction status + * @param walletId Wallet ID + * @param transactionId Transaction ID + * @param status New status + * @return Response object + */ + public Response updateTransactionStatus(String walletId, String transactionId, Transaction.TransactionStatus status) { + logger.info("Updating transaction status for transaction ID: {} to {}", transactionId, status); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + pathParams.put("transactionId", transactionId); + + Map body = new HashMap<>(); + body.put("status", status); + + return patch("/wallets/{walletId}/transactions/{transactionId}/status", pathParams, body); + } + + /** + * Get wallet balance + * @param walletId Wallet ID + * @return Response object + */ + public Response getWalletBalance(String walletId) { + logger.info("Getting balance for wallet ID: {}", walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + return get("/wallets/{walletId}/balance", pathParams); + } + + /** + * Deposit funds to wallet + * @param walletId Wallet ID + * @param amount Amount to deposit + * @param description Description of the deposit + * @return Response object + */ + public Response depositFunds(String walletId, double amount, String description) { + logger.info("Depositing {} to wallet ID: {}", amount, walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + + Map body = new HashMap<>(); + body.put("amount", amount); + body.put("description", description); + body.put("type", Transaction.TransactionType.DEPOSIT); + + return post("/wallets/{walletId}/deposit", pathParams, body); + } + + /** + * Withdraw funds from wallet + * @param walletId Wallet ID + * @param amount Amount to withdraw + * @param description Description of the withdrawal + * @return Response object + */ + public Response withdrawFunds(String walletId, double amount, String description) { + logger.info("Withdrawing {} from wallet ID: {}", amount, walletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", walletId); + + Map body = new HashMap<>(); + body.put("amount", amount); + body.put("description", description); + body.put("type", Transaction.TransactionType.WITHDRAWAL); + + return post("/wallets/{walletId}/withdraw", pathParams, body); + } + + /** + * Transfer funds between wallets + * @param fromWalletId Source wallet ID + * @param toWalletId Destination wallet ID + * @param amount Amount to transfer + * @param description Description of the transfer + * @return Response object + */ + public Response transferFunds(String fromWalletId, String toWalletId, double amount, String description) { + logger.info("Transferring {} from wallet ID: {} to wallet ID: {}", amount, fromWalletId, toWalletId); + Map pathParams = new HashMap<>(); + pathParams.put("walletId", fromWalletId); + + Map body = new HashMap<>(); + body.put("toWalletId", toWalletId); + body.put("amount", amount); + body.put("description", description); + body.put("type", Transaction.TransactionType.TRANSFER); + + return post("/wallets/{walletId}/transfer", pathParams, body); + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/utils/AnnotationTransformer.java b/src/main/java/com/financial/api/utils/AnnotationTransformer.java new file mode 100644 index 0000000..7e72fbc --- /dev/null +++ b/src/main/java/com/financial/api/utils/AnnotationTransformer.java @@ -0,0 +1,271 @@ +package com.financial.api.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.IAnnotationTransformer; +import org.testng.annotations.ITestAnnotation; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Annotation transformer for dynamically modifying test annotations + */ +public class AnnotationTransformer implements IAnnotationTransformer { + private static final Logger logger = LogManager.getLogger(AnnotationTransformer.class); + + // Configuration for test groups + private static final Map> TEST_GROUPS_CONFIG = new HashMap<>(); + + static { + // Define which test methods belong to which groups + TEST_GROUPS_CONFIG.put("smoke", Arrays.asList( + "testCreateWallet", + "testGetWallet", + "testSubmitApplication", + "testGetApplication" + )); + + TEST_GROUPS_CONFIG.put("regression", Arrays.asList( + "testCreateWallet", + "testGetWallet", + "testGetWalletByAccountNumber", + "testUpdateWallet", + "testGetWalletBalance", + "testCreateTransaction", + "testGetTransaction", + "testGetTransactions", + "testUpdateTransactionStatus", + "testDepositFunds", + "testWithdrawFunds", + "testSubmitApplication", + "testGetApplication", + "testUpdateApplication", + "testGetApplicationsByStatus", + "testCalculateCreditScore", + "testGetCreditScore", + "testGetCreditScoreHistory", + "testApproveApplication", + "testRejectApplication", + "testGetCreditProducts", + "testGetCreditProduct" + )); + + TEST_GROUPS_CONFIG.put("performance", Arrays.asList( + "testCreateMultipleWalletsPerformance", + "testGetMultipleWalletsPerformance", + "testCreateMultipleTransactionsPerformance", + "testDepositFundsPerformance", + "testGetWalletBalancePerformance", + "testGetTransactionsPerformance", + "testTransferFundsPerformance", + "testSubmitMultipleApplicationsPerformance", + "testGetMultipleApplicationsPerformance", + "testCalculateMultipleCreditScoresPerformance", + "testGetMultipleCreditScoresPerformance", + "testGetMultipleCreditScoreHistoriesPerformance", + "testGetApplicationsByStatusPerformance", + "testGetCreditProductsPerformance" + )); + + TEST_GROUPS_CONFIG.put("security", Arrays.asList( + "testSqlInjectionInWalletCreation", + "testXssInWalletCreation", + "testAuthenticationBypassInWalletRetrieval", + "testAuthorizationBypassInWalletRetrieval", + "testIdorInWalletRetrieval", + "testSqlInjectionInTransactionCreation", + "testXssInTransactionCreation", + "testNegativeAmountInTransactionCreation", + "testExtremelyLargeAmountInTransactionCreation", + "testAuthenticationBypassInTransactionRetrieval", + "testAuthorizationBypassInTransactionRetrieval", + "testCsrfProtectionInWalletUpdate", + "testRateLimitingInWalletCreation", + "testInputValidationInWalletCreation", + "testSensitiveDataExposureInWalletResponse", + "testSqlInjectionInCreditApplicationSubmission", + "testXssInCreditApplicationSubmission", + "testAuthenticationBypassInCreditApplicationRetrieval", + "testAuthorizationBypassInCreditApplicationRetrieval", + "testIdorInCreditApplicationRetrieval", + "testSqlInjectionInCreditScoreCalculation", + "testXssInCreditScoreCalculation", + "testNegativeLoanAmountInCreditApplicationSubmission", + "testExtremelyLargeLoanAmountInCreditApplicationSubmission", + "testNegativeLoanTermInCreditApplicationSubmission", + "testExtremelyLargeLoanTermInCreditApplicationSubmission", + "testAuthenticationBypassInCreditScoreRetrieval", + "testAuthorizationBypassInCreditScoreRetrieval", + "testCsrfProtectionInCreditApplicationUpdate", + "testRateLimitingInCreditApplicationSubmission", + "testInputValidationInCreditApplicationSubmission", + "testSensitiveDataExposureInCreditApplicationResponse", + "testSensitiveDataExposureInCreditScoreResponse" + )); + + TEST_GROUPS_CONFIG.put("wallet", Arrays.asList( + "testCreateWallet", + "testGetWallet", + "testGetWalletByAccountNumber", + "testUpdateWallet", + "testGetWalletBalance", + "testCreateTransaction", + "testGetTransaction", + "testGetTransactions", + "testUpdateTransactionStatus", + "testDepositFunds", + "testWithdrawFunds", + "testCreateMultipleWalletsPerformance", + "testGetMultipleWalletsPerformance", + "testCreateMultipleTransactionsPerformance", + "testDepositFundsPerformance", + "testGetWalletBalancePerformance", + "testGetTransactionsPerformance", + "testTransferFundsPerformance", + "testSqlInjectionInWalletCreation", + "testXssInWalletCreation", + "testAuthenticationBypassInWalletRetrieval", + "testAuthorizationBypassInWalletRetrieval", + "testIdorInWalletRetrieval", + "testSqlInjectionInTransactionCreation", + "testXssInTransactionCreation", + "testNegativeAmountInTransactionCreation", + "testExtremelyLargeAmountInTransactionCreation", + "testAuthenticationBypassInTransactionRetrieval", + "testAuthorizationBypassInTransactionRetrieval", + "testCsrfProtectionInWalletUpdate", + "testRateLimitingInWalletCreation", + "testInputValidationInWalletCreation", + "testSensitiveDataExposureInWalletResponse" + )); + + TEST_GROUPS_CONFIG.put("credit", Arrays.asList( + "testSubmitApplication", + "testGetApplication", + "testUpdateApplication", + "testGetApplicationsByStatus", + "testCalculateCreditScore", + "testGetCreditScore", + "testGetCreditScoreHistory", + "testApproveApplication", + "testRejectApplication", + "testGetCreditProducts", + "testGetCreditProduct", + "testSubmitMultipleApplicationsPerformance", + "testGetMultipleApplicationsPerformance", + "testCalculateMultipleCreditScoresPerformance", + "testGetMultipleCreditScoresPerformance", + "testGetMultipleCreditScoreHistoriesPerformance", + "testGetApplicationsByStatusPerformance", + "testGetCreditProductsPerformance", + "testSqlInjectionInCreditApplicationSubmission", + "testXssInCreditApplicationSubmission", + "testAuthenticationBypassInCreditApplicationRetrieval", + "testAuthorizationBypassInCreditApplicationRetrieval", + "testIdorInCreditApplicationRetrieval", + "testSqlInjectionInCreditScoreCalculation", + "testXssInCreditScoreCalculation", + "testNegativeLoanAmountInCreditApplicationSubmission", + "testExtremelyLargeLoanAmountInCreditApplicationSubmission", + "testNegativeLoanTermInCreditApplicationSubmission", + "testExtremelyLargeLoanTermInCreditApplicationSubmission", + "testAuthenticationBypassInCreditScoreRetrieval", + "testAuthorizationBypassInCreditScoreRetrieval", + "testCsrfProtectionInCreditApplicationUpdate", + "testRateLimitingInCreditApplicationSubmission", + "testInputValidationInCreditApplicationSubmission", + "testSensitiveDataExposureInCreditApplicationResponse", + "testSensitiveDataExposureInCreditScoreResponse" + )); + } + + @Override + public void transform(ITestAnnotation annotation, Class testClass, Constructor testConstructor, Method testMethod) { + if (testMethod == null) { + return; + } + + String methodName = testMethod.getName(); + logger.debug("Transforming annotations for method: {}", methodName); + + // Get existing groups + String[] existingGroups = annotation.getGroups(); + + // Determine which groups this test method should belong to + String[] groupsToAdd = determineGroupsForMethod(methodName); + + // Merge existing groups with new groups + String[] mergedGroups = mergeGroups(existingGroups, groupsToAdd); + + // Set the merged groups + annotation.setGroups(mergedGroups); + + // Set default invocation count and thread pool size for performance tests + if (methodName.contains("Performance") || methodName.contains("performance")) { + if (annotation.invocationCount() == -1) { // Default value + annotation.setInvocationCount(1); + } + if (annotation.threadPoolSize() == -1) { // Default value + annotation.setThreadPoolSize(5); + } + } + + // Set default data provider for tests that need it + if (methodName.contains("Data") || methodName.contains("data")) { + if (annotation.dataProvider() == null || annotation.dataProvider().isEmpty()) { + annotation.setDataProvider("testDataProvider"); + } + } + + logger.debug("Method {} will be executed in groups: {}", methodName, Arrays.toString(mergedGroups)); + } + + /** + * Determine which groups a test method should belong to based on its name + * @param methodName Test method name + * @return Array of group names + */ + private String[] determineGroupsForMethod(String methodName) { + // Check if the method name matches any of the configured groups + for (Map.Entry> entry : TEST_GROUPS_CONFIG.entrySet()) { + if (entry.getValue().contains(methodName)) { + return new String[]{entry.getKey()}; + } + } + + // Default group if no match found + return new String[]{"default"}; + } + + /** + * Merge existing groups with new groups + * @param existingGroups Existing groups array + * @param groupsToAdd Groups to add array + * @return Merged groups array + */ + private String[] mergeGroups(String[] existingGroups, String[] groupsToAdd) { + if (existingGroups == null || existingGroups.length == 0) { + return groupsToAdd; + } + + if (groupsToAdd == null || groupsToAdd.length == 0) { + return existingGroups; + } + + // Create a new array with combined length + String[] merged = new String[existingGroups.length + groupsToAdd.length]; + + // Copy existing groups + System.arraycopy(existingGroups, 0, merged, 0, existingGroups.length); + + // Copy new groups + System.arraycopy(groupsToAdd, 0, merged, existingGroups.length, groupsToAdd.length); + + return merged; + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/utils/CouchbaseUtil.java b/src/main/java/com/financial/api/utils/CouchbaseUtil.java new file mode 100644 index 0000000..5f30e26 --- /dev/null +++ b/src/main/java/com/financial/api/utils/CouchbaseUtil.java @@ -0,0 +1,484 @@ +package com.financial.api.utils; + +import com.couchbase.client.java.Cluster; +import com.couchbase.client.java.ClusterOptions; +import com.couchbase.client.java.env.ClusterEnvironment; +import com.couchbase.client.java.json.JsonObject; +import com.couchbase.client.java.kv.GetResult; +import com.couchbase.client.java.kv.InsertOptions; +import com.couchbase.client.java.kv.MutationResult; +import com.couchbase.client.java.kv.ReplaceOptions; +import com.couchbase.client.java.kv.UpsertOptions; +import com.couchbase.client.java.query.QueryOptions; +import com.couchbase.client.java.query.QueryResult; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.financial.api.config.ConfigManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility class for Couchbase operations to store and retrieve test data and logs. + */ +public class CouchbaseUtil { + private static final Logger logger = LogManager.getLogger(CouchbaseUtil.class); + private static ConfigManager configManager; + private static Cluster cluster; + private static ObjectMapper objectMapper; + + static { + configManager = ConfigManager.getInstance(); + objectMapper = new ObjectMapper(); + } + + /** + * Get Couchbase cluster + * @return Couchbase cluster + */ + public static Cluster getCluster() { + if (cluster == null) { + initializeCluster(); + } + return cluster; + } + + /** + * Initialize Couchbase cluster + */ + private static void initializeCluster() { + try { + String host = configManager.getCouchbaseHost(); + String username = configManager.getCouchbaseUsername(); + String password = configManager.getCouchbasePassword(); + + // Create cluster environment + ClusterEnvironment env = ClusterEnvironment.builder() + .timeoutConfig(timeoutConfig -> timeoutConfig + .connectTimeout(Duration.ofSeconds(10)) + .kvTimeout(Duration.ofSeconds(5)) + .queryTimeout(Duration.ofSeconds(30))) + .build(); + + // Create cluster options + ClusterOptions options = ClusterOptions.clusterOptions(username, password) + .environment(env); + + // Connect to cluster + cluster = Cluster.connect(host, options); + + logger.info("Couchbase cluster connected successfully"); + } catch (Exception e) { + logger.error("Error connecting to Couchbase cluster: {}", e.getMessage(), e); + throw new RuntimeException("Failed to connect to Couchbase cluster", e); + } + } + + /** + * Check if Couchbase is running + * @return true if Couchbase is running, false otherwise + */ + public static boolean isRunning() { + try { + getCluster().ping(); + return true; + } catch (Exception e) { + logger.error("Error pinging Couchbase cluster: {}", e.getMessage(), e); + return false; + } + } + + /** + * Get a document by ID + * @param bucketName Bucket name + * @param documentId Document ID + * @param documentClass Class of the document + * @return Document object if found, null otherwise + */ + public static T getDocument(String bucketName, String documentId, Class documentClass) { + try { + GetResult result = getCluster().bucket(bucketName) + .defaultCollection() + .get(documentId); + + logger.info("Document found with ID: {} in bucket: {}", documentId, bucketName); + return result.contentAs(documentClass); + } catch (Exception e) { + logger.error("Error getting document from bucket {}: {}", bucketName, e.getMessage(), e); + return null; + } + } + + /** + * Get a document as JsonObject + * @param bucketName Bucket name + * @param documentId Document ID + * @return JsonObject if found, null otherwise + */ + public static JsonObject getDocumentAsJson(String bucketName, String documentId) { + try { + GetResult result = getCluster().bucket(bucketName) + .defaultCollection() + .get(documentId); + + logger.info("Document found with ID: {} in bucket: {}", documentId, bucketName); + return result.contentAsObject(); + } catch (Exception e) { + logger.error("Error getting document from bucket {}: {}", bucketName, e.getMessage(), e); + return null; + } + } + + /** + * Insert a document + * @param bucketName Bucket name + * @param documentId Document ID + * @param document Document object + * @return true if document inserted successfully, false otherwise + */ + public static boolean insertDocument(String bucketName, String documentId, Object document) { + try { + MutationResult result = getCluster().bucket(bucketName) + .defaultCollection() + .insert(documentId, document); + + logger.info("Document inserted with ID: {} in bucket: {}", documentId, bucketName); + return true; + } catch (Exception e) { + logger.error("Error inserting document in bucket {}: {}", bucketName, e.getMessage(), e); + return false; + } + } + + /** + * Insert a document with options + * @param bucketName Bucket name + * @param documentId Document ID + * @param document Document object + * @param options Insert options + * @return true if document inserted successfully, false otherwise + */ + public static boolean insertDocument(String bucketName, String documentId, Object document, InsertOptions options) { + try { + MutationResult result = getCluster().bucket(bucketName) + .defaultCollection() + .insert(documentId, document, options); + + logger.info("Document inserted with ID: {} in bucket: {}", documentId, bucketName); + return true; + } catch (Exception e) { + logger.error("Error inserting document in bucket {}: {}", bucketName, e.getMessage(), e); + return false; + } + } + + /** + * Upsert a document (insert or update) + * @param bucketName Bucket name + * @param documentId Document ID + * @param document Document object + * @return true if document upserted successfully, false otherwise + */ + public static boolean upsertDocument(String bucketName, String documentId, Object document) { + try { + MutationResult result = getCluster().bucket(bucketName) + .defaultCollection() + .upsert(documentId, document); + + logger.info("Document upserted with ID: {} in bucket: {}", documentId, bucketName); + return true; + } catch (Exception e) { + logger.error("Error upserting document in bucket {}: {}", bucketName, e.getMessage(), e); + return false; + } + } + + /** + * Upsert a document with options + * @param bucketName Bucket name + * @param documentId Document ID + * @param document Document object + * @param options Upsert options + * @return true if document upserted successfully, false otherwise + */ + public static boolean upsertDocument(String bucketName, String documentId, Object document, UpsertOptions options) { + try { + MutationResult result = getCluster().bucket(bucketName) + .defaultCollection() + .upsert(documentId, document, options); + + logger.info("Document upserted with ID: {} in bucket: {}", documentId, bucketName); + return true; + } catch (Exception e) { + logger.error("Error upserting document in bucket {}: {}", bucketName, e.getMessage(), e); + return false; + } + } + + /** + * Replace a document + * @param bucketName Bucket name + * @param documentId Document ID + * @param document Document object + * @return true if document replaced successfully, false otherwise + */ + public static boolean replaceDocument(String bucketName, String documentId, Object document) { + try { + MutationResult result = getCluster().bucket(bucketName) + .defaultCollection() + .replace(documentId, document); + + logger.info("Document replaced with ID: {} in bucket: {}", documentId, bucketName); + return true; + } catch (Exception e) { + logger.error("Error replacing document in bucket {}: {}", bucketName, e.getMessage(), e); + return false; + } + } + + /** + * Replace a document with options + * @param bucketName Bucket name + * @param documentId Document ID + * @param document Document object + * @param options Replace options + * @return true if document replaced successfully, false otherwise + */ + public static boolean replaceDocument(String bucketName, String documentId, Object document, ReplaceOptions options) { + try { + MutationResult result = getCluster().bucket(bucketName) + .defaultCollection() + .replace(documentId, document, options); + + logger.info("Document replaced with ID: {} in bucket: {}", documentId, bucketName); + return true; + } catch (Exception e) { + logger.error("Error replacing document in bucket {}: {}", bucketName, e.getMessage(), e); + return false; + } + } + + /** + * Delete a document + * @param bucketName Bucket name + * @param documentId Document ID + * @return true if document deleted successfully, false otherwise + */ + public static boolean deleteDocument(String bucketName, String documentId) { + try { + MutationResult result = getCluster().bucket(bucketName) + .defaultCollection() + .remove(documentId); + + logger.info("Document deleted with ID: {} in bucket: {}", documentId, bucketName); + return true; + } catch (Exception e) { + logger.error("Error deleting document from bucket {}: {}", bucketName, e.getMessage(), e); + return false; + } + } + + /** + * Execute a N1QL query + * @param bucketName Bucket name + * @param query N1QL query + * @return Query result + */ + public static QueryResult executeQuery(String bucketName, String query) { + try { + QueryResult result = getCluster().bucket(bucketName) + .scope("_default") + .query(query); + + logger.info("Query executed successfully: {}", query); + return result; + } catch (Exception e) { + logger.error("Error executing query: {} - {}", query, e.getMessage(), e); + throw new RuntimeException("Failed to execute query: " + query, e); + } + } + + /** + * Execute a N1QL query with parameters + * @param bucketName Bucket name + * @param query N1QL query + * @param parameters Query parameters + * @return Query result + */ + public static QueryResult executeQuery(String bucketName, String query, Map parameters) { + try { + QueryOptions options = QueryOptions.queryOptions() + .parameters(JsonObject.from(parameters)); + + QueryResult result = getCluster().bucket(bucketName) + .scope("_default") + .query(query, options); + + logger.info("Query executed successfully: {} with parameters: {}", query, parameters); + return result; + } catch (Exception e) { + logger.error("Error executing query: {} with parameters: {} - {}", query, parameters, e.getMessage(), e); + throw new RuntimeException("Failed to execute query: " + query, e); + } + } + + /** + * Get all documents from a bucket + * @param bucketName Bucket name + * @param documentClass Class of the documents + * @return List of documents + */ + public static List getAllDocuments(String bucketName, Class documentClass) { + try { + String query = "SELECT META().id as id, `*` FROM `" + bucketName + "`"; + QueryResult result = executeQuery(bucketName, query); + + List documents = new ArrayList<>(); + for (JsonObject row : result.rowsAsObject()) { + // Extract the document content (excluding the id field) + JsonObject content = row.getObject(bucketName); + if (content != null) { + T document = objectMapper.readValue(content.toString(), documentClass); + documents.add(document); + } + } + + logger.info("Retrieved {} documents from bucket: {}", documents.size(), bucketName); + return documents; + } catch (Exception e) { + logger.error("Error getting all documents from bucket {}: {}", bucketName, e.getMessage(), e); + return new ArrayList<>(); + } + } + + /** + * Store test execution results + * @param testName Test name + * @param status Test status + * @param duration Test duration in milliseconds + * @param result Test result details + * @return Document ID if stored successfully, null otherwise + */ + public static String storeTestResult(String testName, String status, long duration, Map result) { + String bucketName = configManager.getCouchbaseBucketName(); + + // Create test result document + Map testResult = new HashMap<>(); + testResult.put("testName", testName); + testResult.put("status", status); + testResult.put("duration", duration); + testResult.put("timestamp", System.currentTimeMillis()); + testResult.put("result", result); + testResult.put("type", "test_result"); + + // Generate document ID + String documentId = "test_result::" + testName + "::" + System.currentTimeMillis(); + + if (upsertDocument(bucketName, documentId, testResult)) { + return documentId; + } + + return null; + } + + /** + * Store test logs + * @param testName Test name + * @param level Log level + * @param message Log message + * @param timestamp Log timestamp + * @return Document ID if stored successfully, null otherwise + */ + public static String storeTestLog(String testName, String level, String message, long timestamp) { + String bucketName = configManager.getCouchbaseBucketName(); + + // Create log document + Map logEntry = new HashMap<>(); + logEntry.put("testName", testName); + logEntry.put("level", level); + logEntry.put("message", message); + logEntry.put("timestamp", timestamp); + logEntry.put("type", "test_log"); + + // Generate document ID + String documentId = "test_log::" + testName + "::" + System.currentTimeMillis(); + + if (upsertDocument(bucketName, documentId, logEntry)) { + return documentId; + } + + return null; + } + + /** + * Get test results by test name + * @param testName Test name + * @return List of test results + */ + public static List> getTestResults(String testName) { + String bucketName = configManager.getCouchbaseBucketName(); + + try { + String query = "SELECT `*` FROM `" + bucketName + "` WHERE type = 'test_result' AND testName = $testName ORDER BY timestamp DESC"; + + Map parameters = new HashMap<>(); + parameters.put("testName", testName); + + QueryResult result = executeQuery(bucketName, query, parameters); + + List> testResults = new ArrayList<>(); + for (JsonObject row : result.rowsAsObject()) { + // Extract the document content + JsonObject content = row.getObject(bucketName); + if (content != null) { + Map testResult = objectMapper.readValue(content.toString(), Map.class); + testResults.add(testResult); + } + } + + logger.info("Retrieved {} test results for test: {}", testResults.size(), testName); + return testResults; + } catch (Exception e) { + logger.error("Error getting test results for test {}: {}", testName, e.getMessage(), e); + return new ArrayList<>(); + } + } + + /** + * Get test logs by test name + * @param testName Test name + * @return List of test logs + */ + public static List> getTestLogs(String testName) { + String bucketName = configManager.getCouchbaseBucketName(); + + try { + String query = "SELECT `*` FROM `" + bucketName + "` WHERE type = 'test_log' AND testName = $testName ORDER BY timestamp DESC"; + + Map parameters = new HashMap<>(); + parameters.put("testName", testName); + + QueryResult result = executeQuery(bucketName, query, parameters); + + List> testLogs = new ArrayList<>(); + for (JsonObject row : result.rowsAsObject()) { + // Extract the document content + JsonObject content = row.getObject(bucketName); + if (content != null) { + Map logEntry = objectMapper.readValue(content.toString(), Map.class); + testLogs.add(logEntry); + } + } + + logger.info("Retrieved {} test logs for test: {}", testLogs.size(), testName); + return testLogs; + } catch (Exception e) { + logger.error("Error getting test logs for test {}: {}", testName, e.getMessage(), e); + return new ArrayList<>(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/utils/DatabaseUtil.java b/src/main/java/com/financial/api/utils/DatabaseUtil.java new file mode 100644 index 0000000..8cbd3ad --- /dev/null +++ b/src/main/java/com/financial/api/utils/DatabaseUtil.java @@ -0,0 +1,274 @@ +package com.financial.api.utils; + +import com.financial.api.config.ConfigManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.sql.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility class for database operations to support SQL validation. + */ +public class DatabaseUtil { + private static final Logger logger = LogManager.getLogger(DatabaseUtil.class); + private static ConfigManager configManager; + private static Connection connection; + + static { + configManager = ConfigManager.getInstance(); + } + + /** + * Get database connection + * @return Database connection + * @throws SQLException if a database access error occurs + */ + public static Connection getConnection() throws SQLException { + if (connection == null || connection.isClosed()) { + String url = configManager.getDbUrl(); + String username = configManager.getDbUsername(); + String password = configManager.getDbPassword(); + + logger.info("Establishing database connection to: {}", url); + connection = DriverManager.getConnection(url, username, password); + logger.info("Database connection established successfully"); + } + return connection; + } + + /** + * Close database connection + */ + public static void closeConnection() { + if (connection != null) { + try { + connection.close(); + logger.info("Database connection closed"); + } catch (SQLException e) { + logger.error("Error closing database connection: {}", e.getMessage(), e); + } + } + } + + /** + * Execute a query and return the result as a list of maps + * @param query SQL query + * @return List of maps representing the result set + * @throws SQLException if a database access error occurs + */ + public static List> executeQuery(String query) throws SQLException { + return executeQuery(query, null); + } + + /** + * Execute a query with parameters and return the result as a list of maps + * @param query SQL query + * @param parameters Query parameters + * @return List of maps representing the result set + * @throws SQLException if a database access error occurs + */ + public static List> executeQuery(String query, List parameters) throws SQLException { + List> result = new ArrayList<>(); + + try (Connection conn = getConnection(); + PreparedStatement statement = conn.prepareStatement(query)) { + + // Set parameters if any + if (parameters != null) { + for (int i = 0; i < parameters.size(); i++) { + statement.setObject(i + 1, parameters.get(i)); + } + } + + // Execute query + try (ResultSet resultSet = statement.executeQuery()) { + ResultSetMetaData metaData = resultSet.getMetaData(); + int columnCount = metaData.getColumnCount(); + + // Process result set + while (resultSet.next()) { + Map row = new HashMap<>(); + for (int i = 1; i <= columnCount; i++) { + String columnName = metaData.getColumnName(i); + Object value = resultSet.getObject(i); + row.put(columnName, value); + } + result.add(row); + } + } + } + + logger.debug("Executed query: {} with parameters: {}", query, parameters); + logger.debug("Query returned {} rows", result.size()); + + return result; + } + + /** + * Execute an update/insert/delete statement + * @param query SQL statement + * @return Number of rows affected + * @throws SQLException if a database access error occurs + */ + public static int executeUpdate(String query) throws SQLException { + return executeUpdate(query, null); + } + + /** + * Execute an update/insert/delete statement with parameters + * @param query SQL statement + * @param parameters Statement parameters + * @return Number of rows affected + * @throws SQLException if a database access error occurs + */ + public static int executeUpdate(String query, List parameters) throws SQLException { + int rowsAffected; + + try (Connection conn = getConnection(); + PreparedStatement statement = conn.prepareStatement(query)) { + + // Set parameters if any + if (parameters != null) { + for (int i = 0; i < parameters.size(); i++) { + statement.setObject(i + 1, parameters.get(i)); + } + } + + // Execute update + rowsAffected = statement.executeUpdate(); + } + + logger.debug("Executed update: {} with parameters: {}", query, parameters); + logger.debug("Update affected {} rows", rowsAffected); + + return rowsAffected; + } + + /** + * Execute a query and return a single value + * @param query SQL query + * @return Single value result + * @throws SQLException if a database access error occurs + */ + public static Object executeScalar(String query) throws SQLException { + return executeScalar(query, null); + } + + /** + * Execute a query with parameters and return a single value + * @param query SQL query + * @param parameters Query parameters + * @return Single value result + * @throws SQLException if a database access error occurs + */ + public static Object executeScalar(String query, List parameters) throws SQLException { + Object result = null; + + try (Connection conn = getConnection(); + PreparedStatement statement = conn.prepareStatement(query)) { + + // Set parameters if any + if (parameters != null) { + for (int i = 0; i < parameters.size(); i++) { + statement.setObject(i + 1, parameters.get(i)); + } + } + + // Execute query + try (ResultSet resultSet = statement.executeQuery()) { + if (resultSet.next()) { + result = resultSet.getObject(1); + } + } + } + + logger.debug("Executed scalar query: {} with parameters: {}", query, parameters); + logger.debug("Scalar query returned: {}", result); + + return result; + } + + /** + * Check if a table exists in the database + * @param tableName Table name + * @return true if table exists, false otherwise + * @throws SQLException if a database access error occurs + */ + public static boolean tableExists(String tableName) throws SQLException { + String query = "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = ?"; + List parameters = new ArrayList<>(); + parameters.add(tableName); + + Object result = executeScalar(query, parameters); + return result != null && ((Long) result) > 0; + } + + /** + * Get row count from a table + * @param tableName Table name + * @return Number of rows in the table + * @throws SQLException if a database access error occurs + */ + public static int getRowCount(String tableName) throws SQLException { + String query = "SELECT COUNT(*) FROM " + tableName; + Object result = executeScalar(query); + return result != null ? ((Long) result).intValue() : 0; + } + + /** + * Validate data consistency between API response and database + * @param query SQL query to validate + * @param expectedValue Expected value + * @return true if validation passes, false otherwise + * @throws SQLException if a database access error occurs + */ + public static boolean validateData(String query, Object expectedValue) throws SQLException { + Object actualValue = executeScalar(query); + return (expectedValue == null && actualValue == null) || + (expectedValue != null && expectedValue.equals(actualValue)); + } + + /** + * Validate data consistency between API response and database with parameters + * @param query SQL query to validate + * @param parameters Query parameters + * @param expectedValue Expected value + * @return true if validation passes, false otherwise + * @throws SQLException if a database access error occurs + */ + public static boolean validateData(String query, List parameters, Object expectedValue) throws SQLException { + Object actualValue = executeScalar(query, parameters); + return (expectedValue == null && actualValue == null) || + (expectedValue != null && expectedValue.equals(actualValue)); + } + + /** + * Execute a batch of SQL statements + * @param queries List of SQL statements + * @return Array of update counts for each statement + * @throws SQLException if a database access error occurs + */ + public static int[] executeBatch(List queries) throws SQLException { + int[] result; + + try (Connection conn = getConnection(); + Statement statement = conn.createStatement()) { + + // Add all statements to batch + for (String query : queries) { + statement.addBatch(query); + } + + // Execute batch + result = statement.executeBatch(); + } + + logger.debug("Executed batch of {} statements", queries.size()); + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/utils/ElasticsearchUtil.java b/src/main/java/com/financial/api/utils/ElasticsearchUtil.java new file mode 100644 index 0000000..fe01a79 --- /dev/null +++ b/src/main/java/com/financial/api/utils/ElasticsearchUtil.java @@ -0,0 +1,395 @@ +package com.financial.api.utils; + +import co.elastic.clients.elasticsearch.ElasticsearchClient; +import co.elastic.clients.elasticsearch._types.query_dsl.Query; +import co.elastic.clients.elasticsearch.core.*; +import co.elastic.clients.elasticsearch.core.search.Hit; +import co.elastic.clients.json.JsonData; +import co.elastic.clients.json.jackson.JacksonJsonpMapper; +import co.elastic.clients.transport.ElasticsearchTransport; +import co.elastic.clients.transport.rest_client.RestClientTransport; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.financial.api.config.ConfigManager; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.client.RestClient; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Utility class for Elasticsearch operations to store and retrieve test data and logs. + */ +public class ElasticsearchUtil { + private static final Logger logger = LogManager.getLogger(ElasticsearchUtil.class); + private static ConfigManager configManager; + private static ElasticsearchClient client; + private static ObjectMapper objectMapper; + + static { + configManager = ConfigManager.getInstance(); + objectMapper = new ObjectMapper(); + } + + /** + * Get Elasticsearch client + * @return Elasticsearch client + */ + public static ElasticsearchClient getClient() { + if (client == null) { + initializeClient(); + } + return client; + } + + /** + * Initialize Elasticsearch client + */ + private static void initializeClient() { + try { + String host = configManager.getElasticsearchHost(); + int port = configManager.getElasticsearchPort(); + + // Create the low-level client + RestClient restClient = RestClient.builder( + new HttpHost(host, port, "http")) + .build(); + + // Create the transport with a Jackson mapper + ElasticsearchTransport transport = new RestClientTransport( + restClient, new JacksonJsonpMapper()); + + // Create the API client + client = new ElasticsearchClient(transport); + + logger.info("Elasticsearch client initialized successfully"); + } catch (Exception e) { + logger.error("Error initializing Elasticsearch client: {}", e.getMessage(), e); + throw new RuntimeException("Failed to initialize Elasticsearch client", e); + } + } + + /** + * Check if Elasticsearch is running + * @return true if Elasticsearch is running, false otherwise + */ + public static boolean isRunning() { + try { + PingResponse pingResponse = getClient().ping(); + return pingResponse.value(); + } catch (IOException e) { + logger.error("Error pinging Elasticsearch: {}", e.getMessage(), e); + return false; + } + } + + /** + * Create an index + * @param indexName Index name + * @return true if index created successfully, false otherwise + */ + public static boolean createIndex(String indexName) { + try { + CreateIndexResponse createIndexResponse = getClient().indices() + .create(c -> c.index(indexName)); + + logger.info("Index {} created successfully: {}", indexName, createIndexResponse.acknowledged()); + return createIndexResponse.acknowledged(); + } catch (IOException e) { + logger.error("Error creating index {}: {}", indexName, e.getMessage(), e); + return false; + } + } + + /** + * Check if an index exists + * @param indexName Index name + * @return true if index exists, false otherwise + */ + public static boolean indexExists(String indexName) { + try { + return getClient().indices().exists(e -> e.index(indexName)).value(); + } catch (IOException e) { + logger.error("Error checking if index {} exists: {}", indexName, e.getMessage(), e); + return false; + } + } + + /** + * Index a document + * @param indexName Index name + * @param documentId Document ID + * @param document Document object + * @return true if document indexed successfully, false otherwise + */ + public static boolean indexDocument(String indexName, String documentId, Object document) { + try { + IndexRequest request = IndexRequest.of(i -> i + .index(indexName) + .id(documentId) + .document(document) + ); + + IndexResponse response = getClient().index(request); + logger.info("Document indexed with ID: {} in index: {}", response.id(), response.index()); + return true; + } catch (IOException e) { + logger.error("Error indexing document in index {}: {}", indexName, e.getMessage(), e); + return false; + } + } + + /** + * Index a document with auto-generated ID + * @param indexName Index name + * @param document Document object + * @return Document ID if indexed successfully, null otherwise + */ + public static String indexDocument(String indexName, Object document) { + try { + IndexRequest request = IndexRequest.of(i -> i + .index(indexName) + .document(document) + ); + + IndexResponse response = getClient().index(request); + logger.info("Document indexed with ID: {} in index: {}", response.id(), response.index()); + return response.id(); + } catch (IOException e) { + logger.error("Error indexing document in index {}: {}", indexName, e.getMessage(), e); + return null; + } + } + + /** + * Get a document by ID + * @param indexName Index name + * @param documentId Document ID + * @param documentClass Class of the document + * @return Document object if found, null otherwise + */ + public static T getDocument(String indexName, String documentId, Class documentClass) { + try { + GetRequest getRequest = GetRequest.of(g -> g + .index(indexName) + .id(documentId) + ); + + GetResponse getResponse = getClient().get(getRequest, documentClass); + + if (getResponse.found()) { + logger.info("Document found with ID: {} in index: {}", documentId, indexName); + return getResponse.source(); + } else { + logger.info("Document not found with ID: {} in index: {}", documentId, indexName); + return null; + } + } catch (IOException e) { + logger.error("Error getting document from index {}: {}", indexName, e.getMessage(), e); + return null; + } + } + + /** + * Search documents + * @param indexName Index name + * @param query Search query + * @param documentClass Class of the documents + * @return List of documents + */ + public static List searchDocuments(String indexName, Query query, Class documentClass) { + try { + SearchRequest searchRequest = SearchRequest.of(s -> s + .index(indexName) + .query(query) + ); + + SearchResponse searchResponse = getClient().search(searchRequest, documentClass); + + List documents = new ArrayList<>(); + for (Hit hit : searchResponse.hits().hits()) { + documents.add(hit.source()); + } + + logger.info("Found {} documents in index: {}", documents.size(), indexName); + return documents; + } catch (IOException e) { + logger.error("Error searching documents in index {}: {}", indexName, e.getMessage(), e); + return new ArrayList<>(); + } + } + + /** + * Search all documents in an index + * @param indexName Index name + * @param documentClass Class of the documents + * @return List of documents + */ + public static List searchAllDocuments(String indexName, Class documentClass) { + try { + SearchRequest searchRequest = SearchRequest.of(s -> s + .index(indexName) + .query(Query.of(q -> q.matchAll(m -> m))) + ); + + SearchResponse searchResponse = getClient().search(searchRequest, documentClass); + + List documents = new ArrayList<>(); + for (Hit hit : searchResponse.hits().hits()) { + documents.add(hit.source()); + } + + logger.info("Found {} documents in index: {}", documents.size(), indexName); + return documents; + } catch (IOException e) { + logger.error("Error searching all documents in index {}: {}", indexName, e.getMessage(), e); + return new ArrayList<>(); + } + } + + /** + * Update a document + * @param indexName Index name + * @param documentId Document ID + * @param document Document object with updates + * @return true if document updated successfully, false otherwise + */ + public static boolean updateDocument(String indexName, String documentId, Object document) { + try { + Map documentMap = objectMapper.convertValue(document, Map.class); + + UpdateRequest, Object> request = UpdateRequest.of(u -> u + .index(indexName) + .id(documentId) + .doc(documentMap) + ); + + UpdateResponse response = getClient().update(request, Object.class); + logger.info("Document updated with ID: {} in index: {}", documentId, indexName); + return true; + } catch (IOException e) { + logger.error("Error updating document in index {}: {}", indexName, e.getMessage(), e); + return false; + } + } + + /** + * Delete a document + * @param indexName Index name + * @param documentId Document ID + * @return true if document deleted successfully, false otherwise + */ + public static boolean deleteDocument(String indexName, String documentId) { + try { + DeleteRequest request = DeleteRequest.of(d -> d + .index(indexName) + .id(documentId) + ); + + DeleteResponse response = getClient().delete(request); + logger.info("Document deleted with ID: {} in index: {}", documentId, indexName); + return true; + } catch (IOException e) { + logger.error("Error deleting document from index {}: {}", indexName, e.getMessage(), e); + return false; + } + } + + /** + * Delete an index + * @param indexName Index name + * @return true if index deleted successfully, false otherwise + */ + public static boolean deleteIndex(String indexName) { + try { + DeleteIndexResponse response = getClient().indices().delete(d -> d.index(indexName)); + logger.info("Index {} deleted successfully: {}", indexName, response.acknowledged()); + return response.acknowledged(); + } catch (IOException e) { + logger.error("Error deleting index {}: {}", indexName, e.getMessage(), e); + return false; + } + } + + /** + * Count documents in an index + * @param indexName Index name + * @return Number of documents + */ + public static long countDocuments(String indexName) { + try { + CountRequest countRequest = CountRequest.of(c -> c + .index(indexName) + ); + + CountResponse countResponse = getClient().count(countRequest); + long count = countResponse.count(); + logger.info("Counted {} documents in index: {}", count, indexName); + return count; + } catch (IOException e) { + logger.error("Error counting documents in index {}: {}", indexName, e.getMessage(), e); + return 0; + } + } + + /** + * Store test execution results + * @param testName Test name + * @param status Test status + * @param duration Test duration in milliseconds + * @param result Test result details + * @return Document ID if stored successfully, null otherwise + */ + public static String storeTestResult(String testName, String status, long duration, Map result) { + String indexName = configManager.getProperty("elasticsearch.index.prefix", "financial_test_") + "results"; + + // Create index if it doesn't exist + if (!indexExists(indexName)) { + createIndex(indexName); + } + + // Create test result document + Map testResult = new HashMap<>(); + testResult.put("testName", testName); + testResult.put("status", status); + testResult.put("duration", duration); + testResult.put("timestamp", System.currentTimeMillis()); + testResult.put("result", result); + + return indexDocument(indexName, testResult); + } + + /** + * Store test logs + * @param testName Test name + * @param level Log level + * @param message Log message + * @param timestamp Log timestamp + * @return Document ID if stored successfully, null otherwise + */ + public static String storeTestLog(String testName, String level, String message, long timestamp) { + String indexName = configManager.getProperty("elasticsearch.index.prefix", "financial_test_") + "logs"; + + // Create index if it doesn't exist + if (!indexExists(indexName)) { + createIndex(indexName); + } + + // Create log document + Map logEntry = new HashMap<>(); + logEntry.put("testName", testName); + logEntry.put("level", level); + logEntry.put("message", message); + logEntry.put("timestamp", timestamp); + + return indexDocument(indexName, logEntry); + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/utils/TestDataFactory.java b/src/main/java/com/financial/api/utils/TestDataFactory.java new file mode 100644 index 0000000..6b9e9bb --- /dev/null +++ b/src/main/java/com/financial/api/utils/TestDataFactory.java @@ -0,0 +1,445 @@ +package com.financial.api.utils; + +import com.financial.api.models.credit.CreditApplication; +import com.financial.api.models.credit.CreditScore; +import com.financial.api.models.wallet.Transaction; +import com.financial.api.models.wallet.Wallet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.Year; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.UUID; + +/** + * Factory class to generate test data for wallet and credit scoring APIs. + */ +public class TestDataFactory { + private static final Logger logger = LogManager.getLogger(TestDataFactory.class); + private static final Random random = new Random(); + + /** + * Generate a random wallet for testing + * @return Wallet object + */ + public static Wallet generateRandomWallet() { + String customerId = UUID.randomUUID().toString(); + String accountNumber = generateRandomAccountNumber(); + BigDecimal balance = generateRandomBalance(); + String currency = generateRandomCurrency(); + + Wallet wallet = new Wallet(customerId, accountNumber, balance, currency); + wallet.setCreatedBy("test_user"); + + logger.info("Generated random wallet: {}", wallet); + return wallet; + } + + /** + * Generate a wallet with specific parameters + * @param customerId Customer ID + * @param balance Initial balance + * @param currency Currency code + * @return Wallet object + */ + public static Wallet generateWallet(String customerId, BigDecimal balance, String currency) { + String accountNumber = generateRandomAccountNumber(); + + Wallet wallet = new Wallet(customerId, accountNumber, balance, currency); + wallet.setCreatedBy("test_user"); + + logger.info("Generated wallet with parameters: {}", wallet); + return wallet; + } + + /** + * Generate a random transaction for testing + * @param walletId Wallet ID + * @return Transaction object + */ + public static Transaction generateRandomTransaction(String walletId) { + Transaction.TransactionType type = generateRandomTransactionType(); + BigDecimal amount = generateRandomAmount(); + String currency = generateRandomCurrency(); + String description = generateRandomDescription(type); + + Transaction transaction = new Transaction(walletId, type, amount, currency, description); + transaction.setCreatedBy("test_user"); + + logger.info("Generated random transaction: {}", transaction); + return transaction; + } + + /** + * Generate a transaction with specific parameters + * @param walletId Wallet ID + * @param type Transaction type + * @param amount Transaction amount + * @param currency Currency code + * @param description Transaction description + * @return Transaction object + */ + public static Transaction generateTransaction(String walletId, Transaction.TransactionType type, + BigDecimal amount, String currency, String description) { + Transaction transaction = new Transaction(walletId, type, amount, currency, description); + transaction.setCreatedBy("test_user"); + + logger.info("Generated transaction with parameters: {}", transaction); + return transaction; + } + + /** + * Generate a random credit application for testing + * @return CreditApplication object + */ + public static CreditApplication generateRandomCreditApplication() { + String customerId = UUID.randomUUID().toString(); + String applicantName = generateRandomName(); + String applicantEmail = generateRandomEmail(applicantName); + String applicantPhone = generateRandomPhoneNumber(); + LocalDate dateOfBirth = generateRandomDateOfBirth(); + BigDecimal monthlyIncome = generateRandomMonthlyIncome(); + BigDecimal loanAmount = generateRandomLoanAmount(); + int loanTermMonths = generateRandomLoanTerm(); + String loanPurpose = generateRandomLoanPurpose(); + CreditApplication.EmploymentStatus employmentStatus = generateRandomEmploymentStatus(); + + CreditApplication application = new CreditApplication( + customerId, applicantName, applicantEmail, applicantPhone, dateOfBirth, + monthlyIncome, loanAmount, loanTermMonths, loanPurpose, employmentStatus + ); + + logger.info("Generated random credit application: {}", application); + return application; + } + + /** + * Generate a credit application with specific parameters + * @param customerId Customer ID + * @param applicantName Applicant name + * @param monthlyIncome Monthly income + * @param loanAmount Loan amount + * @param loanTermMonths Loan term in months + * @param loanPurpose Loan purpose + * @param employmentStatus Employment status + * @return CreditApplication object + */ + public static CreditApplication generateCreditApplication( + String customerId, String applicantName, BigDecimal monthlyIncome, + BigDecimal loanAmount, int loanTermMonths, String loanPurpose, + CreditApplication.EmploymentStatus employmentStatus + ) { + String applicantEmail = generateRandomEmail(applicantName); + String applicantPhone = generateRandomPhoneNumber(); + LocalDate dateOfBirth = generateRandomDateOfBirth(); + + CreditApplication application = new CreditApplication( + customerId, applicantName, applicantEmail, applicantPhone, dateOfBirth, + monthlyIncome, loanAmount, loanTermMonths, loanPurpose, employmentStatus + ); + + logger.info("Generated credit application with parameters: {}", application); + return application; + } + + /** + * Generate a random credit score for testing + * @param applicationId Application ID + * @param customerId Customer ID + * @return CreditScore object + */ + public static CreditScore generateRandomCreditScore(String applicationId, String customerId) { + int score = generateRandomCreditScoreValue(); + String calculationMethod = generateRandomCalculationMethod(); + + CreditScore creditScore = new CreditScore(applicationId, customerId, score, calculationMethod); + + logger.info("Generated random credit score: {}", creditScore); + return creditScore; + } + + /** + * Generate a credit score with specific parameters + * @param applicationId Application ID + * @param customerId Customer ID + * @param score Credit score value + * @param calculationMethod Calculation method + * @return CreditScore object + */ + public static CreditScore generateCreditScore( + String applicationId, String customerId, int score, String calculationMethod + ) { + CreditScore creditScore = new CreditScore(applicationId, customerId, score, calculationMethod); + + logger.info("Generated credit score with parameters: {}", creditScore); + return creditScore; + } + + /** + * Generate a random account number + * @return Random account number + */ + private static String generateRandomAccountNumber() { + return "ACC" + String.format("%010d", random.nextInt(1000000000)); + } + + /** + * Generate a random balance + * @return Random balance + */ + private static BigDecimal generateRandomBalance() { + double min = 100.0; + double max = 10000.0; + double balance = min + (max - min) * random.nextDouble(); + return BigDecimal.valueOf(balance).setScale(2, RoundingMode.HALF_UP); + } + + /** + * Generate a random currency + * @return Random currency code + */ + private static String generateRandomCurrency() { + String[] currencies = {"USD", "EUR", "GBP", "JPY", "CAD", "AUD"}; + return currencies[random.nextInt(currencies.length)]; + } + + /** + * Generate a random transaction type + * @return Random transaction type + */ + private static Transaction.TransactionType generateRandomTransactionType() { + Transaction.TransactionType[] types = Transaction.TransactionType.values(); + return types[random.nextInt(types.length)]; + } + + /** + * Generate a random amount + * @return Random amount + */ + private static BigDecimal generateRandomAmount() { + double min = 10.0; + double max = 1000.0; + double amount = min + (max - min) * random.nextDouble(); + return BigDecimal.valueOf(amount).setScale(2, RoundingMode.HALF_UP); + } + + /** + * Generate a random description based on transaction type + * @param type Transaction type + * @return Random description + */ + private static String generateRandomDescription(Transaction.TransactionType type) { + switch (type) { + case DEPOSIT: + return "Deposit from " + generateRandomName(); + case WITHDRAWAL: + return "Withdrawal to " + generateRandomName(); + case TRANSFER: + return "Transfer to " + generateRandomName(); + case PAYMENT: + return "Payment to " + generateRandomName(); + case REFUND: + return "Refund from " + generateRandomName(); + default: + return "Transaction description"; + } + } + + /** + * Generate a random name + * @return Random name + */ + private static String generateRandomName() { + String[] firstNames = {"John", "Jane", "Michael", "Emily", "David", "Sarah", "Robert", "Lisa"}; + String[] lastNames = {"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis"}; + + String firstName = firstNames[random.nextInt(firstNames.length)]; + String lastName = lastNames[random.nextInt(lastNames.length)]; + + return firstName + " " + lastName; + } + + /** + * Generate a random email + * @param name Name + * @return Random email + */ + private static String generateRandomEmail(String name) { + String[] domains = {"gmail.com", "yahoo.com", "hotmail.com", "outlook.com"}; + String domain = domains[random.nextInt(domains.length)]; + + // Remove spaces and convert to lowercase + String emailName = name.toLowerCase().replace(" ", "."); + + // Add random number to avoid duplicates + int randomNumber = random.nextInt(1000); + + return emailName + randomNumber + "@" + domain; + } + + /** + * Generate a random phone number + * @return Random phone number + */ + private static String generateRandomPhoneNumber() { + return "+" + (random.nextInt(90) + 10) + " " + + String.format("%03d", random.nextInt(1000)) + " " + + String.format("%03d", random.nextInt(1000)) + " " + + String.format("%04d", random.nextInt(10000)); + } + + /** + * Generate a random date of birth + * @return Random date of birth + */ + private static LocalDate generateRandomDateOfBirth() { + int minYear = Year.now().getValue() - 70; // 70 years ago + int maxYear = Year.now().getValue() - 18; // 18 years ago + + int year = minYear + random.nextInt(maxYear - minYear + 1); + int month = 1 + random.nextInt(12); + int day = 1 + random.nextInt(28); // Simplified to avoid month/day validation + + return LocalDate.of(year, month, day); + } + + /** + * Generate a random monthly income + * @return Random monthly income + */ + private static BigDecimal generateRandomMonthlyIncome() { + double min = 1000.0; + double max = 20000.0; + double income = min + (max - min) * random.nextDouble(); + return BigDecimal.valueOf(income).setScale(2, RoundingMode.HALF_UP); + } + + /** + * Generate a random loan amount + * @return Random loan amount + */ + private static BigDecimal generateRandomLoanAmount() { + double min = 1000.0; + double max = 100000.0; + double amount = min + (max - min) * random.nextDouble(); + return BigDecimal.valueOf(amount).setScale(2, RoundingMode.HALF_UP); + } + + /** + * Generate a random loan term + * @return Random loan term in months + */ + private static int generateRandomLoanTerm() { + int[] terms = {12, 24, 36, 48, 60, 72, 84, 96, 108, 120}; + return terms[random.nextInt(terms.length)]; + } + + /** + * Generate a random loan purpose + * @return Random loan purpose + */ + private static String generateRandomLoanPurpose() { + String[] purposes = { + "Home Purchase", "Home Improvement", "Debt Consolidation", + "Car Purchase", "Education", "Business", "Medical Expenses", + "Vacation", "Wedding", "Other" + }; + return purposes[random.nextInt(purposes.length)]; + } + + /** + * Generate a random employment status + * @return Random employment status + */ + private static CreditApplication.EmploymentStatus generateRandomEmploymentStatus() { + CreditApplication.EmploymentStatus[] statuses = CreditApplication.EmploymentStatus.values(); + return statuses[random.nextInt(statuses.length)]; + } + + /** + * Generate a random credit score value + * @return Random credit score value + */ + private static int generateRandomCreditScoreValue() { + return 300 + random.nextInt(551); // 300-850 + } + + /** + * Generate a random calculation method + * @return Random calculation method + */ + private static String generateRandomCalculationMethod() { + String[] methods = {"FICO", "VantageScore", "CustomAlgorithm"}; + return methods[random.nextInt(methods.length)]; + } + + /** + * Generate test data map for API requests + * @param dataType Type of test data to generate + * @return Map of test data + */ + public static Map generateTestDataMap(String dataType) { + Map testData = new HashMap<>(); + + switch (dataType.toLowerCase()) { + case "wallet": + Wallet wallet = generateRandomWallet(); + testData.put("customerId", wallet.getCustomerId()); + testData.put("accountNumber", wallet.getAccountNumber()); + testData.put("balance", wallet.getBalance()); + testData.put("currency", wallet.getCurrency()); + testData.put("status", wallet.getStatus()); + break; + + case "transaction": + String walletId = UUID.randomUUID().toString(); + Transaction transaction = generateRandomTransaction(walletId); + testData.put("walletId", transaction.getWalletId()); + testData.put("type", transaction.getType()); + testData.put("amount", transaction.getAmount()); + testData.put("currency", transaction.getCurrency()); + testData.put("description", transaction.getDescription()); + testData.put("status", transaction.getStatus()); + break; + + case "creditapplication": + CreditApplication application = generateRandomCreditApplication(); + testData.put("customerId", application.getCustomerId()); + testData.put("applicantName", application.getApplicantName()); + testData.put("applicantEmail", application.getApplicantEmail()); + testData.put("applicantPhone", application.getApplicantPhone()); + testData.put("dateOfBirth", application.getDateOfBirth()); + testData.put("monthlyIncome", application.getMonthlyIncome()); + testData.put("loanAmount", application.getLoanAmount()); + testData.put("loanTermMonths", application.getLoanTermMonths()); + testData.put("loanPurpose", application.getLoanPurpose()); + testData.put("employmentStatus", application.getEmploymentStatus()); + testData.put("status", application.getStatus()); + break; + + case "creditscore": + String applicationId = UUID.randomUUID().toString(); + String customerId = UUID.randomUUID().toString(); + CreditScore creditScore = generateRandomCreditScore(applicationId, customerId); + testData.put("applicationId", creditScore.getApplicationId()); + testData.put("customerId", creditScore.getCustomerId()); + testData.put("score", creditScore.getScore()); + testData.put("scoreBand", creditScore.getScoreBand()); + testData.put("rating", creditScore.getRating()); + testData.put("calculationMethod", creditScore.getCalculationMethod()); + break; + + default: + logger.warn("Unknown data type: {}", dataType); + break; + } + + return testData; + } +} \ No newline at end of file diff --git a/src/main/java/com/financial/api/utils/TestListener.java b/src/main/java/com/financial/api/utils/TestListener.java new file mode 100644 index 0000000..458210d --- /dev/null +++ b/src/main/java/com/financial/api/utils/TestListener.java @@ -0,0 +1,207 @@ +package com.financial.api.utils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.ITestContext; +import org.testng.ITestListener; +import org.testng.ITestResult; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Test listener for handling test events and generating reports + */ +public class TestListener implements ITestListener { + private static final Logger logger = LogManager.getLogger(TestListener.class); + private static final String PROJECT_NAME = "Dracarys"; + + // Test statistics + private final ConcurrentHashMap testStats = new ConcurrentHashMap<>(); + private final AtomicInteger passedTests = new AtomicInteger(0); + private final AtomicInteger failedTests = new AtomicInteger(0); + private final AtomicInteger skippedTests = new AtomicInteger(0); + private final AtomicInteger totalTests = new AtomicInteger(0); + + // Test timing + private final ConcurrentHashMap testStartTimes = new ConcurrentHashMap<>(); + private final ConcurrentHashMap testEndTimes = new ConcurrentHashMap<>(); + + @Override + public void onStart(ITestContext context) { + logger.info("======== {} Test Suite: {} started ========", PROJECT_NAME, context.getName()); + logger.info("Total test count: {}", context.getAllTestMethods().length); + + // Reset statistics for new test suite + passedTests.set(0); + failedTests.set(0); + skippedTests.set(0); + totalTests.set(context.getAllTestMethods().length); + } + + @Override + public void onFinish(ITestContext context) { + logger.info("======== {} Test Suite: {} finished ========", PROJECT_NAME, context.getName()); + + // Print test statistics + logger.info("Test Statistics:"); + logger.info(" Total tests: {}", totalTests.get()); + logger.info(" Passed tests: {}", passedTests.get()); + logger.info(" Failed tests: {}", failedTests.get()); + logger.info(" Skipped tests: {}", skippedTests.get()); + + // Calculate success rate + double successRate = totalTests.get() > 0 ? + (double) passedTests.get() / totalTests.get() * 100 : 0; + logger.info(" Success rate: {:.2f}%", successRate); + + // Print test group statistics + logger.info("Test Group Statistics:"); + testStats.forEach((group, count) -> + logger.info(" {}: {}", group, count.get())); + + // Generate HTML report (in a real implementation, this would generate a detailed HTML report) + generateHtmlReport(context); + } + + @Override + public void onTestStart(ITestResult result) { + String testName = result.getMethod().getMethodName(); + String className = result.getMethod().getTestClass().getName(); + String testDescription = result.getMethod().getDescription() != null ? + result.getMethod().getDescription() : "No description"; + + logger.info("Starting test: {}.{} - {}", className, testName, testDescription); + + // Record test start time + testStartTimes.put(testName, System.currentTimeMillis()); + } + + @Override + public void onTestSuccess(ITestResult result) { + String testName = result.getMethod().getMethodName(); + String className = result.getMethod().getTestClass().getName(); + + // Record test end time + testEndTimes.put(testName, System.currentTimeMillis()); + + // Calculate test duration + long duration = testEndTimes.get(testName) - testStartTimes.get(testName); + + logger.info("Test passed: {}.{} - Duration: {} ms", className, testName, duration); + + // Update statistics + passedTests.incrementAndGet(); + updateTestGroupStats(result); + + // Log test parameters if any + if (result.getParameters() != null && result.getParameters().length > 0) { + logger.info("Test parameters:"); + for (Object param : result.getParameters()) { + logger.info(" {}", param); + } + } + } + + @Override + public void onTestFailure(ITestResult result) { + String testName = result.getMethod().getMethodName(); + String className = result.getMethod().getTestClass().getName(); + + // Record test end time + testEndTimes.put(testName, System.currentTimeMillis()); + + // Calculate test duration + long duration = testEndTimes.get(testName) - testStartTimes.get(testName); + + logger.error("Test failed: {}.{} - Duration: {} ms", className, testName, duration); + + // Log exception + if (result.getThrowable() != null) { + logger.error("Exception: ", result.getThrowable()); + } + + // Update statistics + failedTests.incrementAndGet(); + updateTestGroupStats(result); + + // Log test parameters if any + if (result.getParameters() != null && result.getParameters().length > 0) { + logger.error("Test parameters:"); + for (Object param : result.getParameters()) { + logger.error(" {}", param); + } + } + } + + @Override + public void onTestSkipped(ITestResult result) { + String testName = result.getMethod().getMethodName(); + String className = result.getMethod().getTestClass().getName(); + + logger.warn("Test skipped: {}.{}", className, testName); + + // Update statistics + skippedTests.incrementAndGet(); + updateTestGroupStats(result); + + // Log skip reason if available + if (result.getThrowable() != null) { + logger.warn("Skip reason: {}", result.getThrowable().getMessage()); + } + } + + @Override + public void onTestFailedButWithinSuccessPercentage(ITestResult result) { + String testName = result.getMethod().getMethodName(); + String className = result.getMethod().getTestClass().getName(); + + logger.warn("Test failed but within success percentage: {}.{}", className, testName); + + // Update statistics + failedTests.incrementAndGet(); + updateTestGroupStats(result); + } + + /** + * Update test group statistics + * @param result Test result + */ + private void updateTestGroupStats(ITestResult result) { + String[] groups = result.getMethod().getGroups(); + + if (groups != null && groups.length > 0) { + for (String group : groups) { + testStats.computeIfAbsent(group, k -> new AtomicInteger(0)).incrementAndGet(); + } + } else { + testStats.computeIfAbsent("default", k -> new AtomicInteger(0)).incrementAndGet(); + } + } + + /** + * Generate HTML report + * @param context Test context + */ + private void generateHtmlReport(ITestContext context) { + // In a real implementation, this would generate a detailed HTML report + // For now, we'll just log that the report would be generated + + logger.info("Generating {} HTML report...", PROJECT_NAME); + + // Report file path + String reportPath = "test-output/" + context.getName() + "_report.html"; + + logger.info("{} HTML report would be generated at: {}", PROJECT_NAME, reportPath); + + // In a real implementation, you would: + // 1. Create an HTML file + // 2. Add CSS styling + // 3. Add test suite information + // 4. Add test statistics + // 5. Add test results with pass/fail status + // 6. Add test execution times + // 7. Add any screenshots or logs + // 8. Add charts for visual representation of results + } +} \ No newline at end of file diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties new file mode 100644 index 0000000..b3f636f --- /dev/null +++ b/src/main/resources/config.properties @@ -0,0 +1,44 @@ +# API Configuration +api.base.url=https://api.example.com +api.version=v1 +api.timeout=10000 +api.retry.count=3 + +# Database Configuration +db.url=jdbc:mysql://localhost:3306/financial_db +db.username=test_user +db.password=test_password +db.driver=com.mysql.cj.jdbc.Driver + +# Elasticsearch Configuration +elasticsearch.host=localhost +elasticsearch.port=9200 +elasticsearch.scheme=http +elasticsearch.index.prefix=financial_test_ + +# Couchbase Configuration +couchbase.host=localhost +couchbase.username=test_user +couchbase.password=test_password +couchbase.bucket.name=financial_test_data +couchbase.bucket.password= + +# Test Configuration +test.data.dir=src/test/resources/testdata +test.report.dir=target/test-reports +test.log.level=INFO + +# Performance Test Configuration +performance.test.users=100 +performance.test.ramp-up=30 +performance.test.duration=300 +performance.test.throughput=10 + +# Environment Configuration (dev, staging, prod) +env=dev + +# Logging Configuration +log.dir=target/logs +log.file.name=api-test-framework +log.max.size=10MB +log.max.history=30 \ No newline at end of file diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..35b9588 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,85 @@ + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + target/logs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/financial/api/tests/CreditApiTest.java b/src/test/java/com/financial/api/tests/CreditApiTest.java new file mode 100644 index 0000000..ff8365a --- /dev/null +++ b/src/test/java/com/financial/api/tests/CreditApiTest.java @@ -0,0 +1,486 @@ +package com.financial.api.tests; + +import com.financial.api.models.credit.CreditApplication; +import com.financial.api.models.credit.CreditScore; +import com.financial.api.services.CreditService; +import com.financial.api.utils.DatabaseUtil; +import com.financial.api.utils.TestDataFactory; +import io.restassured.response.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +/** + * Test class for Credit API operations + */ +public class CreditApiTest { + private static final Logger logger = LogManager.getLogger(CreditApiTest.class); + private CreditService creditService; + private CreditApplication testApplication; + private CreditScore testCreditScore; + + @BeforeClass + public void setUp() { + logger.info("Setting up CreditApiTest"); + creditService = new CreditService(); + + // Create test credit application + testApplication = TestDataFactory.generateRandomCreditApplication(); + + // Create test credit score + testCreditScore = TestDataFactory.generateRandomCreditScore(testApplication.getId(), testApplication.getCustomerId()); + } + + @AfterClass + public void tearDown() { + logger.info("Tearing down CreditApiTest"); + + // Clean up test data if needed + try { + // Close database connection + DatabaseUtil.closeConnection(); + } catch (Exception e) { + logger.error("Error during tear down: {}", e.getMessage(), e); + } + } + + @Test(description = "Submit a new credit application") + public void testSubmitApplication() { + logger.info("Executing testSubmitApplication"); + + // Submit application via API + Response response = creditService.submitApplication(testApplication); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 201, "Expected status code 201 for application submission"); + + // Parse response + CreditApplication submittedApplication = response.as(CreditApplication.class); + + // Validate application data + Assert.assertNotNull(submittedApplication.getId(), "Application ID should not be null"); + Assert.assertEquals(submittedApplication.getCustomerId(), testApplication.getCustomerId(), "Customer ID should match"); + Assert.assertEquals(submittedApplication.getApplicantName(), testApplication.getApplicantName(), "Applicant name should match"); + Assert.assertEquals(submittedApplication.getApplicantEmail(), testApplication.getApplicantEmail(), "Applicant email should match"); + Assert.assertEquals(submittedApplication.getApplicantPhone(), testApplication.getApplicantPhone(), "Applicant phone should match"); + Assert.assertEquals(submittedApplication.getDateOfBirth(), testApplication.getDateOfBirth(), "Date of birth should match"); + Assert.assertEquals(submittedApplication.getMonthlyIncome(), testApplication.getMonthlyIncome(), "Monthly income should match"); + Assert.assertEquals(submittedApplication.getLoanAmount(), testApplication.getLoanAmount(), "Loan amount should match"); + Assert.assertEquals(submittedApplication.getLoanTermMonths(), testApplication.getLoanTermMonths(), "Loan term should match"); + Assert.assertEquals(submittedApplication.getLoanPurpose(), testApplication.getLoanPurpose(), "Loan purpose should match"); + Assert.assertEquals(submittedApplication.getEmploymentStatus(), testApplication.getEmploymentStatus(), "Employment status should match"); + Assert.assertEquals(submittedApplication.getStatus(), CreditApplication.CreditApplicationStatus.SUBMITTED, "Application status should be SUBMITTED"); + + // Update test application with generated ID + testApplication.setId(submittedApplication.getId()); + + // Validate application data in database + validateApplicationInDatabase(submittedApplication); + + logger.info("testSubmitApplication completed successfully"); + } + + @Test(description = "Get credit application by ID", dependsOnMethods = "testSubmitApplication") + public void testGetApplication() { + logger.info("Executing testGetApplication"); + + // Get application via API + Response response = creditService.getApplication(testApplication.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting application"); + + // Parse response + CreditApplication retrievedApplication = response.as(CreditApplication.class); + + // Validate application data + Assert.assertEquals(retrievedApplication.getId(), testApplication.getId(), "Application ID should match"); + Assert.assertEquals(retrievedApplication.getCustomerId(), testApplication.getCustomerId(), "Customer ID should match"); + Assert.assertEquals(retrievedApplication.getApplicantName(), testApplication.getApplicantName(), "Applicant name should match"); + Assert.assertEquals(retrievedApplication.getMonthlyIncome(), testApplication.getMonthlyIncome(), "Monthly income should match"); + Assert.assertEquals(retrievedApplication.getLoanAmount(), testApplication.getLoanAmount(), "Loan amount should match"); + Assert.assertEquals(retrievedApplication.getStatus(), CreditApplication.CreditApplicationStatus.SUBMITTED, "Application status should be SUBMITTED"); + + logger.info("testGetApplication completed successfully"); + } + + @Test(description = "Update credit application", dependsOnMethods = "testSubmitApplication") + public void testUpdateApplication() { + logger.info("Executing testUpdateApplication"); + + // Update application data + testApplication.setLoanAmount(new BigDecimal("25000.00")); + testApplication.setLoanTermMonths(48); + + // Update application via API + Response response = creditService.updateApplication(testApplication.getId(), testApplication); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for updating application"); + + // Parse response + CreditApplication updatedApplication = response.as(CreditApplication.class); + + // Validate application data + Assert.assertEquals(updatedApplication.getId(), testApplication.getId(), "Application ID should match"); + Assert.assertEquals(updatedApplication.getLoanAmount(), testApplication.getLoanAmount(), "Loan amount should match"); + Assert.assertEquals(updatedApplication.getLoanTermMonths(), testApplication.getLoanTermMonths(), "Loan term should match"); + + // Validate application data in database + validateApplicationInDatabase(updatedApplication); + + logger.info("testUpdateApplication completed successfully"); + } + + @Test(description = "Get credit applications by status", dependsOnMethods = "testSubmitApplication") + public void testGetApplicationsByStatus() { + logger.info("Executing testGetApplicationsByStatus"); + + // Get applications by status via API + Response response = creditService.getApplicationsByStatus(CreditApplication.CreditApplicationStatus.SUBMITTED); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting applications by status"); + + // Parse response + List> applications = response.jsonPath().getList("$"); + + // Validate applications + Assert.assertTrue(applications.size() > 0, "Should have at least one application with status SUBMITTED"); + + // Find our test application + boolean found = false; + for (Map applicationData : applications) { + if (applicationData.get("id").equals(testApplication.getId())) { + found = true; + break; + } + } + + Assert.assertTrue(found, "Test application should be in the list"); + + logger.info("testGetApplicationsByStatus completed successfully"); + } + + @Test(description = "Request credit score calculation", dependsOnMethods = "testSubmitApplication") + public void testCalculateCreditScore() { + logger.info("Executing testCalculateCreditScore"); + + // Request credit score calculation via API + String calculationMethod = "FICO"; + Response response = creditService.calculateCreditScore(testApplication.getId(), calculationMethod); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for credit score calculation"); + + // Parse response + CreditScore calculatedScore = response.as(CreditScore.class); + + // Validate credit score data + Assert.assertNotNull(calculatedScore.getId(), "Credit score ID should not be null"); + Assert.assertEquals(calculatedScore.getApplicationId(), testApplication.getId(), "Application ID should match"); + Assert.assertEquals(calculatedScore.getCustomerId(), testApplication.getCustomerId(), "Customer ID should match"); + Assert.assertTrue(calculatedScore.getScore() >= 300 && calculatedScore.getScore() <= 850, "Credit score should be between 300 and 850"); + Assert.assertEquals(calculatedScore.getCalculationMethod(), calculationMethod, "Calculation method should match"); + + // Update test credit score with generated ID + testCreditScore.setId(calculatedScore.getId()); + testCreditScore.setScore(calculatedScore.getScore()); + testCreditScore.setScoreBand(calculatedScore.getScoreBand()); + testCreditScore.setRating(calculatedScore.getRating()); + + // Validate credit score data in database + validateCreditScoreInDatabase(calculatedScore); + + logger.info("testCalculateCreditScore completed successfully"); + } + + @Test(description = "Get credit score for an application", dependsOnMethods = "testCalculateCreditScore") + public void testGetCreditScore() { + logger.info("Executing testGetCreditScore"); + + // Get credit score via API + Response response = creditService.getCreditScore(testApplication.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting credit score"); + + // Parse response + CreditScore retrievedScore = response.as(CreditScore.class); + + // Validate credit score data + Assert.assertEquals(retrievedScore.getId(), testCreditScore.getId(), "Credit score ID should match"); + Assert.assertEquals(retrievedScore.getApplicationId(), testApplication.getId(), "Application ID should match"); + Assert.assertEquals(retrievedScore.getCustomerId(), testApplication.getCustomerId(), "Customer ID should match"); + Assert.assertEquals(retrievedScore.getScore(), testCreditScore.getScore(), "Score should match"); + Assert.assertEquals(retrievedScore.getScoreBand(), testCreditScore.getScoreBand(), "Score band should match"); + Assert.assertEquals(retrievedScore.getRating(), testCreditScore.getRating(), "Rating should match"); + Assert.assertEquals(retrievedScore.getCalculationMethod(), testCreditScore.getCalculationMethod(), "Calculation method should match"); + + logger.info("testGetCreditScore completed successfully"); + } + + @Test(description = "Get credit score history for an application", dependsOnMethods = "testCalculateCreditScore") + public void testGetCreditScoreHistory() { + logger.info("Executing testGetCreditScoreHistory"); + + // Get credit score history via API + Response response = creditService.getCreditScoreHistory(testApplication.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting credit score history"); + + // Parse response + List> scoreHistory = response.jsonPath().getList("$"); + + // Validate score history + Assert.assertTrue(scoreHistory.size() > 0, "Should have at least one credit score in history"); + + // Find our test credit score + boolean found = false; + for (Map scoreData : scoreHistory) { + if (scoreData.get("id").equals(testCreditScore.getId())) { + found = true; + break; + } + } + + Assert.assertTrue(found, "Test credit score should be in the history"); + + logger.info("testGetCreditScoreHistory completed successfully"); + } + + @Test(description = "Approve credit application", dependsOnMethods = "testCalculateCreditScore") + public void testApproveApplication() { + logger.info("Executing testApproveApplication"); + + // Only approve if credit score is good enough + if (testCreditScore.getScore() >= 670) { + // Approve application via API + double approvedAmount = testApplication.getLoanAmount().doubleValue(); + double interestRate = 5.5; // Example interest rate + String reviewedBy = "test_reviewer"; + + Response response = creditService.approveApplication( + testApplication.getId(), + approvedAmount, + interestRate, + reviewedBy + ); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for approving application"); + + // Parse response + CreditApplication approvedApplication = response.as(CreditApplication.class); + + // Validate application data + Assert.assertEquals(approvedApplication.getId(), testApplication.getId(), "Application ID should match"); + Assert.assertEquals(approvedApplication.getStatus(), CreditApplication.CreditApplicationStatus.APPROVED, "Application status should be APPROVED"); + Assert.assertEquals(approvedApplication.getApprovedAmount(), new BigDecimal(approvedAmount), "Approved amount should match"); + Assert.assertEquals(approvedApplication.getInterestRate(), new BigDecimal(interestRate), "Interest rate should match"); + Assert.assertEquals(approvedApplication.getReviewedBy(), reviewedBy, "Reviewed by should match"); + + // Update test application + testApplication.setStatus(approvedApplication.getStatus()); + testApplication.setApprovedAmount(approvedApplication.getApprovedAmount()); + testApplication.setInterestRate(approvedApplication.getInterestRate()); + testApplication.setReviewedBy(approvedApplication.getReviewedBy()); + + // Validate application data in database + validateApplicationInDatabase(approvedApplication); + + logger.info("testApproveApplication completed successfully"); + } else { + logger.info("Skipping testApproveApplication due to low credit score: {}", testCreditScore.getScore()); + } + } + + @Test(description = "Reject credit application", dependsOnMethods = "testCalculateCreditScore") + public void testRejectApplication() { + logger.info("Executing testRejectApplication"); + + // Only reject if credit score is low enough and application is not already approved + if (testCreditScore.getScore() < 670 && testApplication.getStatus() != CreditApplication.CreditApplicationStatus.APPROVED) { + // Reject application via API + String rejectionReason = "Low credit score"; + String reviewedBy = "test_reviewer"; + + Response response = creditService.rejectApplication( + testApplication.getId(), + rejectionReason, + reviewedBy + ); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for rejecting application"); + + // Parse response + CreditApplication rejectedApplication = response.as(CreditApplication.class); + + // Validate application data + Assert.assertEquals(rejectedApplication.getId(), testApplication.getId(), "Application ID should match"); + Assert.assertEquals(rejectedApplication.getStatus(), CreditApplication.CreditApplicationStatus.REJECTED, "Application status should be REJECTED"); + Assert.assertEquals(rejectedApplication.getRejectionReason(), rejectionReason, "Rejection reason should match"); + Assert.assertEquals(rejectedApplication.getReviewedBy(), reviewedBy, "Reviewed by should match"); + + // Update test application + testApplication.setStatus(rejectedApplication.getStatus()); + testApplication.setRejectionReason(rejectedApplication.getRejectionReason()); + testApplication.setReviewedBy(rejectedApplication.getReviewedBy()); + + // Validate application data in database + validateApplicationInDatabase(rejectedApplication); + + logger.info("testRejectApplication completed successfully"); + } else { + logger.info("Skipping testRejectApplication due to high credit score or already approved application"); + } + } + + @Test(description = "Get credit products") + public void testGetCreditProducts() { + logger.info("Executing testGetCreditProducts"); + + // Get credit products via API + Response response = creditService.getCreditProducts(); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting credit products"); + + // Parse response + List> products = response.jsonPath().getList("$"); + + // Validate products + Assert.assertTrue(products.size() > 0, "Should have at least one credit product"); + + // Validate product structure + for (Map product : products) { + Assert.assertTrue(product.containsKey("id"), "Product should have an ID"); + Assert.assertTrue(product.containsKey("name"), "Product should have a name"); + Assert.assertTrue(product.containsKey("description"), "Product should have a description"); + Assert.assertTrue(product.containsKey("minAmount"), "Product should have a minimum amount"); + Assert.assertTrue(product.containsKey("maxAmount"), "Product should have a maximum amount"); + Assert.assertTrue(product.containsKey("minTerm"), "Product should have a minimum term"); + Assert.assertTrue(product.containsKey("maxTerm"), "Product should have a maximum term"); + Assert.assertTrue(product.containsKey("interestRate"), "Product should have an interest rate"); + } + + logger.info("testGetCreditProducts completed successfully"); + } + + @Test(description = "Get credit product by ID") + public void testGetCreditProduct() { + logger.info("Executing testGetCreditProduct"); + + // First get all products to find a valid ID + Response productsResponse = creditService.getCreditProducts(); + List> products = productsResponse.jsonPath().getList("$"); + + Assert.assertTrue(products.size() > 0, "Should have at least one credit product"); + + String productId = (String) products.get(0).get("id"); + + // Get product by ID via API + Response response = creditService.getCreditProduct(productId); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting credit product"); + + // Parse response + Map product = response.as(Map.class); + + // Validate product data + Assert.assertEquals(product.get("id"), productId, "Product ID should match"); + Assert.assertTrue(product.containsKey("name"), "Product should have a name"); + Assert.assertTrue(product.containsKey("description"), "Product should have a description"); + Assert.assertTrue(product.containsKey("minAmount"), "Product should have a minimum amount"); + Assert.assertTrue(product.containsKey("maxAmount"), "Product should have a maximum amount"); + Assert.assertTrue(product.containsKey("minTerm"), "Product should have a minimum term"); + Assert.assertTrue(product.containsKey("maxTerm"), "Product should have a maximum term"); + Assert.assertTrue(product.containsKey("interestRate"), "Product should have an interest rate"); + + logger.info("testGetCreditProduct completed successfully"); + } + + /** + * Validate application data in database + * @param application Application to validate + */ + private void validateApplicationInDatabase(CreditApplication application) { + try { + String query = "SELECT * FROM credit_applications WHERE id = ?"; + List parameters = List.of(application.getId()); + + List> results = DatabaseUtil.executeQuery(query, parameters); + Assert.assertEquals(results.size(), 1, "Should find exactly one application with ID: " + application.getId()); + + Map applicationData = results.get(0); + + Assert.assertEquals(applicationData.get("customer_id"), application.getCustomerId(), "Customer ID should match in database"); + Assert.assertEquals(applicationData.get("applicant_name"), application.getApplicantName(), "Applicant name should match in database"); + Assert.assertEquals(applicationData.get("applicant_email"), application.getApplicantEmail(), "Applicant email should match in database"); + Assert.assertEquals(applicationData.get("monthly_income"), application.getMonthlyIncome(), "Monthly income should match in database"); + Assert.assertEquals(applicationData.get("loan_amount"), application.getLoanAmount(), "Loan amount should match in database"); + Assert.assertEquals(applicationData.get("loan_term_months"), application.getLoanTermMonths(), "Loan term should match in database"); + Assert.assertEquals(applicationData.get("loan_purpose"), application.getLoanPurpose(), "Loan purpose should match in database"); + Assert.assertEquals(applicationData.get("employment_status"), application.getEmploymentStatus().toString(), "Employment status should match in database"); + Assert.assertEquals(applicationData.get("status"), application.getStatus().toString(), "Status should match in database"); + + if (application.getApprovedAmount() != null) { + Assert.assertEquals(new BigDecimal(applicationData.get("approved_amount").toString()), application.getApprovedAmount(), "Approved amount should match in database"); + } + + if (application.getInterestRate() != null) { + Assert.assertEquals(new BigDecimal(applicationData.get("interest_rate").toString()), application.getInterestRate(), "Interest rate should match in database"); + } + + if (application.getRejectionReason() != null) { + Assert.assertEquals(applicationData.get("rejection_reason"), application.getRejectionReason(), "Rejection reason should match in database"); + } + + if (application.getReviewedBy() != null) { + Assert.assertEquals(applicationData.get("reviewed_by"), application.getReviewedBy(), "Reviewed by should match in database"); + } + + } catch (SQLException e) { + logger.error("Error validating application in database: {}", e.getMessage(), e); + Assert.fail("Error validating application in database: " + e.getMessage()); + } + } + + /** + * Validate credit score data in database + * @param creditScore Credit score to validate + */ + private void validateCreditScoreInDatabase(CreditScore creditScore) { + try { + String query = "SELECT * FROM credit_scores WHERE id = ?"; + List parameters = List.of(creditScore.getId()); + + List> results = DatabaseUtil.executeQuery(query, parameters); + Assert.assertEquals(results.size(), 1, "Should find exactly one credit score with ID: " + creditScore.getId()); + + Map scoreData = results.get(0); + + Assert.assertEquals(scoreData.get("application_id"), creditScore.getApplicationId(), "Application ID should match in database"); + Assert.assertEquals(scoreData.get("customer_id"), creditScore.getCustomerId(), "Customer ID should match in database"); + Assert.assertEquals(scoreData.get("score"), creditScore.getScore(), "Score should match in database"); + Assert.assertEquals(scoreData.get("score_band"), creditScore.getScoreBand(), "Score band should match in database"); + Assert.assertEquals(scoreData.get("rating"), creditScore.getRating().toString(), "Rating should match in database"); + Assert.assertEquals(scoreData.get("calculation_method"), creditScore.getCalculationMethod(), "Calculation method should match in database"); + + } catch (SQLException e) { + logger.error("Error validating credit score in database: {}", e.getMessage(), e); + Assert.fail("Error validating credit score in database: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/financial/api/tests/CreditPerformanceTest.java b/src/test/java/com/financial/api/tests/CreditPerformanceTest.java new file mode 100644 index 0000000..c25f784 --- /dev/null +++ b/src/test/java/com/financial/api/tests/CreditPerformanceTest.java @@ -0,0 +1,469 @@ +package com.financial.api.tests; + +import com.financial.api.models.credit.CreditApplication; +import com.financial.api.models.credit.CreditScore; +import com.financial.api.services.CreditService; +import com.financial.api.utils.TestDataFactory; +import io.restassured.response.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; + +/** + * Performance test class for Credit API operations + */ +public class CreditPerformanceTest { + private static final Logger logger = LogManager.getLogger(CreditPerformanceTest.class); + private CreditService creditService; + private List testApplications = new ArrayList<>(); + private List testCreditScores = new ArrayList<>(); + private static final int APPLICATIONS_TO_CREATE = 10; + private static final int MAX_RESPONSE_TIME_MS = 10000; // Maximum acceptable response time in milliseconds + + @BeforeClass + public void setUp() { + logger.info("Setting up CreditPerformanceTest"); + creditService = new CreditService(); + + // Create test credit applications + for (int i = 0; i < APPLICATIONS_TO_CREATE; i++) { + CreditApplication application = TestDataFactory.generateRandomCreditApplication(); + testApplications.add(application); + } + } + + @AfterClass + public void tearDown() { + logger.info("Tearing down CreditPerformanceTest"); + + // Clean up test data if needed + // Note: In a real scenario, you might want to delete the created applications and credit scores + } + + @Test(description = "Performance test for submitting multiple credit applications", invocationCount = 1, threadPoolSize = 5) + public void testSubmitMultipleApplicationsPerformance() { + logger.info("Executing testSubmitMultipleApplicationsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (CreditApplication application : testApplications) { + long requestStartTime = System.currentTimeMillis(); + + // Submit application via API + Response response = creditService.submitApplication(application); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 201) { + successCount++; + + // Parse response + CreditApplication submittedApplication = response.as(CreditApplication.class); + + // Update application with generated ID + application.setId(submittedApplication.getId()); + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for submitting application should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to submit application. Status code: {}", response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for submitting credit applications:"); + logger.info("Total applications submitted: {}/{}", successCount, testApplications.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testApplications.size(), + "All applications should be submitted successfully. Submitted: " + successCount + ", Expected: " + testApplications.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testSubmitMultipleApplicationsPerformance completed successfully"); + } + + @Test(description = "Performance test for getting multiple credit applications", dependsOnMethods = "testSubmitMultipleApplicationsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testGetMultipleApplicationsPerformance() { + logger.info("Executing testGetMultipleApplicationsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (CreditApplication application : testApplications) { + long requestStartTime = System.currentTimeMillis(); + + // Get application via API + Response response = creditService.getApplication(application.getId()); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for getting application should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to get application with ID: {}. Status code: {}", application.getId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for getting credit applications:"); + logger.info("Total applications retrieved: {}/{}", successCount, testApplications.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testApplications.size(), + "All applications should be retrieved successfully. Retrieved: " + successCount + ", Expected: " + testApplications.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testGetMultipleApplicationsPerformance completed successfully"); + } + + @Test(description = "Performance test for calculating credit scores for multiple applications", dependsOnMethods = "testSubmitMultipleApplicationsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testCalculateMultipleCreditScoresPerformance() { + logger.info("Executing testCalculateMultipleCreditScoresPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (CreditApplication application : testApplications) { + long requestStartTime = System.currentTimeMillis(); + + // Request credit score calculation via API + String calculationMethod = "FICO"; + Response response = creditService.calculateCreditScore(application.getId(), calculationMethod); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Parse response + CreditScore calculatedScore = response.as(CreditScore.class); + + // Create test credit score + CreditScore creditScore = new CreditScore( + application.getId(), + application.getCustomerId(), + calculatedScore.getScore(), + calculatedScore.getCalculationMethod() + ); + creditScore.setId(calculatedScore.getId()); + creditScore.setScoreBand(calculatedScore.getScoreBand()); + creditScore.setRating(calculatedScore.getRating()); + testCreditScores.add(creditScore); + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for calculating credit score should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to calculate credit score for application with ID: {}. Status code: {}", + application.getId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for calculating credit scores:"); + logger.info("Total credit scores calculated: {}/{}", successCount, testApplications.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testApplications.size(), + "All credit scores should be calculated successfully. Calculated: " + successCount + ", Expected: " + testApplications.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testCalculateMultipleCreditScoresPerformance completed successfully"); + } + + @Test(description = "Performance test for getting credit scores for multiple applications", dependsOnMethods = "testCalculateMultipleCreditScoresPerformance", invocationCount = 1, threadPoolSize = 5) + public void testGetMultipleCreditScoresPerformance() { + logger.info("Executing testGetMultipleCreditScoresPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (CreditScore creditScore : testCreditScores) { + long requestStartTime = System.currentTimeMillis(); + + // Get credit score via API + Response response = creditService.getCreditScore(creditScore.getApplicationId()); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for getting credit score should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to get credit score for application with ID: {}. Status code: {}", + creditScore.getApplicationId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for getting credit scores:"); + logger.info("Total credit scores retrieved: {}/{}", successCount, testCreditScores.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testCreditScores.size(), + "All credit scores should be retrieved successfully. Retrieved: " + successCount + ", Expected: " + testCreditScores.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testGetMultipleCreditScoresPerformance completed successfully"); + } + + @Test(description = "Performance test for getting credit score history for multiple applications", dependsOnMethods = "testCalculateMultipleCreditScoresPerformance", invocationCount = 1, threadPoolSize = 5) + public void testGetMultipleCreditScoreHistoriesPerformance() { + logger.info("Executing testGetMultipleCreditScoreHistoriesPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (CreditScore creditScore : testCreditScores) { + long requestStartTime = System.currentTimeMillis(); + + // Get credit score history via API + Response response = creditService.getCreditScoreHistory(creditScore.getApplicationId()); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for getting credit score history should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to get credit score history for application with ID: {}. Status code: {}", + creditScore.getApplicationId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for getting credit score histories:"); + logger.info("Total credit score histories retrieved: {}/{}", successCount, testCreditScores.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testCreditScores.size(), + "All credit score histories should be retrieved successfully. Retrieved: " + successCount + ", Expected: " + testCreditScores.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testGetMultipleCreditScoreHistoriesPerformance completed successfully"); + } + + @Test(description = "Performance test for getting applications by status", dependsOnMethods = "testSubmitMultipleApplicationsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testGetApplicationsByStatusPerformance() { + logger.info("Executing testGetApplicationsByStatusPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + // Test all possible statuses + CreditApplication.CreditApplicationStatus[] statuses = CreditApplication.CreditApplicationStatus.values(); + + for (CreditApplication.CreditApplicationStatus status : statuses) { + long requestStartTime = System.currentTimeMillis(); + + // Get applications by status via API + Response response = creditService.getApplicationsByStatus(status); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for getting applications by status should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to get applications by status: {}. Status code: {}", status, response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for getting applications by status:"); + logger.info("Total status queries completed: {}/{}", successCount, statuses.length); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, statuses.length, + "All status queries should be completed successfully. Completed: " + successCount + ", Expected: " + statuses.length); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testGetApplicationsByStatusPerformance completed successfully"); + } + + @Test(description = "Performance test for getting credit products", invocationCount = 1, threadPoolSize = 5) + public void testGetCreditProductsPerformance() { + logger.info("Executing testGetCreditProductsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + int iterations = 10; // Number of times to call the API + + for (int i = 0; i < iterations; i++) { + long requestStartTime = System.currentTimeMillis(); + + // Get credit products via API + Response response = creditService.getCreditProducts(); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for getting credit products should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to get credit products. Status code: {}", response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for getting credit products:"); + logger.info("Total credit products queries completed: {}/{}", successCount, iterations); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, iterations, + "All credit products queries should be completed successfully. Completed: " + successCount + ", Expected: " + iterations); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testGetCreditProductsPerformance completed successfully"); + } +} \ No newline at end of file diff --git a/src/test/java/com/financial/api/tests/CreditSecurityTest.java b/src/test/java/com/financial/api/tests/CreditSecurityTest.java new file mode 100644 index 0000000..b3412a3 --- /dev/null +++ b/src/test/java/com/financial/api/tests/CreditSecurityTest.java @@ -0,0 +1,496 @@ +package com.financial.api.tests; + +import com.financial.api.models.credit.CreditApplication; +import com.financial.api.models.credit.CreditScore; +import com.financial.api.services.CreditService; +import com.financial.api.utils.TestDataFactory; +import io.restassured.response.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +/** + * Security test class for Credit API operations + */ +public class CreditSecurityTest { + private static final Logger logger = LogManager.getLogger(CreditSecurityTest.class); + private CreditService creditService; + private CreditApplication testApplication; + + @BeforeClass + public void setUp() { + logger.info("Setting up CreditSecurityTest"); + creditService = new CreditService(); + + // Create test credit application + testApplication = TestDataFactory.generateRandomCreditApplication(); + + // Create application via API for testing + Response response = creditService.submitApplication(testApplication); + if (response.getStatusCode() == 201) { + CreditApplication createdApplication = response.as(CreditApplication.class); + testApplication.setId(createdApplication.getId()); + } + } + + @Test(description = "Test SQL injection in credit application submission") + public void testSqlInjectionInCreditApplicationSubmission() { + logger.info("Executing testSqlInjectionInCreditApplicationSubmission"); + + // Create credit application with SQL injection payload + CreditApplication maliciousApplication = new CreditApplication( + "test-customer'; DROP TABLE credit_applications; --", + "Test Applicant", + "test@example.com", + "+1234567890", + java.time.LocalDate.of(1990, 1, 1), + new BigDecimal("5000.00"), + new BigDecimal("10000.00"), + 24, + "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + ); + + // Attempt to submit application via API + Response response = creditService.submitApplication(maliciousApplication); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not submit application with SQL injection payload"); + + logger.info("testSqlInjectionInCreditApplicationSubmission completed successfully"); + } + + @Test(description = "Test XSS in credit application submission") + public void testXssInCreditApplicationSubmission() { + logger.info("Executing testXssInCreditApplicationSubmission"); + + // Create credit application with XSS payload + CreditApplication maliciousApplication = new CreditApplication( + "test-customer", + "", + "test@example.com", + "+1234567890", + java.time.LocalDate.of(1990, 1, 1), + new BigDecimal("5000.00"), + new BigDecimal("10000.00"), + 24, + "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + ); + + // Attempt to submit application via API + Response response = creditService.submitApplication(maliciousApplication); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not submit application with XSS payload"); + + logger.info("testXssInCreditApplicationSubmission completed successfully"); + } + + @Test(description = "Test authentication bypass in credit application retrieval") + public void testAuthenticationBypassInCreditApplicationRetrieval() { + logger.info("Executing testAuthenticationBypassInCreditApplicationRetrieval"); + + // Attempt to get application without authentication + Response response = creditService.getApplicationWithoutAuth(testApplication.getId()); + + // Validate response + // Should return 401 Unauthorized or 403 Forbidden + Assert.assertTrue(response.getStatusCode() == 401 || response.getStatusCode() == 403, + "API should require authentication for application retrieval"); + + logger.info("testAuthenticationBypassInCreditApplicationRetrieval completed successfully"); + } + + @Test(description = "Test authorization bypass in credit application retrieval") + public void testAuthorizationBypassInCreditApplicationRetrieval() { + logger.info("Executing testAuthorizationBypassInCreditApplicationRetrieval"); + + // Attempt to get application with different user's authentication + Response response = creditService.getApplicationWithDifferentUserAuth(testApplication.getId()); + + // Validate response + // Should return 403 Forbidden + Assert.assertEquals(response.getStatusCode(), 403, "API should prevent unauthorized access to application"); + + logger.info("testAuthorizationBypassInCreditApplicationRetrieval completed successfully"); + } + + @Test(description = "Test IDOR in credit application retrieval") + public void testIdorInCreditApplicationRetrieval() { + logger.info("Executing testIdorInCreditApplicationRetrieval"); + + // Attempt to get application with different application ID + String differentApplicationId = "different-application-id-12345"; + Response response = creditService.getApplication(differentApplicationId); + + // Validate response + // Should return 404 Not Found or 403 Forbidden + Assert.assertTrue(response.getStatusCode() == 404 || response.getStatusCode() == 403, + "API should prevent access to non-existent or unauthorized application"); + + logger.info("testIdorInCreditApplicationRetrieval completed successfully"); + } + + @Test(description = "Test SQL injection in credit score calculation") + public void testSqlInjectionInCreditScoreCalculation() { + logger.info("Executing testSqlInjectionInCreditScoreCalculation"); + + // Attempt to calculate credit score with SQL injection payload + String maliciousMethod = "FICO'; DROP TABLE credit_scores; --"; + Response response = creditService.calculateCreditScore(testApplication.getId(), maliciousMethod); + + // Validate response + // Should return 400 Bad Request or similar error code, not 200 OK + Assert.assertNotEquals(response.getStatusCode(), 200, "API should not calculate credit score with SQL injection payload"); + + logger.info("testSqlInjectionInCreditScoreCalculation completed successfully"); + } + + @Test(description = "Test XSS in credit score calculation") + public void testXssInCreditScoreCalculation() { + logger.info("Executing testXssInCreditScoreCalculation"); + + // Attempt to calculate credit score with XSS payload + String maliciousMethod = ""; + Response response = creditService.calculateCreditScore(testApplication.getId(), maliciousMethod); + + // Validate response + // Should return 400 Bad Request or similar error code, not 200 OK + Assert.assertNotEquals(response.getStatusCode(), 200, "API should not calculate credit score with XSS payload"); + + logger.info("testXssInCreditScoreCalculation completed successfully"); + } + + @Test(description = "Test negative loan amount in credit application submission") + public void testNegativeLoanAmountInCreditApplicationSubmission() { + logger.info("Executing testNegativeLoanAmountInCreditApplicationSubmission"); + + // Create credit application with negative loan amount + CreditApplication maliciousApplication = new CreditApplication( + "test-customer", + "Test Applicant", + "test@example.com", + "+1234567890", + java.time.LocalDate.of(1990, 1, 1), + new BigDecimal("5000.00"), + new BigDecimal("-10000.00"), + 24, + "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + ); + + // Attempt to submit application via API + Response response = creditService.submitApplication(maliciousApplication); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not submit application with negative loan amount"); + + logger.info("testNegativeLoanAmountInCreditApplicationSubmission completed successfully"); + } + + @Test(description = "Test extremely large loan amount in credit application submission") + public void testExtremelyLargeLoanAmountInCreditApplicationSubmission() { + logger.info("Executing testExtremelyLargeLoanAmountInCreditApplicationSubmission"); + + // Create credit application with extremely large loan amount + CreditApplication maliciousApplication = new CreditApplication( + "test-customer", + "Test Applicant", + "test@example.com", + "+1234567890", + java.time.LocalDate.of(1990, 1, 1), + new BigDecimal("5000.00"), + new BigDecimal("99999999999999999999.99"), + 24, + "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + ); + + // Attempt to submit application via API + Response response = creditService.submitApplication(maliciousApplication); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not submit application with extremely large loan amount"); + + logger.info("testExtremelyLargeLoanAmountInCreditApplicationSubmission completed successfully"); + } + + @Test(description = "Test negative loan term in credit application submission") + public void testNegativeLoanTermInCreditApplicationSubmission() { + logger.info("Executing testNegativeLoanTermInCreditApplicationSubmission"); + + // Create credit application with negative loan term + CreditApplication maliciousApplication = new CreditApplication( + "test-customer", + "Test Applicant", + "test@example.com", + "+1234567890", + java.time.LocalDate.of(1990, 1, 1), + new BigDecimal("5000.00"), + new BigDecimal("10000.00"), + -24, + "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + ); + + // Attempt to submit application via API + Response response = creditService.submitApplication(maliciousApplication); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not submit application with negative loan term"); + + logger.info("testNegativeLoanTermInCreditApplicationSubmission completed successfully"); + } + + @Test(description = "Test extremely large loan term in credit application submission") + public void testExtremelyLargeLoanTermInCreditApplicationSubmission() { + logger.info("Executing testExtremelyLargeLoanTermInCreditApplicationSubmission"); + + // Create credit application with extremely large loan term + CreditApplication maliciousApplication = new CreditApplication( + "test-customer", + "Test Applicant", + "test@example.com", + "+1234567890", + java.time.LocalDate.of(1990, 1, 1), + new BigDecimal("5000.00"), + new BigDecimal("10000.00"), + 999999, + "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + ); + + // Attempt to submit application via API + Response response = creditService.submitApplication(maliciousApplication); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not submit application with extremely large loan term"); + + logger.info("testExtremelyLargeLoanTermInCreditApplicationSubmission completed successfully"); + } + + @Test(description = "Test authentication bypass in credit score retrieval") + public void testAuthenticationBypassInCreditScoreRetrieval() { + logger.info("Executing testAuthenticationBypassInCreditScoreRetrieval"); + + // Attempt to get credit score without authentication + Response response = creditService.getCreditScoreWithoutAuth(testApplication.getId()); + + // Validate response + // Should return 401 Unauthorized or 403 Forbidden + Assert.assertTrue(response.getStatusCode() == 401 || response.getStatusCode() == 403, + "API should require authentication for credit score retrieval"); + + logger.info("testAuthenticationBypassInCreditScoreRetrieval completed successfully"); + } + + @Test(description = "Test authorization bypass in credit score retrieval") + public void testAuthorizationBypassInCreditScoreRetrieval() { + logger.info("Executing testAuthorizationBypassInCreditScoreRetrieval"); + + // Attempt to get credit score with different user's authentication + Response response = creditService.getCreditScoreWithDifferentUserAuth(testApplication.getId()); + + // Validate response + // Should return 403 Forbidden + Assert.assertEquals(response.getStatusCode(), 403, "API should prevent unauthorized access to credit score"); + + logger.info("testAuthorizationBypassInCreditScoreRetrieval completed successfully"); + } + + @Test(description = "Test CSRF protection in credit application update") + public void testCsrfProtectionInCreditApplicationUpdate() { + logger.info("Executing testCsrfProtectionInCreditApplicationUpdate"); + + // Attempt to update application without CSRF token + Map updateData = new HashMap<>(); + updateData.put("loan_amount", new BigDecimal("15000.00")); + + Response response = creditService.updateApplicationWithoutCsrfToken(testApplication.getId(), updateData); + + // Validate response + // Should return 400 Bad Request or 403 Forbidden if CSRF protection is enabled + Assert.assertTrue(response.getStatusCode() == 400 || response.getStatusCode() == 403, + "API should require CSRF token for application update"); + + logger.info("testCsrfProtectionInCreditApplicationUpdate completed successfully"); + } + + @Test(description = "Test rate limiting in credit application submission") + public void testRateLimitingInCreditApplicationSubmission() { + logger.info("Executing testRateLimitingInCreditApplicationSubmission"); + + int requestCount = 10; + int tooManyRequestsCount = 0; + + // Send multiple requests in quick succession + for (int i = 0; i < requestCount; i++) { + CreditApplication application = TestDataFactory.generateRandomCreditApplication(); + Response response = creditService.submitApplication(application); + + if (response.getStatusCode() == 429) { // Too Many Requests + tooManyRequestsCount++; + } + } + + // Validate response + // Should receive at least one 429 Too Many Requests response if rate limiting is enabled + Assert.assertTrue(tooManyRequestsCount > 0, "API should implement rate limiting for application submission"); + + logger.info("testRateLimitingInCreditApplicationSubmission completed successfully"); + } + + @Test(description = "Test input validation in credit application submission") + public void testInputValidationInCreditApplicationSubmission() { + logger.info("Executing testInputValidationInCreditApplicationSubmission"); + + // Test cases for input validation + Map testCases = new HashMap<>(); + + // Empty customer ID + testCases.put("Empty customer ID", new CreditApplication( + "", "Test Applicant", "test@example.com", "+1234567890", + java.time.LocalDate.of(1990, 1, 1), new BigDecimal("5000.00"), + new BigDecimal("10000.00"), 24, "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + )); + + // Empty applicant name + testCases.put("Empty applicant name", new CreditApplication( + "test-customer", "", "test@example.com", "+1234567890", + java.time.LocalDate.of(1990, 1, 1), new BigDecimal("5000.00"), + new BigDecimal("10000.00"), 24, "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + )); + + // Invalid email + testCases.put("Invalid email", new CreditApplication( + "test-customer", "Test Applicant", "invalid-email", "+1234567890", + java.time.LocalDate.of(1990, 1, 1), new BigDecimal("5000.00"), + new BigDecimal("10000.00"), 24, "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + )); + + // Invalid phone number + testCases.put("Invalid phone number", new CreditApplication( + "test-customer", "Test Applicant", "test@example.com", "invalid-phone", + java.time.LocalDate.of(1990, 1, 1), new BigDecimal("5000.00"), + new BigDecimal("10000.00"), 24, "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + )); + + // Null monthly income + testCases.put("Null monthly income", new CreditApplication( + "test-customer", "Test Applicant", "test@example.com", "+1234567890", + java.time.LocalDate.of(1990, 1, 1), null, + new BigDecimal("10000.00"), 24, "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + )); + + // Null loan amount + testCases.put("Null loan amount", new CreditApplication( + "test-customer", "Test Applicant", "test@example.com", "+1234567890", + java.time.LocalDate.of(1990, 1, 1), new BigDecimal("5000.00"), + null, 24, "Test purpose", + CreditApplication.EmploymentStatus.EMPLOYED + )); + + // Empty loan purpose + testCases.put("Empty loan purpose", new CreditApplication( + "test-customer", "Test Applicant", "test@example.com", "+1234567890", + java.time.LocalDate.of(1990, 1, 1), new BigDecimal("5000.00"), + new BigDecimal("10000.00"), 24, "", + CreditApplication.EmploymentStatus.EMPLOYED + )); + + // Null employment status + testCases.put("Null employment status", new CreditApplication( + "test-customer", "Test Applicant", "test@example.com", "+1234567890", + java.time.LocalDate.of(1990, 1, 1), new BigDecimal("5000.00"), + new BigDecimal("10000.00"), 24, "Test purpose", + null + )); + + // Test each case + for (Map.Entry entry : testCases.entrySet()) { + String testCase = entry.getKey(); + CreditApplication application = entry.getValue(); + + Response response = creditService.submitApplication(application); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, + "API should validate input for " + testCase + " and not submit application"); + + logger.info("Input validation test passed for: {}", testCase); + } + + logger.info("testInputValidationInCreditApplicationSubmission completed successfully"); + } + + @Test(description = "Test sensitive data exposure in credit application response") + public void testSensitiveDataExposureInCreditApplicationResponse() { + logger.info("Executing testSensitiveDataExposureInCreditApplicationResponse"); + + // Get application via API + Response response = creditService.getApplication(testApplication.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting application"); + + // Parse response + Map applicationData = response.as(Map.class); + + // Check for sensitive data that should not be exposed + Assert.assertFalse(applicationData.containsKey("password"), "Application response should not contain password"); + Assert.assertFalse(applicationData.containsKey("pin"), "Application response should not contain PIN"); + Assert.assertFalse(applicationData.containsKey("ssn"), "Application response should not contain SSN"); + Assert.assertFalse(applicationData.containsKey("credit_card_number"), "Application response should not contain credit card number"); + Assert.assertFalse(applicationData.containsKey("bank_account_number"), "Application response should not contain bank account number"); + + logger.info("testSensitiveDataExposureInCreditApplicationResponse completed successfully"); + } + + @Test(description = "Test sensitive data exposure in credit score response") + public void testSensitiveDataExposureInCreditScoreResponse() { + logger.info("Executing testSensitiveDataExposureInCreditScoreResponse"); + + // First calculate a credit score + Response scoreResponse = creditService.calculateCreditScore(testApplication.getId(), "FICO"); + Assert.assertEquals(scoreResponse.getStatusCode(), 200, "Expected status code 200 for calculating credit score"); + + CreditScore creditScore = scoreResponse.as(CreditScore.class); + + // Get credit score via API + Response response = creditService.getCreditScore(testApplication.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting credit score"); + + // Parse response + Map scoreData = response.as(Map.class); + + // Check for sensitive data that should not be exposed + Assert.assertFalse(scoreData.containsKey("password"), "Credit score response should not contain password"); + Assert.assertFalse(scoreData.containsKey("pin"), "Credit score response should not contain PIN"); + Assert.assertFalse(scoreData.containsKey("ssn"), "Credit score response should not contain SSN"); + Assert.assertFalse(scoreData.containsKey("credit_card_number"), "Credit score response should not contain credit card number"); + Assert.assertFalse(scoreData.containsKey("bank_account_number"), "Credit score response should not contain bank account number"); + + logger.info("testSensitiveDataExposureInCreditScoreResponse completed successfully"); + } +} \ No newline at end of file diff --git a/src/test/java/com/financial/api/tests/WalletApiTest.java b/src/test/java/com/financial/api/tests/WalletApiTest.java new file mode 100644 index 0000000..18f5aca --- /dev/null +++ b/src/test/java/com/financial/api/tests/WalletApiTest.java @@ -0,0 +1,420 @@ +package com.financial.api.tests; + +import com.financial.api.models.wallet.Transaction; +import com.financial.api.models.wallet.Wallet; +import com.financial.api.services.WalletService; +import com.financial.api.utils.DatabaseUtil; +import com.financial.api.utils.TestDataFactory; +import io.restassured.response.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Test class for Wallet API operations + */ +public class WalletApiTest { + private static final Logger logger = LogManager.getLogger(WalletApiTest.class); + private WalletService walletService; + private Wallet testWallet; + private Transaction testTransaction; + + @BeforeClass + public void setUp() { + logger.info("Setting up WalletApiTest"); + walletService = new WalletService(); + + // Create test wallet + testWallet = TestDataFactory.generateRandomWallet(); + + // Create test transaction + testTransaction = TestDataFactory.generateRandomTransaction(testWallet.getId()); + } + + @AfterClass + public void tearDown() { + logger.info("Tearing down WalletApiTest"); + + // Clean up test data if needed + try { + // Close database connection + DatabaseUtil.closeConnection(); + } catch (Exception e) { + logger.error("Error during tear down: {}", e.getMessage(), e); + } + } + + @Test(description = "Create a new wallet") + public void testCreateWallet() { + logger.info("Executing testCreateWallet"); + + // Create wallet via API + Response response = walletService.createWallet(testWallet); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 201, "Expected status code 201 for wallet creation"); + + // Parse response + Wallet createdWallet = response.as(Wallet.class); + + // Validate wallet data + Assert.assertNotNull(createdWallet.getId(), "Wallet ID should not be null"); + Assert.assertEquals(createdWallet.getCustomerId(), testWallet.getCustomerId(), "Customer ID should match"); + Assert.assertEquals(createdWallet.getAccountNumber(), testWallet.getAccountNumber(), "Account number should match"); + Assert.assertEquals(createdWallet.getBalance(), testWallet.getBalance(), "Balance should match"); + Assert.assertEquals(createdWallet.getCurrency(), testWallet.getCurrency(), "Currency should match"); + Assert.assertEquals(createdWallet.getStatus(), Wallet.WalletStatus.ACTIVE, "Wallet status should be ACTIVE"); + + // Update test wallet with generated ID + testWallet.setId(createdWallet.getId()); + + // Validate wallet data in database + validateWalletInDatabase(createdWallet); + + logger.info("testCreateWallet completed successfully"); + } + + @Test(description = "Get wallet by ID", dependsOnMethods = "testCreateWallet") + public void testGetWallet() { + logger.info("Executing testGetWallet"); + + // Get wallet via API + Response response = walletService.getWallet(testWallet.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting wallet"); + + // Parse response + Wallet retrievedWallet = response.as(Wallet.class); + + // Validate wallet data + Assert.assertEquals(retrievedWallet.getId(), testWallet.getId(), "Wallet ID should match"); + Assert.assertEquals(retrievedWallet.getCustomerId(), testWallet.getCustomerId(), "Customer ID should match"); + Assert.assertEquals(retrievedWallet.getAccountNumber(), testWallet.getAccountNumber(), "Account number should match"); + Assert.assertEquals(retrievedWallet.getBalance(), testWallet.getBalance(), "Balance should match"); + Assert.assertEquals(retrievedWallet.getCurrency(), testWallet.getCurrency(), "Currency should match"); + + logger.info("testGetWallet completed successfully"); + } + + @Test(description = "Get wallet by account number", dependsOnMethods = "testCreateWallet") + public void testGetWalletByAccountNumber() { + logger.info("Executing testGetWalletByAccountNumber"); + + // Get wallet via API + Response response = walletService.getWalletByAccountNumber(testWallet.getAccountNumber()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting wallet by account number"); + + // Parse response + Wallet retrievedWallet = response.as(Wallet.class); + + // Validate wallet data + Assert.assertEquals(retrievedWallet.getId(), testWallet.getId(), "Wallet ID should match"); + Assert.assertEquals(retrievedWallet.getCustomerId(), testWallet.getCustomerId(), "Customer ID should match"); + Assert.assertEquals(retrievedWallet.getAccountNumber(), testWallet.getAccountNumber(), "Account number should match"); + Assert.assertEquals(retrievedWallet.getBalance(), testWallet.getBalance(), "Balance should match"); + Assert.assertEquals(retrievedWallet.getCurrency(), testWallet.getCurrency(), "Currency should match"); + + logger.info("testGetWalletByAccountNumber completed successfully"); + } + + @Test(description = "Update wallet information", dependsOnMethods = "testCreateWallet") + public void testUpdateWallet() { + logger.info("Executing testUpdateWallet"); + + // Update wallet data + testWallet.setBalance(new BigDecimal("5000.00")); + + // Update wallet via API + Response response = walletService.updateWallet(testWallet.getId(), testWallet); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for updating wallet"); + + // Parse response + Wallet updatedWallet = response.as(Wallet.class); + + // Validate wallet data + Assert.assertEquals(updatedWallet.getId(), testWallet.getId(), "Wallet ID should match"); + Assert.assertEquals(updatedWallet.getBalance(), testWallet.getBalance(), "Balance should match"); + + // Validate wallet data in database + validateWalletInDatabase(updatedWallet); + + logger.info("testUpdateWallet completed successfully"); + } + + @Test(description = "Get wallet balance", dependsOnMethods = "testCreateWallet") + public void testGetWalletBalance() { + logger.info("Executing testGetWalletBalance"); + + // Get wallet balance via API + Response response = walletService.getWalletBalance(testWallet.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting wallet balance"); + + // Parse response + Map balanceData = response.as(Map.class); + + // Validate balance data + Assert.assertTrue(balanceData.containsKey("balance"), "Response should contain balance"); + Assert.assertTrue(balanceData.containsKey("currency"), "Response should contain currency"); + + BigDecimal balance = new BigDecimal(balanceData.get("balance").toString()); + String currency = (String) balanceData.get("currency"); + + Assert.assertEquals(balance, testWallet.getBalance(), "Balance should match"); + Assert.assertEquals(currency, testWallet.getCurrency(), "Currency should match"); + + logger.info("testGetWalletBalance completed successfully"); + } + + @Test(description = "Create a transaction", dependsOnMethods = "testCreateWallet") + public void testCreateTransaction() { + logger.info("Executing testCreateTransaction"); + + // Create transaction via API + Response response = walletService.createTransaction(testWallet.getId(), testTransaction); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 201, "Expected status code 201 for transaction creation"); + + // Parse response + Transaction createdTransaction = response.as(Transaction.class); + + // Validate transaction data + Assert.assertNotNull(createdTransaction.getId(), "Transaction ID should not be null"); + Assert.assertEquals(createdTransaction.getWalletId(), testWallet.getId(), "Wallet ID should match"); + Assert.assertEquals(createdTransaction.getType(), testTransaction.getType(), "Transaction type should match"); + Assert.assertEquals(createdTransaction.getAmount(), testTransaction.getAmount(), "Amount should match"); + Assert.assertEquals(createdTransaction.getCurrency(), testTransaction.getCurrency(), "Currency should match"); + Assert.assertEquals(createdTransaction.getDescription(), testTransaction.getDescription(), "Description should match"); + Assert.assertEquals(createdTransaction.getStatus(), Transaction.TransactionStatus.PENDING, "Transaction status should be PENDING"); + + // Update test transaction with generated ID + testTransaction.setId(createdTransaction.getId()); + + // Validate transaction data in database + validateTransactionInDatabase(createdTransaction); + + logger.info("testCreateTransaction completed successfully"); + } + + @Test(description = "Get transaction by ID", dependsOnMethods = "testCreateTransaction") + public void testGetTransaction() { + logger.info("Executing testGetTransaction"); + + // Get transaction via API + Response response = walletService.getTransaction(testWallet.getId(), testTransaction.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting transaction"); + + // Parse response + Transaction retrievedTransaction = response.as(Transaction.class); + + // Validate transaction data + Assert.assertEquals(retrievedTransaction.getId(), testTransaction.getId(), "Transaction ID should match"); + Assert.assertEquals(retrievedTransaction.getWalletId(), testWallet.getId(), "Wallet ID should match"); + Assert.assertEquals(retrievedTransaction.getType(), testTransaction.getType(), "Transaction type should match"); + Assert.assertEquals(retrievedTransaction.getAmount(), testTransaction.getAmount(), "Amount should match"); + Assert.assertEquals(retrievedTransaction.getCurrency(), testTransaction.getCurrency(), "Currency should match"); + Assert.assertEquals(retrievedTransaction.getDescription(), testTransaction.getDescription(), "Description should match"); + + logger.info("testGetTransaction completed successfully"); + } + + @Test(description = "Get all transactions for a wallet", dependsOnMethods = "testCreateTransaction") + public void testGetTransactions() { + logger.info("Executing testGetTransactions"); + + // Get transactions via API + Response response = walletService.getTransactions(testWallet.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting transactions"); + + // Parse response + List> transactions = response.jsonPath().getList("$"); + + // Validate transactions + Assert.assertTrue(transactions.size() > 0, "Should have at least one transaction"); + + // Find our test transaction + boolean found = false; + for (Map transactionData : transactions) { + if (transactionData.get("id").equals(testTransaction.getId())) { + found = true; + break; + } + } + + Assert.assertTrue(found, "Test transaction should be in the list"); + + logger.info("testGetTransactions completed successfully"); + } + + @Test(description = "Update transaction status", dependsOnMethods = "testCreateTransaction") + public void testUpdateTransactionStatus() { + logger.info("Executing testUpdateTransactionStatus"); + + // Update transaction status via API + Response response = walletService.updateTransactionStatus( + testWallet.getId(), + testTransaction.getId(), + Transaction.TransactionStatus.COMPLETED + ); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for updating transaction status"); + + // Parse response + Transaction updatedTransaction = response.as(Transaction.class); + + // Validate transaction data + Assert.assertEquals(updatedTransaction.getId(), testTransaction.getId(), "Transaction ID should match"); + Assert.assertEquals(updatedTransaction.getStatus(), Transaction.TransactionStatus.COMPLETED, "Transaction status should be COMPLETED"); + + // Validate transaction data in database + validateTransactionInDatabase(updatedTransaction); + + logger.info("testUpdateTransactionStatus completed successfully"); + } + + @Test(description = "Deposit funds to wallet", dependsOnMethods = "testCreateWallet") + public void testDepositFunds() { + logger.info("Executing testDepositFunds"); + + // Deposit funds via API + double depositAmount = 1000.00; + String description = "Test deposit"; + Response response = walletService.depositFunds(testWallet.getId(), depositAmount, description); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for depositing funds"); + + // Parse response + Transaction depositTransaction = response.as(Transaction.class); + + // Validate transaction data + Assert.assertNotNull(depositTransaction.getId(), "Transaction ID should not be null"); + Assert.assertEquals(depositTransaction.getWalletId(), testWallet.getId(), "Wallet ID should match"); + Assert.assertEquals(depositTransaction.getType(), Transaction.TransactionType.DEPOSIT, "Transaction type should be DEPOSIT"); + Assert.assertEquals(depositTransaction.getAmount(), new BigDecimal(depositAmount), "Amount should match"); + Assert.assertEquals(depositTransaction.getDescription(), description, "Description should match"); + + // Get updated wallet balance + Response balanceResponse = walletService.getWalletBalance(testWallet.getId()); + Map balanceData = balanceResponse.as(Map.class); + BigDecimal newBalance = new BigDecimal(balanceData.get("balance").toString()); + + // Validate balance increased + Assert.assertTrue(newBalance.compareTo(testWallet.getBalance()) > 0, "Balance should increase after deposit"); + + // Update test wallet balance + testWallet.setBalance(newBalance); + + logger.info("testDepositFunds completed successfully"); + } + + @Test(description = "Withdraw funds from wallet", dependsOnMethods = "testDepositFunds") + public void testWithdrawFunds() { + logger.info("Executing testWithdrawFunds"); + + // Withdraw funds via API + double withdrawAmount = 500.00; + String description = "Test withdrawal"; + Response response = walletService.withdrawFunds(testWallet.getId(), withdrawAmount, description); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for withdrawing funds"); + + // Parse response + Transaction withdrawTransaction = response.as(Transaction.class); + + // Validate transaction data + Assert.assertNotNull(withdrawTransaction.getId(), "Transaction ID should not be null"); + Assert.assertEquals(withdrawTransaction.getWalletId(), testWallet.getId(), "Wallet ID should match"); + Assert.assertEquals(withdrawTransaction.getType(), Transaction.TransactionType.WITHDRAWAL, "Transaction type should be WITHDRAWAL"); + Assert.assertEquals(withdrawTransaction.getAmount(), new BigDecimal(withdrawAmount), "Amount should match"); + Assert.assertEquals(withdrawTransaction.getDescription(), description, "Description should match"); + + // Get updated wallet balance + Response balanceResponse = walletService.getWalletBalance(testWallet.getId()); + Map balanceData = balanceResponse.as(Map.class); + BigDecimal newBalance = new BigDecimal(balanceData.get("balance").toString()); + + // Validate balance decreased + Assert.assertTrue(newBalance.compareTo(testWallet.getBalance()) < 0, "Balance should decrease after withdrawal"); + + // Update test wallet balance + testWallet.setBalance(newBalance); + + logger.info("testWithdrawFunds completed successfully"); + } + + /** + * Validate wallet data in database + * @param wallet Wallet to validate + */ + private void validateWalletInDatabase(Wallet wallet) { + try { + String query = "SELECT * FROM wallets WHERE id = ?"; + List parameters = List.of(wallet.getId()); + + List> results = DatabaseUtil.executeQuery(query, parameters); + Assert.assertEquals(results.size(), 1, "Should find exactly one wallet with ID: " + wallet.getId()); + + Map walletData = results.get(0); + + Assert.assertEquals(walletData.get("customer_id"), wallet.getCustomerId(), "Customer ID should match in database"); + Assert.assertEquals(walletData.get("account_number"), wallet.getAccountNumber(), "Account number should match in database"); + Assert.assertEquals(new BigDecimal(walletData.get("balance").toString()), wallet.getBalance(), "Balance should match in database"); + Assert.assertEquals(walletData.get("currency"), wallet.getCurrency(), "Currency should match in database"); + Assert.assertEquals(walletData.get("status"), wallet.getStatus().toString(), "Status should match in database"); + + } catch (SQLException e) { + logger.error("Error validating wallet in database: {}", e.getMessage(), e); + Assert.fail("Error validating wallet in database: " + e.getMessage()); + } + } + + /** + * Validate transaction data in database + * @param transaction Transaction to validate + */ + private void validateTransactionInDatabase(Transaction transaction) { + try { + String query = "SELECT * FROM transactions WHERE id = ?"; + List parameters = List.of(transaction.getId()); + + List> results = DatabaseUtil.executeQuery(query, parameters); + Assert.assertEquals(results.size(), 1, "Should find exactly one transaction with ID: " + transaction.getId()); + + Map transactionData = results.get(0); + + Assert.assertEquals(transactionData.get("wallet_id"), transaction.getWalletId(), "Wallet ID should match in database"); + Assert.assertEquals(transactionData.get("type"), transaction.getType().toString(), "Type should match in database"); + Assert.assertEquals(new BigDecimal(transactionData.get("amount").toString()), transaction.getAmount(), "Amount should match in database"); + Assert.assertEquals(transactionData.get("currency"), transaction.getCurrency(), "Currency should match in database"); + Assert.assertEquals(transactionData.get("description"), transaction.getDescription(), "Description should match in database"); + Assert.assertEquals(transactionData.get("status"), transaction.getStatus().toString(), "Status should match in database"); + + } catch (SQLException e) { + logger.error("Error validating transaction in database: {}", e.getMessage(), e); + Assert.fail("Error validating transaction in database: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/financial/api/tests/WalletPerformanceTest.java b/src/test/java/com/financial/api/tests/WalletPerformanceTest.java new file mode 100644 index 0000000..d8c0fe2 --- /dev/null +++ b/src/test/java/com/financial/api/tests/WalletPerformanceTest.java @@ -0,0 +1,472 @@ +package com.financial.api.tests; + +import com.financial.api.models.wallet.Transaction; +import com.financial.api.models.wallet.Wallet; +import com.financial.api.services.WalletService; +import com.financial.api.utils.TestDataFactory; +import io.restassured.response.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Performance test class for Wallet API operations + */ +public class WalletPerformanceTest { + private static final Logger logger = LogManager.getLogger(WalletPerformanceTest.class); + private WalletService walletService; + private List testWallets = new ArrayList<>(); + private List testTransactions = new ArrayList<>(); + private static final int WALLETS_TO_CREATE = 10; + private static final int TRANSACTIONS_PER_WALLET = 5; + private static final int MAX_RESPONSE_TIME_MS = 5000; // Maximum acceptable response time in milliseconds + + @BeforeClass + public void setUp() { + logger.info("Setting up WalletPerformanceTest"); + walletService = new WalletService(); + + // Create test wallets + for (int i = 0; i < WALLETS_TO_CREATE; i++) { + Wallet wallet = TestDataFactory.generateRandomWallet(); + testWallets.add(wallet); + } + + // Create test transactions + for (Wallet wallet : testWallets) { + for (int i = 0; i < TRANSACTIONS_PER_WALLET; i++) { + Transaction transaction = TestDataFactory.generateRandomTransaction(wallet.getId()); + testTransactions.add(transaction); + } + } + } + + @AfterClass + public void tearDown() { + logger.info("Tearing down WalletPerformanceTest"); + + // Clean up test data if needed + // Note: In a real scenario, you might want to delete the created wallets and transactions + } + + @Test(description = "Performance test for creating multiple wallets", invocationCount = 1, threadPoolSize = 5) + public void testCreateMultipleWalletsPerformance() { + logger.info("Executing testCreateMultipleWalletsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (Wallet wallet : testWallets) { + long requestStartTime = System.currentTimeMillis(); + + // Create wallet via API + Response response = walletService.createWallet(wallet); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 201) { + successCount++; + + // Parse response + Wallet createdWallet = response.as(Wallet.class); + + // Update wallet with generated ID + wallet.setId(createdWallet.getId()); + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for creating wallet should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to create wallet. Status code: {}", response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for creating wallets:"); + logger.info("Total wallets created: {}/{}", successCount, testWallets.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testWallets.size(), + "All wallets should be created successfully. Created: " + successCount + ", Expected: " + testWallets.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testCreateMultipleWalletsPerformance completed successfully"); + } + + @Test(description = "Performance test for getting multiple wallets", dependsOnMethods = "testCreateMultipleWalletsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testGetMultipleWalletsPerformance() { + logger.info("Executing testGetMultipleWalletsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (Wallet wallet : testWallets) { + long requestStartTime = System.currentTimeMillis(); + + // Get wallet via API + Response response = walletService.getWallet(wallet.getId()); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for getting wallet should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to get wallet with ID: {}. Status code: {}", wallet.getId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for getting wallets:"); + logger.info("Total wallets retrieved: {}/{}", successCount, testWallets.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testWallets.size(), + "All wallets should be retrieved successfully. Retrieved: " + successCount + ", Expected: " + testWallets.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testGetMultipleWalletsPerformance completed successfully"); + } + + @Test(description = "Performance test for creating multiple transactions", dependsOnMethods = "testCreateMultipleWalletsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testCreateMultipleTransactionsPerformance() { + logger.info("Executing testCreateMultipleTransactionsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (Transaction transaction : testTransactions) { + long requestStartTime = System.currentTimeMillis(); + + // Create transaction via API + Response response = walletService.createTransaction(transaction.getWalletId(), transaction); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 201) { + successCount++; + + // Parse response + Transaction createdTransaction = response.as(Transaction.class); + + // Update transaction with generated ID + transaction.setId(createdTransaction.getId()); + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for creating transaction should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to create transaction. Status code: {}", response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for creating transactions:"); + logger.info("Total transactions created: {}/{}", successCount, testTransactions.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testTransactions.size(), + "All transactions should be created successfully. Created: " + successCount + ", Expected: " + testTransactions.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testCreateMultipleTransactionsPerformance completed successfully"); + } + + @Test(description = "Performance test for depositing funds to multiple wallets", dependsOnMethods = "testCreateMultipleWalletsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testDepositFundsPerformance() { + logger.info("Executing testDepositFundsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (Wallet wallet : testWallets) { + long requestStartTime = System.currentTimeMillis(); + + // Deposit funds via API + double depositAmount = 1000.00; + String description = "Performance test deposit"; + Response response = walletService.depositFunds(wallet.getId(), depositAmount, description); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for depositing funds should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to deposit funds to wallet with ID: {}. Status code: {}", wallet.getId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for depositing funds:"); + logger.info("Total deposits completed: {}/{}", successCount, testWallets.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testWallets.size(), + "All deposits should be completed successfully. Completed: " + successCount + ", Expected: " + testWallets.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testDepositFundsPerformance completed successfully"); + } + + @Test(description = "Performance test for getting wallet balance for multiple wallets", dependsOnMethods = "testDepositFundsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testGetWalletBalancePerformance() { + logger.info("Executing testGetWalletBalancePerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (Wallet wallet : testWallets) { + long requestStartTime = System.currentTimeMillis(); + + // Get wallet balance via API + Response response = walletService.getWalletBalance(wallet.getId()); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for getting wallet balance should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to get wallet balance for wallet with ID: {}. Status code: {}", wallet.getId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for getting wallet balance:"); + logger.info("Total wallet balances retrieved: {}/{}", successCount, testWallets.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testWallets.size(), + "All wallet balances should be retrieved successfully. Retrieved: " + successCount + ", Expected: " + testWallets.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testGetWalletBalancePerformance completed successfully"); + } + + @Test(description = "Performance test for getting transactions for multiple wallets", dependsOnMethods = "testCreateMultipleTransactionsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testGetTransactionsPerformance() { + logger.info("Executing testGetTransactionsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + for (Wallet wallet : testWallets) { + long requestStartTime = System.currentTimeMillis(); + + // Get transactions via API + Response response = walletService.getTransactions(wallet.getId()); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for getting transactions should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to get transactions for wallet with ID: {}. Status code: {}", wallet.getId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for getting transactions:"); + logger.info("Total transactions retrieved: {}/{}", successCount, testWallets.size()); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testWallets.size(), + "All transactions should be retrieved successfully. Retrieved: " + successCount + ", Expected: " + testWallets.size()); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testGetTransactionsPerformance completed successfully"); + } + + @Test(description = "Performance test for transferring funds between wallets", dependsOnMethods = "testDepositFundsPerformance", invocationCount = 1, threadPoolSize = 5) + public void testTransferFundsPerformance() { + logger.info("Executing testTransferFundsPerformance"); + + long startTime = System.currentTimeMillis(); + List responseTimes = new ArrayList<>(); + int successCount = 0; + + // Perform transfers between wallets + for (int i = 0; i < testWallets.size() - 1; i++) { + Wallet fromWallet = testWallets.get(i); + Wallet toWallet = testWallets.get(i + 1); + + long requestStartTime = System.currentTimeMillis(); + + // Transfer funds via API + double transferAmount = 100.00; + String description = "Performance test transfer"; + Response response = walletService.transferFunds(fromWallet.getId(), toWallet.getId(), transferAmount, description); + + long requestEndTime = System.currentTimeMillis(); + long responseTime = requestEndTime - requestStartTime; + responseTimes.add(responseTime); + + // Validate response + if (response.getStatusCode() == 200) { + successCount++; + + // Validate response time + Assert.assertTrue(responseTime <= MAX_RESPONSE_TIME_MS, + "Response time for transferring funds should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + responseTime + "ms"); + } else { + logger.error("Failed to transfer funds from wallet {} to wallet {}. Status code: {}", + fromWallet.getId(), toWallet.getId(), response.getStatusCode()); + } + } + + long endTime = System.currentTimeMillis(); + long totalTime = endTime - startTime; + + // Calculate statistics + double averageResponseTime = responseTimes.stream().mapToLong(Long::longValue).average().orElse(0); + long maxResponseTime = responseTimes.stream().mapToLong(Long::longValue).max().orElse(0); + long minResponseTime = responseTimes.stream().mapToLong(Long::longValue).min().orElse(0); + + // Log performance metrics + logger.info("Performance metrics for transferring funds:"); + logger.info("Total transfers completed: {}/{}", successCount, testWallets.size() - 1); + logger.info("Total time: {} ms", totalTime); + logger.info("Average response time: {} ms", averageResponseTime); + logger.info("Min response time: {} ms", minResponseTime); + logger.info("Max response time: {} ms", maxResponseTime); + + // Validate performance + Assert.assertEquals(successCount, testWallets.size() - 1, + "All transfers should be completed successfully. Completed: " + successCount + ", Expected: " + (testWallets.size() - 1)); + + Assert.assertTrue(averageResponseTime <= MAX_RESPONSE_TIME_MS, + "Average response time should be less than " + MAX_RESPONSE_TIME_MS + "ms, but was " + averageResponseTime + "ms"); + + logger.info("testTransferFundsPerformance completed successfully"); + } +} \ No newline at end of file diff --git a/src/test/java/com/financial/api/tests/WalletSecurityTest.java b/src/test/java/com/financial/api/tests/WalletSecurityTest.java new file mode 100644 index 0000000..90c931e --- /dev/null +++ b/src/test/java/com/financial/api/tests/WalletSecurityTest.java @@ -0,0 +1,359 @@ +package com.financial.api.tests; + +import com.financial.api.models.wallet.Transaction; +import com.financial.api.models.wallet.Wallet; +import com.financial.api.services.WalletService; +import com.financial.api.utils.TestDataFactory; +import io.restassured.response.Response; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; + +/** + * Security test class for Wallet API operations + */ +public class WalletSecurityTest { + private static final Logger logger = LogManager.getLogger(WalletSecurityTest.class); + private WalletService walletService; + private Wallet testWallet; + private Transaction testTransaction; + + @BeforeClass + public void setUp() { + logger.info("Setting up WalletSecurityTest"); + walletService = new WalletService(); + + // Create test wallet + testWallet = TestDataFactory.generateRandomWallet(); + + // Create test transaction + testTransaction = TestDataFactory.generateRandomTransaction(testWallet.getId()); + + // Create wallet via API for testing + Response response = walletService.createWallet(testWallet); + if (response.getStatusCode() == 201) { + Wallet createdWallet = response.as(Wallet.class); + testWallet.setId(createdWallet.getId()); + } + } + + @Test(description = "Test SQL injection in wallet creation") + public void testSqlInjectionInWalletCreation() { + logger.info("Executing testSqlInjectionInWalletCreation"); + + // Create wallet with SQL injection payload + Wallet maliciousWallet = new Wallet( + "test-customer'; DROP TABLE wallets; --", + "test-account'; DROP TABLE wallets; --", + new BigDecimal("1000.00"), + "USD" + ); + + // Attempt to create wallet via API + Response response = walletService.createWallet(maliciousWallet); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not create wallet with SQL injection payload"); + + logger.info("testSqlInjectionInWalletCreation completed successfully"); + } + + @Test(description = "Test XSS in wallet creation") + public void testXssInWalletCreation() { + logger.info("Executing testXssInWalletCreation"); + + // Create wallet with XSS payload + Wallet maliciousWallet = new Wallet( + "", + "test-account", + new BigDecimal("1000.00"), + "USD" + ); + + // Attempt to create wallet via API + Response response = walletService.createWallet(maliciousWallet); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not create wallet with XSS payload"); + + logger.info("testXssInWalletCreation completed successfully"); + } + + @Test(description = "Test authentication bypass in wallet retrieval") + public void testAuthenticationBypassInWalletRetrieval() { + logger.info("Executing testAuthenticationBypassInWalletRetrieval"); + + // Attempt to get wallet without authentication + Response response = walletService.getWalletWithoutAuth(testWallet.getId()); + + // Validate response + // Should return 401 Unauthorized or 403 Forbidden + Assert.assertTrue(response.getStatusCode() == 401 || response.getStatusCode() == 403, + "API should require authentication for wallet retrieval"); + + logger.info("testAuthenticationBypassInWalletRetrieval completed successfully"); + } + + @Test(description = "Test authorization bypass in wallet retrieval") + public void testAuthorizationBypassInWalletRetrieval() { + logger.info("Executing testAuthorizationBypassInWalletRetrieval"); + + // Attempt to get wallet with different user's authentication + Response response = walletService.getWalletWithDifferentUserAuth(testWallet.getId()); + + // Validate response + // Should return 403 Forbidden + Assert.assertEquals(response.getStatusCode(), 403, "API should prevent unauthorized access to wallet"); + + logger.info("testAuthorizationBypassInWalletRetrieval completed successfully"); + } + + @Test(description = "Test IDOR in wallet retrieval") + public void testIdorInWalletRetrieval() { + logger.info("Executing testIdorInWalletRetrieval"); + + // Attempt to get wallet with different wallet ID + String differentWalletId = "different-wallet-id-12345"; + Response response = walletService.getWallet(differentWalletId); + + // Validate response + // Should return 404 Not Found or 403 Forbidden + Assert.assertTrue(response.getStatusCode() == 404 || response.getStatusCode() == 403, + "API should prevent access to non-existent or unauthorized wallet"); + + logger.info("testIdorInWalletRetrieval completed successfully"); + } + + @Test(description = "Test SQL injection in transaction creation") + public void testSqlInjectionInTransactionCreation() { + logger.info("Executing testSqlInjectionInTransactionCreation"); + + // Create transaction with SQL injection payload + Transaction maliciousTransaction = new Transaction( + testWallet.getId(), + Transaction.TransactionType.DEPOSIT, + new BigDecimal("1000.00"), + "USD", + "test-description'; DROP TABLE transactions; --" + ); + + // Attempt to create transaction via API + Response response = walletService.createTransaction(testWallet.getId(), maliciousTransaction); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not create transaction with SQL injection payload"); + + logger.info("testSqlInjectionInTransactionCreation completed successfully"); + } + + @Test(description = "Test XSS in transaction creation") + public void testXssInTransactionCreation() { + logger.info("Executing testXssInTransactionCreation"); + + // Create transaction with XSS payload + Transaction maliciousTransaction = new Transaction( + testWallet.getId(), + Transaction.TransactionType.DEPOSIT, + new BigDecimal("1000.00"), + "USD", + "" + ); + + // Attempt to create transaction via API + Response response = walletService.createTransaction(testWallet.getId(), maliciousTransaction); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not create transaction with XSS payload"); + + logger.info("testXssInTransactionCreation completed successfully"); + } + + @Test(description = "Test negative amount in transaction creation") + public void testNegativeAmountInTransactionCreation() { + logger.info("Executing testNegativeAmountInTransactionCreation"); + + // Create transaction with negative amount + Transaction maliciousTransaction = new Transaction( + testWallet.getId(), + Transaction.TransactionType.DEPOSIT, + new BigDecimal("-1000.00"), + "USD", + "Test transaction with negative amount" + ); + + // Attempt to create transaction via API + Response response = walletService.createTransaction(testWallet.getId(), maliciousTransaction); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not create transaction with negative amount"); + + logger.info("testNegativeAmountInTransactionCreation completed successfully"); + } + + @Test(description = "Test extremely large amount in transaction creation") + public void testExtremelyLargeAmountInTransactionCreation() { + logger.info("Executing testExtremelyLargeAmountInTransactionCreation"); + + // Create transaction with extremely large amount + Transaction maliciousTransaction = new Transaction( + testWallet.getId(), + Transaction.TransactionType.DEPOSIT, + new BigDecimal("99999999999999999999.99"), + "USD", + "Test transaction with extremely large amount" + ); + + // Attempt to create transaction via API + Response response = walletService.createTransaction(testWallet.getId(), maliciousTransaction); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, "API should not create transaction with extremely large amount"); + + logger.info("testExtremelyLargeAmountInTransactionCreation completed successfully"); + } + + @Test(description = "Test authentication bypass in transaction retrieval") + public void testAuthenticationBypassInTransactionRetrieval() { + logger.info("Executing testAuthenticationBypassInTransactionRetrieval"); + + // Attempt to get transaction without authentication + Response response = walletService.getTransactionWithoutAuth(testWallet.getId(), "test-transaction-id"); + + // Validate response + // Should return 401 Unauthorized or 403 Forbidden + Assert.assertTrue(response.getStatusCode() == 401 || response.getStatusCode() == 403, + "API should require authentication for transaction retrieval"); + + logger.info("testAuthenticationBypassInTransactionRetrieval completed successfully"); + } + + @Test(description = "Test authorization bypass in transaction retrieval") + public void testAuthorizationBypassInTransactionRetrieval() { + logger.info("Executing testAuthorizationBypassInTransactionRetrieval"); + + // Attempt to get transaction with different user's authentication + Response response = walletService.getTransactionWithDifferentUserAuth(testWallet.getId(), "test-transaction-id"); + + // Validate response + // Should return 403 Forbidden + Assert.assertEquals(response.getStatusCode(), 403, "API should prevent unauthorized access to transaction"); + + logger.info("testAuthorizationBypassInTransactionRetrieval completed successfully"); + } + + @Test(description = "Test CSRF protection in wallet update") + public void testCsrfProtectionInWalletUpdate() { + logger.info("Executing testCsrfProtectionInWalletUpdate"); + + // Attempt to update wallet without CSRF token + Map updateData = new HashMap<>(); + updateData.put("balance", new BigDecimal("2000.00")); + + Response response = walletService.updateWalletWithoutCsrfToken(testWallet.getId(), updateData); + + // Validate response + // Should return 400 Bad Request or 403 Forbidden if CSRF protection is enabled + Assert.assertTrue(response.getStatusCode() == 400 || response.getStatusCode() == 403, + "API should require CSRF token for wallet update"); + + logger.info("testCsrfProtectionInWalletUpdate completed successfully"); + } + + @Test(description = "Test rate limiting in wallet creation") + public void testRateLimitingInWalletCreation() { + logger.info("Executing testRateLimitingInWalletCreation"); + + int requestCount = 10; + int tooManyRequestsCount = 0; + + // Send multiple requests in quick succession + for (int i = 0; i < requestCount; i++) { + Wallet wallet = TestDataFactory.generateRandomWallet(); + Response response = walletService.createWallet(wallet); + + if (response.getStatusCode() == 429) { // Too Many Requests + tooManyRequestsCount++; + } + } + + // Validate response + // Should receive at least one 429 Too Many Requests response if rate limiting is enabled + Assert.assertTrue(tooManyRequestsCount > 0, "API should implement rate limiting for wallet creation"); + + logger.info("testRateLimitingInWalletCreation completed successfully"); + } + + @Test(description = "Test input validation in wallet creation") + public void testInputValidationInWalletCreation() { + logger.info("Executing testInputValidationInWalletCreation"); + + // Test cases for input validation + Map testCases = new HashMap<>(); + + // Empty customer ID + testCases.put("Empty customer ID", new Wallet("", "test-account", new BigDecimal("1000.00"), "USD")); + + // Empty account number + testCases.put("Empty account number", new Wallet("test-customer", "", new BigDecimal("1000.00"), "USD")); + + // Null balance + testCases.put("Null balance", new Wallet("test-customer", "test-account", null, "USD")); + + // Empty currency + testCases.put("Empty currency", new Wallet("test-customer", "test-account", new BigDecimal("1000.00"), "")); + + // Invalid currency + testCases.put("Invalid currency", new Wallet("test-customer", "test-account", new BigDecimal("1000.00"), "INVALID")); + + // Test each case + for (Map.Entry entry : testCases.entrySet()) { + String testCase = entry.getKey(); + Wallet wallet = entry.getValue(); + + Response response = walletService.createWallet(wallet); + + // Validate response + // Should return 400 Bad Request or similar error code, not 201 Created + Assert.assertNotEquals(response.getStatusCode(), 201, + "API should validate input for " + testCase + " and not create wallet"); + + logger.info("Input validation test passed for: {}", testCase); + } + + logger.info("testInputValidationInWalletCreation completed successfully"); + } + + @Test(description = "Test sensitive data exposure in wallet response") + public void testSensitiveDataExposureInWalletResponse() { + logger.info("Executing testSensitiveDataExposureInWalletResponse"); + + // Get wallet via API + Response response = walletService.getWallet(testWallet.getId()); + + // Validate response + Assert.assertEquals(response.getStatusCode(), 200, "Expected status code 200 for getting wallet"); + + // Parse response + Map walletData = response.as(Map.class); + + // Check for sensitive data that should not be exposed + Assert.assertFalse(walletData.containsKey("password"), "Wallet response should not contain password"); + Assert.assertFalse(walletData.containsKey("pin"), "Wallet response should not contain PIN"); + Assert.assertFalse(walletData.containsKey("ssn"), "Wallet response should not contain SSN"); + Assert.assertFalse(walletData.containsKey("credit_card_number"), "Wallet response should not contain credit card number"); + + logger.info("testSensitiveDataExposureInWalletResponse completed successfully"); + } +} \ No newline at end of file diff --git a/src/test/resources/testng.xml b/src/test/resources/testng.xml new file mode 100644 index 0000000..bfbd1e5 --- /dev/null +++ b/src/test/resources/testng.xml @@ -0,0 +1,193 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file