Files
datapipeline/test/examples/doc_examples_test.rb
T

185 lines
7.9 KiB
Ruby

require_relative '../test_helper'
require_relative '../../examples/demo_api'
require 'open3'
require 'fileutils'
#
# the examples of README.md section 2: the code in the documentation is the file,
# and every example runs and produces the documented result
#
class DocExamplesTest < PipelineTest
DOC = File.read(File.join(ROOT, 'README.md'))
EXAMPLES = DOC.scan(/<!-- beispiel: (\S+) -->\n```ruby\n(.*?)\n```/m)
def run_example dir, name, *args, env: {}
env = { 'BUNDLE_GEMFILE' => File.join(ROOT, 'Gemfile') }.merge(env)
Open3.capture3(env, RbConfig.ruby, File.join(ROOT, 'bin/etl'), 'run', File.join(ROOT, name), "--out=#{dir}", *args,
chdir: dir)
end
def test_documented_code_equals_files
assert_equal %w[examples/kunden_import.rb examples/rechnungen_satzarten.rb examples/bewegungen_inkrementell.rb
examples/eigenes_plugin.rb examples/rest_lesen.rb examples/rest_schreiben.rb
examples/rest_zu_rest.rb examples/buchungen_export.rb], EXAMPLES.map(&:first)
EXAMPLES.each do |file, code|
assert_equal File.read(File.join(ROOT, file)).rstrip, code.rstrip, "#{file} differs from README.md"
end
end
def test_kunden_import
with_tmpdir do |dir|
out, err, status = run_example(dir, 'examples/kunden_import.rb')
assert_equal 2, status.exitstatus, err
assert_includes out, 'kunden_import: read=9 written=4 rejected=4'
db = DataPipeline::SqliteStorage.new(database: "#{dir}/kunden.sqlite3", query: 'select * from kunden order by kundennr')
rows = db.read
db.close
assert_equal %w[100001 100002 100003 100009], rows.map { |r| r['kundennr'] }
assert_equal({ 'kundennr' => '100002', 'name' => 'Huber & Partner', 'land' => 'Österreich', 'eu' => 'ja', 'umsatz' => 8300.0 }, rows[1])
excel = DataPipeline::ExcelStorage.new(file: "#{dir}/eu_kunden.xlsx", sheet: 'EU-Kunden').read
assert_equal %w[100001 100002 100009], excel.map { |r| r['kundennr'] }
errors = DataPipeline::CsvStorage.new(file: "#{dir}/kunden_fehler.csv").read
assert_equal ['name: missing', 'kundennr: format /\A\d{6}\z/', 'Land XX unbekannt', 'umsatz: not decimal'],
errors.map { |r| r['_error'] }
# a second run replaces the table content (truncate)
run_example(dir, 'examples/kunden_import.rb')
db = DataPipeline::SqliteStorage.new(database: "#{dir}/kunden.sqlite3", query: 'select count(*) as n from kunden')
assert_equal 4, db.read.first['n']
db.close
end
end
def test_kunden_import_dry_run_creates_no_files
with_tmpdir do |dir|
out, _err, status = run_example(dir, 'examples/kunden_import.rb', '--dry-run')
assert_equal 2, status.exitstatus
assert_includes out, 'DRY RUN'
assert_empty Dir.children(dir)
end
end
def test_rechnungen_satzarten
with_tmpdir do |dir|
_out, err, status = run_example(dir, 'examples/rechnungen_satzarten.rb')
assert status.success?, err
assert_includes err, 'TEXT: longer than 30'
documented = DOC[/Ergebnis `tmp\/rechnungen.txt`.*?```text\n(.*?)```/m, 1]
expected = documented.sub(/\ADATEI\|\d{8}/, "DATEI|#{Date.today.strftime('%Y%m%d')}")
assert_equal expected, File.read("#{dir}/rechnungen.txt")
assert_equal 3, File.readlines("#{dir}/positionen.ndjson").size
end
end
def test_bewegungen_inkrementell
with_tmpdir do |dir|
out, err, status = run_example(dir, 'examples/bewegungen_inkrementell.rb')
assert status.success?, err
assert_includes out, 'neu: 4, Stand jetzt: 4'
file = Dir["#{dir}/bewegungen_*.ndjson"].first
assert_equal({ 'id' => 2, 'konto' => '4000', 'betrag' => 20.0, 'soll_haben' => 'H' }, JSON.parse(File.readlines(file)[1]))
FileUtils.rm(file)
out, = run_example(dir, 'examples/bewegungen_inkrementell.rb')
assert_includes out, 'neu: 0, Stand jetzt: 4'
assert_empty Dir["#{dir}/bewegungen_*.ndjson"], 'no file without new rows'
end
end
def test_eigenes_plugin
with_tmpdir do |dir|
_out, err, status = run_example(dir, 'examples/eigenes_plugin.rb')
assert status.success?, err
documented = DOC[/Ergebnis `tmp\/alle_filialen.csv`:\n\n```text\n(.*?)```/m, 1]
assert_equal documented, File.read("#{dir}/alle_filialen.csv")
end
end
def with_demo_api
api = DemoApi.start(port: 0)
yield api
ensure
api&.stop
end
def test_rest_lesen
with_demo_api do |api|
with_tmpdir do |dir|
out, err, status = run_example(dir, 'examples/rest_lesen.rb', "--url=#{api.url}")
assert_equal 2, status.exitstatus, err
assert_includes out, 'rest_lesen: read=7 written=6 rejected=1'
documented = DOC[/Ergebnis `tmp\/api_kunden.csv`:\n\n```text\n(.*?)```/m, 1]
assert_equal documented, File.read("#{dir}/api_kunden.csv")
assert_equal 6, JSON.parse(File.read("#{dir}/api_kunden.json")).size
assert_equal ['umsatz: not decimal'], DataPipeline::CsvStorage.new(file: "#{dir}/api_fehler.csv").read.map { |r| r['_error'] }
end
end
end
def test_rest_schreiben_and_dry_run
with_demo_api do |api|
with_tmpdir do |dir|
out, _err, status = run_example(dir, 'examples/rest_schreiben.rb', "--url=#{api.url}", '--dry-run')
assert_equal 2, status.exitstatus
assert_includes out, 'DRY RUN'
assert_empty api.imported, 'dry run sends nothing'
out, err, status = run_example(dir, 'examples/rest_schreiben.rb', "--url=#{api.url}")
assert_equal 2, status.exitstatus, err
assert_includes out, 'gesendet: 6 Kunden in 3 Paketen'
assert_equal %w[100001 100002 100003 100007 100008 100009], api.imported.map { |r| r['kundennr'] }
assert_equal 2, DataPipeline::CsvStorage.new(file: "#{dir}/import_fehler.csv").read.size
end
end
end
def test_rest_zu_rest
with_demo_api do |api|
with_tmpdir do |dir|
out, err, status = run_example(dir, 'examples/rest_zu_rest.rb', "--url=#{api.url}")
assert status.success?, err
assert_includes out, 'archiviert: 5, Stand jetzt: 5'
assert_equal %w[1 2 3 4 5], api.archive.keys
assert_equal Date.today.iso8601, api.archive['3']['archiviert']
api.archive.clear
out, = run_example(dir, 'examples/rest_zu_rest.rb', "--url=#{api.url}")
assert_includes out, 'archiviert: 0, Stand jetzt: 5'
assert_empty api.archive
end
end
end
def test_buchungen_export
with_tmpdir do |dir|
env = { 'BUCHHALTUNG_DB' => "#{dir}/buchhaltung.sqlite3", 'SLACK_TOKEN' => nil }
out, err, status = run_example(dir, 'examples/buchungen_export.rb', '--dry-run', env:)
assert_equal 0, status.exitstatus, err
refute File.exist?("#{dir}/buchhaltung.sqlite3")
2.times do
out, err, status = run_example(dir, 'examples/buchungen_export.rb', env:)
assert_equal 0, status.exitstatus, err
end
assert_includes out, 'buchungen_export: read=7 written=7 rejected=0 betrag=0/0'
db = DataPipeline::SqliteStorage.new(database: "#{dir}/buchhaltung.sqlite3", query: 'select * from buchungen order by belnr, buzei')
rows = db.read
db.close
assert_equal 7, rows.size # upsert: the second run updates instead of inserting
assert_equal({ 'belnr' => '5100000002', 'buzei' => 3, 'datum' => '2026-10-02', 'konto' => '2000', 'shkzg' => 'H',
'dmbtr' => '409.80', 'betrag' => -409.8, 'buchungstext' => 'Papeterie Zürcher AG' }, rows[4])
assert_equal 'ohne Text', rows[5]['buchungstext']
csv = File.read("#{dir}/buchungen_sap.csv", encoding: 'Windows-1252').encode('UTF-8')
assert_includes csv, "5100000001;1;2026-10-01;1020;S;1250.00;Zahlung Müller GmbH\n"
history = DataPipeline.history("#{dir}/runs.ndjson", name: 'buchungen_export')
assert_equal [true, true, true], history.map { |run| run['ok'] }
assert_equal [true, false, false], history.map { |run| run['dry_run'] }
end
end
end