181 lines
6.6 KiB
Go
181 lines
6.6 KiB
Go
package luckygift_test
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"hyapp-luck-gateway/internal/config"
|
||
"hyapp-luck-gateway/internal/integration/luckygiftclient"
|
||
"hyapp-luck-gateway/internal/modules/luckygift"
|
||
"hyapp-luck-gateway/internal/router"
|
||
)
|
||
|
||
type fakeClient struct {
|
||
got luckygiftclient.SendCommand
|
||
calls int
|
||
}
|
||
|
||
func (f *fakeClient) SendLuckyGift(_ context.Context, cmd luckygiftclient.SendCommand) (luckygiftclient.SendResult, error) {
|
||
f.calls++
|
||
f.got = cmd
|
||
return luckygiftclient.SendResult{DrawID: "draw-1", RequestID: cmd.RequestID, AppCode: cmd.AppCode, RewardStatus: "granted"}, nil
|
||
}
|
||
|
||
func TestSendAcceptsAllowedAppAndComputesAmount(t *testing.T) {
|
||
client := &fakeClient{}
|
||
handler := testRouter(client, "aslan,yumi")
|
||
body := []byte(`{"app_code":"Aslan","request_id":"req-1","external_user_id":"u-1001","device_id":"device-attested-by-aslan","paid_at_ms":1700000000000,"gift_count":3,"unit_amount":7,"currency":"COIN","metadata":{"gift":"rose"}}`)
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/lucky-gifts/send", bytes.NewReader(body))
|
||
rec := httptest.NewRecorder()
|
||
|
||
handler.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusOK {
|
||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||
}
|
||
if client.calls != 1 {
|
||
t.Fatalf("client calls=%d", client.calls)
|
||
}
|
||
if client.got.AppCode != "aslan" || client.got.RequestID != "req-1" || client.got.DeviceID != "device-attested-by-aslan" || client.got.PaidAtMS != 1700000000000 || client.got.TotalAmount != 21 || client.got.GiftCount != 3 {
|
||
t.Fatalf("unexpected command: %+v", client.got)
|
||
}
|
||
}
|
||
|
||
func TestSendKeepsMissingDeviceCompatibleForFixedV2OwnerDecision(t *testing.T) {
|
||
client := &fakeClient{}
|
||
handler := testRouter(client, "aslan")
|
||
// luck-gateway 不读规则版本,因此旧 fixed_v2 调用方可继续缺省 device_id;
|
||
// 如果本 App 已发布 dynamic_v3,lucky-gift owner 会在规则事务内明确拒绝。
|
||
body := []byte(`{"app_code":"aslan","request_id":"req-fixed-legacy","external_user_id":"u-legacy","gift_count":1,"unit_amount":7}`)
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/lucky-gifts/send", bytes.NewReader(body))
|
||
rec := httptest.NewRecorder()
|
||
|
||
handler.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusOK || client.calls != 1 || client.got.DeviceID != "" {
|
||
t.Fatalf("fixed-compatible request status=%d calls=%d command=%+v body=%s", rec.Code, client.calls, client.got, rec.Body.String())
|
||
}
|
||
}
|
||
|
||
func TestSendRejectsPoolOverride(t *testing.T) {
|
||
client := &fakeClient{}
|
||
handler := testRouter(client, "aslan")
|
||
body := []byte(`{"app_code":"aslan","request_id":"req-1","external_user_id":"u-1001","pool_id":"pool-1","paid_at_ms":1700000000000,"gift_count":1,"unit_amount":7}`)
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/lucky-gifts/send", bytes.NewReader(body))
|
||
rec := httptest.NewRecorder()
|
||
|
||
handler.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusBadRequest {
|
||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||
}
|
||
if client.calls != 0 {
|
||
t.Fatalf("client should not be called, calls=%d", client.calls)
|
||
}
|
||
}
|
||
|
||
func TestSendRejectsNegativePaidAt(t *testing.T) {
|
||
client := &fakeClient{}
|
||
handler := testRouter(client, "aslan")
|
||
body := []byte(`{"app_code":"aslan","request_id":"req-negative-paid-at","external_user_id":"u-1001","paid_at_ms":-1,"gift_count":1,"unit_amount":7}`)
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/lucky-gifts/send", bytes.NewReader(body))
|
||
rec := httptest.NewRecorder()
|
||
|
||
handler.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusBadRequest || client.calls != 0 {
|
||
t.Fatalf("negative paid_at_ms status=%d calls=%d body=%s", rec.Code, client.calls, rec.Body.String())
|
||
}
|
||
}
|
||
|
||
func TestSendRejectsDisallowedAppBeforeClientCall(t *testing.T) {
|
||
client := &fakeClient{}
|
||
handler := testRouter(client, "aslan,yumi")
|
||
body := []byte(`{"app_code":"hyapp","request_id":"req-1","external_user_id":"u-1001","gift_count":1,"unit_amount":9}`)
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/lucky-gifts/send", bytes.NewReader(body))
|
||
rec := httptest.NewRecorder()
|
||
|
||
handler.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusForbidden {
|
||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||
}
|
||
if client.calls != 0 {
|
||
t.Fatalf("client should not be called, calls=%d", client.calls)
|
||
}
|
||
}
|
||
|
||
func TestSendRejectsInvalidAmount(t *testing.T) {
|
||
client := &fakeClient{}
|
||
handler := testRouter(client, "aslan")
|
||
body, _ := json.Marshal(map[string]any{
|
||
"app_code": "aslan",
|
||
"request_id": "req-1",
|
||
"external_user_id": "u-1",
|
||
"gift_count": 0,
|
||
"unit_amount": 10,
|
||
})
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/lucky-gifts/send", bytes.NewReader(body))
|
||
rec := httptest.NewRecorder()
|
||
|
||
handler.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusBadRequest {
|
||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||
}
|
||
if client.calls != 0 {
|
||
t.Fatalf("client should not be called, calls=%d", client.calls)
|
||
}
|
||
}
|
||
|
||
func TestSendRejectsMoreThan999SequentialDraws(t *testing.T) {
|
||
client := &fakeClient{}
|
||
handler := testRouter(client, "aslan")
|
||
body := []byte(`{"app_code":"aslan","request_id":"req-1","external_user_id":"u-1","gift_count":1000,"unit_amount":1}`)
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/lucky-gifts/send", bytes.NewReader(body))
|
||
rec := httptest.NewRecorder()
|
||
|
||
handler.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusBadRequest || client.calls != 0 {
|
||
t.Fatalf("oversized sequential draw status=%d calls=%d body=%s", rec.Code, client.calls, rec.Body.String())
|
||
}
|
||
}
|
||
|
||
func TestSendRejectsTooLongExternalFields(t *testing.T) {
|
||
client := &fakeClient{}
|
||
handler := testRouter(client, "aslan")
|
||
body, _ := json.Marshal(map[string]any{
|
||
"app_code": "aslan",
|
||
"request_id": "req-1",
|
||
"external_user_id": strings.Repeat("u", 129),
|
||
"gift_count": 1,
|
||
"unit_amount": 10,
|
||
})
|
||
req := httptest.NewRequest(http.MethodPost, "/api/v1/lucky-gifts/send", bytes.NewReader(body))
|
||
rec := httptest.NewRecorder()
|
||
|
||
handler.ServeHTTP(rec, req)
|
||
if rec.Code != http.StatusBadRequest {
|
||
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||
}
|
||
if client.calls != 0 {
|
||
t.Fatalf("client should not be called, calls=%d", client.calls)
|
||
}
|
||
}
|
||
|
||
func testRouter(client *fakeClient, allowedApps string) http.Handler {
|
||
cfg := config.Config{AllowedApps: parseAllowedAppsForTest(allowedApps)}
|
||
return router.New(cfg, router.Handlers{LuckyGift: luckygift.New(client, time.Second, cfg.AllowedApps)})
|
||
}
|
||
|
||
func parseAllowedAppsForTest(raw string) map[string]struct{} {
|
||
out := map[string]struct{}{}
|
||
for _, app := range strings.Split(raw, ",") {
|
||
app = strings.ToLower(strings.TrimSpace(app))
|
||
if app != "" {
|
||
out[app] = struct{}{}
|
||
}
|
||
}
|
||
return out
|
||
}
|