This commit is contained in:
@@ -1,259 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/apiversion"
|
||||
"github.com/perfect-panel/server/pkg/authmethod"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCheckVerificationCodeCanonicalConsume(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
VerifyCodeExpireTime: 900,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
email := "user@example.com"
|
||||
code := "123456"
|
||||
scene := constant.Register.String()
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email)
|
||||
setEmailCodePayload(t, redisClient, cacheKey, code, time.Now().Unix())
|
||||
|
||||
logic := NewCheckVerificationCodeLogic(context.Background(), svcCtx)
|
||||
req := &types.CheckVerificationCodeRequest{
|
||||
Method: authmethod.Email,
|
||||
Account: email,
|
||||
Code: code,
|
||||
Type: uint8(constant.Register),
|
||||
}
|
||||
|
||||
resp, err := logic.CheckVerificationCode(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.Status)
|
||||
assert.True(t, resp.Exist)
|
||||
|
||||
exists, err := redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), exists)
|
||||
|
||||
resp, err = logic.CheckVerificationCode(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.False(t, resp.Status)
|
||||
assert.False(t, resp.Exist)
|
||||
}
|
||||
|
||||
func TestCheckVerificationCodeLegacyNoConsumeAndType3Mapping(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
VerifyCodeExpireTime: 900,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
email := "legacy@example.com"
|
||||
code := "654321"
|
||||
scene := constant.Security.String()
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email)
|
||||
setEmailCodePayload(t, redisClient, cacheKey, code, time.Now().Unix())
|
||||
|
||||
legacyReq := &types.LegacyCheckVerificationCodeRequest{
|
||||
Email: email,
|
||||
Code: code,
|
||||
Type: 3,
|
||||
}
|
||||
|
||||
normalizedReq, type3Mapped, err := NormalizeLegacyCheckVerificationCodeRequest(legacyReq)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, type3Mapped)
|
||||
assert.Equal(t, uint8(constant.Security), normalizedReq.Type)
|
||||
assert.Equal(t, authmethod.Email, normalizedReq.Method)
|
||||
assert.Equal(t, email, normalizedReq.Account)
|
||||
|
||||
logic := NewCheckVerificationCodeLogic(context.Background(), svcCtx)
|
||||
legacyBehavior := VerifyCodeCheckBehavior{
|
||||
Source: "legacy",
|
||||
Consume: false,
|
||||
LegacyType3Mapped: true,
|
||||
AllowSceneFallback: true,
|
||||
}
|
||||
|
||||
resp, err := logic.CheckVerificationCodeWithBehavior(normalizedReq, legacyBehavior)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.Status)
|
||||
assert.True(t, resp.Exist)
|
||||
|
||||
exists, err := redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), exists)
|
||||
|
||||
resp, err = logic.CheckVerificationCodeWithBehavior(normalizedReq, legacyBehavior)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.Status)
|
||||
|
||||
resp, err = logic.CheckVerificationCode(normalizedReq)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.Status)
|
||||
|
||||
exists, err = redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), exists)
|
||||
}
|
||||
|
||||
func TestCheckVerificationCodeLegacySceneFallback(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
VerifyCodeExpireTime: 900,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
email := "fallback@example.com"
|
||||
code := "778899"
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Register.String(), email)
|
||||
setEmailCodePayload(t, redisClient, cacheKey, code, time.Now().Unix())
|
||||
|
||||
logic := NewCheckVerificationCodeLogic(context.Background(), svcCtx)
|
||||
req := &types.CheckVerificationCodeRequest{
|
||||
Method: authmethod.Email,
|
||||
Account: email,
|
||||
Code: code,
|
||||
Type: uint8(constant.Security),
|
||||
}
|
||||
|
||||
resp, err := logic.CheckVerificationCodeWithBehavior(req, VerifyCodeCheckBehavior{
|
||||
Source: "legacy",
|
||||
Consume: false,
|
||||
AllowSceneFallback: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.Status)
|
||||
|
||||
resp, err = logic.CheckVerificationCodeWithBehavior(req, VerifyCodeCheckBehavior{
|
||||
Source: "legacy",
|
||||
Consume: false,
|
||||
AllowSceneFallback: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.False(t, resp.Status)
|
||||
}
|
||||
|
||||
func setEmailCodePayload(t *testing.T, redisClient *redis.Client, cacheKey string, code string, lastAt int64) {
|
||||
t.Helper()
|
||||
|
||||
payload := CacheKeyPayload{
|
||||
Code: code,
|
||||
LastAt: lastAt,
|
||||
}
|
||||
value, err := json.Marshal(payload)
|
||||
require.NoError(t, err)
|
||||
err = redisClient.Set(context.Background(), cacheKey, value, time.Minute*15).Err()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCheckVerificationCodeWithApiHeaderGate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
header string
|
||||
expectConsume bool
|
||||
}{
|
||||
{name: "missing header", header: "", expectConsume: false},
|
||||
{name: "invalid header", header: "invalid", expectConsume: false},
|
||||
{name: "equal threshold", header: "1.0.0", expectConsume: false},
|
||||
{name: "greater threshold", header: "1.0.1", expectConsume: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
VerifyCodeExpireTime: 900,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
email := "gate@example.com"
|
||||
code := "101010"
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Register.String(), email)
|
||||
setEmailCodePayload(t, redisClient, cacheKey, code, time.Now().Unix())
|
||||
|
||||
logic := NewCheckVerificationCodeLogic(context.Background(), svcCtx)
|
||||
req := &types.CheckVerificationCodeRequest{
|
||||
Method: authmethod.Email,
|
||||
Account: email,
|
||||
Code: code,
|
||||
Type: uint8(constant.Register),
|
||||
}
|
||||
|
||||
resp, err := logic.CheckVerificationCodeWithBehavior(req, VerifyCodeCheckBehavior{
|
||||
Source: "canonical",
|
||||
Consume: apiversion.UseLatest(tt.header, apiversion.DefaultThreshold),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.Status)
|
||||
|
||||
exists, err := redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
require.NoError(t, err)
|
||||
if tt.expectConsume {
|
||||
assert.Equal(t, int64(0), exists)
|
||||
} else {
|
||||
assert.Equal(t, int64(1), exists)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
"testing"
|
||||
|
||||
modelUser "github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
pkgerrors "github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func extractFamilyEntitlementCode(err error) uint32 {
|
||||
if err == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var codeErr *xerr.CodeError
|
||||
if stderrors.As(pkgerrors.Cause(err), &codeErr) {
|
||||
return codeErr.GetErrCode()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func TestBuildEntitlementContext(t *testing.T) {
|
||||
t.Run("default self entitlement", func(t *testing.T) {
|
||||
entitlement := buildEntitlementContext(1001, nil)
|
||||
require.Equal(t, int64(1001), entitlement.EffectiveUserID)
|
||||
require.Equal(t, EntitlementSourceSelf, entitlement.Source)
|
||||
require.Equal(t, int64(0), entitlement.OwnerUserID)
|
||||
require.False(t, entitlement.ReadOnly)
|
||||
})
|
||||
|
||||
t.Run("active family member uses owner entitlement", func(t *testing.T) {
|
||||
entitlement := buildEntitlementContext(1001, &familyEntitlementRelation{
|
||||
Role: modelUser.FamilyRoleMember,
|
||||
FamilyStatus: modelUser.FamilyStatusActive,
|
||||
OwnerUserID: 2001,
|
||||
})
|
||||
require.Equal(t, int64(2001), entitlement.EffectiveUserID)
|
||||
require.Equal(t, EntitlementSourceFamilyOwner, entitlement.Source)
|
||||
require.Equal(t, int64(2001), entitlement.OwnerUserID)
|
||||
require.True(t, entitlement.ReadOnly)
|
||||
})
|
||||
|
||||
t.Run("owner relation keeps self entitlement", func(t *testing.T) {
|
||||
entitlement := buildEntitlementContext(2001, &familyEntitlementRelation{
|
||||
Role: modelUser.FamilyRoleOwner,
|
||||
FamilyStatus: modelUser.FamilyStatusActive,
|
||||
OwnerUserID: 2001,
|
||||
})
|
||||
require.Equal(t, int64(2001), entitlement.EffectiveUserID)
|
||||
require.Equal(t, EntitlementSourceSelf, entitlement.Source)
|
||||
require.False(t, entitlement.ReadOnly)
|
||||
})
|
||||
|
||||
t.Run("disabled family keeps self entitlement", func(t *testing.T) {
|
||||
entitlement := buildEntitlementContext(1001, &familyEntitlementRelation{
|
||||
Role: modelUser.FamilyRoleMember,
|
||||
FamilyStatus: 0,
|
||||
OwnerUserID: 2001,
|
||||
})
|
||||
require.Equal(t, int64(1001), entitlement.EffectiveUserID)
|
||||
require.Equal(t, EntitlementSourceSelf, entitlement.Source)
|
||||
require.False(t, entitlement.ReadOnly)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDenyReadonlyEntitlement(t *testing.T) {
|
||||
require.NoError(t, denyReadonlyEntitlement(&EntitlementContext{ReadOnly: false}))
|
||||
|
||||
err := denyReadonlyEntitlement(&EntitlementContext{
|
||||
Source: EntitlementSourceFamilyOwner,
|
||||
ReadOnly: true,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Equal(t, xerr.FamilyOwnerOperationForbidden, extractFamilyEntitlementCode(err))
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
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"))
|
||||
})
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/perfect-panel/server/internal/model/user"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestResolvePurchaseRoute(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("single mode disabled", func(t *testing.T) {
|
||||
called := false
|
||||
decision, err := ResolvePurchaseRoute(ctx, false, 1, 100, func(ctx context.Context, userID int64) (*user.Subscribe, error) {
|
||||
called = true
|
||||
return nil, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, decision)
|
||||
require.Equal(t, PurchaseRouteNewPurchase, decision.Route)
|
||||
require.Equal(t, int64(100), decision.ResolvedSubscribeID)
|
||||
require.False(t, called)
|
||||
})
|
||||
|
||||
t.Run("single mode but empty user", func(t *testing.T) {
|
||||
decision, err := ResolvePurchaseRoute(ctx, true, 0, 100, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, decision)
|
||||
require.Equal(t, PurchaseRouteNewPurchase, decision.Route)
|
||||
require.Equal(t, int64(100), decision.ResolvedSubscribeID)
|
||||
})
|
||||
|
||||
t.Run("single mode no anchor", func(t *testing.T) {
|
||||
decision, err := ResolvePurchaseRoute(ctx, true, 1, 100, func(ctx context.Context, userID int64) (*user.Subscribe, error) {
|
||||
return nil, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, decision)
|
||||
require.Equal(t, PurchaseRouteNewPurchase, decision.Route)
|
||||
require.Equal(t, int64(100), decision.ResolvedSubscribeID)
|
||||
})
|
||||
|
||||
t.Run("single mode routed to renewal", func(t *testing.T) {
|
||||
decision, err := ResolvePurchaseRoute(ctx, true, 1, 100, func(ctx context.Context, userID int64) (*user.Subscribe, error) {
|
||||
return &user.Subscribe{
|
||||
Id: 11,
|
||||
SubscribeId: 100,
|
||||
OrderId: 7,
|
||||
Token: "token",
|
||||
}, nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, decision)
|
||||
require.Equal(t, PurchaseRoutePurchaseToRenewal, decision.Route)
|
||||
require.Equal(t, int64(100), decision.ResolvedSubscribeID)
|
||||
require.NotNil(t, decision.Anchor)
|
||||
require.Equal(t, int64(11), decision.Anchor.Id)
|
||||
})
|
||||
|
||||
t.Run("single mode plan mismatch", func(t *testing.T) {
|
||||
decision, err := ResolvePurchaseRoute(ctx, true, 1, 100, func(ctx context.Context, userID int64) (*user.Subscribe, error) {
|
||||
return &user.Subscribe{
|
||||
Id: 11,
|
||||
SubscribeId: 200,
|
||||
}, nil
|
||||
})
|
||||
require.ErrorIs(t, err, ErrSingleModePlanMismatch)
|
||||
require.Nil(t, decision)
|
||||
})
|
||||
|
||||
t.Run("single mode anchor query error", func(t *testing.T) {
|
||||
queryErr := errors.New("query failed")
|
||||
decision, err := ResolvePurchaseRoute(ctx, true, 1, 100, func(ctx context.Context, userID int64) (*user.Subscribe, error) {
|
||||
return nil, queryErr
|
||||
})
|
||||
require.ErrorIs(t, err, queryErr)
|
||||
require.Nil(t, decision)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user