2025-09-16 09:32:40 +00:00

8.8 KiB

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

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

import Config

# Configure your application
config :email_allowlist_test, :allowed_emails,
  ["user@domain.com", "admin@example.org"]

3. lib/email_allowlist.ex - Main Implementation

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

ExUnit.start()

5. test/email_allowlist_test.exs - Test Cases

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

# 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

mix test

License

MIT


## How to Run the Tests

1. **Create the project structure:**
   ```bash
   mkdir email_allowlist_test
   cd email_allowlist_test
  1. Create all files with the content above

  2. Initialize the Mix project:

    mix init
    
  3. Run the tests:

    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):

def deps do
  [
    {:email_allowlist_test, path: "../email_allowlist_test"}
  ]
end

After (git dependency):

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:

# 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:

{: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:

{:email_allowlist_test, 
 git: "https://github.com/yourusername/email_allowlist_test.git",
 github: "yourusername/email_allowlist_test",
 branch: "main"}

Complete Example:

Original (local path):

def deps do
  [
    {:email_allowlist_test, path: "../email_allowlist_test"}
  ]
end

After switching to git:

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:

# 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.