Files

196 lines
6.8 KiB
Go
Raw Permalink Normal View History

package httpapi
import (
"bytes"
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/descrybe/descrybe-v2/apps/api/internal/billing"
"github.com/descrybe/descrybe-v2/apps/api/internal/campaigns"
"github.com/descrybe/descrybe-v2/apps/api/internal/config"
"github.com/google/uuid"
)
func TestRequireFeatureBillingNilFailsClosedInProduction(t *testing.T) {
t.Parallel()
s := &Server{Config: config.Config{AppEnv: "production"}}
h := s.RequireFeature("capability.api_access")(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
}))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/gated", nil))
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status=%d want 503 body=%s", rec.Code, rec.Body.String())
}
}
func TestRequireFeatureBillingNilPassThroughOutsideProduction(t *testing.T) {
t.Parallel()
s := &Server{Config: config.Config{AppEnv: "development"}}
called := false
h := s.RequireFeature("capability.api_access")(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
called = true
w.WriteHeader(http.StatusNoContent)
}))
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/gated", nil))
if rec.Code != http.StatusNoContent || !called {
t.Fatalf("status=%d called=%v want 204 pass-through", rec.Code, called)
}
}
func TestRequireFeaturesPlanGateViaHook(t *testing.T) {
t.Parallel()
cid := uuid.MustParse("11111111-1111-1111-1111-111111111111")
s := &Server{
testAssertFeatures: func(_ context.Context, keys ...string) error {
return fmt.Errorf("%w: %s", billing.ErrFeatureDisabled, keys[0])
},
}
ctx := context.WithValue(context.Background(), ctxCompanyID, cid)
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
rec := httptest.NewRecorder()
if s.requireFeatures(rec, req, "marketing.campaigns") {
t.Fatal("requireFeatures should reject disabled feature")
}
if rec.Code != http.StatusPaymentRequired {
t.Fatalf("status=%d want 402 body=%s", rec.Code, rec.Body.String())
}
if !strings.Contains(rec.Body.String(), "feature_disabled") {
t.Fatalf("body=%s want feature_disabled", rec.Body.String())
}
}
func TestCreateCampaignPlanGate(t *testing.T) {
t.Parallel()
cid := uuid.MustParse("11111111-1111-1111-1111-111111111111")
uid := uuid.MustParse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
s := &Server{
Campaigns: &campaigns.Service{},
testAssertFeatures: func(_ context.Context, keys ...string) error {
return fmt.Errorf("%w: %s", billing.ErrFeatureDisabled, keys[0])
},
}
ctx := context.WithValue(context.Background(), ctxUserID, uid)
ctx = context.WithValue(ctx, ctxCompanyID, cid)
ctx = context.WithValue(ctx, ctxRole, "admin")
t.Run("list", func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest(http.MethodGet, "/api/campaigns", nil).WithContext(ctx)
rec := httptest.NewRecorder()
s.handleListCampaigns(rec, req)
if rec.Code != http.StatusPaymentRequired {
t.Fatalf("status=%d want 402 body=%s", rec.Code, rec.Body.String())
}
})
t.Run("create", func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest(http.MethodPost, "/api/campaigns", bytes.NewBufferString(`{"name":"x"}`)).WithContext(ctx)
rec := httptest.NewRecorder()
s.handleCreateCampaign(rec, req)
if rec.Code != http.StatusPaymentRequired {
t.Fatalf("status=%d want 402 body=%s", rec.Code, rec.Body.String())
}
})
}
func TestCreateAPIKeyPlanGate(t *testing.T) {
t.Parallel()
cid := uuid.MustParse("11111111-1111-1111-1111-111111111111")
uid := uuid.MustParse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
s := &Server{
testAssertFeatures: func(_ context.Context, keys ...string) error {
return fmt.Errorf("%w: settings.api_keys", billing.ErrFeatureDisabled)
},
}
ctx := context.WithValue(context.Background(), ctxUserID, uid)
ctx = context.WithValue(ctx, ctxCompanyID, cid)
ctx = context.WithValue(ctx, ctxRole, "admin")
req := httptest.NewRequest(http.MethodPost, "/api/api-keys", bytes.NewBufferString(`{"name":"x"}`)).WithContext(ctx)
rec := httptest.NewRecorder()
s.handleCreateAPIKey(rec, req)
if rec.Code != http.StatusPaymentRequired {
t.Fatalf("status=%d want 402 body=%s", rec.Code, rec.Body.String())
}
}
func TestCreateAPIKeyBillingNilFailsClosedInProduction(t *testing.T) {
t.Parallel()
cid := uuid.MustParse("11111111-1111-1111-1111-111111111111")
uid := uuid.MustParse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
s := &Server{Config: config.Config{AppEnv: "production"}}
ctx := context.WithValue(context.Background(), ctxUserID, uid)
ctx = context.WithValue(ctx, ctxCompanyID, cid)
ctx = context.WithValue(ctx, ctxRole, "admin")
req := httptest.NewRequest(http.MethodPost, "/api/api-keys", bytes.NewBufferString(`{"name":"x"}`)).WithContext(ctx)
rec := httptest.NewRecorder()
s.handleCreateAPIKey(rec, req)
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status=%d want 503 body=%s", rec.Code, rec.Body.String())
}
}
func TestUpdateShopifyConfigPlanGate(t *testing.T) {
t.Parallel()
cid := uuid.MustParse("11111111-1111-1111-1111-111111111111")
uid := uuid.MustParse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa")
s := &Server{
testAssertFeatures: func(_ context.Context, keys ...string) error {
return fmt.Errorf("%w: stores.shopify", billing.ErrFeatureDisabled)
},
}
ctx := context.WithValue(context.Background(), ctxUserID, uid)
ctx = context.WithValue(ctx, ctxCompanyID, cid)
ctx = context.WithValue(ctx, ctxRole, "admin")
req := httptest.NewRequest(http.MethodPut, "/api/shopify", bytes.NewBufferString(`{}`)).WithContext(ctx)
rec := httptest.NewRecorder()
s.handleUpdateShopifyConfig(rec, req)
if rec.Code != http.StatusPaymentRequired {
t.Fatalf("status=%d want 402 body=%s", rec.Code, rec.Body.String())
}
}
func TestHandleListPublicPlansBillingNilReturnsEmpty(t *testing.T) {
t.Parallel()
s := &Server{Config: config.Config{WebOrigin: "http://localhost:5173"}}
rec := httptest.NewRecorder()
s.handleListPublicPlans(rec, httptest.NewRequest(http.MethodGet, "/api/public/plans", nil))
if rec.Code != http.StatusOK {
t.Fatalf("nil billing status=%d want 200 body=%s", rec.Code, rec.Body.String())
}
s.Billing = &billing.Service{} // non-nil service without pool must not 500
rec2 := httptest.NewRecorder()
s.handleListPublicPlans(rec2, httptest.NewRequest(http.MethodGet, "/api/public/plans", nil))
if rec2.Code != http.StatusOK {
t.Fatalf("empty billing status=%d want 200 body=%s", rec2.Code, rec2.Body.String())
}
}
func TestHandleListPlansBillingNilServiceUnavailable(t *testing.T) {
t.Parallel()
s := &Server{}
rec := httptest.NewRecorder()
s.handleListPlans(rec, httptest.NewRequest(http.MethodGet, "/api/admin/plans", nil))
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("status=%d want 503 body=%s", rec.Code, rec.Body.String())
}
}