norma43_parser 2.1.1 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +22 -0
  3. data/.github/workflows/release.yml +33 -0
  4. data/.rspec +1 -0
  5. data/CHANGELOG.md +26 -10
  6. data/Gemfile +2 -2
  7. data/Gemfile.lock +51 -51
  8. data/README.md +2 -0
  9. data/lib/norma43/models/account/spanish_iban.rb +107 -0
  10. data/lib/norma43/models/account.rb +63 -0
  11. data/lib/norma43/models/additional_currency.rb +22 -0
  12. data/lib/norma43/models/additional_item.rb +20 -0
  13. data/lib/norma43/models/document.rb +42 -0
  14. data/lib/norma43/models/mixins/attributes_assignment.rb +33 -0
  15. data/lib/norma43/models/transaction.rb +51 -0
  16. data/lib/norma43/models.rb +0 -78
  17. data/lib/norma43/version.rb +1 -1
  18. data/norma43_parser.gemspec +3 -6
  19. data/spec/example1_parse_spec.rb +1 -1
  20. data/spec/norma43/line_processors/account_end_spec.rb +1 -4
  21. data/spec/norma43/line_processors/account_start_spec.rb +3 -3
  22. data/spec/norma43/line_processors/additional_currency_spec.rb +3 -3
  23. data/spec/norma43/line_processors/{additional_items_spec.rb → additional_item_spec.rb} +3 -3
  24. data/spec/norma43/line_processors/document_end_spec.rb +3 -6
  25. data/spec/norma43/line_processors/document_start_spec.rb +3 -3
  26. data/spec/norma43/line_processors/transaction_spec.rb +3 -3
  27. data/spec/norma43/models/account/spanish_iban_spec.rb +107 -0
  28. data/spec/norma43/models/account_spec.rb +54 -0
  29. data/spec/norma43/models/document_spec.rb +35 -0
  30. data/spec/norma43/models/transaction_spec.rb +25 -0
  31. data/spec/norma43_spec.rb +1 -1
  32. data/spec/spec_helper.rb +3 -0
  33. data/spec/support/shared_examples_for_models.rb +61 -0
  34. metadata +29 -37
@@ -1,86 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "virtus"
4
-
5
3
  module Norma43
6
4
  module Models
7
- # forward declarations
8
- class Account; end
9
- class Transaction; end
10
- class AdditionalItem; end
11
- class AdditionalCurrency; end
12
5
  DEBIT_CODE = 1
13
6
  CREDIT_CODE = 2
14
-
15
- class Document
16
- include Virtus.model
17
-
18
- attribute :id
19
- attribute :created_at
20
- attribute :delivery_number
21
- attribute :file_type
22
- attribute :name
23
- attribute :number_of_lines
24
- attribute :accounts, Array[Account]
25
-
26
- def transaction_date
27
- accounts.map(&:date).compact.first
28
- end
29
- end
30
-
31
- class Account
32
- include Virtus.model
33
-
34
- attribute :bank_code
35
- attribute :branch_code
36
- attribute :account_number
37
- attribute :start_date
38
- attribute :end_date
39
- attribute :balance_code
40
- attribute :balance_amount
41
- attribute :currency_code
42
- attribute :information_mode_code
43
- attribute :abbreviated_name
44
- attribute :debit_entries
45
- attribute :debit_amount
46
- attribute :credit_entries
47
- attribute :credit_amount
48
- attribute :transactions, Array[Transaction]
49
- end
50
-
51
- class Transaction
52
- include Virtus.model
53
-
54
- attribute :origin_branch_code
55
- attribute :transaction_date
56
- attribute :value_date
57
- attribute :shared_item
58
- attribute :own_item
59
- attribute :amount_code
60
- attribute :amount
61
- attribute :document_number
62
- attribute :reference_1
63
- attribute :reference_2
64
- attribute :additional_items, Array[AdditionalItem]
65
- attribute :additional_currency, AdditionalCurrency
66
- def debit?; self.amount_code == DEBIT_CODE end
67
- end
68
-
69
- class AdditionalItem
70
- include Virtus.model
71
-
72
- attribute :data_code
73
- attribute :item_1
74
- attribute :item_2
75
- end
76
-
77
- class AdditionalCurrency
78
- include Virtus.model
79
-
80
- attribute :data_code
81
- attribute :currency_code
82
- attribute :amount
83
- attribute :free
84
- end
85
7
  end
