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 emailsALLOWED_EMAILS="user@example.com,admin@test.co.uk"- Allow specific emailsALLOWED_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
-
Create all files with the content above
-
Initialize the Mix project:
mix init -
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:
- Mix cleans the old dependency (removes it from
_buildanddeps) - Mix fetches the new dependency from the git repository
- Mix compiles the new dependency
- 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/1works the same - Same function signatures - no API changes required
- Cache cleared - you'll need to run
mix deps.getto 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.