More tests
parent
00af52411c
commit
c12ecb9f21
|
@ -9,7 +9,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"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/user"
|
||||||
|
"heckel.io/ntfy/util"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -25,13 +30,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"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): 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 (UI): Ask for confirmation when removing reservation (deadcade)
|
||||||
- MEDIUM: Reservation table delete button: dialog "keep or delete messages?"
|
- 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: UI: Flickering upgrade banner when logging in
|
||||||
- LOW: JS constants
|
- LOW: JS constants
|
||||||
- LOW: Payments reconciliation process
|
- LOW: Payments reconciliation process
|
||||||
|
|
|
@ -524,6 +524,70 @@ func TestPayments_Webhook_Subscription_Updated_Downgrade_From_PastDue_To_Active(
|
||||||
require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, z2.ID))
|
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) {
|
func TestPayments_Subscription_Update_Different_Tier(t *testing.T) {
|
||||||
stripeMock := &testStripeAPI{}
|
stripeMock := &testStripeAPI{}
|
||||||
defer stripeMock.AssertExpectations(t)
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
|
@ -1872,11 +1872,11 @@ func subscribe(t *testing.T, s *Server, url string, rr *httptest.ResponseRecorde
|
||||||
done <- true
|
done <- true
|
||||||
}()
|
}()
|
||||||
cancelAndWaitForDone := func() {
|
cancelAndWaitForDone := func() {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
cancel()
|
cancel()
|
||||||
<-done
|
<-done
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
return cancelAndWaitForDone
|
return cancelAndWaitForDone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -212,3 +214,51 @@ func TestReadJSONWithLimit_NoAllowEmpty(t *testing.T) {
|
||||||
_, err := UnmarshalJSONWithLimit[testJSON](io.NopCloser(strings.NewReader(` `)), 10, false)
|
_, err := UnmarshalJSONWithLimit[testJSON](io.NopCloser(strings.NewReader(` `)), 10, false)
|
||||||
require.Equal(t, ErrUnmarshalJSON, err)
|
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)))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue