diff --git a/.codegenignore b/.codegenignore new file mode 100644 index 0000000..098d417 --- /dev/null +++ b/.codegenignore @@ -0,0 +1,5 @@ +test/** +.gitignore +.github/** +Rakefile +Gemfile diff --git a/.github/workflows/test-runner.yml b/.github/workflows/test-runner.yml new file mode 100644 index 0000000..1dc4615 --- /dev/null +++ b/.github/workflows/test-runner.yml @@ -0,0 +1,69 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake +# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby + +name: Run Tests + +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +permissions: + contents: read + +jobs: + test-runner: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.0 + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - name: Add Minitest Dependencies + run: | + gem install minitest -v 5.24 && bundle add minitest + gem install minitest-proveit -v 1.0 && bundle add minitest-proveit + - name: Add DotEnv Dependency + run: | + gem install dotenv -v 2.8 && bundle add dotenv + - name: Add Web Driver Dependency + run: | + gem install selenium-webdriver -v 4.23 && bundle add selenium-webdriver + - name: Add UI Testing Framework Dependency + run: | + gem install capybara -v 3.40 && bundle add capybara + - name: Create Environment + run: | + touch .env + echo AKOYA_API_VERSION=${{ secrets.AKOYA_API_VERSION }} >> .env + echo AKOYA_PROVIDER_ID=${{ secrets.AKOYA_PROVIDER_ID }} >> .env + echo OAUTH_CLIENT_ID=${{ secrets.OAUTH_CLIENT_ID }} >> .env + echo OAUTH_CLIENT_SECRET=${{ secrets.OAUTH_CLIENT_SECRET }} >> .env + echo OAUTH_REDIRECT_URI=${{ secrets.OAUTH_REDIRECT_URI }} >> .env + echo CONNECTOR=${{ secrets.CONNECTOR }} >> .env + echo STATE=${{ secrets.STATE }} >> .env + echo TEST_USERNAME=${{ secrets.TEST_USERNAME }} >> .env + echo TEST_PASSWORD=${{ secrets.TEST_PASSWORD }} >> .env + echo TAX_USERNAME=${{ secrets.TAX_USERNAME }} >> .env + echo TAX_PASSWORD=${{ secrets.TAX_PASSWORD }} >> .env + echo RETURN_URL=${{ secrets.RETURN_URL }} >> .env + echo STATEMENT_ACCOUNT_ID=${{ secrets.STATEMENT_ACCOUNT_ID }} >> .env + echo STATEMENT_ID=${{ secrets.STATEMENT_ID }} >> .env + echo TAXLOT_ACCOUNT_ID=${{ secrets.TAXLOT_ACCOUNT_ID }} >> .env + echo HOLDING_ID=${{ secrets.HOLDING_ID }} >> .env + cat .env + - name: Run Tests + run: bundle exec rake test + # - name: Run Endpoint Tests Only + # run: bundle exec rake test_endpoints + # - name: Run Auth Tests Only + # run: bundle exec rake test_auth \ No newline at end of file diff --git a/.gitignore b/.gitignore index b844b14..a1e044e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,36 @@ Gemfile.lock +.env +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ +/tmp/ + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore pidfiles, but keep the directory. +/tmp/pids/* +!/tmp/pids/ +!/tmp/pids/.keep + +# Ignore uploaded files in development. +/public/uploads + +# Ignore master key for decrypting credentials and more. +/config/master.key + +# Ignore application configuration. +/config/application.yml + +# Ignore test artifacts +consent_flow_failure.png \ No newline at end of file diff --git a/Gemfile b/Gemfile index fa75df1..1985940 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,7 @@ source 'https://rubygems.org' -gemspec +group :test do + gem 'rake' +end + +gemspec \ No newline at end of file diff --git a/README.md b/README.md index b116d9b..dfb9560 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,80 @@ A new controller class named `HelloController` will be created in a file named ` ![Initialize the library](https://apidocs.io/illustration/ruby?workspaceFolder=Akoya&gemName=akoya&step=addCode3) +## Testing + +The Akoya Ruby SDK includes a comprehensive test suite that follows Ruby best practices and testing patterns. The test suite covers all major API endpoints and authentication scenarios. + +### Test Structure + +The test suite is organized into the following components: + +- **Base Test Class**: `test/controller_test_base.rb` - Handles authentication and environment setup +- **Endpoint Tests**: Tests for all major API endpoints (account info, balances, customers, investments, payments, statements, tax, transactions) +- **Authentication Tests**: Tests for various error scenarios and authentication flows +- **Test Runner**: `test/test_runner.rb` - Main test runner that executes all tests + +### Running Tests + +#### Prerequisites +1. Install Ruby 3.0 or later +2. Install dependencies: `bundle install` +3. Install Playwright: `npx playwright install chromium` +4. Set up environment variables (see `env.example` file) + +#### Running All Tests +```bash +bundle exec rake test +``` + +#### Running Specific Test Categories +```bash +# Run only endpoint tests +bundle exec rake test_endpoints + +# Run only authentication tests +bundle exec rake test_auth +``` + +#### Running Individual Test Files +```bash +# Run a specific test file +ruby test/test_account_info_controller.rb + +# Run the test runner +ruby test/test_runner.rb +``` + +### Environment Setup + +Copy `env.example` to `.env` and configure the following environment variables: + +**Required Variables:** +- `AKOYA_API_VERSION` - API version to use +- `AKOYA_PROVIDER_ID` - Provider ID for testing +- `OAUTH_CLIENT_ID` - OAuth client ID +- `OAUTH_CLIENT_SECRET` - OAuth client secret +- `OAUTH_REDIRECT_URI` - OAuth redirect URI +- `CONNECTOR` - Connector identifier +- `STATE` - OAuth state parameter +- `TEST_USERNAME` - Username for test authentication +- `TEST_PASSWORD` - Password for test authentication +- `TAX_USERNAME` - Username for tax-related tests +- `TAX_PASSWORD` - Password for tax-related tests +- `RETURN_URL` - Return URL for OAuth flow + +**Optional Variables:** +- `STATEMENT_ACCOUNT_ID` - Account ID for statement tests +- `STATEMENT_ID` - Statement ID for specific statement tests +- `TAXLOT_ACCOUNT_ID` - Account ID for tax lot tests +- `HOLDING_ID` - Holding ID for investment tests + +### CI/CD Integration + +The test suite is integrated with GitHub Actions via `.github/workflows/test-runner.yml`. The workflow automatically runs tests on push to main/master branches and pull requests. + +For more detailed information about the test suite, see the [test README](test/README.md). + ## Initialize the API Client **_Note:_** Documentation for the client can be found [here.](https://www.github.com/akoya-llc/akoya-ruby-sdk/tree/0.2.0/doc/client.md) diff --git a/Rakefile b/Rakefile index 8944c8a..a306c53 100644 --- a/Rakefile +++ b/Rakefile @@ -2,3 +2,27 @@ lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "bundler/gem_tasks" +require "rake/testtask" + +Rake::TestTask.new(:test) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/test_*.rb"] + t.verbose = true +end + +Rake::TestTask.new(:test_endpoints) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/test_*_controller.rb"] + t.verbose = true +end + +Rake::TestTask.new(:test_auth) do |t| + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/test_error_scenarios.rb"] + t.verbose = true +end + +task default: :test \ No newline at end of file diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..61c8b5c --- /dev/null +++ b/test/README.md @@ -0,0 +1,151 @@ +# Akoya Ruby SDK Test Suite + +This directory contains the comprehensive test suite for the Akoya Ruby SDK, following Ruby best practices and testing patterns. + +## Test Structure + +The test suite is organized into the following components: + +### Base Test Class +- `controller_test_base.rb` - Base class for all endpoint tests, handles authentication and environment setup + +### Endpoint Tests +- `test_account_info_controller.rb` - Tests for account information endpoints +- `test_balances_controller.rb` - Tests for balances endpoints +- `test_customers_controller.rb` - Tests for customer information endpoints +- `test_investments_controller.rb` - Tests for investment endpoints +- `test_payments_controller.rb` - Tests for payment network endpoints +- `test_statements_controller.rb` - Tests for statement endpoints +- `test_tax_controller.rb` - Tests for tax form endpoints +- `test_transactions_controller.rb` - Tests for transaction endpoints + +### Authentication Tests +- `test_error_scenarios.rb` - Tests for various error scenarios and authentication flows + +### Test Runner +- `test_runner.rb` - Main test runner that executes all tests + +## Environment Setup + +The tests require the following environment variables to be set: + +### Required Variables +- `AKOYA_API_VERSION` - API version to use +- `AKOYA_PROVIDER_ID` - Provider ID for testing +- `OAUTH_CLIENT_ID` - OAuth client ID +- `OAUTH_CLIENT_SECRET` - OAuth client secret +- `OAUTH_REDIRECT_URI` - OAuth redirect URI +- `CONNECTOR` - Connector identifier +- `STATE` - OAuth state parameter +- `TEST_USERNAME` - Username for test authentication +- `TEST_PASSWORD` - Password for test authentication +- `TAX_USERNAME` - Username for tax-related tests +- `TAX_PASSWORD` - Password for tax-related tests +- `RETURN_URL` - Return URL for OAuth flow + +### Optional Variables +- `STATEMENT_ACCOUNT_ID` - Account ID for statement tests +- `STATEMENT_ID` - Statement ID for specific statement tests +- `TAXLOT_ACCOUNT_ID` - Account ID for tax lot tests +- `HOLDING_ID` - Holding ID for investment tests + +## Running Tests + +### Prerequisites +1. Install Ruby 3.0 or later +2. Install dependencies: `bundle install` +3. Install Playwright: `npx playwright install chromium` +4. Set up environment variables (see above) + +### Running All Tests +```bash +bundle exec rake test +``` + +### Running Specific Test Categories +```bash +# Run only endpoint tests +bundle exec rake test_endpoints + +# Run only authentication tests +bundle exec rake test_auth +``` + +### Running Individual Test Files +```bash +# Run a specific test file +ruby test/test_account_info_controller.rb + +# Run the test runner +ruby test/test_runner.rb +``` + +## Test Features + +### Authentication Flow +- Uses Playwright for browser automation +- Handles OAuth consent flow automatically +- Supports different user credentials for different test scenarios + +### Error Testing +- Tests various error scenarios (500, 501, 601, 602, 701, 702, 703, 704) +- Validates proper exception handling +- Ensures error codes match expected values + +### Endpoint Testing +- Tests all major API endpoints +- Validates response structure and content +- Ensures proper status codes +- Verifies data integrity + +## CI/CD Integration + +The test suite is integrated with GitHub Actions via `.github/workflows/test-runner.yml`. The workflow: + +1. Sets up Ruby environment +2. Installs dependencies +3. Configures environment variables from GitHub secrets +4. Installs Playwright +5. Runs all test categories + +## Best Practices + +### Ruby Testing Standards +- Uses Minitest framework (Ruby's standard testing library) +- Follows Ruby naming conventions +- Implements proper setup and teardown methods +- Uses descriptive test method names + +### Test Organization +- Clear separation between endpoint and auth tests +- Consistent test structure across all controllers +- Proper error handling and validation +- Comprehensive documentation + +### Environment Management +- Uses dotenv for environment variable management +- Validates required environment variables +- Supports both local and CI environments + +## Troubleshooting + +### Common Issues +1. **Playwright not found**: Ensure Playwright is installed with `npx playwright install chromium` +2. **Environment variables missing**: Check that all required variables are set in `.env` file +3. **Authentication failures**: Verify test credentials are correct +4. **Network issues**: Ensure stable internet connection for API calls + +### Debug Mode +To run tests with additional logging: +```bash +LOG_LEVEL=DEBUG bundle exec rake test +``` + +## Contributing + +When adding new tests: +1. Follow the existing naming conventions +2. Extend the appropriate base class +3. Include proper documentation +4. Add corresponding Rake tasks if needed +5. Update this README if necessary \ No newline at end of file diff --git a/test/controller_test_base.rb b/test/controller_test_base.rb new file mode 100644 index 0000000..43f666a --- /dev/null +++ b/test/controller_test_base.rb @@ -0,0 +1,191 @@ +require 'json' +require 'minitest/autorun' +require 'minitest/hell' +require 'minitest/proveit' +require 'akoya' +require 'dotenv' +require 'selenium-webdriver' + +class ControllerTestBase < Minitest::Test + include Akoya + include CoreLibrary + + Dotenv.load + + def setup + load_properties + end + + private + + def load_properties + @api_version = ENV.fetch('AKOYA_API_VERSION') { raise "Required property AKOYA_API_VERSION must be set" } + @provider_id = ENV.fetch('AKOYA_PROVIDER_ID') { raise "Required property AKOYA_PROVIDER_ID must be set" } + @oauth_client_id = ENV.fetch('OAUTH_CLIENT_ID') { raise "Required property OAUTH_CLIENT_ID must be set" } + @oauth_client_secret = ENV.fetch('OAUTH_CLIENT_SECRET') { raise "Required property OAUTH_CLIENT_SECRET must be set" } + @oauth_redirect_uri = ENV.fetch('OAUTH_REDIRECT_URI') { raise "Required property OAUTH_REDIRECT_URI must be set" } + @connector = ENV.fetch('CONNECTOR') { raise "Required property CONNECTOR must be set" } + @state = ENV.fetch('STATE') { raise "Required property STATE must be set" } + @test_username = ENV.fetch('TEST_USERNAME') { raise "Required property TEST_USERNAME must be set" } + @test_password = ENV.fetch('TEST_PASSWORD') { raise "Required property TEST_PASSWORD must be set" } + @tax_username = ENV.fetch('TAX_USERNAME') { raise "Required property TAX_USERNAME must be set" } + @tax_password = ENV.fetch('TAX_PASSWORD') { raise "Required property TAX_PASSWORD must be set" } + @return_url = ENV.fetch('RETURN_URL') { raise "Required property RETURN_URL must be set" } + @statement_account_id = ENV['STATEMENT_ACCOUNT_ID'] + @statement_id = ENV['STATEMENT_ID'] + @account_id = ENV['TAXLOT_ACCOUNT_ID'] + @holding_id = ENV['HOLDING_ID'] + # Note: The below properties were not previously validated; keeping them optional. + end + + + + def get_akoya_client_with_consent(username, password) + client = Akoya::Client.new( + authorization_code_auth_credentials: AuthorizationCodeAuthCredentials.new( + oauth_client_id: @oauth_client_id, + oauth_client_secret: @oauth_client_secret, + oauth_redirect_uri: @oauth_redirect_uri + ), + environment: Environment::SANDBOX + ) + + auth_url = client.acg_auth.get_authorization_url( + connector: @connector, + state: @state + ) + + code = run_consent_flow_with_credentials(auth_url, username, password) + + token = client.acg_auth.fetch_token(code) + if token.nil? + raise "Failed to obtain OAuth token" + end + + config = client.config.clone_with( + authorization_code_auth_credentials: client.config.authorization_code_auth_credentials.clone_with( + oauth_token: token + ) + ) + + Akoya::Client.new(config: config) + end + + def run_consent_flow_with_credentials(auth_url, username, password, retry_attempts = 0) + options = Selenium::WebDriver::Chrome::Options.new + options.add_argument('--headless') + options.add_argument('--no-sandbox') + options.add_argument('--disable-dev-shm-usage') + options.add_argument('--disable-gpu') + options.add_argument('--window-size=1920,1080') + + driver = Selenium::WebDriver.for :chrome, options: options + wait = Selenium::WebDriver::Wait.new(timeout: 10) + + begin + # Navigate to auth URL + driver.get(auth_url) + sleep(2) # Give page time to load + + # Login - try multiple selectors + username_field = find_element_with_fallbacks(driver, [ + "input[type=\"text\"]", + "input[name=\"username\"]", + "input[id*=\"username\"]", + "input[placeholder*=\"username\"]" + ]) + username_field.send_keys(username) + + password_field = find_element_with_fallbacks(driver, [ + "input[type=\"password\"]", + "input[name=\"password\"]", + "input[id*=\"password\"]" + ]) + password_field.send_keys(password) + + # Try to find sign in button with multiple selectors + sign_in_button = find_element_with_fallbacks(driver, [ + "//button[contains(text(), 'Sign in')]", + "//button[contains(text(), 'Login')]", + "//button[contains(text(), 'Submit')]", + "//input[@type='submit']", + "button[type=\"submit\"]" + ]) + sign_in_button.click + sleep(2) # Wait for page transition + + # Try to find Next button with multiple selectors + next_button = find_element_with_fallbacks(driver, [ + "//button[contains(text(), 'Next')]", + "//button[contains(text(), 'Continue')]", + "//button[contains(text(), 'Proceed')]", + "//a[contains(text(), 'Next')]", + "//a[contains(text(), 'Continue')]" + ]) + next_button.click + sleep(2) # Wait for page transition + + # Check all account checkboxes - try multiple approaches + checkboxes = driver.find_elements(css: "input[type=\"checkbox\"][name=\"account\"]") + if checkboxes.empty? + checkboxes = driver.find_elements(css: "input[type=\"checkbox\"]") + end + + checkboxes.each do |checkbox| + begin + driver.execute_script("arguments[0].click();", checkbox) + rescue => e + # Try alternative click method + checkbox.click + end + end + + # Try to find Approve button with multiple selectors + approve_button = find_element_with_fallbacks(driver, [ + "//button[contains(text(), 'Approve')]", + "//button[contains(text(), 'Authorize')]", + "//button[contains(text(), 'Allow')]", + "//button[contains(text(), 'Confirm')]", + "//input[@value='Approve']" + ]) + approve_button.click + + # Wait for redirect and extract code + wait.until { driver.current_url.start_with?(@return_url) } + + current_url = driver.current_url + code = current_url.split("code=")[1].split("&")[0] + + return code + rescue => e + driver.save_screenshot("consent_flow_failure.png") + puts "Current URL: #{driver.current_url}" + puts "Page source: #{driver.page_source}" + if retry_attempts < 3 + puts "Retrying consent flow (attempt #{retry_attempts + 1})..." + driver.quit + return run_consent_flow_with_credentials(auth_url, username, password, retry_attempts + 1) + end + raise "Consent flow failed after #{retry_attempts + 1} attempts: #{e.message}" + ensure + driver.quit + end + end + + def find_element_with_fallbacks(driver, selectors) + selectors.each do |selector| + begin + if selector.start_with?("//") + # XPath selector + return driver.find_element(xpath: selector) + else + # CSS selector + return driver.find_element(css: selector) + end + rescue Selenium::WebDriver::Error::NoSuchElementError + next + end + end + raise "Could not find element with any of the selectors: #{selectors.join(', ')}" + end +end \ No newline at end of file diff --git a/test/test_account_info_controller.rb b/test/test_account_info_controller.rb new file mode 100644 index 0000000..accaa21 --- /dev/null +++ b/test/test_account_info_controller.rb @@ -0,0 +1,22 @@ +require_relative 'controller_test_base' + +class AccountInfoControllerTest < ControllerTestBase + + def setup + super + @client = get_akoya_client_with_consent(@test_username, @test_password) + end + + def test_accounts_info_returns_successful_response_with_data + result = @client.account_information.get_accounts_info( + @api_version, + @provider_id, + mode: Mode::RAW + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + +end \ No newline at end of file diff --git a/test/test_balances_controller.rb b/test/test_balances_controller.rb new file mode 100644 index 0000000..2de7bd3 --- /dev/null +++ b/test/test_balances_controller.rb @@ -0,0 +1,22 @@ +require_relative 'controller_test_base' + +class BalancesControllerTest < ControllerTestBase + + def setup + super + @client = get_akoya_client_with_consent(@test_username, @test_password) + end + + def test_balances_returns_successful_response_with_data + result = @client.balances.get_balances( + @api_version, + @provider_id, + mode: Mode::RAW + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + +end \ No newline at end of file diff --git a/test/test_customers_controller.rb b/test/test_customers_controller.rb new file mode 100644 index 0000000..ef9b167 --- /dev/null +++ b/test/test_customers_controller.rb @@ -0,0 +1,21 @@ +require_relative 'controller_test_base' + +class CustomersControllerTest < ControllerTestBase + + def setup + super + @client = get_akoya_client_with_consent(@test_username, @test_password) + end + + def test_customer_info_returns_successful_response_with_data + result = @client.customers.customer_info( + @api_version, + @provider_id + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + +end \ No newline at end of file diff --git a/test/test_error_scenarios.rb b/test/test_error_scenarios.rb new file mode 100644 index 0000000..c45c151 --- /dev/null +++ b/test/test_error_scenarios.rb @@ -0,0 +1,242 @@ +require_relative 'controller_test_base' + +class ErrorScenariosTest < ControllerTestBase + + def setup + super + # Initialize Selenium for auth tests + options = Selenium::WebDriver::Chrome::Options.new + options.add_argument('--headless') + options.add_argument('--no-sandbox') + options.add_argument('--disable-dev-shm-usage') + options.add_argument('--disable-gpu') + options.add_argument('--window-size=1920,1080') + + @driver = Selenium::WebDriver.for :chrome, options: options + end + + def teardown + @driver&.quit + end + + def get_akoya_client_with_consent(username, password) + client = Akoya::Client.new( + authorization_code_auth_credentials: AuthorizationCodeAuthCredentials.new( + oauth_client_id: @oauth_client_id, + oauth_client_secret: @oauth_client_secret, + oauth_redirect_uri: @oauth_redirect_uri + ), + environment: Environment::SANDBOX + ) + + auth_url = client.acg_auth.get_authorization_url( + connector: @connector, + state: @state + ) + + code = run_consent_flow_with_credentials(auth_url, username, password) + + token = client.acg_auth.fetch_token(code) + if token.nil? + raise "Failed to obtain OAuth token" + end + + config = client.config.clone_with( + authorization_code_auth_credentials: client.config.authorization_code_auth_credentials.clone_with( + oauth_token: token + ) + ) + + Akoya::Client.new(config: config) + end + + def run_consent_flow_with_credentials(auth_url, username, password) + wait = Selenium::WebDriver::Wait.new(timeout: 10) + + begin + # Navigate to auth URL + @driver.get(auth_url) + sleep(2) # Give page time to load + + # Login - try multiple selectors + username_field = find_element_with_fallbacks(@driver, [ + "input[type=\"text\"]", + "input[name=\"username\"]", + "input[id*=\"username\"]", + "input[placeholder*=\"username\"]" + ]) + username_field.send_keys(username) + + password_field = find_element_with_fallbacks(@driver, [ + "input[type=\"password\"]", + "input[name=\"password\"]", + "input[id*=\"password\"]" + ]) + password_field.send_keys(password) + + # Try to find sign in button with multiple selectors + sign_in_button = find_element_with_fallbacks(@driver, [ + "//button[contains(text(), 'Sign in')]", + "//button[contains(text(), 'Login')]", + "//button[contains(text(), 'Submit')]", + "//input[@type='submit']", + "button[type=\"submit\"]" + ]) + sign_in_button.click + sleep(2) # Wait for page transition + + # Try to find Next button with multiple selectors + next_button = find_element_with_fallbacks(@driver, [ + "//button[contains(text(), 'Next')]", + "//button[contains(text(), 'Continue')]", + "//button[contains(text(), 'Proceed')]", + "//a[contains(text(), 'Next')]", + "//a[contains(text(), 'Continue')]" + ]) + next_button.click + sleep(2) # Wait for page transition + + # Check all account checkboxes - try multiple approaches + checkboxes = @driver.find_elements(css: "input[type=\"checkbox\"][name=\"account\"]") + if checkboxes.empty? + checkboxes = @driver.find_elements(css: "input[type=\"checkbox\"]") + end + + checkboxes.each do |checkbox| + begin + @driver.execute_script("arguments[0].click();", checkbox) + rescue => e + # Try alternative click method + checkbox.click + end + end + + # Try to find Approve button with multiple selectors + approve_button = find_element_with_fallbacks(@driver, [ + "//button[contains(text(), 'Approve')]", + "//button[contains(text(), 'Authorize')]", + "//button[contains(text(), 'Allow')]", + "//button[contains(text(), 'Confirm')]", + "//input[@value='Approve']" + ]) + approve_button.click + + # Wait for redirect and extract code + wait.until { @driver.current_url.start_with?(@return_url) } + + current_url = @driver.current_url + code = current_url.split("code=")[1].split("&")[0] + + return code + rescue => e + # Take screenshot on failure + @driver.save_screenshot("consent_flow_failure.png") + puts "Current URL: #{@driver.current_url}" + puts "Page source: #{@driver.page_source}" + raise "Consent flow failed: #{e.message}" + end + end + + def find_element_with_fallbacks(driver, selectors) + selectors.each do |selector| + begin + if selector.start_with?("//") + # XPath selector + return driver.find_element(xpath: selector) + else + # CSS selector + return driver.find_element(css: selector) + end + rescue Selenium::WebDriver::Error::NoSuchElementError + next + end + end + raise "Could not find element with any of the selectors: #{selectors.join(', ')}" + end + + def get_api_with_user(username, password) + client = get_akoya_client_with_consent(username, password) + client.account_information + end + + def test_mikomo_500_internal_server_error + begin + api = get_api_with_user("mikomo_500", "mikomo_500") + api.get_accounts_info(@api_version, @provider_id, mode: Mode::RAW) + flunk("ErrorErrorException was not raised") + rescue ErrorErrorException => e + assert_equal 500, e.code + end + end + + def test_mikomo_501_subsystem_unavailable + begin + api = get_api_with_user("mikomo_501", "mikomo_501") + api.get_accounts_info(@api_version, @provider_id, mode: Mode::RAW) + flunk("ErrorErrorException was not raised") + rescue ErrorErrorException => e + assert_equal 501, e.code + end + end + + def test_mikomo_601_customer_not_found + begin + api = get_api_with_user("mikomo_601", "mikomo_601") + api.get_accounts_info(@api_version, @provider_id, mode: Mode::RAW) + flunk("ErrorErrorException was not raised") + rescue ErrorErrorException => e + assert_equal 601, e.code + end + end + + def test_mikomo_602_customer_not_authorized + begin + api = get_api_with_user("mikomo_602", "mikomo_602") + api.get_accounts_info(@api_version, @provider_id, mode: Mode::RAW) + flunk("ErrorErrorException was not raised") + rescue ErrorErrorException => e + assert_equal 602, e.code + end + end + + def test_mikomo_701_account_not_found + begin + api = get_api_with_user("mikomo_701", "mikomo_701") + api.get_accounts_info(@api_version, @provider_id, mode: Mode::RAW) + flunk("ErrorErrorException was not raised") + rescue ErrorErrorException => e + assert_equal 701, e.code + end + end + + def test_mikomo_702_invalid_start_or_end_date + begin + api = get_api_with_user("mikomo_702", "mikomo_702") + api.get_accounts_info(@api_version, @provider_id, mode: Mode::RAW) + flunk("ErrorErrorException was not raised") + rescue ErrorErrorException => e + assert_equal 702, e.code + end + end + + def test_mikomo_703_invalid_date_range + begin + api = get_api_with_user("mikomo_703", "mikomo_703") + api.get_accounts_info(@api_version, @provider_id, mode: Mode::RAW) + flunk("ErrorErrorException was not raised") + rescue ErrorErrorException => e + assert_equal 703, e.code + end + end + + def test_mikomo_704_account_type_not_supported + begin + api = get_api_with_user("mikomo_704", "mikomo_704") + api.get_accounts_info(@api_version, @provider_id, mode: Mode::RAW) + flunk("ApiException was not raised") + rescue ApiException => e + # For 704, we just verify ApiException is raised (no specific code check) + puts "Expected ApiException for 704: #{e}" + end + end +end \ No newline at end of file diff --git a/test/test_investments_controller.rb b/test/test_investments_controller.rb new file mode 100644 index 0000000..058e224 --- /dev/null +++ b/test/test_investments_controller.rb @@ -0,0 +1,35 @@ +require_relative 'controller_test_base' + +class InvestmentsControllerTest < ControllerTestBase + + def setup + super + @client = get_akoya_client_with_consent(@test_username, @test_password) + end + + def test_investment_accounts_returns_successful_response_with_data + result = @client.investments.get_accounts( + @api_version, + @provider_id, + mode: Mode::RAW + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + + def test_investment_taxlots_returns_successful_response_with_data + result = @client.investments.get_taxlots( + @api_version, + @provider_id, + @account_id, + @holding_id + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + +end \ No newline at end of file diff --git a/test/test_payments_controller.rb b/test/test_payments_controller.rb new file mode 100644 index 0000000..1774fd3 --- /dev/null +++ b/test/test_payments_controller.rb @@ -0,0 +1,22 @@ +require_relative 'controller_test_base' + +class PaymentsControllerTest < ControllerTestBase + + def setup + super + @client = get_akoya_client_with_consent(@test_username, @test_password) + end + + def test_payment_networks_returns_successful_response_with_data + result = @client.payments.payment_networks( + @api_version, + @provider_id, + @statement_account_id + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + +end \ No newline at end of file diff --git a/test/test_runner.rb b/test/test_runner.rb new file mode 100644 index 0000000..610d38a --- /dev/null +++ b/test/test_runner.rb @@ -0,0 +1,22 @@ +require 'minitest/autorun' +require_relative 'controller_test_base' +require_relative 'test_account_info_controller' +require_relative 'test_balances_controller' +require_relative 'test_customers_controller' +require_relative 'test_investments_controller' +require_relative 'test_payments_controller' +require_relative 'test_statements_controller' +require_relative 'test_tax_controller' +require_relative 'test_transactions_controller' +require_relative 'test_error_scenarios' + +# Test runner for Akoya Ruby SDK +# This file runs all the endpoint and auth tests + +if __FILE__ == $0 + puts "Running Akoya Ruby SDK Tests..." + puts "=" * 50 + + # Run all tests + Minitest::Test.run +end \ No newline at end of file diff --git a/test/test_statements_controller.rb b/test/test_statements_controller.rb new file mode 100644 index 0000000..a7ba6c2 --- /dev/null +++ b/test/test_statements_controller.rb @@ -0,0 +1,46 @@ +require_relative 'controller_test_base' + +class StatementsControllerTest < ControllerTestBase + + def setup + super + @client = get_akoya_client_with_consent(@test_username, @test_password) + end + + def test_statement_list_returns_successful_response_with_data + + start_time = DateTimeHelper.from_rfc3339('2020-03-30T04:00:01Z') + end_time = DateTimeHelper.from_rfc3339('2026-03-30T04:00:01Z') + offset = '0' + limit = 5 + + result = @client.statements.get_statement_list( + @statement_account_id, + @api_version, + @provider_id, + start_time: start_time, + end_time: end_time, + offset: offset, + limit: limit + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + + def test_statements_returns_successful_response_with_file + accept = Accept::ENUM_APPLICATIONPDF + result = @client.statements.get_statements( + @statement_account_id, + @api_version, + @provider_id, + @statement_id, + accept: accept + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + end + +end \ No newline at end of file diff --git a/test/test_tax_controller.rb b/test/test_tax_controller.rb new file mode 100644 index 0000000..b8b97a4 --- /dev/null +++ b/test/test_tax_controller.rb @@ -0,0 +1,33 @@ +require_relative 'controller_test_base' + +class TaxControllerTest < ControllerTestBase + + def setup + super + @client = get_akoya_client_with_consent(@tax_username, @tax_password) + end + + def test_tax_form_returns_successful_response_with_data + result = @client.tax_beta.get_tax_form( + @api_version, + @provider_id, + "tax2020" + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + + def test_tax_forms_search_returns_successful_response_with_data + result = @client.tax_beta.tax_forms_search( + @api_version, + @provider_id + ) + + assert result.success?, "API call should be successful" + assert_equal 200, result.status_code, "Status code should be 200" + assert result.data, "Response should contain data" + end + +end \ No newline at end of file diff --git a/test/test_transactions_controller.rb b/test/test_transactions_controller.rb new file mode 100644 index 0000000..b8ae9b2 --- /dev/null +++ b/test/test_transactions_controller.rb @@ -0,0 +1,48 @@ +require_relative 'controller_test_base' + +class TransactionsControllerTest < ControllerTestBase + + def setup + super + @client = get_akoya_client_with_consent(@test_username, @test_password) + end + + def test_transactions_pagination_yields_items_and_pages + + start_time = DateTimeHelper.from_rfc3339('2020-03-30T04:00:00Z') + end_time = DateTimeHelper.from_rfc3339('2026-03-30T04:00:00Z') + offset = '0' + limit = 50 + + result = @client.transactions.get_transactions( + @api_version, + @provider_id, + @account_id, + start_time: start_time, + end_time: end_time, + offset: offset, + limit: limit, + mode: Mode::RAW + ) + + # Assert that result responds to :each and :pages (i.e., is paginated) + assert_respond_to result, :each + assert_respond_to result, :pages + + # Iterate through items across all pages and count them + item_count = 0 + result.each do |item| + refute_nil item, "Expected item to not be nil" + item_count += 1 + end + assert item_count > 0, "No items fetched from transactions PagedIterable." + + # Iterate through pages and count them + page_count = 0 + result.pages.each do |page| + refute_nil page, "Expected page to not be nil" + page_count += 1 + end + assert page_count > 0, "No pages fetched from transactions PagedIterable." + end +end \ No newline at end of file