diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..525446d --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa6e8ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +ex_pesel-*.tar + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c98d6fc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: elixir +sudo: false + +branches: + only: + - master + +elixir: + - 1.5.3 + +otp_release: + - 20.2.2 + +script: + - mix credo --strict + - mix test + +notifications: + email: false \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..010788c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## v0.1.0 + +* here it begins diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d6c2ba7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Marcin Szczepaniak + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d88b20 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# ExPesel + +**Elixir library for PESEL number.** + + * `ExPesel.valid?/1` - you can check if PESEL number is valid + * `ExPesel.valid_with?/2` - you can check if PESEL number is valid with additional check about sex or date of birth + * `ExPesel.birthdate/1` - you can obtain date of birth from PESEL number + * `ExPesel.sex/1` - you can obtain sex from the PESEL number + * `ExPesel.zombie?/1` - you can check is PESEL number is a zombie (more than 123 years before today) + + More about PESEL number: + * [Wikipedia EN](https://en.wikipedia.org/wiki/PESEL) + * [Wikipedia PL](https://pl.wikipedia.org/wiki/PESEL) + * [obywatel.gov.pl](https://obywatel.gov.pl/dokumenty-i-dane-osobowe/czym-jest-numer-pesel) + +## Usage + +```elixir +iex> ExPesel.valid?("44051401458") +true + +iex> ExPesel.valid?("90720312611") +false + +iex> ExPesel.valid_with?("44051401458", :male) +true + +iex> ExPesel.valid_with?("88122302080", :male) +false + +iex> ExPesel.valid_with?("44051401458", :female) +false + +iex> ExPesel.valid_with?("88122302080", :female) +true + +iex> ExPesel.valid_with?("44051401458", {1944, 5, 14}) +true + +iex> ExPesel.valid_with?("44051401458", {1944, 6, 13}) +false + +iex> ExPesel.birthdate("10320305853") +{2010, 12, 3} + +iex> ExPesel.sex("88122302080") +:female + +iex> ExPesel.zombie?("01920300359") +true +``` + +## Installation + +Package can be installed by adding `ex_pesel` +to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:ex_pesel, "~> 0.1.0"} + ] +end +``` + +## Documentation + +Docs can be found at [https://hexdocs.pm/ex_pesel](https://hexdocs.pm/ex_pesel). + +## License + +Source code of ex_pesel is released under the Apache 2.0 license, see the [LICENSE](LICENSE) file. diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..cf27801 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,30 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +# This configuration is loaded before any dependency and is restricted +# to this project. If another project depends on this project, this +# file won't be loaded nor affect the parent project. For this reason, +# if you want to provide default values for your application for +# 3rd-party users, it should be done in your "mix.exs" file. + +# You can configure your application as: +# +# config :ex_pesel, key: :value +# +# and access this configuration in your application as: +# +# Application.get_env(:ex_pesel, :key) +# +# You can also configure a 3rd-party app: +# +# config :logger, level: :info +# + +# It is also possible to import configuration files, relative to this +# directory. For example, you can emulate configuration per environment +# by uncommenting the line below and defining dev.exs, test.exs and such. +# Configuration from the imported file will override the ones defined +# here (which is why it is important to import them last). +# +# import_config "#{Mix.env}.exs" diff --git a/lib/ex_pesel.ex b/lib/ex_pesel.ex new file mode 100644 index 0000000..aae4567 --- /dev/null +++ b/lib/ex_pesel.ex @@ -0,0 +1,153 @@ +defmodule ExPesel do + @moduledoc """ + Library for PESEL number. + + * `ExPesel.valid?/1` - you can check if PESEL number is valid + * `ExPesel.valid_with?/2` - you can check if PESEL number is valid with additional check about sex or birthdate + * `ExPesel.birthdate/1` - you can obtain date of birth from PESEL number + * `ExPesel.sex/1` - you can obtain sex from the PESEL number + * `ExPesel.zombie?/1` - you can obtain is PESEL number is a zombie (more than 123 years before today) + + More about PESEL number: + * [Wikipedia EN](https://en.wikipedia.org/wiki/PESEL) + * [Wikipedia PL](https://pl.wikipedia.org/wiki/PESEL) + * [obywatel.gov.pl](https://obywatel.gov.pl/dokumenty-i-dane-osobowe/czym-jest-numer-pesel) + """ + + alias ExPesel.Pesel + + @doc """ + Is PESEL number valid? + + PESEL is valid when: + * length of it is 11 + * last digit is proper checksum for first ten digits + * date of birt from first 6 digits are proper + * date of birth is until today. + + For example: + iex> ExPesel.valid?("44051401458") + true + + iex> ExPesel.valid?("90720312611") + false + + iex> ExPesel.valid?("90520308014") + false + + iex> ExPesel.valid?("44051401459") + false + + iex> ExPesel.valid?("00000000000") + false + + iex> ExPesel.valid?("234555") + false + + iex> ExPesel.valid?("23455532312131123123") + false + """ + @spec valid?(String.t()) :: boolean() + defdelegate valid?(pesel), to: Pesel + + @doc """ + Is PESEL valid and additionaly: + * belongs to male? + * or belongs to female? + * or date of birth extracted from PESEL is equal birthdate? + + For example: + + iex> ExPesel.valid_with?("44051401458", :male) + true + + iex> ExPesel.valid_with?("88122302080", :male) + false + + iex> ExPesel.valid_with?("44051401458", :female) + false + + iex> ExPesel.valid_with?("88122302080", :female) + true + + iex> ExPesel.valid_with?("44051401458", {1944, 5, 14}) + true + + iex> ExPesel.valid_with?("44051401458", {1944, 6, 13}) + false + """ + def valid_with?(pesel, sex_or_birthday) + + # sorry have to do by def because there is no defdelegate with atoms + @spec valid_with?(String.t(), :male) :: boolean() + def valid_with?(pesel, :male), do: Pesel.valid_with?(pesel, :male) + + @spec valid_with?(String.t(), :female) :: boolean() + def valid_with?(pesel, :female), do: Pesel.valid_with?(pesel, :female) + + @spec valid_with?(String.t(), {1800..2299, 1..12, 1..31}) :: boolean() + defdelegate valid_with?(pesel, birthdate), to: Pesel + + @doc """ + Returns date of birth extracted from PESEL. + + Date of birth is encoded by first 6 digits as: `yymmdd` + + Century is encoded in `mm` part of it: + * 1800 - 1899 there is addition of 80 to `mm` + * 1900 - 1999 there is addition of 0 to `mm` + * 2000 - 2099 there is addition of 20 to `mm` + * 2100 - 2199 there is addition of 40 to `mm` + * 2200 - 2299 there is addition of 50 to `mm` + + For example: + iex> ExPesel.birthdate("01920300359") + {1801, 12, 3} + + iex> ExPesel.birthdate("44051401459") + {1944, 5, 14} + + iex> ExPesel.birthdate("10320305853") + {2010, 12, 3} + + iex> ExPesel.birthdate("90520308014") + {2190, 12, 3} + + iex> ExPesel.birthdate("90720312611") + {2290, 12, 3} + """ + @spec birthdate(String.t()) :: {1800..2299, 1..12, 1..31} + defdelegate birthdate(pesel), to: Pesel + + @doc """ + Returns sex extracted from PESEL. + + Sex is encoded by digit at 10 position: + * even number for female + * odd number for male + + For example: + iex> ExPesel.sex("44051401459") + :male + + iex> ExPesel.sex("88122302080") + :female + """ + @spec sex(String.t()) :: :male | :female + defdelegate sex(pesel), to: Pesel + + @doc """ + Is PESEL number a zombie? + + We got zombie PESEL when it has date of birth 123 years before today. + + For example: + iex> ExPesel.zombie?("01920300359") + true + + iex> ExPesel.zombie?("88122302080") + false + """ + @spec zombie?(String.t()) :: boolean() + defdelegate zombie?(pesel), to: Pesel +end diff --git a/lib/ex_pesel/pesel.ex b/lib/ex_pesel/pesel.ex new file mode 100644 index 0000000..86eb2a4 --- /dev/null +++ b/lib/ex_pesel/pesel.ex @@ -0,0 +1,101 @@ +defmodule ExPesel.Pesel do + @moduledoc false + + # wages for calculating PESEL checksum + @w [9, 7, 3, 1, 9, 7, 3, 1, 9, 7] + + # longest human lifespan in years + @years 123 + + # PESEL is valid + # when checksum for 10 first digits is equal to 11 digit + # when first 6 digits gives proper date + # when first 6 digits gives date until today, PESEL for future is invalid + @spec valid?(String.t()) :: boolean() + def valid?(pesel) when is_binary(pesel) do + checksum?(to_list(pesel), @w, 0) + and valid_birthdate?(birthdate(pesel)) + end + + defp checksum?([crc], [], acc), do: crc == rem(acc, 10) + defp checksum?([x | r], [w | rw], acc), do: checksum?(r, rw, x * w + acc) + defp checksum?(_, _, _), do: false + + defp valid_birthdate?(date), do: valid_date?(date) and till_today?(date) + defp valid_date?(date), do: :calendar.valid_date(date) + defp till_today?(date), do: date <= today() + defp today, do: extract_date(:calendar.universal_time) + defp extract_date({date, _}), do: date + + # PESEL is valid and was issued for specified birthdate + @spec valid_with?(String.t(), {1800..2299, 1..12, 1..3}) :: boolean() + def valid_with?(pesel, birthday) + when is_binary(pesel) and is_tuple(birthday) do + valid?(pesel) and birthdate(pesel) == birthday + end + + # PESEL is valid and was issued for specified sex + @spec valid_with?(String.t(), :male|:female) :: boolean() + def valid_with?(pesel, sex) + when is_binary(pesel) and is_atom(sex) do + valid?(pesel) and sex(pesel) == sex + end + + # Date of birth is encoded in first 6 digits as yymmdd + # in month mm there is encoded century as: + # for 1800-1899 we got addition of 80 to mm + # for 1900-1999 we got addition of 0 to mm + # for 2000-2099 we got addition of 20 to mm + # for 2100-2199 we got addition of 40 to mm + # for 2200-2299 we got addition of 60 to mm + @spec birthdate(String.t()) :: {1800..2299, 1..12, 1..3} + def birthdate(pesel), do: do_birthdate(to_list(pesel)) + + # sorry for that but in elixir there is no multiguards + defp do_birthdate([y1, y2, m1, m2, d1, d2 | _]) + when m1 < 2, do: date({19, y1, y2}, {m1, m2}, {d1, d2}) + + defp do_birthdate([y1, y2, m1, m2, d1, d2 | _]) + when m1 < 4, do: date({20, y1, y2}, {m1 - 2, m2}, {d1, d2}) + + defp do_birthdate([y1, y2, m1, m2, d1, d2 | _]) + when m1 < 6, do: date({21, y1, y2}, {m1 - 4, m2}, {d1, d2}) + + defp do_birthdate([y1, y2, m1, m2, d1, d2 | _]) + when m1 < 8, do: date({22, y1, y2}, {m1 - 6, m2}, {d1, d2}) + + defp do_birthdate([y1, y2, m1, m2, d1, d2 | _]) + when m1 < 10, do: date({18, y1, y2}, {m1 - 8, m2}, {d1, d2}) + + defp date(as_year, as_month, as_day) do + {year(as_year), month(as_month), day(as_day)} + end + + defp day({d1, d2}), do: d1 * 10 + d2 + defp month({m1, m2}), do: m1 * 10 + m2 + defp year({century, y1, y2}), do: century * 100 + y1 * 10 + y2 + + # Sex is encoded with 10 digit + # odd for male + # even for female + @spec sex(String.t()) :: :male | :female + def sex(pesel) when is_binary(pesel), do: do_sex(to_list(pesel)) + + defp do_sex([_, _, _, _, _, _, _, _, _, s | _]) when rem(s, 2) == 1, do: :male + defp do_sex(_), do: :female + + # PESEL is zombie if + # date of birth is 123 years before today + @spec zombie?(String.t()) :: boolean() + def zombie?(pesel) when is_binary(pesel), do: is_zombie?(pesel) + + defp is_zombie?(pesel), do: birthdate(pesel) < years_before(today(), @years) + defp years_before({year, month, day}, dist), do: {year - dist, month, day} + + # Transpose from string to list + defp to_list(pesel) do + pesel + |> String.split("", trim: true) + |> Enum.map(&String.to_integer/1) + end +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..42796cd --- /dev/null +++ b/mix.exs @@ -0,0 +1,54 @@ +defmodule ExPesel.MixProject do + use Mix.Project + + @version "0.1.0" + + @description "Library for PESEL number." + @repo_url "https://github.com/crabonature/ex_pesel" + @docs "https://hexdocs.pm/ex_pesel" + + def project do + [ + app: :ex_pesel, + version: @version, + elixir: "~> 1.5", + start_permanent: Mix.env() == :prod, + deps: deps(), + + # Hex + description: @description, + package: package(), + # docs + homepage_url: @docs, + source_url: @repo_url + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + defp package do + [ + files: ["lib/**/*.ex", "mix.exs", "README.md", "LICENSE"], + maintainers: ["Marcin Szczepaniak"], + licenses: ["Apache 2.0"], + links: %{ + "GitHub" => @repo_url, + "Docs" => @docs + } + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:ex_doc, "~> 0.18.1", only: :dev, runtime: false}, + {:dialyxir, "~> 0.5", only: :dev, runtime: false}, + {:credo, "~> 0.8", only: [:dev, :test], runtime: false} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..02233c8 --- /dev/null +++ b/mix.lock @@ -0,0 +1,7 @@ +%{ + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [], [], "hexpm"}, + "credo": {:hex, :credo, "0.8.10", "261862bb7363247762e1063713bb85df2bbd84af8d8610d1272cd9c1943bba63", [], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, + "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, +} diff --git a/test/ex_pesel/pesel_test.exs b/test/ex_pesel/pesel_test.exs new file mode 100644 index 0000000..6a6e473 --- /dev/null +++ b/test/ex_pesel/pesel_test.exs @@ -0,0 +1,4 @@ +defmodule ExPesel.PeselTest do + use ExUnit.Case + doctest ExPesel.Pesel +end diff --git a/test/ex_pesel_test.exs b/test/ex_pesel_test.exs new file mode 100644 index 0000000..42b74cb --- /dev/null +++ b/test/ex_pesel_test.exs @@ -0,0 +1,12 @@ +defmodule ExPeselTest do + use ExUnit.Case + doctest ExPesel + + test "PESEL validation only works for string not integer" do + assert catch_error(ExPesel.valid?(12345678901)) == :function_clause + end + + test "zombie PESEL is more than 123 years before than today: 2018-01-07" do + # TODO: + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()