Skip to content

Commit

Permalink
Admin creates csv upload
Browse files Browse the repository at this point in the history
  • Loading branch information
jonallured committed Feb 18, 2024
1 parent 6411a78 commit bd90e69
Show file tree
Hide file tree
Showing 14 changed files with 243 additions and 0 deletions.
23 changes: 23 additions & 0 deletions app/controllers/admin/csv_uploads_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class Admin::CsvUploadsController < ApplicationController
expose(:csv_upload)

def create
if csv_upload.save
ParseCsvUploadJob.perform_later(csv_upload.id)
flash.notice = "CSV Upload successfully created"
redirect_to admin_csv_upload_path(csv_upload)
else
flash.alert = csv_upload.errors.full_messages.join(",")
redirect_to new_admin_csv_upload_path
end
end

private

def csv_upload_params
safe_params = params.require(:csv_upload).permit(:parser_class_name)
safe_params[:original_filename] = params[:file].original_filename
safe_params[:data] = params[:file].read
safe_params
end
end
9 changes: 9 additions & 0 deletions app/jobs/parse_csv_upload_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class ParseCsvUploadJob < ApplicationJob
def perform(csv_upload_id)
csv_upload = CsvUpload.find_by(id: csv_upload_id)
return unless csv_upload

parser = csv_upload.parser_class_name.constantize
parser.parse(csv_upload)
end
end
8 changes: 8 additions & 0 deletions app/models/csv_upload.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
require "csv"

class CsvUpload < ApplicationRecord
validates_presence_of :data, :original_filename, :parser_class_name

def parsed_data
CSV.parse(data)
rescue CSV::MalformedCSVError
nil
end
end
21 changes: 21 additions & 0 deletions app/parsers/wells_fargo_parser.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class WellsFargoParser
def self.parse(csv_upload)
table = csv_upload.parsed_data
return unless table

wf_checking = FinancialAccount.wf_checking

table.each do |row|
amount_cents = (row[1].to_r * 100).to_i
posted_on = Date.strptime(row[0], "%m/%d/%Y")

attrs = {
amount_cents: amount_cents,
description: row[4],
posted_on: posted_on
}

wf_checking.financial_transactions.create!(attrs)
end
end
end
8 changes: 8 additions & 0 deletions app/views/admin/csv_uploads/new.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
%h1 New CSV Upload

= form_with model: [:admin, csv_upload], multipart: true do |form|
%select#csv_upload_parser_class_name(name="csv_upload[parser_class_name]" required="true")
%option(value="" disabled="true" selected hidden) pick parser
%option(value="WellsFargoParser") WellsFargoParser
%input#file_picker(required="true" type="file" name="file")
= form.button "create"
1 change: 1 addition & 0 deletions app/views/admin/csv_uploads/show.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
%h1 CSV Upload #{csv_upload.id}
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

namespace :admin do
resources :books, only: %i[create edit new update]
resources :csv_uploads, only: %i[create new show]
resources :gift_ideas
resources :hooks, only: %i[create edit index]
resources :post_bin_requests, only: %i[index show]
Expand Down
Empty file added spec/csv_files/empty.csv
Empty file.
1 change: 1 addition & 0 deletions spec/csv_files/one_wf_transaction.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"12/29/2023","0.89","","","random fee"
41 changes: 41 additions & 0 deletions spec/jobs/parse_csv_upload_job_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
require "rails_helper"

describe ParseCsvUploadJob do
let(:parser_class_name) { "WellsFargoParser" }

let(:csv_upload) do
FactoryBot.create(:csv_upload, parser_class_name: parser_class_name)
end

let(:csv_upload_id) { csv_upload.id }

context "with an invalid csv_upload_id" do
let(:csv_upload_id) { "invalid" }

it "exits early" do
job = ParseCsvUploadJob.new
expect do
job.perform(csv_upload_id)
end.to_not raise_error
end
end

context "with a CsvUpload that has an invalid parser_class_name" do
let(:parser_class_name) { "InvalidParser" }

it "raises an error" do
job = ParseCsvUploadJob.new
expect do
job.perform(csv_upload_id)
end.to raise_error(NameError)
end
end

context "with a valid CsvUpload" do
it "calls the parser with that CsvUpload" do
job = ParseCsvUploadJob.new
expect(WellsFargoParser).to receive(:parse).with(csv_upload)
job.perform(csv_upload_id)
end
end
end
20 changes: 20 additions & 0 deletions spec/models/csv_upload_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,24 @@
end
end
end

