From 6e59041b17f62cde3fd05b1525115391af381d1e Mon Sep 17 00:00:00 2001 From: Austin Reifsteck Date: Fri, 28 Jul 2023 12:02:33 -0400 Subject: [PATCH] added more string generators and tests --- CHANGELOG.md | 4 ++ lib/faker/string.ex | 99 +++++++++++++++++++++++++++++++++++++- mix.exs | 1 + mix.lock | 1 + test/faker/string_test.exs | 62 ++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef1e56ead..b6cf5551b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ Change log itself follows [Keep a CHANGELOG](http://keepachangelog.com) format. ### Added - `Faker.Fruits.En` - add english fruits [[@KevinDaSilvaS](https://github.com/KevinDaSilvaS)] +- `Faker.String.from_regex/1` - Adds the ability to generate a random string that conforms to given regex [[@apreifsteck](https://github.com/apreifsteck)] +- `Faker.String.alpha/1` - Creates a random string with only alphabetical characters [[@apreifsteck](https://github.com/apreifsteck)] +- `Faker.String.alphanumeric/1` - Creates a random string of the given length with only alphabetical or numerical characters [[@apreifsteck](https://github.com/apreifsteck)] +- `Faker.String.numeric/1` - Creates a random string of the given length with only numerical characters [[@apreifsteck](https://github.com/apreifsteck)] ### Changed - `Faker.Commerce.PtBr` - add more products in product_name_product [[@igorgbr](https://github.com/igorgbr)] diff --git a/lib/faker/string.ex b/lib/faker/string.ex index 422a377b5..7139ff9b2 100644 --- a/lib/faker/string.ex +++ b/lib/faker/string.ex @@ -1,5 +1,6 @@ defmodule Faker.String do import Faker, only: [sampler: 2] + @default_len 8 @moduledoc """ Function for generating Strings @@ -20,13 +21,109 @@ defmodule Faker.String do "KLJyZ7xbfJZPMy3J7dAsyfOB3vnZIqFGv4VQil8D/xh1C/Nj9K7xJk47zJtcKsy5mjpJk61Wt3jcJu3bfgwuScTmOOYt4ykzvDUl" """ @spec base64(pos_integer) :: String.t() - def base64(length \\ 8) do + def base64(length \\ @default_len) do length |> Faker.random_bytes() |> Base.encode64() |> binary_part(0, length) end + @doc """ + Returns a String that conforms to the given regex. + + ## Examples + + iex> Faker.String.from_regex(~r/a|b/) + "a" + iex> Faker.String.from_regex(~r/a|b/) + "b" + iex> Faker.String.from_regex(~r/[0-9]{3}-[0-9]{3}-[0-9]{4}/) + "240-614-2330" + """ + @spec from_regex(regex :: Regex.t()) :: String.t() + def from_regex(regex) when is_struct(regex, Regex) do + regex + |> Randex.stream() + |> Enum.take(1) + |> hd() + end + + def from_regex(arg), + do: raise(ArgumentError, "Must provide a Regex struct, got #{inspect(arg)}") + + defguardp non_neg_int(value) when is_integer(value) and value > 0 + + @doc """ + Returns a string of digits of the given length + + ## Examples + + iex> Faker.String.numeric() + "12345678" + iex> Faker.String.numeric() + "16820381" + iex> Faker.String.numeric(2) + "12" + iex> Faker.String.numeric(-2) + ** (ArgumentError) len must be greater than 0 + """ + @spec numeric(len :: pos_integer()) :: String.t() + def numeric(len \\ @default_len) + + def numeric(len) when non_neg_int(len) do + from_regex(~r/[0-9]{#{len}}/) + end + + def numeric(_), do: raise_len_too_small() + + @doc """ + Returns a string of letters and digits of the given length + + ## Examples + + iex> Faker.String.alphanumeric() + "12a45Hn8" + iex> Faker.String.alphanumeric() + "S6n2o3d1" + iex> Faker.String.alphanumeric(2) + "a1" + iex> Faker.String.alphanumeric(-2) + ** (ArgumentError) len must be an integer greater than 0 + """ + @spec alphanumeric(len :: pos_integer()) :: String.t() + def alphanumeric(len \\ @default_len) + + def alphanumeric(len) when non_neg_int(len) do + from_regex(~r/[A-Za-z0-9]{#{len}}/) + end + + def alphanumeric(_), do: raise_len_too_small() + + @doc """ + Returns a string of letters of the given length + + ## Examples + + iex> Faker.String.alphanumeric() + "aNiReSoa" + iex> Faker.String.alphanumeric() + "OrlaOhuo" + iex> Faker.String.alphanumeric(2) + "cB" + iex> Faker.String.alphanumeric(-2) + ** (ArgumentError) len must be an integer greater than 0 + """ + @spec alpha(len :: pos_integer()) :: String.t() + def alpha(len \\ @default_len) + + def alpha(len) when non_neg_int(len) do + from_regex(~r/[A-Za-z]{#{len}}/) + end + + def alpha(_len), do: raise_len_too_small() + + defp raise_len_too_small(), do: raise(ArgumentError, "len must be an integer greater than 0") + @doc """ Returns a random string taken from the [Big List of Naughty Strings](https://github.com/minimaxir/big-list-of-naughty-strings). diff --git a/mix.exs b/mix.exs index 9392ddadd..5eb2a90ae 100644 --- a/mix.exs +++ b/mix.exs @@ -41,6 +41,7 @@ defmodule Faker.Mixfile do defp deps do [ + {:randex, "~> 0.4.0", runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:earmark, ">= 0.0.0", only: :dev, runtime: false}, {:credo, ">= 0.0.0", only: [:dev, :test], runtime: false}, diff --git a/mix.lock b/mix.lock index 460daff90..fe61a0a8a 100644 --- a/mix.lock +++ b/mix.lock @@ -13,4 +13,5 @@ "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, + "randex": {:hex, :randex, "0.4.0", "f76712da3663dd7d4166e017829ec428dc67df9e11b82d923dfd6f9b4854b373", [:mix], [{:stream_data, "~> 0.4", [hex: :stream_data, repo: "hexpm", optional: true]}], "hexpm", "f803f256542bd264acea832a69f6e5fea3df4cd3f8a9f03588e5593f04c61095"}, } diff --git a/test/faker/string_test.exs b/test/faker/string_test.exs index 6d62a6a57..3dadee946 100644 --- a/test/faker/string_test.exs +++ b/test/faker/string_test.exs @@ -3,6 +3,68 @@ defmodule Faker.StringTest do doctest Faker.String + describe "from_regex/1" do + test "is random" do + generated_values = for _ <- 0..25, do: Faker.String.from_regex(~r/a|b/) + assert "a" in generated_values and "b" in generated_values + end + + test "conforms to regex" do + regex = ~r/[0-9]{3}-[0-9]{3}-[0-9]{4}/ + assert Regex.match?(regex, Faker.String.from_regex(regex)) + end + end + + describe "alphanumeric/1" do + test "is random" do + generated_values = for _ <- 0..25, do: Faker.String.alphanumeric(100) + assert generated_values |> MapSet.new() |> MapSet.size() == length(generated_values) + end + + test "has only alphabetical and numeric characters" do + generated_values = for _ <- 0..25, do: Faker.String.alphanumeric(100) + + assert [""] = + generated_values + |> Enum.map(&String.replace(&1, ~r/[a-zA-Z0-9]/, "")) + |> Enum.dedup() + end + end + + describe "numeric/1" do + test "is random" do + generated_values = for _ <- 0..25, do: Faker.String.numeric(100) + assert generated_values |> MapSet.new() |> MapSet.size() == length(generated_values) + end + + test "has only alphabetical and numeric characters" do + generated_values = for _ <- 0..25, do: Faker.String.numeric(100) + + assert [""] = + generated_values + |> Enum.map(&String.replace(&1, ~r/[0-9]/, "")) + |> Enum.dedup() + end + end + + describe "alpha/1" do + test "is random" do + generated_values = for _ <- 0..25, do: Faker.String.alpha(100) + + assert generated_values |> MapSet.new() |> MapSet.size() == + length(generated_values) + end + + test "has only alphabetical characters" do + generated_values = for _ <- 0..25, do: Faker.String.alpha(100) + + assert [""] = + generated_values + |> Enum.map(&String.replace(&1, ~r/[a-zA-Z]/, "")) + |> Enum.dedup() + end + end + test "base64/1" do length = Faker.String.base64(12) |> String.length() assert length == 12