simpleshop_theme/test/simpleshop_theme/vault_test.exs
Jamey Greenwood c5c06d9979 feat: add Products context with provider integration (Phase 1)
Implement the schema foundation for syncing products from POD providers
like Printify. This includes encrypted credential storage, product/variant
schemas, and an Oban worker for background sync.

New modules:
- Vault: AES-256-GCM encryption for API keys
- Products context: CRUD and sync operations for products
- Provider behaviour: abstraction for POD provider implementations
- ProductSyncWorker: Oban job for async product sync

Schemas: ProviderConnection, Product, ProductImage, ProductVariant

Also reorganizes Printify client to lib/simpleshop_theme/clients/ and
mockup generator to lib/simpleshop_theme/mockups/ for better structure.

134 tests added covering all new functionality.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 20:32:20 +00:00

107 lines
3.0 KiB
Elixir

defmodule SimpleshopTheme.VaultTest do
use SimpleshopTheme.DataCase, async: true
alias SimpleshopTheme.Vault
describe "encrypt/1 and decrypt/1" do
test "round-trips a string successfully" do
plaintext = "my_secret_api_key_12345"
assert {:ok, ciphertext} = Vault.encrypt(plaintext)
assert is_binary(ciphertext)
assert ciphertext != plaintext
assert {:ok, decrypted} = Vault.decrypt(ciphertext)
assert decrypted == plaintext
end
test "produces different ciphertext for same plaintext (random IV)" do
plaintext = "same_secret"
{:ok, ciphertext1} = Vault.encrypt(plaintext)
{:ok, ciphertext2} = Vault.encrypt(plaintext)
assert ciphertext1 != ciphertext2
# Both should decrypt to same value
assert {:ok, ^plaintext} = Vault.decrypt(ciphertext1)
assert {:ok, ^plaintext} = Vault.decrypt(ciphertext2)
end
test "handles empty string" do
assert {:ok, ciphertext} = Vault.encrypt("")
assert {:ok, ""} = Vault.decrypt(ciphertext)
end
test "handles unicode characters" do
plaintext = "héllo wörld 你好 🎉"
{:ok, ciphertext} = Vault.encrypt(plaintext)
{:ok, decrypted} = Vault.decrypt(ciphertext)
assert decrypted == plaintext
end
test "handles long strings" do
plaintext = String.duplicate("a", 10_000)
{:ok, ciphertext} = Vault.encrypt(plaintext)
{:ok, decrypted} = Vault.decrypt(ciphertext)
assert decrypted == plaintext
end
test "encrypt/1 returns {:ok, nil} for nil input" do
assert {:ok, nil} = Vault.encrypt(nil)
end
test "decrypt/1 returns {:ok, nil} for nil input" do
assert {:ok, nil} = Vault.decrypt(nil)
end
test "decrypt/1 returns {:ok, empty} for empty string" do
assert {:ok, ""} = Vault.decrypt("")
end
test "decrypt/1 returns error for invalid ciphertext" do
assert {:error, :invalid_ciphertext} = Vault.decrypt("not_valid")
assert {:error, :invalid_ciphertext} = Vault.decrypt(<<1, 2, 3>>)
end
test "decrypt/1 returns error for tampered ciphertext" do
{:ok, ciphertext} = Vault.encrypt("secret")
# Tamper with the ciphertext (flip a bit in the middle)
tampered = :binary.bin_to_list(ciphertext)
middle = div(length(tampered), 2)
tampered = List.update_at(tampered, middle, &Bitwise.bxor(&1, 0xFF))
tampered = :binary.list_to_bin(tampered)
assert {:error, :decryption_failed} = Vault.decrypt(tampered)
end
end
describe "encrypt!/1 and decrypt!/1" do
test "round-trips successfully" do
plaintext = "test_secret"
ciphertext = Vault.encrypt!(plaintext)
decrypted = Vault.decrypt!(ciphertext)
assert decrypted == plaintext
end
test "encrypt!/1 raises on invalid input type" do
assert_raise FunctionClauseError, fn ->
Vault.encrypt!(123)
end
end
test "decrypt!/1 raises on invalid ciphertext" do
assert_raise RuntimeError, ~r/Decryption failed/, fn ->
Vault.decrypt!("invalid")
end
end
end
end