86
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Norma43
4
- VERSION = "2.1.1"
4
+ VERSION = "3.1.0"
5
5
  end
@@ -13,17 +13,14 @@ Gem::Specification.new do |spec|
13
13
  spec.homepage = "https://github.com/sequra/norma43_parser"
14
14
  spec.license = "MIT"
15
15
 
16
- spec.required_ruby_version = "~> 2.4"
16
+ spec.required_ruby_version = ">= 3.0"
17
17
 
18
18
  spec.files = `git ls-files -z`.split("\x0")
19
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
20
  spec.require_paths = ["lib"]
22
21
 
23
- spec.add_runtime_dependency "virtus", "~> 1.0"
24
22
  spec.add_runtime_dependency "zeitwerk", "~> 2.0"
25
23
 
26
- spec.add_development_dependency "bundler", "~> 1.0"
27
- spec.add_development_dependency "rake", "~> 13.0"
28
- spec.add_development_dependency "rspec", "~> 3.0"
24
+ spec.add_development_dependency "rake", "~> 13.0"
25
+ spec.add_development_dependency "rspec", "~> 3.9"
29
26
  end
@@ -26,7 +26,7 @@ RSpec.describe Norma43 do
26
26
  "end_date" => Date.parse("2004-09-05"),
27
27
  "currency_code" => 1,
28
28
  "information_mode_code" => 3,
29
- "abbreviated_name" => "MY ACCOUNT"
29
+ "abbreviated_name" => "MY ACCOUNT",
30
30
  )
31
31
  end
32
32
 
@@ -3,10 +3,7 @@
3
3
  module Norma43
4
4
  module LineProcessors
5
5
  RSpec.describe AccountEnd do
6
- let :line do
7
- double "Line", attributes: {}
8
- end
9
-
6
+ let(:line) { instance_double(LineParsers::AccountEnd, attributes: {}) }
10
7
  let(:account) { Norma43::Models::Account.new }
