Files
sure/test/models/provider/sophtron_test.rb
Juan José Mata 81cdccb768 [codex] Complete Sophtron account mapping (#1698)
* Complete Sophtron account mapping

* Clarify Sophtron login challenge flow

* Add Sophtron connection UI timeout

* Treat Sophtron timeout jobs as failed

* Reset failed Sophtron connection state

* Handle stale Sophtron connection jobs

* Advance Sophtron polling timeout

* Shorten Sophtron connection timeout

* Fix Sophtron modal polling updates

* Stabilize Sophtron MFA polling

* Give Sophtron OTP challenges more time

* Clarify Sophtron institution login failures

* Extend Sophtron polling during login progress

* Probe Sophtron accounts after completed MFA step

* Align Sophtron dialogs with design system

* Start Sophtron initial load after linking accounts

* Fix Sophtron initial transaction load

* Fail Sophtron sync without institution connection

* Fix tests

* Wrap Sophtron account linking in transaction

* Wrap Sophtron provider responses

* Fix Sophtron MFA security tests

* Guard Sophtron MFA challenge arrays

* Respect Sophtron initial load window

* Use unique Sophtron MFA answer field ids

* Address Sophtron review follow-ups

* Fix Sophtron transaction sync refresh

* Avoid blocking Sophtron refresh polling

* Move Sophtron account helpers to model

* Keep Sophtron grouping provider-level

* Start new Sophtron institution links

* Isolate Sophtron institution connections

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
2026-05-08 15:15:23 +02:00

226 lines
7.3 KiB
Ruby

require "test_helper"
class Provider::SophtronTest < ActiveSupport::TestCase
setup do
@access_key = Base64.strict_encode64("secret-key")
@provider = Provider::Sophtron.new("developer-user", @access_key)
end
test "builds FIApiAUTH header from last path segment only" do
auth_header = @provider.auth_header_for("POST", "/api/UserInstitution/CreateUserInstitution")
expected_auth_path = "/createuserinstitution"
expected_signature = Base64.strict_encode64(
OpenSSL::HMAC.digest(
OpenSSL::Digest.new("sha256"),
"secret-key",
"POST\n#{expected_auth_path}"
)
)
assert_equal "FIApiAUTH:developer-user:#{expected_signature}:#{expected_auth_path}", auth_header
end
test "lists v2 customers" do
stub_request(:get, "https://api.sophtron.com/api/v2/customers")
.to_return(status: 200, body: [ { CustomerID: "cust-1", CustomerName: "Sure family 1" } ].to_json)
customers = provider_data(@provider.list_customers)
assert_equal 1, customers.length
assert_equal "cust-1", customers.first[:CustomerID]
end
test "creates customer with documented body" do
stub_request(:post, "https://api.sophtron.com/api/v2/customers")
.with(body: { UniqueID: "sure-family-1", Name: "Sure family 1", Source: "Sure" }.to_json)
.to_return(status: 200, body: { CustomerID: "cust-1", CustomerName: "Sure family 1" }.to_json)
customer = provider_data(@provider.create_customer(unique_id: "sure-family-1", name: "Sure family 1", source: "Sure"))
assert_equal "cust-1", customer[:CustomerID]
end
test "health check auth accepts empty success body" do
stub_request(:get, "https://api.sophtron.com/api/Institution/HealthCheckAuth")
.to_return(status: 200, body: "")
assert_equal({}, provider_data(@provider.health_check_auth))
end
test "health check auth accepts non json success body" do
stub_request(:get, "https://api.sophtron.com/api/Institution/HealthCheckAuth")
.to_return(status: 200, body: "OK")
assert_equal "OK", provider_data(@provider.health_check_auth)
end
test "creates user institution with documented V1 body" do
stub_request(:post, "https://api.sophtron.com/api/UserInstitution/CreateUserInstitution")
.with(body: {
UserID: "developer-user",
InstitutionID: "inst-1",
UserName: "bank-user",
Password: "bank-pass",
PIN: ""
}.to_json)
.to_return(status: 200, body: { JobID: "job-1", UserInstitutionID: "ui-1" }.to_json)
response = provider_data(@provider.create_user_institution(
institution_id: "inst-1",
username: "bank-user",
password: "bank-pass"
))
assert_equal "job-1", response[:JobID]
assert_equal "ui-1", response[:UserInstitutionID]
end
test "classifies Sophtron timeout job as failed" do
job = {
SuccessFlag: false,
LastStep: "LogInPanel",
LastStatus: "Timeout"
}
assert Provider::Sophtron.job_failed?(job)
end
test "classifies completed job without failure flag as completed" do
job = {
LastStep: "TokenInput",
LastStatus: "Completed"
}
assert Provider::Sophtron.job_completed?(job)
assert_not Provider::Sophtron.job_failed?(job)
end
test "does not classify completed job with failure flag as completed" do
job = {
SuccessFlag: false,
LastStep: "LogInPanel",
LastStatus: "Completed"
}
assert Provider::Sophtron.job_failed?(job)
assert_not Provider::Sophtron.job_completed?(job)
end
test "classifies token input prompt as requiring input" do
job = {
TokenInputName: "Token",
LastStep: "TokenInput",
LastStatus: "Started"
}
assert Provider::Sophtron.job_requires_input?(job)
end
test "does not classify submitted token as requiring input" do
job = {
TokenInputName: "Token",
TokenInput: "123456",
LastStep: "TokenInput",
LastStatus: "Started"
}
assert_not Provider::Sophtron.job_requires_input?(job)
end
test "submits security answers as JSON array string" do
stub_request(:post, "https://api.sophtron.com/api/Job/UpdateJobSecurityAnswer")
.with(body: { JobID: "job-1", SecurityAnswer: [ "blue" ].to_json }.to_json)
.to_return(status: 200, body: "")
assert_equal({}, provider_data(@provider.update_job_security_answer("job-1", [ "blue" ])))
end
test "fetches transactions by transaction date with documented body" do
stub_request(:post, "https://api.sophtron.com/api/Transaction/GetTransactionsByTransactionDate")
.with(body: {
AccountID: "acct-1",
StartDate: "2026-01-01",
EndDate: "2026-02-01"
}.to_json)
.to_return(status: 200, body: [
{
TransactionID: "tx-1",
Amount: "-12.34",
TransactionDate: "2026-01-15",
Description: "CHECKCARD 1234 Coffee Shop NY"
}
].to_json)
result = provider_data(@provider.get_account_transactions("acct-1", start_date: Date.new(2026, 1, 1), end_date: Date.new(2026, 2, 1)))
transaction = result[:transactions].first
assert_equal "tx-1", transaction[:id]
assert_equal "acct-1", transaction[:accountId]
assert_equal "-12.34", transaction[:amount]
assert_equal "2026-01-15", transaction[:date]
end
test "normalizes TransactionId transaction identifiers" do
stub_request(:post, "https://api.sophtron.com/api/Transaction/GetTransactionsByTransactionDate")
.to_return(status: 200, body: [
{
TransactionId: "tx-1",
Amount: "-12.34",
TransactionDate: "2026-01-15",
Description: "CHECKCARD 1234 Coffee Shop NY"
}
].to_json)
result = provider_data(@provider.get_account_transactions("acct-1", start_date: Date.new(2026, 1, 1), end_date: Date.new(2026, 2, 1)))
assert_equal "tx-1", result[:transactions].first[:id]
end
test "maps user institution accounts from V1 fields" do
stub_request(:post, "https://api.sophtron.com/api/UserInstitution/GetUserInstitutionAccounts")
.with(body: { UserInstitutionID: "ui-1" }.to_json)
.to_return(status: 200, body: [
{
AccountID: "acct-1",
AccountName: "Checking",
AccountType: "checking",
AccountBalance: "123.45",
AccountNumber: "00001234"
}
].to_json)
result = provider_data(@provider.get_accounts("ui-1"))
account = result[:accounts].first
assert_equal "acct-1", account[:account_id]
assert_equal "Checking", account[:account_name]
assert_equal "123.45", account[:balance]
assert_equal "****1234", account[:account_number_mask]
end
test "empty success body parses as empty hash" do
stub_request(:post, "https://api.sophtron.com/api/Job/UpdateJobCaptcha")
.to_return(status: 200, body: "")
assert_equal({}, provider_data(@provider.update_job_captcha("job-1", "abc123")))
end
test "raises typed error on unauthorized response" do
stub_request(:get, "https://api.sophtron.com/api/Institution/HealthCheckAuth")
.to_return(status: 401, body: "bad auth")
response = @provider.health_check_auth
assert_not response.success?
assert_instance_of Provider::Sophtron::Error, response.error
assert_equal :unauthorized, response.error.error_type
end
private
def provider_data(response)
assert response.success?
response.data
end
end