Files
llink/go/internal/waitlist/service_test.go
T
2026-03-29 09:04:15 -07:00

210 lines
5.7 KiB
Go

package waitlist_test
import (
"context"
"os"
"testing"
"github.com/flowy-live/llink/internal/testhelper"
"github.com/flowy-live/llink/internal/waitlist"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/stretchr/testify/assert"
)
var dbPool *pgxpool.Pool
func TestMain(m *testing.M) {
dbPool = testhelper.SetupTestDB()
defer testhelper.TeardownTestDB()
ret := m.Run()
os.Exit(ret)
}
func TestWaitlistService_AddAndGet(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
email := "newuser@example.com"
metadata := map[string]string{"source": "landing-page", "plan": "pro"}
// Add to waitlist
err := svc.AddToWaitlist(ctx, email, metadata)
assert.NoError(t, err)
// Get entry by email
entry, err := svc.GetWaitlistEntryByEmail(ctx, email)
assert.NoError(t, err)
assert.Equal(t, email, entry.Email)
assert.Equal(t, "landing-page", entry.Metadata["source"])
assert.Equal(t, "pro", entry.Metadata["plan"])
assert.NotZero(t, entry.CreatedAt)
assert.Nil(t, entry.InvitedAt)
// Clean up
_, err = dbPool.Exec(ctx, `DELETE FROM waitlist_entries WHERE email = $1`, email)
assert.NoError(t, err)
}
func TestWaitlistService_AddDuplicate(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
email := "duplicate@example.com"
err := svc.AddToWaitlist(ctx, email, nil)
assert.NoError(t, err)
// Adding the same email again should return AlreadyInWaitlistError
err = svc.AddToWaitlist(ctx, email, nil)
assert.ErrorIs(t, err, waitlist.AlreadyInWaitlistError)
// Clean up
_, err = dbPool.Exec(ctx, `DELETE FROM waitlist_entries WHERE email = $1`, email)
assert.NoError(t, err)
}
func TestWaitlistService_AddNilMetadata(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
email := "nilmeta@example.com"
err := svc.AddToWaitlist(ctx, email, nil)
assert.NoError(t, err)
entry, err := svc.GetWaitlistEntryByEmail(ctx, email)
assert.NoError(t, err)
assert.NotNil(t, entry.Metadata)
// Clean up
_, err = dbPool.Exec(ctx, `DELETE FROM waitlist_entries WHERE email = $1`, email)
assert.NoError(t, err)
}
func TestWaitlistService_GetEntryNotFound(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
_, err := svc.GetWaitlistEntryByEmail(ctx, "nonexistent@example.com")
assert.ErrorIs(t, err, waitlist.EntryNotFoundError)
}
func TestWaitlistService_MarkInvited(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
email := "invite-me@example.com"
err := svc.AddToWaitlist(ctx, email, nil)
assert.NoError(t, err)
// Mark as invited
err = svc.MarkWaitlistEntryInvited(ctx, email)
assert.NoError(t, err)
// Verify invited_at is set
entry, err := svc.GetWaitlistEntryByEmail(ctx, email)
assert.NoError(t, err)
assert.NotNil(t, entry.InvitedAt)
// Clean up
_, err = dbPool.Exec(ctx, `DELETE FROM waitlist_entries WHERE email = $1`, email)
assert.NoError(t, err)
}
func TestWaitlistService_MarkInvitedNotFound(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
err := svc.MarkWaitlistEntryInvited(ctx, "ghost@example.com")
assert.ErrorIs(t, err, waitlist.EntryNotFoundError)
}
func TestWaitlistService_GetWaitlistFilters(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
uninvitedEmail := "uninvited-filter@example.com"
invitedEmail := "invited-filter@example.com"
// Add two entries
err := svc.AddToWaitlist(ctx, uninvitedEmail, nil)
assert.NoError(t, err)
err = svc.AddToWaitlist(ctx, invitedEmail, nil)
assert.NoError(t, err)
// Mark one as invited
err = svc.MarkWaitlistEntryInvited(ctx, invitedEmail)
assert.NoError(t, err)
// Filter: all
entries, err := svc.GetWaitlist(ctx, waitlist.GetWaitlistFilterAll)
assert.NoError(t, err)
emails := extractEmails(entries)
assert.Contains(t, emails, uninvitedEmail)
assert.Contains(t, emails, invitedEmail)
// Filter: invited only
entries, err = svc.GetWaitlist(ctx, waitlist.GetWaitlistFilterInvitedOnly)
assert.NoError(t, err)
emails = extractEmails(entries)
assert.Contains(t, emails, invitedEmail)
assert.NotContains(t, emails, uninvitedEmail)
// Filter: uninvited only
entries, err = svc.GetWaitlist(ctx, waitlist.GetWaitlistFilterUninvitedOnly)
assert.NoError(t, err)
emails = extractEmails(entries)
assert.Contains(t, emails, uninvitedEmail)
assert.NotContains(t, emails, invitedEmail)
// Clean up
_, err = dbPool.Exec(ctx, `DELETE FROM waitlist_entries WHERE email IN ($1, $2)`, uninvitedEmail, invitedEmail)
assert.NoError(t, err)
}
func TestWaitlistService_EmailNormalization(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
// Add with uppercase email
err := svc.AddToWaitlist(ctx, "UPPER@EXAMPLE.COM", nil)
assert.NoError(t, err)
// Should find with lowercase
entry, err := svc.GetWaitlistEntryByEmail(ctx, "upper@example.com")
assert.NoError(t, err)
assert.Equal(t, "upper@example.com", entry.Email)
// Adding with different case should be a duplicate
err = svc.AddToWaitlist(ctx, "Upper@Example.Com", nil)
assert.ErrorIs(t, err, waitlist.AlreadyInWaitlistError)
// Clean up
_, err = dbPool.Exec(ctx, `DELETE FROM waitlist_entries WHERE email = $1`, "upper@example.com")
assert.NoError(t, err)
}
func TestWaitlistService_InvalidEmail(t *testing.T) {
ctx := context.Background()
svc := waitlist.NewService(dbPool)
err := svc.AddToWaitlist(ctx, "not-an-email", nil)
assert.Error(t, err)
_, err = svc.GetWaitlistEntryByEmail(ctx, "not-an-email")
assert.Error(t, err)
err = svc.MarkWaitlistEntryInvited(ctx, "not-an-email")
assert.Error(t, err)
}
func extractEmails(entries []*waitlist.WaitlistEntry) []string {
emails := make([]string, len(entries))
for i, e := range entries {
emails[i] = e.Email
}
return emails
}