package common import ( "context" "errors" "net/url" "testing" "github.com/alicebob/miniredis/v2" "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/svc" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" ) func buildInviteResolverForTest(t *testing.T, cfg config.Config) (*InviteLinkResolver, *miniredis.Miniredis) { t.Helper() redisServer, err := miniredis.Run() require.NoError(t, err) t.Cleanup(func() { redisServer.Close() }) redisClient := redis.NewClient(&redis.Options{ Addr: redisServer.Addr(), DB: 0, }) t.Cleanup(func() { _ = redisClient.Close() }) serviceCtx := &svc.ServiceContext{ Config: cfg, Redis: redisClient, } resolver := NewInviteLinkResolver(context.Background(), serviceCtx) return resolver, redisServer } func TestInviteLinkResolverResolveInviteLink(t *testing.T) { t.Run("kutt disabled returns long link", func(t *testing.T) { cfg := config.Config{} cfg.Kutt.TargetURL = "https://example.com/register" resolver, _ := buildInviteResolverForTest(t, cfg) link := resolver.ResolveInviteLink("abc123") require.Equal(t, "https://example.com/register?ic=abc123", link) }) t.Run("cache hit returns cached short link", func(t *testing.T) { cfg := config.Config{} cfg.Kutt.Enable = true cfg.Kutt.ApiURL = "https://kutt.local/api/v2" cfg.Kutt.ApiKey = "token" cfg.Kutt.TargetURL = "https://example.com/register" resolver, redisServer := buildInviteResolverForTest(t, cfg) redisServer.Set(inviteShortLinkCachePrefix+"abc123", "https://sho.rt/cached") called := 0 resolver.createShortLink = func(ctx context.Context, targetURL, domain string) (string, error) { called++ return "", errors.New("should not call createShortLink on cache hit") } link := resolver.ResolveInviteLink("abc123") require.Equal(t, "https://sho.rt/cached", link) require.Equal(t, 0, called) }) t.Run("cache miss kutt success returns short link and writes cache", func(t *testing.T) { cfg := config.Config{} cfg.Kutt.Enable = true cfg.Kutt.ApiURL = "https://kutt.local/api/v2" cfg.Kutt.ApiKey = "token" cfg.Kutt.TargetURL = "https://example.com/register" resolver, _ := buildInviteResolverForTest(t, cfg) resolver.createShortLink = func(ctx context.Context, targetURL, domain string) (string, error) { return "https://sho.rt/new", nil } link := resolver.ResolveInviteLink("abc123") require.Equal(t, "https://sho.rt/new", link) cached := resolver.getCachedShortLink("abc123") require.Equal(t, "https://sho.rt/new", cached) }) t.Run("kutt failure falls back to long link", func(t *testing.T) { cfg := config.Config{} cfg.Kutt.Enable = true cfg.Kutt.ApiURL = "https://kutt.local/api/v2" cfg.Kutt.ApiKey = "token" cfg.Kutt.TargetURL = "https://example.com/register" resolver, _ := buildInviteResolverForTest(t, cfg) resolver.createShortLink = func(ctx context.Context, targetURL, domain string) (string, error) { return "", errors.New("kutt request failed") } link := resolver.ResolveInviteLink("abc123") require.Equal(t, "https://example.com/register?ic=abc123", link) }) t.Run("long link preserves existing query string", func(t *testing.T) { cfg := config.Config{} cfg.Kutt.TargetURL = "https://example.com/register?channel=ios" resolver, _ := buildInviteResolverForTest(t, cfg) link := resolver.ResolveInviteLink("abc123") parsed, err := url.Parse(link) require.NoError(t, err) require.Equal(t, "https", parsed.Scheme) require.Equal(t, "example.com", parsed.Host) require.Equal(t, "/register", parsed.Path) require.Equal(t, "ios", parsed.Query().Get("channel")) require.Equal(t, "abc123", parsed.Query().Get("ic")) }) t.Run("kutt target preserves existing query string", func(t *testing.T) { cfg := config.Config{} cfg.Kutt.Enable = true cfg.Kutt.ApiURL = "https://kutt.local/api/v2" cfg.Kutt.ApiKey = "token" cfg.Kutt.TargetURL = "https://example.com/register?channel=ios" resolver, _ := buildInviteResolverForTest(t, cfg) capturedTargetURL := "" resolver.createShortLink = func(ctx context.Context, targetURL, domain string) (string, error) { capturedTargetURL = targetURL return "https://sho.rt/query", nil } link := resolver.ResolveInviteLink("abc123") require.Equal(t, "https://sho.rt/query", link) parsed, err := url.Parse(capturedTargetURL) require.NoError(t, err) require.Equal(t, "ios", parsed.Query().Get("channel")) require.Equal(t, "abc123", parsed.Query().Get("ic")) }) }