3 changed files with 328 additions and 0 deletions
@ -0,0 +1,70 @@ |
|||||||
|
require "csv" |
||||||
|
|
||||||
|
class BulkUpload::Sales::Year2022::CsvParser |
||||||
|
MIN_COLUMNS = 125 |
||||||
|
MAX_COLUMNS = 126 |
||||||
|
|
||||||
|
attr_reader :path |
||||||
|
|
||||||
|
def initialize(path:) |
||||||
|
@path = path |
||||||
|
end |
||||||
|
|
||||||
|
def row_offset |
||||||
|
with_headers? ? 5 : 0 |
||||||
|
end |
||||||
|
|
||||||
|
def col_offset |
||||||
|
with_headers? ? 1 : 0 |
||||||
|
end |
||||||
|
|
||||||
|
def cols |
||||||
|
@cols ||= ("A".."DV").to_a |
||||||
|
end |
||||||
|
|
||||||
|
def row_parsers |
||||||
|
@row_parsers ||= body_rows.map do |row| |
||||||
|
stripped_row = row[col_offset..] |
||||||
|
headers = ("field_1".."field_125").to_a |
||||||
|
hash = Hash[headers.zip(stripped_row)] |
||||||
|
|
||||||
|
BulkUpload::Lettings::Year2022::RowParser.new(hash) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def body_rows |
||||||
|
rows[row_offset..] |
||||||
|
end |
||||||
|
|
||||||
|
def rows |
||||||
|
@rows ||= CSV.parse(normalised_string, row_sep:) |
||||||
|
end |
||||||
|
|
||||||
|
def column_for_field(field) |
||||||
|
cols[headers.find_index(field) + col_offset] |
||||||
|
end |
||||||
|
|
||||||
|
private |
||||||
|
|
||||||
|
def headers |
||||||
|
@headers ||= ("field_1".."field_125").to_a |
||||||
|
end |
||||||
|
|
||||||
|
def with_headers? |
||||||
|
rows[0][0]&.match?(/\D+/) |
||||||
|
end |
||||||
|
|
||||||
|
def row_sep |
||||||
|
"\n" |
||||||
|
end |
||||||
|
|
||||||
|
def normalised_string |
||||||
|
return @normalised_string if @normalised_string |
||||||
|
|
||||||
|
@normalised_string = File.read(path, encoding: "bom|utf-8") |
||||||
|
@normalised_string.gsub!("\r\n", "\n") |
||||||
|
@normalised_string.scrub!("") |
||||||
|
|
||||||
|
@normalised_string |
||||||
|
end |
||||||
|
end |
||||||
@ -0,0 +1,97 @@ |
|||||||
|
require "rails_helper" |
||||||
|
|
||||||
|
RSpec.describe BulkUpload::Sales::Year2022::CsvParser do |
||||||
|
subject(:service) { described_class.new(path:) } |
||||||
|
|
||||||
|
let(:path) { file_fixture("2022_23_sales_bulk_upload.csv") } |
||||||
|
|
||||||
|
context "when parsing csv with headers" do |
||||||
|
it "returns correct offsets" do |
||||||
|
expect(service.row_offset).to eq(5) |
||||||
|
expect(service.col_offset).to eq(1) |
||||||
|
end |
||||||
|
|
||||||
|
it "parses csv correctly" do |
||||||
|
expect(service.row_parsers[0].field_7.to_i).to eq(30) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context "when parsing csv without headers" do |
||||||
|
let(:file) { Tempfile.new } |
||||||
|
let(:path) { file.path } |
||||||
|
let(:log) { build(:sales_log, :completed) } |
||||||
|
|
||||||
|
before do |
||||||
|
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_sales_csv_row) |
||||||
|
file.rewind |
||||||
|
end |
||||||
|
|
||||||
|
it "returns correct offsets" do |
||||||
|
expect(service.row_offset).to eq(0) |
||||||
|
expect(service.col_offset).to eq(0) |
||||||
|
end |
||||||
|
|
||||||
|
it "parses csv correctly" do |
||||||
|
expect(service.row_parsers[0].field_7.to_i).to eql(log.age1) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context "when parsing with BOM aka byte order mark" do |
||||||
|
let(:file) { Tempfile.new } |
||||||
|
let(:path) { file.path } |
||||||
|
let(:log) { build(:sales_log, :completed) } |
||||||
|
let(:bom) { "\uFEFF" } |
||||||
|
|
||||||
|
before do |
||||||
|
file.write(bom) |
||||||
|
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_sales_csv_row) |
||||||
|
file.close |
||||||
|
end |
||||||
|
|
||||||
|
it "parses csv correctly" do |
||||||
|
expect(service.row_parsers[0].field_7.to_i).to eql(log.age1) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context "when an invalid byte sequence" do |
||||||
|
let(:file) { Tempfile.new } |
||||||
|
let(:path) { file.path } |
||||||
|
let(:log) { build(:sales_log, :completed) } |
||||||
|
let(:invalid_sequence) { "\x81" } |
||||||
|
|
||||||
|
before do |
||||||
|
file.write(invalid_sequence) |
||||||
|
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_sales_csv_row) |
||||||
|
file.close |
||||||
|
end |
||||||
|
|
||||||
|
it "parses csv correctly" do |
||||||
|
expect(service.row_parsers[0].field_7.to_i).to eql(log.age1) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
describe "#column_for_field", aggregate_failures: true do |
||||||
|
context "when headers present" do |
||||||
|
it "returns correct column" do |
||||||
|
expect(service.column_for_field("field_1")).to eql("B") |
||||||
|
expect(service.column_for_field("field_125")).to eql("DV") |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context "when no headers" do |
||||||
|
let(:file) { Tempfile.new } |
||||||
|
let(:path) { file.path } |
||||||
|
let(:log) { build(:sales_log, :completed) } |
||||||
|
|
||||||
|
before do |
||||||
|
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_sales_csv_row) |
||||||
|
file.rewind |
||||||
|
end |
||||||
|
|
||||||
|
it "returns correct column" do |
||||||
|
expect(service.column_for_field("field_1")).to eql("A") |
||||||
|
expect(service.column_for_field("field_125")).to eql("DU") |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
Loading…
Reference in new issue