From 325084e342552f51caf46001e56afe0decbfc6ba Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Mon, 11 May 2026 15:14:13 -0700 Subject: [PATCH] fix(api): include disabled-account transaction history (#1723) * fix(api): include disabled-account transaction history * fix(api): hide pending deletion transaction history --- .../api/v1/transactions_controller.rb | 9 +- docs/api/openapi.yaml | 7 +- spec/requests/api/v1/transactions_spec.rb | 3 +- .../api/v1/transactions_controller_test.rb | 117 +++++++++++++++++- 4 files changed, 130 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/v1/transactions_controller.rb b/app/controllers/api/v1/transactions_controller.rb index 5dd65aba3..f208d83b2 100644 --- a/app/controllers/api/v1/transactions_controller.rb +++ b/app/controllers/api/v1/transactions_controller.rb @@ -10,8 +10,11 @@ class Api::V1::TransactionsController < Api::V1::BaseController def index family = current_resource_owner.family - accessible_account_ids = family.accounts.accessible_by(current_resource_owner).select(:id) - transactions_query = family.transactions.visible + accessible_account_ids = family.accounts + .accessible_by(current_resource_owner) + .where.not(status: "pending_deletion") + .select(:id) + transactions_query = family.transactions .joins(:entry).where(entries: { account_id: accessible_account_ids }) # Apply filters @@ -198,6 +201,8 @@ end private def set_transaction + raise ActiveRecord::RecordNotFound unless valid_uuid?(params[:id]) + family = current_resource_owner.family @transaction = family.transactions .joins(entry: :account) diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index ab6a787bb..ebc4c4425 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -6505,6 +6505,8 @@ paths: - Transactions security: - apiKeyAuth: [] + description: Returns global ledger history for accessible accounts, including + disabled accounts but excluding accounts pending deletion. parameters: - name: page in: query @@ -6716,10 +6718,11 @@ paths: parameters: - name: id in: path - required: true - description: Transaction ID schema: type: string + format: uuid + required: true + description: Transaction ID get: summary: Retrieve a transaction tags: diff --git a/spec/requests/api/v1/transactions_spec.rb b/spec/requests/api/v1/transactions_spec.rb index d57a40b6a..2a346906c 100644 --- a/spec/requests/api/v1/transactions_spec.rb +++ b/spec/requests/api/v1/transactions_spec.rb @@ -89,6 +89,7 @@ RSpec.describe 'API V1 Transactions', type: :request do get 'List transactions' do tags 'Transactions' security [ { apiKeyAuth: [] } ] + description 'Returns global ledger history for accessible accounts, including disabled accounts but excluding accounts pending deletion.' produces 'application/json' parameter name: :page, in: :query, type: :integer, required: false, description: 'Page number (default: 1)' @@ -268,7 +269,7 @@ RSpec.describe 'API V1 Transactions', type: :request do end path '/api/v1/transactions/{id}' do - parameter name: :id, in: :path, type: :string, required: true, description: 'Transaction ID' + parameter name: :id, in: :path, schema: { type: :string, format: :uuid }, required: true, description: 'Transaction ID' get 'Retrieve a transaction' do tags 'Transactions' diff --git a/test/controllers/api/v1/transactions_controller_test.rb b/test/controllers/api/v1/transactions_controller_test.rb index 002c80c30..1f5123d9e 100644 --- a/test/controllers/api/v1/transactions_controller_test.rb +++ b/test/controllers/api/v1/transactions_controller_test.rb @@ -66,6 +66,44 @@ class Api::V1::TransactionsControllerTest < ActionDispatch::IntegrationTest end end + test "should include disabled account transactions in index history" do + disabled_transaction = create_disabled_account_transaction(name: "Closed Account Grocery") + + get api_v1_transactions_url, headers: api_headers(@api_key) + assert_response :success + + response_data = JSON.parse(response.body) + transaction_ids = response_data["transactions"].map { |transaction| transaction["id"] } + assert_includes transaction_ids, disabled_transaction.id + end + + test "should exclude pending deletion account transactions from index history" do + pending_deletion_transaction = create_account_transaction( + status: "pending_deletion", + name: "Pending Delete Account Grocery" + ) + + get api_v1_transactions_url, headers: api_headers(@api_key) + assert_response :success + + response_data = JSON.parse(response.body) + transaction_ids = response_data["transactions"].map { |transaction| transaction["id"] } + assert_not_includes transaction_ids, pending_deletion_transaction.id + end + + test "should filter disabled account transactions by account_id" do + disabled_transaction = create_disabled_account_transaction(name: "Closed Account Filter") + disabled_account = disabled_transaction.entry.account + + get api_v1_transactions_url, + params: { account_id: disabled_account.id }, + headers: api_headers(@api_key) + assert_response :success + + response_data = JSON.parse(response.body) + assert_equal [ disabled_transaction.id ], response_data["transactions"].map { |transaction| transaction["id"] } + end + test "should filter transactions by date range" do start_date = 1.month.ago.to_date end_date = Date.current @@ -83,6 +121,22 @@ class Api::V1::TransactionsControllerTest < ActionDispatch::IntegrationTest end end + test "should filter disabled account transactions by date range" do + disabled_transaction = create_disabled_account_transaction( + name: "Closed Account Date Range", + date: Date.current - 3.days + ) + + get api_v1_transactions_url, + params: { start_date: Date.current - 4.days, end_date: Date.current - 2.days }, + headers: api_headers(@api_key) + assert_response :success + + response_data = JSON.parse(response.body) + transaction_ids = response_data["transactions"].map { |transaction| transaction["id"] } + assert_includes transaction_ids, disabled_transaction.id + end + test "should search transactions" do # Create a transaction with a specific name for testing entry = @account.entries.create!( @@ -103,6 +157,19 @@ class Api::V1::TransactionsControllerTest < ActionDispatch::IntegrationTest assert_not_nil found_transaction, "Should find the coffee transaction" end + test "should search disabled account transactions" do + disabled_transaction = create_disabled_account_transaction(name: "Closed Account Coffee") + + get api_v1_transactions_url, + params: { search: "Closed Account Coffee" }, + headers: api_headers(@api_key) + assert_response :success + + response_data = JSON.parse(response.body) + found_transaction = response_data["transactions"].find { |transaction| transaction["id"] == disabled_transaction.id } + assert_not_nil found_transaction, "Should find disabled account transactions in global history search" + end + test "should paginate transactions" do get api_v1_transactions_url, params: { page: 1, per_page: 5 }, @@ -144,9 +211,33 @@ class Api::V1::TransactionsControllerTest < ActionDispatch::IntegrationTest assert_response :success end - test "should return 404 for non-existent transaction" do + test "should show disabled account transaction" do + disabled_transaction = create_disabled_account_transaction(name: "Closed Account Show") + + get api_v1_transaction_url(disabled_transaction), headers: api_headers(@api_key) + assert_response :success + + response_data = JSON.parse(response.body) + assert_equal disabled_transaction.id, response_data["id"] + assert_equal disabled_transaction.entry.account_id, response_data["account"]["id"] + end + + test "should return 404 for valid missing transaction id" do + get api_v1_transaction_url(SecureRandom.uuid), headers: api_headers(@api_key) + assert_response :not_found + + response_data = JSON.parse(response.body) + assert_equal "not_found", response_data["error"] + assert_equal "Transaction not found", response_data["message"] + end + + test "should return 404 for malformed id" do get api_v1_transaction_url(999999), headers: api_headers(@api_key) assert_response :not_found + + response_data = JSON.parse(response.body) + assert_equal "not_found", response_data["error"] + assert_equal "Transaction not found", response_data["message"] end test "should reject show request without API key" do @@ -689,4 +780,28 @@ end "non-income transactions should have non-positive signed_amount_cents" end end + + def create_disabled_account_transaction(name:, date: Date.current) + create_account_transaction(status: "disabled", name: name, date: date) + end + + def create_account_transaction(status:, name:, date: Date.current) + account = @family.accounts.create!( + name: "#{status.titleize} Checking #{SecureRandom.hex(4)}", + balance: 0, + currency: "USD", + status: status, + accountable: Depository.new + ) + + entry = account.entries.create!( + name: name, + amount: 12.34, + currency: "USD", + date: date, + entryable: Transaction.new + ) + + entry.transaction + end end