11
8
  let(:contexts) { Norma43::Utils::Contexts.new(
12
9
  [
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Norma43
4
4
  module LineProcessors
5
- RSpec.describe "AccountStart" do
6
- let(:line) { double "Line", attributes: {} }
5
+ RSpec.describe AccountStart do
6
+ let(:line) { instance_double(LineParsers::AccountStart, attributes: {}) }
7
7
  let(:document) { Norma43::Models::Document.new }
8
8
  let(:contexts) { Norma43::Utils::Contexts.new }
9
9
 
@@ -20,7 +20,7 @@ module Norma43
20
20
  end
21
21
 
22
22
  context "when AccountStart is called" do
23
- let(:fake_account) { double "Norma43::Models::Account" }
23
+ let(:fake_account) { instance_double(Norma43::Models::Account) }
24
24
  before do
25
25
  allow(Norma43::Models::Account).to receive(:new) { fake_account }
26
26
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Norma43
4
4
  module LineProcessors
5
- RSpec.describe "AdditionalCurrency" do
6
- let(:line) { double "Line", attributes: {} }
5
+ RSpec.describe AdditionalCurrency do
6
+ let(:line) { instance_double(LineParsers::AdditionalCurrency, attributes: {}) }
7
7
  let(:transaction) { Norma43::Models::Transaction.new }
8
8
  let(:contexts) { Norma43::Utils::Contexts.new(
9
9
  [
@@ -22,7 +22,7 @@ module Norma43
22
22
  end
23
23
 
24
24
  context "when AdditionalCurrency is called" do
25
- let(:fake_additional_currency) { double "Models::AdditionalCurrency" }
25
+ let(:fake_additional_currency) { instance_double(Models::AdditionalCurrency) }
26
26
  before do
27
27
  allow(Models::AdditionalCurrency).to receive(:new) { fake_additional_currency }
28
28
  end
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Norma43
4
4
  module LineProcessors
5
- RSpec.describe "AdditionalItems" do
6
- let(:line) { double "Line", attributes: {} }
5
+ RSpec.describe AdditionalItem do
6
+ let(:line) { instance_double(LineParsers::AdditionalItem, attributes: {}) }
7
7
  let(:transaction) { Norma43::Models::Transaction.new }
8
8
  let(:contexts) { Norma43::Utils::Contexts.new(
9
9
  [
@@ -22,7 +22,7 @@ module Norma43
22
22
  end
23
23
 
24
24
  context "when AdditionalItem is called" do
25
- let(:fake_additional_item) { double "Models::AdditionalItem" }
25
+ let(:fake_additional_item) { instance_double(Models::AdditionalItem) }
26
26
  before do
27
27
  allow(Models::AdditionalItem).to receive(:new) { fake_additional_item }
28
28
  end
@@ -2,15 +2,12 @@
2
2
 
3
3
  module Norma43
4
4
  module LineProcessors
5
- RSpec.describe "DocumentEnd" do
6
- class Thing
7
- end
8
-
9
- let(:line) { double "Line", record_number: 35 }
5
+ RSpec.describe DocumentEnd do
6
+ let(:line) { instance_double(LineParsers::DocumentEnd, record_number: 35) }
10
7
 
11
8
  it "moves to the nearest document context" do
12
9
  document = Norma43::Models::Document.new
13
- contexts = Norma43::Utils::Contexts.new [Thing.new, document, Thing.new, Thing.new]
10
+ contexts = Norma43::Utils::Contexts.new [anything, document, anything, anything]
14
11
 
15
12
  DocumentEnd.call line, contexts
16
13
 
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Norma43
4
4
  module LineProcessors
5
- RSpec.describe "DocumentStart" do
6
- let (:line) { double "Line", attributes: {} }
5
+ RSpec.describe DocumentStart do
6
+ let(:line) { instance_double(LineParsers::DocumentStart, attributes: {}) }
7
7
 
8
8
  it "instantiates a new document with the line attributes" do
9
9
  allow(Models::Document).to receive :new
@@ -14,7 +14,7 @@ module Norma43
14
14
  end
15
15
 
16
16
  it "sets the document as the current context" do
17
- fake_document = double "Models::Document"
17
+ fake_document = instance_double(Models::Document)
18
18
  allow(Models::Document).to receive(:new) { fake_document }
19
19
 
20
20
  contexts = DocumentStart.call line, Norma43::Utils::Contexts.new
@@ -2,8 +2,8 @@
2
2
 
3
3
  module Norma43
4
4
  module LineProcessors
5
- RSpec.describe "Transaction" do
6
- let(:line) { double "Line", attributes: {} }
5
+ RSpec.describe Transaction do
6
+ let(:line) { instance_double(LineParsers::Transaction, attributes: {}) }
7
7
  let(:account) { Norma43::Models::Account.new }
8
8
  let(:contexts) { Norma43::Utils::Contexts.new(
9
9
  [
@@ -22,7 +22,7 @@ module Norma43
22
22
  end
23
23
 
24
24
  context "when Transaction is called" do
25
- let(:fake_transaction) { double "Models::Transaction" }
25
+ let(:fake_transaction) { instance_double(Models::Transaction) }
26
26
  before do
27
27
  allow(Models::Transaction).to receive(:new) { fake_transaction }
28
28
  end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Norma43
4
+ module Models
5
+ class Account
6
+ RSpec.describe SpanishIban do
7
+ def build_account(**attributes)
8
+ instance_double(Account, **attributes)
9
+ end
10
+
11
+ describe ".from_account" do
12
+ subject(:iban) { described_class.from_account(account) }
13
+
14
+ context "with the example in the documentation" do
15
+ let(:account) { build_account(bank_code: 81, branch_code: 54, account_number: 1234567) }
16
+ it { is_expected.to eq("ES5400810054180001234567") }
17
+ end
18
+
19
+ context "with the account from the example1.n43 fixture" do
20
+ let(:account) {
21
+ file.seek 2
22
+ bank_code = file.read(4).to_i
23
+ branch_code = file.read(4).to_i
24
+ account_number = file.read(10).to_i
25
+
26
+ build_account(bank_code: bank_code, branch_code: branch_code, account_number: account_number)
27
+ }
28
+ let(:file) { File.open(File.join(__dir__, "../../../fixtures/example1.n43"), encoding: "iso-8859-1") }
29
+
30
+ it { is_expected.to eq("ES1799991111710123456789") }
31
+
32
+ after { file.close }
33
+ end
34
+
35
+ context "with minimal edge-case" do
36
+ let(:account) { build_account(bank_code: 3, branch_code: 0, account_number: 0) }
37
+ it { is_expected.to eq("ES8700030000300000000000") }
38
+ end
39
+
40
+ context "with extreme edge-case" do
41
+ let(:account) { build_account(bank_code: 9000, branch_code: 9999, account_number: 9999999999) }
42
+ it { is_expected.to eq("ES3790009999309999999999") }
43
+ end
44
+ end
45
+
46
+ describe "#to_s" do
47
+ subject { described_class.new(bank_code: 0, branch_code: 0, account_number: 0) }
48
+
49
+ it { is_expected.to(respond_to(:to_s)) }
50
+
51
+ context "with incorrect account values" do
52
+ subject { described_class.new(bank_code: bank_code, branch_code: branch_code, account_number: account_number).to_s }
53
+
54
+ let(:bank_code) { 9999 }
55
+ let(:branch_code) { 1111 }
56
+ let(:account_number) { 123456789 }
57
+
58
+ context "with missing bank code" do
59
+ let(:bank_code) { nil }
60
+ it { is_expected.to be_empty }
61
+ end
62
+
63
+ context "with missing branch code" do
64
+ let(:branch_code) { nil }
65
+ it { is_expected.to be_empty }
66
+ end
67
+
68
+ context "with missing account number" do
69
+ let(:account_number) { nil }
70
+ it { is_expected.to be_empty }
71
+ end
72
+
73
+ context "with negative bank code" do
74
+ let(:bank_code) { -3 }
75
+ it { is_expected.to be_empty }
76
+ end
77
+
78
+ context "with negative branch code" do
79
+ let(:bank_code) { -1 }
80
+ it { is_expected.to be_empty }
81
+ end
82
+
83
+ context "with negative account number" do
84
+ let(:account_number) { -123456789 }
85
+ it { is_expected.to be_empty }
86
+ end
87
+
88
+ context "with more than 4 digits in the bank code" do
89
+ let(:bank_code) { 99990 }
90
+ it { is_expected.to be_empty }
91
+ end
92
+
93
+ context "with more than 4 digits in the branch code" do
94
+ let(:bank_code) { 11110 }
95
+ it { is_expected.to be_empty }
96
+ end
97
+
98
+ context "with more than 10 digits in the account number" do
99
+ let(:account_number) { 12345678900 }
100
+ it { is_expected.to be_empty }
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Norma43
4
+ module Models
5
+ RSpec.describe Account do
6
+ it_behaves_like "a model"
7
+
8
+ describe "#iban" do
9
+ it { is_expected.to respond_to(:iban) }
10
+
11
+ context "with the example in the documentation" do
12
+ it "returns the correct IBAN string" do
13
+ account = subject
14
+ account.account_number = 1234567
15
+ account.bank_code = 81
16
+ account.branch_code = 54
17
+
18
+ expect(account.iban).to eq("ES5400810054180001234567")
19
+ end
20
+ end
21
+
22
+ context "with missing bank data" do
23
+ subject { described_class.new.iban }
24
+ it { is_expected.to be_nil }
25
+ end
26
+
27
+ context "with missing bank code" do
28
+ subject { described_class.new(bank_code: nil, branch_code: 1234, account_number: 1234).iban }
29
+ it { is_expected.to be_nil }
30
+ end
31
+
32
+ context "with missing branch code" do
33
+ subject { described_class.new(bank_code: 1234, branch_code: nil, account_number: 1234).iban }
34
+ it { is_expected.to be_nil }
35
+ end
36
+
37
+ context "with missing account number" do
38
+ subject { described_class.new(bank_code: 1234, branch_code: 1234, account_number: nil).iban }
39
+ it { is_expected.to be_nil }
40
+ end
41
+
42
+ context "with negative account number" do
43
+ subject { described_class.new(bank_code: 1234, branch_code: 1234, account_number: -1234).iban }
44
+ it { is_expected.to be_nil }
45
+ end
46
+
47
+ context "with an account number which is too large" do
48
+ subject { described_class.new(bank_code: 1234, branch_code: 1234, account_number: 1*10**10).iban }
49
+ it { is_expected.to be_nil }
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Norma43
4
+ module Models
5
+ RSpec.describe Document do
6
+ it_behaves_like "a model"
7
+
8
+ describe "#transaction_date" do
9
+ it { is_expected.to respond_to(:transaction_date) }
10
+
11
+ context "when there are no accounts" do
12
+ subject(:document) { described_class.new({ accounts: [] }) }
13
+
14
+ it { expect(document.transaction_date).to be_nil }
15
+ end
16
+
17
+ context "when there are accounts" do
18
+ subject(:document) { described_class.new({ accounts: [account] }) }
19
+
20
+ let(:account) {
21
+ Account.new(transactions: [
22
+ nil,
23
+ Transaction.new(transaction_date: nil),
24
+ Transaction.new(transaction_date: Date.parse("2024-01-23")),
25
+ ])
26
+ }
27
+
28
+ it "returns the date of the first account with a transaction date" do
29
+ expect(document.transaction_date).to eq(Date.parse("2024-01-23"))
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Norma43
4
+ module Models
5
+ RSpec.describe Transaction do
6
+ it_behaves_like "a model"
7
+
8
+ describe "#debit?" do
9
+ it { is_expected.to respond_to(:debit?) }
10
+
11
+ context "when amount code is code for debits" do
12
+ subject(:transaction) { described_class.new(amount_code: 1) }
13
+
14
+ it { expect(transaction.debit?).to be_truthy }
15
+ end
16
+
17
+ context "when amount code is code for credits" do
18
+ subject(:transaction) { described_class.new(amount_code: 2) }
19
+
20
+ it { expect(transaction.debit?).to be_falsey }
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
data/spec/norma43_spec.rb CHANGED
@@ -6,7 +6,7 @@ RSpec.describe Norma43 do
6
6
  describe "#parse" do
7
7
  it "returns the parser results" do
8
8
  text = "some total-in text"
9
- parser = double "Parser", result: "result"
9
+ parser = instance_double(Norma43::Parser, result: "result")
10
10
  expect(Norma43::Parser).to receive(:new).with(text) { parser }
11
11
 
12
12
  expect(Norma43.parse(text)).to eq "result"
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "norma43"
4
+
3
5
  RSpec.configure do |config|
4
6
  config.order = :random
5
7
  Kernel.srand config.seed
@@ -11,6 +13,7 @@ RSpec.configure do |config|
11
13
  config.mock_with :rspec do |mocks|
12
14
  mocks.syntax = :expect
13
15
  mocks.verify_partial_doubles = true
16
+ mocks.verify_doubled_constant_names = true
14
17
  end
15
18
  end
16
19
 
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.shared_examples "a model" do
4
+ it { is_expected.to respond_to(:attributes) }
5
+ it { is_expected.to respond_to(:attributes=) }
6
+ it { is_expected.to respond_to(:to_h) }
7
+ it { is_expected.to respond_to(:to_hash) }
8
+
9
+ describe "#new" do
10
+ subject(:model) { described_class.new(attributes) }
11
+
12
+ context "when instantiated with an empty hash" do
13
+ let(:attributes) { {} }
14
+
15
+ it "accepts it without failing" do
16
+ expect { model }.not_to raise_error
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "#attributes" do
22
+ subject { described_class.new.attributes }
23
+
24
+ it { is_expected.not_to be_empty }
25
+ it { is_expected.to respond_to(:each_pair) }
26
+ it { is_expected.to respond_to(:to_h) }
27
+ it { is_expected.to respond_to(:to_hash) }
28
+ it { is_expected.to respond_to(:keys) }
29
+
30
+ describe "#attributes.keys" do
31
+ subject { super().keys }
32
+
33
+ it { is_expected.not_to be_empty }
34
+ it { is_expected.to all(be_a(Symbol)) }
35
+ end
36
+ end
37
+
38
+ describe "#attributes=" do
39
+ subject { described_class.new.attributes=(attributes) }
40
+
41
+ context "when passed an empty hash" do
42
+ let(:attributes) { {} }
43
+
44
+ it "accepts it without failing" do
45
+ expect { subject }.not_to raise_error
46
+ end
47
+ end
48
+
49
+ context "when passed a hash with unknown attribute names" do
50
+ let(:attributes) { { potato: nil } }
51
+
52
+ it "accepts it without failing" do
53
+ expect { subject }.not_to raise_error
54
+ end
55
+
56
+ it "does not set any new method" do
57
+ expect { subject.potato }.to raise_error(NoMethodError, /undefined method.+potato/i)
58
+ end
59
+ end
60
+ end
61
+ end