Files
sure/test/models/brex_item_test.rb
ghost 95f6451b39 feat(sync): add Brex provider connections (#1752)
* feat(sync): add Brex provider schema

Adds Brex item and account tables with per-family credentials, scoped upstream account uniqueness, encrypted token storage, and sanitized provider payload columns.

* feat(sync): add Brex provider core

Adds Brex item/account models, provider client and adapter support, family connection helpers, and provider enum registration for read-only Brex cash and card data.

* feat(sync): add Brex import pipeline

Adds Brex account discovery, linked-account sync, cash/card balance processors, transaction import, sanitized metadata handling, and idempotent provider entry processing.

* feat(sync): add Brex connection flows

Adds Mercury-style Brex connection management, explicit item-scoped account selection and linking, settings provider UI, account index visibility, localized copy, and per-item cache handling.

* test(sync): cover Brex provider workflows

Adds targeted coverage for Brex provider requests, adapter config, item/account guards, importer behavior, entry processing, and Mercury-style controller flows.

* fix(sync): align Brex API edge cases

Tightens Brex account fetching against the official card-account response shape, sends transaction start filters as RFC3339 date-times, and keeps provider error bodies out of user-facing messages while expanding provider client guard coverage.

* fix(sync): harden Brex provider integration

Restrict Brex API base URLs to official hosts, tighten account-selection UI behavior, and add tests for invalid credentials, cache scoping, and provider setup edge cases.

* test(sync): avoid Brex secret-shaped fixtures

* refactor(sync): extract Brex account flows

* fix(sync): address Brex provider review feedback

* fix(sync): address Brex review follow-ups

Move remaining Brex review cleanup into focused model behavior, tighten link/setup edge cases, localize summaries, and add regression coverage from CodeRabbit feedback.

Also records the security-review pass as no-findings after diff-scoped inspection and Brakeman validation.

* refactor(sync): split Brex account flow controllers

Route Brex account selection and setup actions through small namespaced controllers while keeping existing URLs and helpers stable.

Business flow remains in BrexItem::AccountFlow; the main Brex item controller now only handles connection CRUD, provider-panel rendering, destroy, and sync.

* fix(sync): address Brex CodeRabbit review

* fix(sync): address Brex follow-up review

* fix(sync): address Brex review follow-ups

* fix(sync): address Brex sync review findings

* fix(sync): polish Brex review copy and errors

* fix(sync): register Brex provider health

* fix(sync): polish Brex bank sync presentation

* fix(sync): address Brex review follow-ups

* fix(sync): tighten Brex setup params

* test(api): stabilize usage rate-limit window

* fix(sync): polish Brex setup flow nits

* fix(sync): harden Brex setup params

* fix(sync): finalize Brex review cleanup

---------

Signed-off-by: Juan José Mata <juanjo.mata@gmail.com>
Co-authored-by: Juan José Mata <juanjo.mata@gmail.com>
2026-05-13 18:13:48 +02:00

199 lines
6.0 KiB
Ruby

require "test_helper"
class BrexItemTest < ActiveSupport::TestCase
def setup
@brex_item = brex_items(:one)
end
test "fixture is valid" do
assert @brex_item.valid?
end
test "belongs to family" do
assert_equal families(:dylan_family), @brex_item.family
end
test "credentials_configured returns true when token present" do
assert @brex_item.credentials_configured?
end
test "credentials_configured returns false when token blank" do
@brex_item.token = nil
assert_not @brex_item.credentials_configured?
end
test "credentials_configured returns false when token is whitespace" do
@brex_item.token = " "
assert_not @brex_item.credentials_configured?
end
test "effective_base_url returns custom url when set" do
assert_equal "https://api-staging.brex.com", @brex_item.effective_base_url
end
test "effective_base_url returns default when base_url blank" do
@brex_item.base_url = nil
assert_equal "https://api.brex.com", @brex_item.effective_base_url
end
test "base_url accepts official Brex API roots" do
assert BrexItem.new(family: families(:empty), name: "Production", token: "token", base_url: "https://api.brex.com").valid?
assert BrexItem.new(family: families(:empty), name: "Staging", token: "token", base_url: "https://api-staging.brex.com").valid?
end
test "base_url normalizes official URL case and trailing slash" do
item = BrexItem.create!(
family: families(:empty),
name: "Normalized Brex",
token: "token",
base_url: " HTTPS://API.BREX.COM/ "
)
assert_equal "https://api.brex.com", item.base_url
end
test "token is stripped before validation and save" do
item = BrexItem.create!(
family: families(:empty),
name: "Token Normalized Brex",
token: " normalized_token ",
base_url: "https://api.brex.com"
)
assert_equal "normalized_token", item.token
end
test "token cannot be blanked on update" do
original_token = @brex_item.token
assert_raises(ActiveRecord::RecordInvalid) do
@brex_item.update!(token: " ")
end
assert_equal original_token, @brex_item.reload.token
assert_includes @brex_item.errors[:token], "can't be blank"
end
test "base_url rejects non-Brex hosts and endpoint paths" do
[
"http://api.brex.com",
"https://evil.example.test",
"https://localhost",
"https://127.0.0.1",
"https://10.0.0.1",
"https://api.brex.com.evil.example",
"https://api.brex.com@127.0.0.1",
"https://api.brex.com:444",
"https://api.brex.com/v2",
"https://api.brex.com?debug=true",
"//api.brex.com"
].each do |base_url|
item = BrexItem.new(family: families(:empty), name: "Invalid Brex", token: "token", base_url: base_url)
refute item.valid?, "Expected #{base_url.inspect} to be invalid"
assert_includes item.errors[:base_url], I18n.t("activerecord.errors.models.brex_item.attributes.base_url.official_hosts_only")
end
end
test "brex_provider returns Provider::Brex instance" do
provider = @brex_item.brex_provider
assert_instance_of Provider::Brex, provider
assert_equal @brex_item.token, provider.token
end
test "declares Brex token and raw payload as encrypted" do
skip "Encryption not configured" unless BrexItem.encryption_ready?
assert_includes BrexItem.encrypted_attributes.map(&:to_s), "token"
assert_includes BrexItem.encrypted_attributes.map(&:to_s), "raw_payload"
end
test "resolve for returns explicit credentialed item scoped to family" do
resolved = BrexItem.resolve_for(family: @brex_item.family, brex_item_id: " #{@brex_item.id} ")
assert_equal @brex_item, resolved
end
test "resolve for refuses explicit items without usable credentials" do
item = BrexItem.create!(
family: @brex_item.family,
name: "Blank Resolve Brex",
token: "temporary_token",
base_url: "https://api.brex.com"
)
item.update_column(:token, " ")
assert_nil BrexItem.resolve_for(family: @brex_item.family, brex_item_id: item.id)
end
test "resolve for does not select one item when multiple credentialed items exist" do
BrexItem.create!(
family: @brex_item.family,
name: "Second Resolve Brex",
token: "second_resolve_token",
base_url: "https://api.brex.com"
)
assert_nil BrexItem.resolve_for(family: @brex_item.family)
end
test "schema requires name and token" do
columns = BrexItem.columns.index_by(&:name)
assert_equal false, columns["name"].null
assert_equal false, columns["token"].null
end
test "brex_provider returns nil when credentials not configured" do
@brex_item.token = nil
assert_nil @brex_item.brex_provider
end
test "brex_provider returns nil when persisted base_url is not allowed" do
@brex_item.update_column(:base_url, "https://evil.example.test")
assert_nil @brex_item.reload.brex_provider
end
test "family credential check ignores blank and scheduled for deletion items" do
family = families(:empty)
blank_item = BrexItem.create!(
family: family,
name: "Blank Brex",
token: "temporary_token",
base_url: "https://api-staging.brex.com"
)
blank_item.update_column(:token, "")
whitespace_item = BrexItem.create!(
family: family,
name: "Whitespace Brex",
token: "temporary_token",
base_url: "https://api-staging.brex.com"
)
whitespace_item.update_column(:token, " ")
deleted_item = BrexItem.create!(
family: family,
name: "Deleted Brex",
token: "deleted_token",
base_url: "https://api-staging.brex.com",
scheduled_for_deletion: true
)
refute family.has_brex_credentials?
whitespace_item.update_column(:token, "configured_token")
assert family.has_brex_credentials?
whitespace_item.update_column(:token, " ")
deleted_item.update!(scheduled_for_deletion: false)
assert family.has_brex_credentials?
end
test "syncer returns BrexItem::Syncer instance" do
syncer = @brex_item.send(:syncer)
assert_instance_of BrexItem::Syncer, syncer
end
end