소스 검색

More tests

binwiederhier 3 년 전
부모
커밋
c12ecb9f21
4개의 변경된 파일144개의 추가작업 그리고 10개의 파일을 삭제
  1. 5 8
      server/server.go
  2. 87 0
      server/server_payments_test.go
  3. 2 2
      server/server_test.go
  4. 50 0
      util/util_test.go

+ 5 - 8
server/server.go

@@ -9,7 +9,12 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"github.com/emersion/go-smtp"
+	"github.com/gorilla/websocket"
+	"golang.org/x/sync/errgroup"
+	"heckel.io/ntfy/log"
 	"heckel.io/ntfy/user"
+	"heckel.io/ntfy/util"
 	"io"
 	"net"
 	"net/http"
@@ -25,13 +30,6 @@ import (
 	"sync"
 	"time"
 	"unicode/utf8"
-
-	"heckel.io/ntfy/log"
-
-	"github.com/emersion/go-smtp"
-	"github.com/gorilla/websocket"
-	"golang.org/x/sync/errgroup"
-	"heckel.io/ntfy/util"
 )
 
 /*
@@ -43,7 +41,6 @@ import (
 - MEDIUM: Reservation (UI): Show "This topic is reserved" error message when trying to reserve a reserved topic (Thorben)
 - MEDIUM: Reservation (UI): Ask for confirmation when removing reservation (deadcade)
 - MEDIUM: Reservation table delete button: dialog "keep or delete messages?"
-- MEDIUM: Tests for remaining payment endpoints
 - LOW: UI: Flickering upgrade banner when logging in
 - LOW: JS constants
 - LOW: Payments reconciliation process

+ 87 - 0
server/server_payments_test.go

@@ -524,6 +524,70 @@ func TestPayments_Webhook_Subscription_Updated_Downgrade_From_PastDue_To_Active(
 	require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, z2.ID))
 }
 
+func TestPayments_Webhook_Subscription_Deleted(t *testing.T) {
+	// This tests incoming webhooks from Stripe to delete a subscription. It verifies that the database is
+	// updated (all Stripe fields are deleted, and the tier is removed).
+	//
+	// It doesn't fully test the message/attachment deletion. That is tested above in the subscription update call.
+
+	stripeMock := &testStripeAPI{}
+	defer stripeMock.AssertExpectations(t)
+
+	c := newTestConfigWithAuthFile(t)
+	c.StripeSecretKey = "secret key"
+	c.StripeWebhookKey = "webhook key"
+	s := newTestServer(t, c)
+	s.stripe = stripeMock
+
+	// Define how the mock should react
+	stripeMock.
+		On("ConstructWebhookEvent", mock.Anything, "stripe signature", "webhook key").
+		Return(jsonToStripeEvent(t, subscriptionDeletedEventJSON), nil)
+
+	// Create a user with a Stripe subscription and 3 reservations
+	require.Nil(t, s.userManager.CreateTier(&user.Tier{
+		ID:               "ti_1",
+		Code:             "pro",
+		StripePriceID:    "price_1234",
+		ReservationLimit: 1,
+	}))
+	require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
+	require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
+	require.Nil(t, s.userManager.AddReservation("phil", "atopic", user.PermissionDenyAll))
+
+	// Add billing details
+	u, err := s.userManager.User("phil")
+	require.Nil(t, err)
+	require.Nil(t, s.userManager.ChangeBilling(u.Name, &user.Billing{
+		StripeCustomerID:            "acct_5555",
+		StripeSubscriptionID:        "sub_1234",
+		StripeSubscriptionStatus:    stripe.SubscriptionStatusPastDue,
+		StripeSubscriptionPaidUntil: time.Unix(123, 0),
+		StripeSubscriptionCancelAt:  time.Unix(0, 0),
+	}))
+
+	// Call the webhook: This does all the magic
+	rr := request(t, s, "POST", "/v1/account/billing/webhook", "dummy", map[string]string{
+		"Stripe-Signature": "stripe signature",
+	})
+	require.Equal(t, 200, rr.Code)
+
+	// Verify that database columns were updated
+	u, err = s.userManager.User("phil")
+	require.Nil(t, err)
+	require.Nil(t, u.Tier)
+	require.Equal(t, "acct_5555", u.Billing.StripeCustomerID)
+	require.Equal(t, "", u.Billing.StripeSubscriptionID)
+	require.Equal(t, stripe.SubscriptionStatus(""), u.Billing.StripeSubscriptionStatus)
+	require.Equal(t, int64(0), u.Billing.StripeSubscriptionPaidUntil.Unix())
+	require.Equal(t, int64(0), u.Billing.StripeSubscriptionCancelAt.Unix())
+
+	// Verify that reservations were deleted
+	r, err := s.userManager.Reservations("phil")
+	require.Nil(t, err)
+	require.Equal(t, 0, len(r))
+}
+
 func TestPayments_Subscription_Update_Different_Tier(t *testing.T) {
 	stripeMock := &testStripeAPI{}
 	defer stripeMock.AssertExpectations(t)
@@ -739,3 +803,26 @@ const subscriptionUpdatedEventJSON = `
 		}
 	}
 }`
+
+const subscriptionDeletedEventJSON = `
+{
+	"type": "customer.subscription.deleted",
+	"data": {
+		"object": {
+			"id": "sub_1234",
+			"customer": "acct_5555",
+			"status": "active",
+			"current_period_end": 1674268231,
+			"cancel_at": 1674299999,
+			"items": {
+				"data": [
+					{
+						"price": {
+							"id": "price_1234"
+						}
+					}
+				]
+			}
+		}
+	}
+}`

+ 2 - 2
server/server_test.go

@@ -1872,11 +1872,11 @@ func subscribe(t *testing.T, s *Server, url string, rr *httptest.ResponseRecorde
 		done <- true
 	}()
 	cancelAndWaitForDone := func() {
-		time.Sleep(100 * time.Millisecond)
+		time.Sleep(200 * time.Millisecond)
 		cancel()
 		<-done
 	}
-	time.Sleep(100 * time.Millisecond)
+	time.Sleep(200 * time.Millisecond)
 	return cancelAndWaitForDone
 }
 

+ 50 - 0
util/util_test.go

@@ -1,12 +1,14 @@
 package util
 
 import (
+	"errors"
 	"io"
 	"net/netip"
 	"os"
 	"path/filepath"
 	"strings"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/require"
 )
@@ -212,3 +214,51 @@ func TestReadJSONWithLimit_NoAllowEmpty(t *testing.T) {
 	_, err := UnmarshalJSONWithLimit[testJSON](io.NopCloser(strings.NewReader(` `)), 10, false)
 	require.Equal(t, ErrUnmarshalJSON, err)
 }
+
+func TestRetry_Succeeds(t *testing.T) {
+	start := time.Now()
+	delays, i := []time.Duration{10 * time.Millisecond, 50 * time.Millisecond, 100 * time.Millisecond, time.Second}, 0
+	fn := func() (*int, error) {
+		i++
+		if i < len(delays) {
+			return nil, errors.New("error")
+		}
+		return Int(99), nil
+	}
+	result, err := Retry[int](fn, delays...)
+	require.Nil(t, err)
+	require.Equal(t, 99, *result)
+	require.True(t, time.Since(start).Milliseconds() > 150)
+}
+
+func TestRetry_Fails(t *testing.T) {
+	fn := func() (*int, error) {
+		return nil, errors.New("fails")
+	}
+	_, err := Retry[int](fn, 10*time.Millisecond)
+	require.Error(t, err)
+}
+
+func TestMinMax(t *testing.T) {
+	require.Equal(t, 10, MinMax(9, 10, 99))
+	require.Equal(t, 99, MinMax(100, 10, 99))
+	require.Equal(t, 50, MinMax(50, 10, 99))
+}
+
+func TestPointerFunctions(t *testing.T) {
+	i, s, ti := Int(99), String("abc"), Time(time.Unix(99, 0))
+	require.Equal(t, 99, *i)
+	require.Equal(t, "abc", *s)
+	require.Equal(t, time.Unix(99, 0), *ti)
+}
+
+func TestMaybeMarshalJSON(t *testing.T) {
+	require.Equal(t, `"aa"`, MaybeMarshalJSON("aa"))
+	require.Equal(t, `[
+  "aa",
+  "bb"
+]`, MaybeMarshalJSON([]string{"aa", "bb"}))
+	require.Equal(t, "<cannot serialize>", MaybeMarshalJSON(func() {}))
+	require.Equal(t, `"`+strings.Repeat("x", 4999), MaybeMarshalJSON(strings.Repeat("x", 6000)))
+
+}