From 5f1a8c52443406828cf366ffac5c4b2852d4ef95 Mon Sep 17 00:00:00 2001 From: Sascha Skulima Date: Sat, 19 Sep 2026 11:33:32 +0200 Subject: [PATCH 1/2] fix:dev: doppelte Staedte je Postleitzahl, dazu Abdeckungsmessung CityMap#update_existing verglich die neue Stadt gegen den bestehenden Wert als Ganzes. Ab der zweiten Stadt ist der aber selbst zusammengesetzt ("A;B"), und uniq erkennt eine erneut gemeldete Stadt darin nicht wieder. In lib/data/DE.yml stand dadurch eine Zeile mit doppeltem Eintrag: Schoenberg;Rausdorf;Koethel;Koethel;Kasseburg;Hamfelde Die Ortsliste landet in symdok im Adressfeld, dort war das sichtbar. Beide Seiten werden jetzt zerlegt, bevor uniq greift. Die betroffene Zeile in DE.yml ist mitkorrigiert - beim naechsten Converter-Lauf entstuende sie ohnehin richtig, bis dahin waere sie falsch ausgeliefert worden. Dazu die Abdeckungsmessung, die dem Gem noch fehlte: simplecov als Dev-Abhaengigkeit, COVERAGE=1 schaltet sie ein, Schwelle 95. CityMap war bisher vollstaendig ungetestet - das neue Spec deckt sie ab, einschliesslich des Falls, der den Fehler ausloest. Abdeckung 75,60 % -> 100 % (41/41), 20 Beispiele, 0 Fehler. Der in symdok genutzte Pfad (ZipCodes.identify) war schon vorher zu 100 % abgedeckt; die Luecke lag ausschliesslich im Datenpflege-Werkzeug. Co-Authored-By: Claude Opus 5 --- Gemfile.lock | 2 + lib/data/DE.yml | 2 +- lib/zip-codes/city_map.rb | 7 ++- spec/lib/zip-codes/city_map_spec.rb | 75 +++++++++++++++++++++++++++++ spec/spec_helper.rb | 11 +++++ zip-codes.gemspec | 1 + 6 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 spec/lib/zip-codes/city_map_spec.rb diff --git a/Gemfile.lock b/Gemfile.lock index 08574a6..9876f67 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,6 +52,7 @@ GEM rubocop (>= 1.75.0, < 2.0) rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (1.13.0) + simplecov (1.3.0) standard (1.55.0) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.0) @@ -75,6 +76,7 @@ DEPENDENCIES bundler (>= 2.0) rake rspec (~> 3.0) + simplecov standard zip-codes! diff --git a/lib/data/DE.yml b/lib/data/DE.yml index 7e7b18f..b88a6f9 100644 --- a/lib/data/DE.yml +++ b/lib/data/DE.yml @@ -4595,7 +4595,7 @@ '22929': :osm_id: '454243' :code: '22929' - :city: Schönberg;Rausdorf;Köthel;Köthel;Kasseburg;Hamfelde + :city: Schönberg;Rausdorf;Köthel;Kasseburg;Hamfelde :county: Kreis Stormarn :state: Schleswig-Holstein '22941': diff --git a/lib/zip-codes/city_map.rb b/lib/zip-codes/city_map.rb index acee57c..ebf3784 100644 --- a/lib/zip-codes/city_map.rb +++ b/lib/zip-codes/city_map.rb @@ -29,10 +29,15 @@ def store! private # allow multiple cities for one code + # + # Beide Seiten werden zerlegt, bevor uniq greift. Der bestehende Wert ist ab + # der zweiten Stadt selbst zusammengesetzt ("A;B"); ungeteilt verglichen ist + # er ein einziger Eintrag, und uniq erkennt eine erneut gemeldete Stadt dann + # nicht wieder. In DE.yml stand so "Koethel" zweimal in derselben Zeile. def update_existing(code, data) map[code][:city] = [ *data[:city].to_s.split(";"), - map[code][:city] + *map[code][:city].to_s.split(";") ].uniq.join(";") end diff --git a/spec/lib/zip-codes/city_map_spec.rb b/spec/lib/zip-codes/city_map_spec.rb new file mode 100644 index 0000000..15aae1c --- /dev/null +++ b/spec/lib/zip-codes/city_map_spec.rb @@ -0,0 +1,75 @@ +require "tmpdir" + +RSpec.describe ZipCodes::CityMap do + subject(:karte) { described_class.new(datei) } + + let(:datei) { File.join(Dir.tmpdir, "city_map_spec.yml") } + let(:hamburg) { {osm_id: "62782", city: "Hamburg", county: "Hamburg-Mitte", state: "Hamburg"} } + + after { File.delete(datei) if File.exist?(datei) } + + describe "#add" do + it "legt einen neuen Eintrag mit allen Feldern an" do + karte.add("20535", hamburg) + + expect(karte.map["20535"]).to eq( + osm_id: "62782", code: "20535", city: "Hamburg", + county: "Hamburg-Mitte", state: "Hamburg" + ) + end + + it "verlangt die Pflichtfelder" do + expect { karte.add("20535", {city: "Hamburg"}) }.to raise_error(KeyError) + end + + it "haengt eine zweite Stadt an denselben Code an" do + karte.add("20535", hamburg) + karte.add("20535", {city: "Altona"}) + + expect(karte.map["20535"][:city]).to eq("Altona;Hamburg") + end + + # Der Fall, der in DE.yml ein doppeltes "Koethel" erzeugt hat: ab der + # zweiten Stadt ist der bestehende Wert selbst zusammengesetzt. Wird er + # ungeteilt verglichen, erkennt uniq eine erneut gemeldete Stadt nicht. + it "nimmt eine bereits eingetragene Stadt kein zweites Mal auf" do + karte.add("20535", hamburg) + karte.add("20535", {city: "Altona"}) + karte.add("20535", {city: "Hamburg"}) + + expect(karte.map["20535"][:city]).to eq("Hamburg;Altona") + end + + it "zerlegt auch mehrere Staedte in einer Angabe" do + karte.add("20535", hamburg) + karte.add("20535", {city: "Altona;Hamburg"}) + + expect(karte.map["20535"][:city]).to eq("Altona;Hamburg") + end + end + + describe "#to_yaml" do + it "gibt die Karte als YAML zurueck" do + karte.add("20535", hamburg) + + expect(YAML.safe_load(karte.to_yaml, permitted_classes: [Symbol])).to eq( + "20535" => { + osm_id: "62782", code: "20535", city: "Hamburg", + county: "Hamburg-Mitte", state: "Hamburg" + } + ) + end + end + + describe "#store! und #load!" do + it "schreibt die Karte und liest sie unveraendert zurueck" do + karte.add("20535", hamburg) + karte.store! + + gelesen = described_class.new(datei) + gelesen.load! + + expect(gelesen.map).to eq(karte.map) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b584841..50f302e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,14 @@ +# Abdeckungsmessung nur auf Wunsch: COVERAGE=1 bundle exec rspec +if ENV["COVERAGE"] + require "simplecov" + SimpleCov.start do + add_filter "/spec/" + # Schwelle knapp unter dem Ist-Stand (100 %), damit sie einen Absturz + # wirklich faengt. Steigt die Abdeckung dauerhaft, darf sie mitwachsen. + minimum_coverage 95 + end +end + require "bundler/setup" require "zip-codes" diff --git a/zip-codes.gemspec b/zip-codes.gemspec index 68ccb08..e85ac86 100644 --- a/zip-codes.gemspec +++ b/zip-codes.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rake" spec.add_development_dependency "rspec", "~> 3.0" spec.add_development_dependency "standard" + spec.add_development_dependency "simplecov" spec.required_ruby_version = ">= 3.1.0" end From 0b918d3fc2e36096c8133b35a7889fa6e96c48fe Mon Sep 17 00:00:00 2001 From: Sascha Skulima Date: Sat, 19 Sep 2026 11:39:45 +0200 Subject: [PATCH 2/2] fix:dev: COVERAGE=1 in der CI setzen, sonst greift die Schwelle nicht SimpleCov startet in diesem Gem nur, wenn COVERAGE gesetzt ist. In der CI war es das nicht - die Untergrenze aus spec_helper.rb lief damit ins Leere, und ein Absturz der Abdeckung waere gruen durchgegangen. Der Test-Schritt setzt die Variable jetzt in ci.yml und, wo vorhanden, in release.yml. Nebenbei aufgeraeumt, was actionlint an denselben Dateien anmerkte: actions/checkout@v3 ist fuer die heutigen Runner zu alt (v7 wie in den uebrigen Gems), und einer Datei fehlte das abschliessende Zeilenende. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 7 ++++++- .github/workflows/release.yml | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79cd018..78d00a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - name: Setup Ruby uses: ruby/setup-ruby@v1 @@ -29,5 +29,10 @@ jobs: run: bundle exec standardrb - name: Run tests + # SimpleCov laeuft nur mit COVERAGE=1 -- ohne das hier greift auch die + # Untergrenze aus spec_helper.rb nicht, und ein Absturz der Abdeckung + # liefe still durch. + env: + COVERAGE: 1 run: | bundle exec rspec diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ffbae6..3fe4512 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -80,6 +80,11 @@ jobs: run: bundle exec standardrb - name: Run tests + # SimpleCov laeuft nur mit COVERAGE=1 -- ohne das hier greift auch die + # Untergrenze aus spec_helper.rb nicht, und ein Absturz der Abdeckung + # liefe still durch. + env: + COVERAGE: 1 run: bundle exec rspec - name: Release erstellen