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","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.TotalAmount != 21 || client.got.GiftCount != 3 { t.Fatalf("unexpected command: %+v", client.got) } } func TestSendRejectsPoolAndPaidAtOverrides(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 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 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 }