feat(user): magic_link_tokens table + repository (ADR-0028 Phase A.3) (#61)
All checks were successful
CI/CD Pipeline / Build Docker Cache (push) Successful in 8s
CI/CD Pipeline / CI Pipeline (push) Successful in 5m11s
CI/CD Pipeline / Trigger Docker Push (push) Successful in 6s

Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #61.
This commit is contained in:
2026-05-05 11:24:06 +02:00
committed by arcodange
parent b3027d2669
commit c9ab876dfe
4 changed files with 425 additions and 3 deletions

View File

@@ -0,0 +1,78 @@
package user
import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestGenerateMagicLinkToken_ShapeAndHashAgree confirms the contract that
// HashMagicLinkToken(plaintext) == returned hashHex. Without that, the
// consume handler can never look up what request stored.
func TestGenerateMagicLinkToken_ShapeAndHashAgree(t *testing.T) {
plain, hashHex, err := GenerateMagicLinkToken()
require.NoError(t, err)
assert.NotEmpty(t, plain)
assert.NotEmpty(t, hashHex)
assert.Len(t, hashHex, 64, "sha256 hex = 64 chars")
assert.Equal(t, hashHex, HashMagicLinkToken(plain),
"GenerateMagicLinkToken must return a hash that matches HashMagicLinkToken(plain)")
}
// TestGenerateMagicLinkToken_PlainIsURLSafeBase64 confirms the link can
// be embedded in a URL without further escaping. RawURLEncoding => no
// "/", "+", or "=" padding chars.
func TestGenerateMagicLinkToken_PlainIsURLSafeBase64(t *testing.T) {
plain, _, err := GenerateMagicLinkToken()
require.NoError(t, err)
for _, bad := range []string{"/", "+", "="} {
assert.False(t, strings.Contains(plain, bad),
"plaintext token must not contain %q (URL-unsafe)", bad)
}
decoded, err := base64.RawURLEncoding.DecodeString(plain)
require.NoError(t, err, "plaintext must round-trip through RawURLEncoding")
assert.Len(t, decoded, 32, "32 bytes of entropy")
}
// TestGenerateMagicLinkToken_Unique confirms two consecutive calls
// produce different tokens (not a deterministic seeding bug).
func TestGenerateMagicLinkToken_Unique(t *testing.T) {
a, ah, err := GenerateMagicLinkToken()
require.NoError(t, err)
b, bh, err := GenerateMagicLinkToken()
require.NoError(t, err)
assert.NotEqual(t, a, b, "plaintexts must differ between calls")
assert.NotEqual(t, ah, bh, "hashes must differ between calls")
}
// TestHashMagicLinkToken_StableAndCorrect confirms HashMagicLinkToken is
// a pure function (same input -> same output) AND that it produces the
// expected sha256 hex digest. Cross-checked against the stdlib so we
// catch any accidental algorithm swap.
func TestHashMagicLinkToken_StableAndCorrect(t *testing.T) {
const sample = "abc123-test-token"
got1 := HashMagicLinkToken(sample)
got2 := HashMagicLinkToken(sample)
assert.Equal(t, got1, got2, "HashMagicLinkToken must be deterministic")
sum := sha256.Sum256([]byte(sample))
want := hex.EncodeToString(sum[:])
assert.Equal(t, want, got1, "HashMagicLinkToken must be sha256 hex")
}
// TestHashMagicLinkToken_DiffersOnDifferentInput is the tautological
// counter-test of stability : different inputs -> different outputs.
// Catches the (unlikely) case where someone replaces the impl with
// a constant.
func TestHashMagicLinkToken_DiffersOnDifferentInput(t *testing.T) {
assert.NotEqual(t, HashMagicLinkToken("a"), HashMagicLinkToken("b"))
}