Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
196 lines
6.8 KiB
Go
196 lines
6.8 KiB
Go
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())
|
|
}
|
|
}
|