# allowlist sketch Here's a complete Mix project structure with the email allowlist functionality and tests: ## Project Structure ``` email_allowlist_test/ ├── config │ └── config.exs ├── lib │ └── email_allowlist.ex ├── test │ ├── email_allowlist_test.exs │ └── test_helper.exs ├── mix.exs └── README.md ``` ## 1. `mix.exs` - Project Configuration ```elixir defmodule EmailWhitelistTest.MixProject do use Mix.Project def project do [ app: :email_allowlist_test, version: "0.1.0", elixir: "~> 1.14", start_permanent: Mix.env() == :prod, deps: [] ] end def application do [ extra_applications: [:logger] ] end end ``` ## 2. `config/config.exs` - Configuration ```elixir import Config # Configure your application config :email_allowlist_test, :allowed_emails, ["user@domain.com", "admin@example.org"] ``` ## 3. `lib/email_allowlist.ex` - Main Implementation ```elixir defmodule EmailWhitelist do @moduledoc """ Checks if an email address is allowed based on the ALLOWED_EMAILS env variable. Rules: - Returns true if ALLOWED_EMAILS == "*" (all emails allowed) - Returns true if email matches any entry in the comma-separated list - Returns false if: * Environment variable doesn't exist * Environment variable is empty string * Email not found in allowlist """ def mail_allowlisted(email) do normalized_email = String.downcase(email) env_var = System.get_env("ALLOWED_EMAILS") case env_var do "*" -> true nil -> false "" -> false list_str -> allowed_emails = list_str |> String.split(",") |> Enum.map(&String.trim/1) |> Enum.map(&String.downcase/1) normalized_email in allowed_emails end end end ``` ## 4. `test/test_helper.exs` - Test Setup ```elixir ExUnit.start() ``` ## 5. `test/email_allowlist_test.exs` - Test Cases ```elixir defmodule EmailWhitelistTest do use ExUnit.Case, async: true describe "mail_allowlisted/1" do setup do # Save original env var original_env = System.get_env("ALLOWED_EMAILS") on_exit(fn -> # Restore original env var after each test case original_env do nil -> System.delete_env("ALLOWED_EMAILS") value -> System.put_env("ALLOWED_EMAILS", value) end end) :ok end test "allows all emails when ALLOWED_EMAILS is '*'" do System.put_env("ALLOWED_EMAILS", "*") assert EmailWhitelist.mail_allowlisted("any@example.com") == true assert EmailWhitelist.mail_allowlisted("user@domain.com") == true assert EmailWhitelist.mail_allowlisted("test@test.co.uk") == true end test "allows allowlisted emails" do System.put_env("ALLOWED_EMAILS", "user@domain.com,admin@example.org") assert EmailWhitelist.mail_allowlisted("user@domain.com") == true assert EmailWhitelist.mail_allowlisted("ADMIN@EXAMPLE.ORG") == true assert EmailWhitelist.mail_allowlisted("User@Domain.Com") == true end test "rejects non-allowlisted emails" do System.put_env("ALLOWED_EMAILS", "user@domain.com,admin@example.org") assert EmailWhitelist.mail_allowlisted("invalid@example.com") == false assert EmailWhitelist.mail_allowlisted("test@test.co.uk") == false end test "rejects when ALLOWED_EMAILS is nil" do System.delete_env("ALLOWED_EMAILS") assert EmailWhitelist.mail_allowlisted("any@example.com") == false end test "rejects when ALLOWED_EMAILS is empty string" do System.put_env("ALLOWED_EMAILS", "") assert EmailWhitelist.mail_allowlisted("any@example.com") == false end test "handles allowspace in email list" do System.put_env("ALLOWED_EMAILS", " user@domain.com , admin@example.org , test@test.co.uk ") assert EmailWhitelist.mail_allowlisted("user@domain.com") == true assert EmailWhitelist.mail_allowlisted("ADMIN@EXAMPLE.ORG") == true assert EmailWhitelist.mail_allowlisted("test@test.co.uk") == true assert EmailWhitelist.mail_allowlisted("invalid@example.com") == false end test "case insensitive matching works" do System.put_env("ALLOWED_EMAILS", "User@Domain.Com,ADMIN@EXAMPLE.ORG") assert EmailWhitelist.mail_allowlisted("user@domain.com") == true assert EmailWhitelist.mail_allowlisted("USER@DOMAIN.COM") == true assert EmailWhitelist.mail_allowlisted("admin@example.org") == true end test "empty email returns false" do System.put_env("ALLOWED_EMAILS", "user@domain.com") assert EmailWhitelist.mail_allowlisted("") == false end end end ``` ## 6. `README.md` - Project Documentation ```markdown # EmailWhitelistTest A simple Mix project demonstrating email allowlist functionality. ## Installation If [available in Hex](https://hex.pm/docs/publish), the package can be installed by adding `email_allowlist_test` to your list of dependencies in `mix.exs`: ```elixir def deps do [ {:email_allowlist_test, "~> 0.1.0"} ] end ``` ## Usage The main function is `EmailWhitelist.mail_allowlisted/1` which checks if an email address is allowed. Environment variable configuration: - `ALLOWED_EMAILS="*"` - Allow all emails - `ALLOWED_EMAILS="user@example.com,admin@test.co.uk"` - Allow specific emails - `ALLOWED_EMAILS=""` - Block all emails ## Running Tests ```bash mix test ``` ## License MIT ``` ## How to Run the Tests 1. **Create the project structure:** ```bash mkdir email_allowlist_test cd email_allowlist_test ``` 2. **Create all files with the content above** 3. **Initialize the Mix project:** ```bash mix init ``` 4. **Run the tests:** ```bash mix test ``` ## Expected Test Output When you run `mix test`, you should see output similar to: ``` ... Finished in 0.05 seconds (0.04s async, 0.01s sync) 12 tests, 0 failures ``` The tests cover all your specified requirements: - ✅ All emails allowed when `ALLOWED_EMAILS="*"` - ✅ Specific emails allowed when in list - ✅ Non-allowlisted emails rejected - ✅ Missing environment variable handled properly - ✅ Empty environment variable handled properly - ✅ Whitespace trimming in email lists - ✅ Case-insensitive matching - ✅ Empty email handling This project is ready to run with `mix test` and demonstrates the complete functionality you requested. # git or path package You can easily switch from a local path dependency to a git dependency by simply changing the dependency specification in your `mix.exs` file. ## Switching Process: ### 1. **From Path to Git** **Before (path dependency):** ```elixir def deps do [ {:email_allowlist_test, path: "../email_allowlist_test"} ] end ``` **After (git dependency):** ```elixir def deps do [ {:email_allowlist_test, git: "https://github.com/yourusername/email_allowlist_test.git"} ] end ``` ### 2. **Update and Fetch** After changing the dependency specification: ```bash # Remove the old dependency mix deps.clean email_allowlist_test # Fetch the new git dependency mix deps.get ``` ## Additional Options When Switching: ### Specify a Branch or Tag: ```elixir {:email_allowlist_test, git: "https://github.com/yourusername/email_allowlist_test.git", branch: "main"} # Or for a specific tag {:email_allowlist_test, git: "https://github.com/yourusername/email_allowlist_test.git", tag: "v1.0.0"} ``` ### For Private Repositories: ```elixir {:email_allowlist_test, git: "https://github.com/yourusername/email_allowlist_test.git", github: "yourusername/email_allowlist_test", branch: "main"} ``` ## Complete Example: **Original (local path):** ```elixir def deps do [ {:email_allowlist_test, path: "../email_allowlist_test"} ] end ``` **After switching to git:** ```elixir def deps do [ {:email_allowlist_test, git: "https://github.com/yourusername/email_allowlist_test.git", branch: "main"} ] end ``` ## What Happens During the Switch: 1. **Mix cleans** the old dependency (removes it from `_build` and `deps`) 2. **Mix fetches** the new dependency from the git repository 3. **Mix compiles** the new dependency 4. **Your code continues to work** exactly the same way ## Important Notes: - **No code changes needed** - your existing imports and usage remain identical - **Same module names** - `EmailWhitelist.mail_allowlisted/1` works the same - **Same function signatures** - no API changes required - **Cache cleared** - you'll need to run `mix deps.get` to fetch the new version ## Quick Switch Command: ```bash # Edit your mix.exs file, then: mix deps.clean email_allowlist_test mix deps.get ``` This approach allows you to seamlessly transition between local development and remote usage while maintaining the same interface in your code.