diff --git a/plugins/article_import/spec/files/bnn/bnn_bad_encoding.BNN b/plugins/article_import/spec/files/bnn/bnn_bad_encoding.BNN
new file mode 100644
index 00000000..0b7cb14e
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_bad_encoding.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;A;;;4280001958081;4280001958203;Greek Dressing - Kräuter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-ÖKO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/bnn/bnn_flawless.BNN b/plugins/article_import/spec/files/bnn/bnn_flawless.BNN
new file mode 100644
index 00000000..3229196c
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_flawless.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;X;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/bnn_flawless_category.BNN b/plugins/article_import/spec/files/bnn/bnn_flawless_category.BNN
new file mode 100644
index 00000000..78234d92
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_flawless_category.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;4000;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/bnn_flawless_special.BNN b/plugins/article_import/spec/files/bnn/bnn_flawless_special.BNN
new file mode 100644
index 00000000..0f285f6b
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_flawless_special.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;20230101;20230201;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/bnn_missing_entries.BNN b/plugins/article_import/spec/files/bnn/bnn_missing_entries.BNN
new file mode 100644
index 00000000..6c8dafe9
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_missing_entries.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;HDE;;GR;C%;DE-™KO-001;120;1100;10;55;;1;6 x35g;6;35g;1;N;;99260;;1,41;;;;1;;;4,49;2,89;J;;;;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/bnn_missing_order_number.BNN b/plugins/article_import/spec/files/bnn/bnn_missing_order_number.BNN
new file mode 100644
index 00000000..aadcb9b6
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_missing_order_number.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;HDE;;GR;C%;DE-™KO-001;120;1100;10;55;;1;6 x35g;6;35g;1;N;;99260;;1,41;;;;1;;;4,49;2,89;J;;;;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/demo_file.BNN b/plugins/article_import/spec/files/bnn/demo_file.BNN
new file mode 100644
index 00000000..3d9cc23f
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/demo_file.BNN
@@ -0,0 +1,7 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+5;;;;4280001958081;4280001958203;Žpfel Elstar;erntefrisch und knackig;;;obb;;D;C%;DE-?KO-001;120;0301;10;55;;1;10 x1kg;10;1kg;1;N;;;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;1;;
+6;;;;4280001958081;4280001958203;Brokkoli;gesund und lecker;;;fig;;IT;C%;DE-?KO-001;120;03;10;55;;1;6 x400g;6;400g;1;N;;;;1,41;;;;1;;;4,49;2,99;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2,5;;
+7;;;;4280001958081;4280001958203;Tomaten;pomodori italiani, demeter;;;TDP;;IT;C%;DE-?KO-001;120;03;10;55;;1;20 x500g;20;500g;1;N;;;;1,41;;;;1;;;4,49;3,19;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2;;
+8;;;;4280001958081;4280001958203;Reis;Reis im Vorratssack, demeter;;;FIN;;D;C%;DE-?KO-001;120;05;10;55;;1;12 x3k;12;3kg;1;N;;;;1,41;;;;1;;;4,49;3,49;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;0,3;;
+9;;;;4280001958081;4280001958203;Spaghetti;100% italienisches Hartweizengrie?;;;ZLN;;D;C%;DE-?KO-001;120;06;10;55;;1;4 x500g;4;500g;1;N;;;;1,41;;;;1;;;4,49;2,99;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2;;
+10;;;;4280001958081;4280001958203;Kartoffeln;vorwiegend festkochend;;;rsh;;D;C%;DE-?KO-001;120;0311;10;55;;1;6 x5Kg;6;5Kg;1;N;;;;1,41;;;;1;;;4,49;3,00;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;0.2;;
diff --git a/plugins/article_import/spec/files/custom_codes.yml b/plugins/article_import/spec/files/custom_codes.yml
new file mode 100644
index 00000000..5e9020f3
--- /dev/null
+++ b/plugins/article_import/spec/files/custom_codes.yml
@@ -0,0 +1,8 @@
+# BNN Codes
+category:
+ "4000": "Schuhe"
+additional:
+ "additional": "value"
+indeling:
+ 11: Test Indeling
+ 111: Test Subindeling
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/foodsoft/foodsoft_flawless.csv b/plugins/article_import/spec/files/foodsoft/foodsoft_flawless.csv
new file mode 100644
index 00000000..a9a94c22
--- /dev/null
+++ b/plugins/article_import/spec/files/foodsoft/foodsoft_flawless.csv
@@ -0,0 +1,3 @@
+status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
+;1;product;bio;someone;eu;1 kg;1.23;6;0;10;;;coolstuff
+;12;other product;bio;someone;eu;2 kg;3.45;6;0;10;;;coolstuff
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/foodsoft/foodsoft_generate_order_number.csv b/plugins/article_import/spec/files/foodsoft/foodsoft_generate_order_number.csv
new file mode 100644
index 00000000..a50dde34
--- /dev/null
+++ b/plugins/article_import/spec/files/foodsoft/foodsoft_generate_order_number.csv
@@ -0,0 +1,3 @@
+status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
+;;product;bio;someone;eu;1 kg;1.23;6;0;10;;;coolstuff
+;;other product;bio;someone;eu;2 kg;3.45;6;0;10;;;coolstuff
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/foodsoft/foodsoft_missing_entries.csv b/plugins/article_import/spec/files/foodsoft/foodsoft_missing_entries.csv
new file mode 100644
index 00000000..560c11af
--- /dev/null
+++ b/plugins/article_import/spec/files/foodsoft/foodsoft_missing_entries.csv
@@ -0,0 +1,2 @@
+status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
+;12;product;bio;;eu;1 kg;1.23;;0;10;;;coolstuff
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/odin/odin_flawless.xml b/plugins/article_import/spec/files/odin/odin_flawless.xml
new file mode 100644
index 00000000..5b5a28fc
--- /dev/null
+++ b/plugins/article_import/spec/files/odin/odin_flawless.xml
@@ -0,0 +1,75 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+nucli rose
+Nucli rose
+
+0
+0
+0
+750
+g
+Stuk
+0
+NELEMAN
+Biologisch
+
+
+ES
+
+21
+1017515
+0109
+6
+Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+4.52
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/odin/odin_flawless_custom_category.xml b/plugins/article_import/spec/files/odin/odin_flawless_custom_category.xml
new file mode 100644
index 00000000..460da24c
--- /dev/null
+++ b/plugins/article_import/spec/files/odin/odin_flawless_custom_category.xml
@@ -0,0 +1,77 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+nucli rose
+Nucli rose
+
+0
+0
+0
+750
+g
+Stuk
+0
+NELEMAN
+Biologisch
+
+
+ES
+
+21
+1017515
+0109
+11
+111
+6
+Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+4.52
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/odin/odin_missing_entries.xml b/plugins/article_import/spec/files/odin/odin_missing_entries.xml
new file mode 100644
index 00000000..5089b911
--- /dev/null
+++ b/plugins/article_import/spec/files/odin/odin_missing_entries.xml
@@ -0,0 +1,75 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+nucli rose
+Nucli rose
+
+0
+0
+0
+750
+
+Stuk
+0
+
+Biologisch
+
+
+ES
+
+21
+1017515
+0109
+6
+Non Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+4.52
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/odin/odin_missing_order_number.xml b/plugins/article_import/spec/files/odin/odin_missing_order_number.xml
new file mode 100644
index 00000000..d43a9439
--- /dev/null
+++ b/plugins/article_import/spec/files/odin/odin_missing_order_number.xml
@@ -0,0 +1,75 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+nucli rose
+Nucli rose
+
+0
+0
+0
+750
+g
+Stuk
+0
+NELEMAN
+Biologisch
+
+
+ES
+
+21
+1017515
+
+6
+Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+4.52
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/lib/bnn/foodsoft_article_import_bnn_spec.rb b/plugins/article_import/spec/lib/bnn/foodsoft_article_import_bnn_spec.rb
new file mode 100644
index 00000000..d50aa591
--- /dev/null
+++ b/plugins/article_import/spec/lib/bnn/foodsoft_article_import_bnn_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative '../../../lib/foodsoft_article_import'
+
+describe FoodsoftArticleImport do
+ files_path = File.expand_path '../../files', __dir__
+ bnn_files_path = File.join(files_path, 'bnn')
+
+ dummy_article = { name: 'Greek Dressing - Kräuter Mix', order_number: '64721', note: 'Oregano, Basilikum und Minze',
+ manufacturer: 'Medousa, Griechenland Importe', origin: 'GR', article_category: 'Kräutermischungen', unit: '35g', price: '2,89', tax: 7.0, unit_quantity: '6' }
+
+ article = dummy_article.merge({ deposit: 0.08 })
+ article_special = article.merge(note: 'Sonderpreis: 2,89 von 20230101 bis 20230201')
+
+ article_2 = dummy_article.merge({ manufacturer: nil, article_category: nil })
+
+ article_custom_code = article.merge(article_category: 'Schuhe')
+
+ empty = {}
+
+ context 'bnn' do
+ it 'parses bnn file correctly without type parameter' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless.BNN'))) do |new_attrs, status, _line|
+ expect(new_attrs).to eq article
+ expect(status).to eq :outlisted
+ end
+ end
+ it 'parses file correctly with type parameter' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless.BNN')), type: 'bnn') do |new_attrs, status, _line|
+ expect(new_attrs).to eq article
+ expect(status).to eq :outlisted
+ end
+ end
+ it 'raises error wenn wrong type (except odin) specified' do
+ expect do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless.BNN')), type: 'foodsoft')
+ end.to raise_error(RuntimeError)
+
+ expect(FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless.BNN')), type: 'odin')).to eq []
+ end
+ it 'parses article with special correctly' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless_special.BNN')), type: 'bnn') do |new_attrs, status, _line|
+ expect(new_attrs).to eq article_special
+ expect(status).to eq :special
+ end
+ end
+ it 'parses missing entries correctly' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_missing_entries.BNN')), type: 'bnn') do |new_attrs, status, _line|
+ expect(new_attrs).to eq article_2
+ expect(status).to eq nil
+ end
+ end
+ it 'skips rows without order_number' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_missing_order_number.BNN')), type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs).to eq empty
+ end
+ end
+ it 'joins custom_codes file' do
+ custom_file_path = File.join(files_path, 'custom_codes.yml').to_s
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless_category.BNN')), custom_file_path: custom_file_path, type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs).to eq article_custom_code
+ end
+ end
+ it 'parses file with different encoding' do
+ # the bnn file is loaded with encoding ibm850. If file is not ibm850 encoded, some characters might look weird
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_bad_encoding.BNN')), type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs[:order_number]).to eq('64721')
+ expect(new_attrs[:name]).to eq('Greek Dressing - Kräuter Mix')
+ end
+ end
+ end
+end
diff --git a/plugins/article_import/spec/lib/foodsoft/foodsoft_article_import_foodsoft_spec.rb b/plugins/article_import/spec/lib/foodsoft/foodsoft_article_import_foodsoft_spec.rb
new file mode 100644
index 00000000..ce78b12b
--- /dev/null
+++ b/plugins/article_import/spec/lib/foodsoft/foodsoft_article_import_foodsoft_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'foodsoft_article_import'
+
+describe FoodsoftArticleImport do
+ files_path = File.expand_path '../../files', __dir__
+ foodsoft_files_path = File.join(files_path, 'foodsoft')
+
+ dummy_article = { order_number: '1', name: 'product', note: 'bio', manufacturer: 'someone', origin: 'eu',
+ unit: '1 kg', price: '1.23', tax: '6', unit_quantity: '10', article_category: 'coolstuff', deposit: '0' }
+
+ dummy_article_2 = { order_number: '12', name: 'other product', note: 'bio', manufacturer: 'someone',
+ origin: 'eu', unit: '2 kg', price: '3.45', tax: '6', unit_quantity: '10', article_category: 'coolstuff', deposit: '0' }
+
+ articles = [dummy_article, dummy_article_2]
+
+ dummy_article_3 = dummy_article.merge({ order_number: ':d8df298' })
+ dummy_article_4 = dummy_article_2.merge({ order_number: ':1f37e39' })
+ articles_number_generated = [dummy_article_3, dummy_article_4]
+ empty = {}
+
+ context 'foodsoft' do
+ it 'parses file correctly with type parameter foodsoft' do
+ count = 0
+ FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_flawless.csv')), type: 'foodsoft') do |new_attrs, status, _line|
+ expect(new_attrs).to eq articles[count]
+ expect(status).to eq nil
+ count += 1
+ end
+ end
+
+ it 'raises error wenn wrong type specified' do
+ expect(FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_flawless.csv')), type: 'odin')).to eq []
+
+ expect(FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_flawless.csv')), type: 'bnn')).to eq []
+ end
+
+ it 'parses missing entries correctly' do
+ FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_missing_entries.csv')), type: 'foodsoft') do |new_attrs, status, _line|
+ expect(status).to eq 'Error: unit, price and tax must be entered'
+ expect(new_attrs[:unit]).to eq '1 kg'
+ expect(new_attrs[:manufacturer]).to eq nil
+ end
+ end
+
+ it 'generates order numbers for articles without order number' do
+ count = 0
+ FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_generate_order_number.csv')), type: 'foodsoft') do |new_attrs, _status, _line|
+ expect(new_attrs).to eq articles_number_generated[count]
+ count += 1
+ end
+ end
+
+ xit 'joins custom_codes file' do
+ custom_file_path = File.join(files_path, 'custom_codes.yml').to_s
+ FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_flawless_custom_category.csv')), custom_file_path: custom_file_path, type: 'foodsoft') do |new_attrs, _status, _line|
+ expect(new_attrs[:article_category]).to eq 'Test Indeling - Test Subindeling'
+ end
+ end
+ end
+end
diff --git a/plugins/article_import/spec/lib/odin/foodsoft_article_import_odin_spec.rb b/plugins/article_import/spec/lib/odin/foodsoft_article_import_odin_spec.rb
new file mode 100644
index 00000000..af3da3f4
--- /dev/null
+++ b/plugins/article_import/spec/lib/odin/foodsoft_article_import_odin_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative '../../../lib/foodsoft_article_import'
+
+describe FoodsoftArticleImport do
+ files_path = File.expand_path '../../files', __dir__
+ odin_files_path = File.join(files_path, 'odin')
+
+ dummy_article = { order_number: '0109', name: 'nucli rose', note: 'Biologisch', manufacturer: 'NELEMAN',
+ origin: 'ES', unit: '750gr', price: '4.52', unit_quantity: '6', tax: '21', deposit: '0', article_category: '' }
+
+ empty = {}
+
+ context 'odin' do
+ it 'parses file correctly with type parameter odin' do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_flawless.xml')), type: 'odin') do |new_attrs, status, _line|
+ expect(new_attrs).to eq dummy_article
+ expect(status).to eq nil
+ end
+ end
+
+ it 'raises error wenn wrong type specified' do
+ expect do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_flawless.xml')), type: 'foodsoft')
+ end.to raise_error(RuntimeError)
+
+ expect do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_flawless.xml')), type: 'bnn')
+ end.to raise_error(CSV::MalformedCSVError)
+ end
+
+ it 'parses missing entries correctly' do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_missing_entries.xml')), type: 'odin') do |new_attrs, status, _line|
+ expect(status).to eq :outlisted
+ expect(new_attrs[:unit]).to eq '750st'
+ expect(new_attrs[:manufacturer]).to eq ''
+ end
+ end
+
+ it 'skips rows without order_number' do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_missing_order_number.xml')), type: 'odin') do |new_attrs, _status, _line|
+ expect(new_attrs).to eq empty
+ end
+ end
+
+ it 'joins custom_codes file' do
+ custom_file_path = File.join(files_path, 'custom_codes.yml').to_s
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_flawless_custom_category.xml')), custom_file_path: custom_file_path, type: 'odin') do |new_attrs, _status, _line|
+ expect(new_attrs[:article_category]).to eq 'Test Indeling - Test Subindeling'
+ end
+ end
+
+ xit 'parses dummy_article with special correctly' do
+ # TODO: find out whether there are special prices for odin files
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'bnn_flawless_special.BNN')), type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs.manufacturer).to eq nil
+ expect(new_attrs.unit).to eq '750st'
+ end
+ end
+
+ xit 'parses file with different encoding' do
+ # the bnn file is loaded with encoding ibm850. If file is not ibm850 encoded, some characters might look weird
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'bnn_bad_encoding.BNN')), type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs[:order_number]).to eq('64721')
+ expect(new_attrs[:name]).to eq('Greek Dressing - Kräuter Mix')
+ end
+ end
+ end
+end
diff --git a/plugins/article_import/spec/spec_helper.rb b/plugins/article_import/spec/spec_helper.rb
new file mode 100644
index 00000000..d3e43d53
--- /dev/null
+++ b/plugins/article_import/spec/spec_helper.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+require 'simplecov'
+SimpleCov.start
+# This file was generated by the `rspec --init` command. Conventionally, all
+# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
+# The generated `.rspec` file contains `--require spec_helper` which will cause
+# this file to always be loaded, without a need to explicitly require it in any
+# files.
+#
+# Given that it is always loaded, you are encouraged to keep this file as
+# light-weight as possible. Requiring heavyweight dependencies from this file
+# will add to the boot time of your test suite on EVERY test run, even for an
+# individual file that may not need all of that loaded. Instead, consider making
+# a separate helper file that requires the additional dependencies and performs
+# the additional setup, and require it from the spec files that actually need
+# it.
+#
+# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+ # rspec-expectations config goes here. You can use an alternate
+ # assertion/expectation library such as wrong or the stdlib/minitest
+ # assertions if you prefer.
+ config.expect_with :rspec do |expectations|
+ # This option will default to `true` in RSpec 4. It makes the `description`
+ # and `failure_message` of custom matchers include text for helper methods
+ # defined using `chain`, e.g.:
+ # be_bigger_than(2).and_smaller_than(4).description
+ # # => "be bigger than 2 and smaller than 4"
+ # ...rather than:
+ # # => "be bigger than 2"
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+ end
+
+ # rspec-mocks config goes here. You can use an alternate test double
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
+ config.mock_with :rspec do |mocks|
+ # Prevents you from mocking or stubbing a method that does not exist on
+ # a real object. This is generally recommended, and will default to
+ # `true` in RSpec 4.
+ mocks.verify_partial_doubles = true
+ end
+
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
+ # have no way to turn it off -- the option exists only for backwards
+ # compatibility in RSpec 3). It causes shared context metadata to be
+ # inherited by the metadata hash of host groups and examples, rather than
+ # triggering implicit auto-inclusion in groups with matching metadata.
+ config.shared_context_metadata_behavior = :apply_to_host_groups
+
+ # The settings below are suggested to provide a good initial experience
+ # with RSpec, but feel free to customize to your heart's content.
+ # # This allows you to limit a spec run to individual examples or groups
+ # # you care about by tagging them with `:focus` metadata. When nothing
+ # # is tagged with `:focus`, all examples get run. RSpec also provides
+ # # aliases for `it`, `describe`, and `context` that include `:focus`
+ # # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
+ # config.filter_run_when_matching :focus
+ #
+ # # Allows RSpec to persist some state between runs in order to support
+ # # the `--only-failures` and `--next-failure` CLI options. We recommend
+ # # you configure your source control system to ignore this file.
+ # config.example_status_persistence_file_path = "spec/examples.txt"
+ #
+ # # Limits the available syntax to the non-monkey patched syntax that is
+ # # recommended. For more details, see:
+ # # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode
+ # config.disable_monkey_patching!
+ #
+ # # This setting enables warnings. It's recommended, but in some cases may
+ # # be too noisy due to issues in dependencies.
+ # config.warnings = true
+ #
+ # # Many RSpec users commonly either run the entire suite or an individual
+ # # file, and it's useful to allow more verbose output when running an
+ # # individual spec file.
+ # if config.files_to_run.one?
+ # # Use the documentation formatter for detailed output,
+ # # unless a formatter has already been configured
+ # # (e.g. via a command-line flag).
+ # config.default_formatter = "doc"
+ # end
+ #
+ # # Print the 10 slowest examples and example groups at the
+ # # end of the spec run, to help surface which specs are running
+ # # particularly slow.
+ # config.profile_examples = 10
+ #
+ # # Run specs in random order to surface order dependencies. If you find an
+ # # order dependency and want to debug it, you can fix the order by providing
+ # # the seed, which is printed after each run.
+ # # --seed 1234
+ # config.order = :random
+ #
+ # # Seed global randomization in this process using the `--seed` CLI option.
+ # # Setting this allows you to use `--seed` to deterministically reproduce
+ # # test failures related to randomization by passing the same `--seed` value
+ # # as the one that triggered the failure.
+ # Kernel.srand config.seed
+end