diff --git a/AGENTS.md b/AGENTS.md index 0b846ab..ee5a94c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -78,7 +78,7 @@ ItaxCode.decode(tax_code) #=> Hash: # code: String, # the input code, upcased # gender: "M" | "F", # birthdate: String, # "YYYY-MM-DD" -# birthplace: { # nil if not found in either CSV +# birthplace: { # raises InvalidTaxCodeError if not found in either CSV # code: String, # e.g. "F205" # province: String, # e.g. "MI" # name: String, # e.g. "MILANO" diff --git a/lib/itax_code/parser.rb b/lib/itax_code/parser.rb index 97c1ee0..e110025 100644 --- a/lib/itax_code/parser.rb +++ b/lib/itax_code/parser.rb @@ -92,10 +92,12 @@ def birthplace(src = utils.cities, stop: false) place = src.find { |item| item["code"] == birthplace_code && in_dates?(item) } if place.nil? - birthplace(utils.countries, stop: true) unless stop - else - place.to_h.transform_keys(&:to_sym) + return birthplace(utils.countries, stop: true) unless stop + + raise InvalidTaxCodeError end + + place.to_h.transform_keys(&:to_sym) end def birthplace_code diff --git a/test/itax_code/encoder_test.rb b/test/itax_code/encoder_test.rb index 2e30ff5..2d4be82 100644 --- a/test/itax_code/encoder_test.rb +++ b/test/itax_code/encoder_test.rb @@ -54,12 +54,64 @@ class EncoderTest < Minitest::Test ).encode end + test "#encode for female gender applies +40 day offset" do + assert_equal "RSSMRA80A41F205B", + Encoder.new( + surname: "Rossi", + name: "Mario", + gender: "F", + birthdate: "1980-01-01", + birthplace: "Milano" + ).encode + end + test "#encode raises MissingDataError on missing data" do assert_raises Encoder::MissingDataError do Encoder.new.encode end end + test "#encode raises MissingDataError on missing surname" do + assert_raises(Encoder::MissingDataError) do + Encoder.new( + surname: nil, name: "Mario", gender: "M", birthdate: "1980-01-01", birthplace: "Milano" + ) + end + end + + test "#encode raises MissingDataError on missing name" do + assert_raises(Encoder::MissingDataError) do + Encoder.new( + surname: "Rossi", name: nil, gender: "M", birthdate: "1980-01-01", birthplace: "Milano" + ) + end + end + + test "#encode raises MissingDataError on missing gender" do + assert_raises(Encoder::MissingDataError) do + Encoder.new( + surname: "Rossi", name: "Mario", gender: nil, + birthdate: "1980-01-01", birthplace: "Milano" + ) + end + end + + test "#encode raises MissingDataError on missing birthdate" do + assert_raises(Encoder::MissingDataError) do + Encoder.new( + surname: "Rossi", name: "Mario", gender: "M", birthdate: nil, birthplace: "Milano" + ) + end + end + + test "#encode raises MissingDataError on missing birthplace field" do + assert_raises(Encoder::MissingDataError) do + Encoder.new( + surname: "Rossi", name: "Mario", gender: "M", birthdate: "1980-01-01", birthplace: nil + ) + end + end + test "#encode raises MissingDataError on missing birthplace" do assert_raises Encoder::MissingDataError do Encoder.new( diff --git a/test/itax_code/parser_test.rb b/test/itax_code/parser_test.rb index ba12176..754cfcd 100644 --- a/test/itax_code/parser_test.rb +++ b/test/itax_code/parser_test.rb @@ -29,9 +29,8 @@ class ParserTest < Minitest::Test end end - # FIXME: This behaviour needs to be fixed, maybe by raising an InvalidTaxCodeError. - test "returns nil birthplace when the lookup on both cities and countries fails" do - assert_nil klass.new("BRRDRN70M41ZXXXE").decode[:birthplace] + test "raises InvalidTaxCodeError when birthplace code is not found in cities or countries" do + assert_raises(klass::InvalidTaxCodeError) { klass.new("BRRDRN70M41ZXXXE").decode } end test "raises NoTaxCodeError with an empty tax code" do @@ -62,6 +61,45 @@ class ParserTest < Minitest::Test end end + test "raises InvalidTaxCodeError when birthdate falls outside city validity window" do + # Stub cities to expose a single F205 entry whose window excludes birthdate 1980-01-01 + stub_city = { "code" => "F205", "province" => "MI", "name" => "MILANO", + "created_on" => "1960-01-01", "deleted_on" => "1975-12-31" } + Utils.stubs(:cities).returns([stub_city]) + Utils.stubs(:countries).returns([]) + # in_dates? returns false (1980-01-01 > deleted_on) → F205 not matched in cities + # F205 not in countries either → raises InvalidTaxCodeError + assert_raises(klass::InvalidTaxCodeError) { klass.new("RSSMRA80A01F205X").decode } + end + + test "decodes a birthdate in the current year" do + Timecop.freeze(Date.parse("2026-06-01")) do + # year "26" → 20+26=2026, 2026 <= 2026 → 2026 (not 1926) + assert_equal "2026-01-01", klass.new("RSSMRA26A01L219E").decode[:birthdate] + end + end + + test "decodes a birthdate as 1927 when current year is 2026 and year code is 27" do + Timecop.freeze(Date.parse("2026-06-01")) do + # year "27" → 20+27=2027, 2027 > 2026 → 2027-100 = 1927 + assert_equal "1927-01-01", klass.new("RSSMRA27A01L219F").decode[:birthdate] + end + end + + test "decodes day 31 as male gender with day 31" do + result = klass.new("RSSMRA80A31L219P").decode + + assert_equal "M", result[:gender] + assert_equal "1980-01-31", result[:birthdate] + end + + test "decodes day 41 as female gender with day 1" do + result = klass.new("RSSMRA80A41F205B").decode + + assert_equal "F", result[:gender] + assert_equal "1980-01-01", result[:birthdate] + end + private def klass diff --git a/test/itax_code_test.rb b/test/itax_code_test.rb index 63b285e..4045501 100644 --- a/test/itax_code_test.rb +++ b/test/itax_code_test.rb @@ -27,6 +27,33 @@ class ItaxCodeTest < Minitest::Test refute klass.valid?("WRONG") end + test "encode/decode round-trip for male" do + result = klass.decode(klass.encode(surname: "Rossi", name: "Mario", gender: "M", + birthdate: "1980-01-01", birthplace: "Milano")) + + assert_equal "M", result[:gender] + assert_equal "1980-01-01", result[:birthdate] + assert_equal "F205", result[:birthplace][:code] + end + + test "encode/decode round-trip for female (day offset)" do + result = klass.decode(klass.encode(surname: "Rossi", name: "Mario", gender: "F", + birthdate: "1980-01-01", birthplace: "Milano")) + + assert_equal "F", result[:gender] + assert_equal "1980-01-01", result[:birthdate] + assert_equal "F205", result[:birthplace][:code] + end + + test "encode/decode round-trip for foreign birthplace" do + result = klass.decode(klass.encode(surname: "Berardi", name: "Adriana", gender: "F", + birthdate: "1970-08-01", birthplace: "Brasile")) + + assert_equal "F", result[:gender] + assert_equal "1970-08-01", result[:birthdate] + assert_equal "Z602", result[:birthplace][:code] + end + private def klass