diff --git a/app/controllers/api/v1/imports_controller.rb b/app/controllers/api/v1/imports_controller.rb index b3b048bba..e6b78f8bd 100644 --- a/app/controllers/api/v1/imports_controller.rb +++ b/app/controllers/api/v1/imports_controller.rb @@ -52,8 +52,7 @@ class Api::V1::ImportsController < Api::V1::BaseController type = "TransactionImport" unless Import::TYPES.include?(type) # 2. Build the import object with permitted config attributes - @import = family.imports.build(import_config_params) - @import.type = type + @import = family.imports.build(import_config_params.merge(type: type)) @import.account_id = params[:account_id] if params[:account_id].present? # 3. Attach the uploaded file if present (with validation) diff --git a/test/controllers/api/v1/imports_controller_test.rb b/test/controllers/api/v1/imports_controller_test.rb index ec01ca7ba..fb5ac9bc2 100644 --- a/test/controllers/api/v1/imports_controller_test.rb +++ b/test/controllers/api/v1/imports_controller_test.rb @@ -2,15 +2,34 @@ require "test_helper" class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest setup do - @family = families(:dylan_family) @user = users(:family_admin) + @family = @user.family @account = accounts(:depository) @import = imports(:transaction) - @token = valid_token_for(@user) + + @user.api_keys.active.destroy_all + + @api_key = ApiKey.create!( + user: @user, + name: "Test Read-Write Key", + scopes: [ "read_write" ], + display_key: "test_rw_#{SecureRandom.hex(8)}" + ) + + @read_only_api_key = ApiKey.create!( + user: @user, + name: "Test Read-Only Key", + scopes: [ "read" ], + display_key: "test_ro_#{SecureRandom.hex(8)}", + source: "mobile" + ) + + Redis.new.del("api_rate_limit:#{@api_key.id}") + Redis.new.del("api_rate_limit:#{@read_only_api_key.id}") end test "should list imports" do - get api_v1_imports_url, headers: { Authorization: "Bearer #{@token}" } + get api_v1_imports_url, headers: api_headers(@api_key) assert_response :success json_response = JSON.parse(response.body) @@ -19,7 +38,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest end test "should show import" do - get api_v1_import_url(@import), headers: { Authorization: "Bearer #{@token}" } + get api_v1_import_url(@import), headers: api_headers(@api_key) assert_response :success json_response = JSON.parse(response.body) @@ -39,7 +58,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest name_col_label: "name", account_id: @account.id }, - headers: { Authorization: "Bearer #{@token}" } + headers: api_headers(@api_key) end assert_response :created @@ -62,7 +81,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest name_col_label: "name", account_id: @account.id }, - headers: { Authorization: "Bearer #{@token}" } + headers: api_headers(@api_key) end assert_response :created @@ -74,6 +93,44 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest assert_equal "-10.00", import.rows.first.amount # Normalized end + test "should instantiate RuleImport before generating rows" do + @family.categories.create!( + name: "Groceries", + color: "#407706", + lucide_icon: "shopping-basket" + ) + + csv_content = <<~CSV + name,resource_type,active,effective_date,conditions,actions + "Categorize groceries","transaction",true,2024-01-01,"[{""condition_type"":""transaction_name"",""operator"":""like"",""value"":""grocery""}]","[{""action_type"":""set_transaction_category"",""value"":""Groceries""}]" + CSV + + assert_difference([ "Import.count", "Import::Row.count" ], 1) do + post api_v1_imports_url, + params: { + type: "RuleImport", + raw_file_content: csv_content, + col_sep: "," + }, + headers: api_headers(@api_key) + end + + assert_response :created + + json_response = JSON.parse(response.body) + import = Import.find(json_response["data"]["id"]) + row = import.rows.first + + assert_instance_of RuleImport, import + assert_equal 1, import.rows_count + assert_equal "Categorize groceries", row.name + assert_equal "transaction", row.resource_type + assert_equal true, row.active + assert_equal "2024-01-01", row.effective_date + assert_equal '[{"condition_type":"transaction_name","operator":"like","value":"grocery"}]', row.conditions + assert_equal '[{"action_type":"set_transaction_category","value":"Groceries"}]', row.actions + end + test "should create import and auto-publish when configured and requested" do csv_content = "date,amount,name\n2023-01-01,-10.00,Test Transaction" @@ -88,7 +145,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest date_format: "%Y-%m-%d", publish: "true" }, - headers: { Authorization: "Bearer #{@token}" } + headers: api_headers(@api_key) end assert_response :created @@ -108,7 +165,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest raw_file_content: csv_content, account_id: other_account.id }, - headers: { Authorization: "Bearer #{@token}" } + headers: api_headers(@api_key) assert_response :unprocessable_entity json_response = JSON.parse(response.body) @@ -125,7 +182,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest assert_no_difference("Import.count") do post api_v1_imports_url, params: { file: large_file }, - headers: { Authorization: "Bearer #{@token}" } + headers: api_headers(@api_key) end assert_response :unprocessable_entity @@ -143,7 +200,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest assert_no_difference("Import.count") do post api_v1_imports_url, params: { file: invalid_file }, - headers: { Authorization: "Bearer #{@token}" } + headers: api_headers(@api_key) end assert_response :unprocessable_entity @@ -163,7 +220,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest assert_no_difference("Import.count") do post api_v1_imports_url, params: { raw_file_content: large_content }, - headers: { Authorization: "Bearer #{@token}" } + headers: api_headers(@api_key) end assert_response :unprocessable_entity @@ -191,7 +248,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest name_col_label: "name", account_id: @account.id }, - headers: { Authorization: "Bearer #{@token}" } + headers: api_headers(@api_key) end assert_response :created @@ -199,8 +256,7 @@ class Api::V1::ImportsControllerTest < ActionDispatch::IntegrationTest private - def valid_token_for(user) - application = Doorkeeper::Application.create!(name: "Test App", redirect_uri: "urn:ietf:wg:oauth:2.0:oob", scopes: "read read_write") - Doorkeeper::AccessToken.create!(application: application, resource_owner_id: user.id, scopes: "read read_write").token + def api_headers(api_key) + { "X-Api-Key" => api_key.display_key } end end