mirror of
https://github.com/we-promise/sure.git
synced 2026-05-25 13:34:58 +00:00
* 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>
199 lines
6.0 KiB
Ruby
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
|