228 lines
6.3 KiB
Go
228 lines
6.3 KiB
Go
package invoice_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/noderunners/nip05api/internal/db"
|
|
"github.com/noderunners/nip05api/internal/invoice"
|
|
"github.com/noderunners/nip05api/internal/user"
|
|
)
|
|
|
|
const testHex = "0e8c41ebcd55a8d8db2e0a8c3a4b9c5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c"
|
|
|
|
func newServiceFixture(t *testing.T) (*invoice.Service, *user.Service, *httptest.Server) {
|
|
t.Helper()
|
|
d, err := db.Open(filepath.Join(t.TempDir(), "test.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := d.Migrate(context.Background()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = d.Close() })
|
|
|
|
ln := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_ = json.NewEncoder(w).Encode(map[string]string{
|
|
"payment_hash": "hash-" + r.URL.Path,
|
|
"payment_request": "lnbcfake",
|
|
})
|
|
}))
|
|
t.Cleanup(ln.Close)
|
|
|
|
users := user.NewService(user.NewRepo(d), []string{"admin", "root"})
|
|
client := invoice.NewLNbits(ln.URL, "key")
|
|
pricing := invoice.Pricing{YearlySats: 1000, LifetimeSats: 5000, ExpiryMins: 30}
|
|
svc := invoice.NewService(invoice.NewRepo(d), users, client, pricing, "test.local")
|
|
return svc, users, ln
|
|
}
|
|
|
|
func TestCreate_PubkeyOnlyDefaultsLifetime(t *testing.T) {
|
|
svc, _, _ := newServiceFixture(t)
|
|
|
|
p, err := svc.Create(context.Background(), invoice.CreateRequest{
|
|
Pubkey: testHex,
|
|
SubscriptionType: user.SubLifetime,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if p.SubscriptionType != user.SubLifetime {
|
|
t.Errorf("expected lifetime, got %q", p.SubscriptionType)
|
|
}
|
|
if p.AmountSats != 5000 {
|
|
t.Errorf("expected lifetime price, got %d", p.AmountSats)
|
|
}
|
|
if !strings.HasPrefix(p.Username, "u_") {
|
|
t.Errorf("expected provisional username, got %q", p.Username)
|
|
}
|
|
if err := user.ValidateUsername(p.Username, nil); err != nil {
|
|
t.Errorf("provisional name should be valid: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreate_YearlyDefaultsOneYear(t *testing.T) {
|
|
svc, _, _ := newServiceFixture(t)
|
|
|
|
p, err := svc.Create(context.Background(), invoice.CreateRequest{
|
|
Pubkey: testHex,
|
|
Username: "alice",
|
|
SubscriptionType: user.SubYearly,
|
|
Years: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if p.Years != 1 || p.AmountSats != 1000 {
|
|
t.Errorf("unexpected pending: years=%d amount=%d", p.Years, p.AmountSats)
|
|
}
|
|
if p.Username != "alice" {
|
|
t.Errorf("expected explicit username, got %q", p.Username)
|
|
}
|
|
}
|
|
|
|
func TestCreate_RenewalReusesUsername(t *testing.T) {
|
|
svc, users, _ := newServiceFixture(t)
|
|
|
|
if _, err := users.CreateOrActivate(context.Background(), testHex, "alice", user.SubYearly, 1, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
p, err := svc.Create(context.Background(), invoice.CreateRequest{
|
|
Pubkey: testHex,
|
|
SubscriptionType: user.SubLifetime,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
if !p.IsRenewal {
|
|
t.Error("expected renewal flag")
|
|
}
|
|
if p.Username != "alice" {
|
|
t.Errorf("expected stored username, got %q", p.Username)
|
|
}
|
|
}
|
|
|
|
func TestCreate_BlocksActiveLifetimeUser(t *testing.T) {
|
|
svc, users, _ := newServiceFixture(t)
|
|
|
|
if _, err := users.CreateOrActivate(context.Background(), testHex, "alice", user.SubLifetime, 0, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := svc.Create(context.Background(), invoice.CreateRequest{
|
|
Pubkey: testHex,
|
|
SubscriptionType: user.SubYearly,
|
|
Years: 1,
|
|
})
|
|
if !errors.Is(err, invoice.ErrLifetimeAccess) {
|
|
t.Fatalf("expected ErrLifetimeAccess, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreate_RejectsDuplicatePending(t *testing.T) {
|
|
svc, _, _ := newServiceFixture(t)
|
|
|
|
if _, err := svc.Create(context.Background(), invoice.CreateRequest{
|
|
Pubkey: testHex,
|
|
Username: "alice",
|
|
SubscriptionType: user.SubYearly,
|
|
Years: 1,
|
|
}); err != nil {
|
|
t.Fatalf("first create: %v", err)
|
|
}
|
|
|
|
_, err := svc.Create(context.Background(), invoice.CreateRequest{
|
|
Pubkey: testHex,
|
|
Username: "alice",
|
|
SubscriptionType: user.SubYearly,
|
|
Years: 1,
|
|
})
|
|
if !errors.Is(err, invoice.ErrPendingInvoiceExists) {
|
|
t.Fatalf("expected ErrPendingInvoiceExists, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreate_YearlyPersistsTargetExpiry(t *testing.T) {
|
|
svc, users, _ := newServiceFixture(t)
|
|
|
|
if _, err := users.CreateOrActivate(context.Background(), testHex, "alice", user.SubYearly, 1, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
existing, err := users.Repo().GetByPubkey(context.Background(), testHex)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
before := time.Now().UTC()
|
|
p, err := svc.Create(context.Background(), invoice.CreateRequest{
|
|
Pubkey: testHex,
|
|
SubscriptionType: user.SubYearly,
|
|
Years: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
|
|
stored, err := svc.Repo().Get(context.Background(), p.PaymentHash)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !stored.TargetSet || stored.TargetExpiresAt == nil {
|
|
t.Fatalf("expected target persisted, got set=%v at=%v", stored.TargetSet, stored.TargetExpiresAt)
|
|
}
|
|
want := existing.ExpiresAt.AddDate(1, 0, 0)
|
|
if !stored.TargetExpiresAt.Equal(want) {
|
|
t.Errorf("target stacking mismatch: got %v want %v", stored.TargetExpiresAt, want)
|
|
}
|
|
if stored.TargetExpiresAt.Before(before) {
|
|
t.Error("target should be in the future")
|
|
}
|
|
}
|
|
|
|
func TestCreate_YearlyExpiredUserStartsFromNow(t *testing.T) {
|
|
svc, users, _ := newServiceFixture(t)
|
|
|
|
if _, err := users.CreateOrActivate(context.Background(), testHex, "alice", user.SubYearly, 1, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
u, err := users.Repo().GetByPubkey(context.Background(), testHex)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
past := time.Now().UTC().AddDate(0, 0, -1)
|
|
u.ExpiresAt = &past
|
|
if err := users.Repo().Update(context.Background(), u); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
before := time.Now().UTC()
|
|
p, err := svc.Create(context.Background(), invoice.CreateRequest{
|
|
Pubkey: testHex,
|
|
SubscriptionType: user.SubYearly,
|
|
Years: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create: %v", err)
|
|
}
|
|
stored, err := svc.Repo().Get(context.Background(), p.PaymentHash)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if stored.TargetExpiresAt == nil {
|
|
t.Fatal("expected target")
|
|
}
|
|
want := before.AddDate(1, 0, 0)
|
|
delta := stored.TargetExpiresAt.Sub(want)
|
|
if delta < -2*time.Second || delta > 2*time.Second {
|
|
t.Errorf("expected ~now+1y (%v), got %v", want, stored.TargetExpiresAt)
|
|
}
|
|
}
|