describe "#parsed_data" do
let(:csv_upload) { FactoryBot.create(:csv_upload, data: data) }

context "with data that fails to parse" do
let(:data) { '"invalid' }

it "returns nil" do
expect(csv_upload.parsed_data).to eq nil
end
end

context "with data that parses" do
let(:data) { "abc,123,true" }

it "returns that parsed data" do
expect(csv_upload.parsed_data).to eq [["abc", "123", "true"]]
end
end
end
end
62 changes: 62 additions & 0 deletions spec/parsers/wells_fargo_parser_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require "rails_helper"

describe WellsFargoParser do
describe ".parse" do
let!(:wf_checking) { FactoryBot.create(:wf_checking) }
let(:csv_upload) { FactoryBot.create(:csv_upload, data: data) }

context "with nil parsed data" do
let(:data) { '"invalid' }

it "exits early" do
expect do
WellsFargoParser.parse(csv_upload)
end.to_not raise_error
end
end

context "with a properly formatted transaction" do
let(:data) { "12/29/2023,-0.89,,,random fee" }

it "creates a transaction for the Wells Fargo Checking account" do
WellsFargoParser.parse(csv_upload)
expect(wf_checking.financial_transactions.count).to eq 1

financial_transaction = wf_checking.financial_transactions.last
expect(financial_transaction.amount_cents).to eq(-89)
expect(financial_transaction.description).to eq "random fee"
expect(financial_transaction.posted_on).to eq Date.parse("2023-12-29")
end
end

context "with a few transactions" do
let(:data) do
<<~EOL
12/01/2023,100.00,ignore,,check deposit
12/07/2023,-77.77,,ignore,groceries
12/29/2023,-0.89,,,random fee
EOL
end

it "creates those transactions on the Wells Fargo Checking account" do
WellsFargoParser.parse(csv_upload)

ordered_transactions = wf_checking.financial_transactions.order(:posted_on)
expect(ordered_transactions.count).to eq 3

amounts = ordered_transactions.pluck(:amount_cents)
expect(amounts).to eq [100_00, -77_77, -89]

dates = ordered_transactions.pluck(:posted_on)
expect(dates.map(&:to_s)).to eq ["2023-12-01", "2023-12-07", "2023-12-29"]

descriptions = ordered_transactions.pluck(:description)
expect(descriptions).to eq [
"check deposit",
"groceries",
"random fee"
]
end
end
end
end
4 changes: 4 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
config.infer_spec_type_from_file_location!
config.use_transactional_fixtures = true

config.before do
ActiveJob::Base.queue_adapter = :test
end

config.before(:each, type: :system) do
Selenium::WebDriver.logger.ignore(:deprecations)
Capybara.server = :puma, {Silent: true}
Expand Down
44 changes: 44 additions & 0 deletions spec/system/csv_uploads/admin_creates_csv_upload_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require "rails_helper"

describe "Admin creates CsvUpload" do
include_context "admin password matches"

scenario "without a parser" do
visit "/admin/csv_uploads/new"
click_on "create"
expect(CsvUpload.count).to eq 0
select = page.find("#csv_upload_parser_class_name")
error_message = select.native.attribute("validationMessage")
expect(error_message).to eq "Please select an item in the list."
end

scenario "without a file" do
visit "/admin/csv_uploads/new"
select "WellsFargoParser", from: "csv_upload_parser_class_name"
click_on "create"
expect(CsvUpload.count).to eq 0
file_input = page.find("#file_picker")
error_message = file_input.native.attribute("validationMessage")
expect(error_message).to eq "Please select a file."
end

scenario "with an empty file" do
visit "/admin/csv_uploads/new"
select "WellsFargoParser", from: "csv_upload_parser_class_name"
attach_file "file", "spec/csv_files/empty.csv"
click_on "create"
expect(CsvUpload.count).to eq 0
expect(page).to have_content "Data can't be blank"
end

scenario "with valid financial transactions" do
visit "/admin/csv_uploads/new"
select "WellsFargoParser", from: "csv_upload_parser_class_name"
attach_file "file", "spec/csv_files/one_wf_transaction.csv"
click_on "create"
expect(page).to have_content "CSV Upload successfully created"
csv_upload = CsvUpload.last
expect(page).to have_content "CSV Upload #{csv_upload.id}"
expect(ParseCsvUploadJob).to have_been_enqueued
end
end

0 comments on commit bd90e69

Please sign in to comment.