subscription_test.go raw

   1  package bridge
   2  
   3  import (
   4  	"os"
   5  	"path/filepath"
   6  	"testing"
   7  	"time"
   8  )
   9  
  10  func TestSubscription_IsActive(t *testing.T) {
  11  	active := &Subscription{
  12  		PubkeyHex: "abc123",
  13  		ExpiresAt: time.Now().Add(24 * time.Hour),
  14  		CreatedAt: time.Now(),
  15  	}
  16  	if !active.IsActive() {
  17  		t.Error("subscription should be active")
  18  	}
  19  
  20  	expired := &Subscription{
  21  		PubkeyHex: "abc123",
  22  		ExpiresAt: time.Now().Add(-1 * time.Hour),
  23  		CreatedAt: time.Now().Add(-25 * time.Hour),
  24  	}
  25  	if expired.IsActive() {
  26  		t.Error("subscription should be expired")
  27  	}
  28  }
  29  
  30  func TestMemorySubscriptionStore_CRUD(t *testing.T) {
  31  	store := NewMemorySubscriptionStore()
  32  
  33  	sub := &Subscription{
  34  		PubkeyHex: "aabbccdd",
  35  		ExpiresAt: time.Now().Add(30 * 24 * time.Hour),
  36  		CreatedAt: time.Now(),
  37  	}
  38  
  39  	// Save
  40  	if err := store.Save(sub); err != nil {
  41  		t.Fatalf("Save: %v", err)
  42  	}
  43  
  44  	// Get
  45  	got, err := store.Get("aabbccdd")
  46  	if err != nil {
  47  		t.Fatalf("Get: %v", err)
  48  	}
  49  	if got.PubkeyHex != "aabbccdd" {
  50  		t.Errorf("PubkeyHex = %q", got.PubkeyHex)
  51  	}
  52  
  53  	// Get not found
  54  	_, err = store.Get("nonexistent")
  55  	if err == nil {
  56  		t.Error("expected error for nonexistent key")
  57  	}
  58  
  59  	// List
  60  	subs, err := store.List()
  61  	if err != nil {
  62  		t.Fatalf("List: %v", err)
  63  	}
  64  	if len(subs) != 1 {
  65  		t.Errorf("List returned %d, want 1", len(subs))
  66  	}
  67  
  68  	// Delete
  69  	if err := store.Delete("aabbccdd"); err != nil {
  70  		t.Fatalf("Delete: %v", err)
  71  	}
  72  	_, err = store.Get("aabbccdd")
  73  	if err == nil {
  74  		t.Error("expected error after delete")
  75  	}
  76  
  77  	// List after delete
  78  	subs, err = store.List()
  79  	if err != nil {
  80  		t.Fatalf("List after delete: %v", err)
  81  	}
  82  	if len(subs) != 0 {
  83  		t.Errorf("List returned %d after delete, want 0", len(subs))
  84  	}
  85  }
  86  
  87  func TestMemorySubscriptionStore_Overwrite(t *testing.T) {
  88  	store := NewMemorySubscriptionStore()
  89  
  90  	sub1 := &Subscription{
  91  		PubkeyHex:   "aabbccdd",
  92  		ExpiresAt:   time.Now().Add(30 * 24 * time.Hour),
  93  		CreatedAt:   time.Now(),
  94  		InvoiceHash: "hash1",
  95  	}
  96  	sub2 := &Subscription{
  97  		PubkeyHex:   "aabbccdd",
  98  		ExpiresAt:   time.Now().Add(60 * 24 * time.Hour),
  99  		CreatedAt:   time.Now(),
 100  		InvoiceHash: "hash2",
 101  	}
 102  
 103  	store.Save(sub1)
 104  	store.Save(sub2)
 105  
 106  	got, _ := store.Get("aabbccdd")
 107  	if got.InvoiceHash != "hash2" {
 108  		t.Errorf("InvoiceHash = %q, want hash2 (overwrite)", got.InvoiceHash)
 109  	}
 110  }
 111  
 112  func TestFileSubscriptionStore_Persistence(t *testing.T) {
 113  	dir := t.TempDir()
 114  
 115  	// Create store and save
 116  	store1, err := NewFileSubscriptionStore(dir)
 117  	if err != nil {
 118  		t.Fatalf("NewFileSubscriptionStore: %v", err)
 119  	}
 120  
 121  	sub := &Subscription{
 122  		PubkeyHex:   "aabbccdd",
 123  		ExpiresAt:   time.Now().Add(30 * 24 * time.Hour).Truncate(time.Second),
 124  		CreatedAt:   time.Now().Truncate(time.Second),
 125  		InvoiceHash: "testhash",
 126  	}
 127  
 128  	if err := store1.Save(sub); err != nil {
 129  		t.Fatalf("Save: %v", err)
 130  	}
 131  
 132  	// Verify file exists
 133  	path := filepath.Join(dir, "subscriptions.json")
 134  	if _, err := os.Stat(path); err != nil {
 135  		t.Fatalf("file not created: %v", err)
 136  	}
 137  
 138  	// Create a new store from same dir — should load persisted data
 139  	store2, err := NewFileSubscriptionStore(dir)
 140  	if err != nil {
 141  		t.Fatalf("NewFileSubscriptionStore (reload): %v", err)
 142  	}
 143  
 144  	got, err := store2.Get("aabbccdd")
 145  	if err != nil {
 146  		t.Fatalf("Get after reload: %v", err)
 147  	}
 148  	if got.InvoiceHash != "testhash" {
 149  		t.Errorf("InvoiceHash = %q after reload, want testhash", got.InvoiceHash)
 150  	}
 151  
 152  	// Delete and verify persistence
 153  	if err := store2.Delete("aabbccdd"); err != nil {
 154  		t.Fatalf("Delete: %v", err)
 155  	}
 156  
 157  	store3, err := NewFileSubscriptionStore(dir)
 158  	if err != nil {
 159  		t.Fatalf("NewFileSubscriptionStore (after delete): %v", err)
 160  	}
 161  	_, err = store3.Get("aabbccdd")
 162  	if err == nil {
 163  		t.Error("expected error: subscription should be deleted after reload")
 164  	}
 165  }
 166  
 167  func TestFileSubscriptionStore_MultipleSubscriptions(t *testing.T) {
 168  	dir := t.TempDir()
 169  
 170  	store, err := NewFileSubscriptionStore(dir)
 171  	if err != nil {
 172  		t.Fatalf("NewFileSubscriptionStore: %v", err)
 173  	}
 174  
 175  	for i, pk := range []string{"aaaa", "bbbb", "cccc"} {
 176  		sub := &Subscription{
 177  			PubkeyHex: pk,
 178  			ExpiresAt: time.Now().Add(time.Duration(i+1) * 24 * time.Hour),
 179  			CreatedAt: time.Now(),
 180  		}
 181  		if err := store.Save(sub); err != nil {
 182  			t.Fatalf("Save %s: %v", pk, err)
 183  		}
 184  	}
 185  
 186  	subs, err := store.List()
 187  	if err != nil {
 188  		t.Fatalf("List: %v", err)
 189  	}
 190  	if len(subs) != 3 {
 191  		t.Errorf("List returned %d, want 3", len(subs))
 192  	}
 193  
 194  	// Delete middle one
 195  	if err := store.Delete("bbbb"); err != nil {
 196  		t.Fatalf("Delete bbbb: %v", err)
 197  	}
 198  
 199  	subs, err = store.List()
 200  	if err != nil {
 201  		t.Fatalf("List after delete: %v", err)
 202  	}
 203  	if len(subs) != 2 {
 204  		t.Errorf("List after delete returned %d, want 2", len(subs))
 205  	}
 206  }
 207  
 208  func TestFileSubscriptionStore_FlushToReadOnlyDir(t *testing.T) {
 209  	// Create a store with a path in a read-only directory
 210  	store := &FileSubscriptionStore{
 211  		path: "/dev/null/impossible/subscriptions.json",
 212  		subs: make(map[string]*Subscription),
 213  	}
 214  
 215  	sub := &Subscription{
 216  		PubkeyHex: "test",
 217  		ExpiresAt: time.Now().Add(24 * time.Hour),
 218  		CreatedAt: time.Now(),
 219  	}
 220  
 221  	err := store.Save(sub)
 222  	if err == nil {
 223  		t.Error("expected error saving to impossible path")
 224  	}
 225  }
 226  
 227  func TestFileSubscriptionStore_DeleteFlushError(t *testing.T) {
 228  	store := &FileSubscriptionStore{
 229  		path: "/dev/null/impossible/subscriptions.json",
 230  		subs: map[string]*Subscription{
 231  			"test": {PubkeyHex: "test", ExpiresAt: time.Now().Add(time.Hour)},
 232  		},
 233  	}
 234  
 235  	err := store.Delete("test")
 236  	if err == nil {
 237  		t.Error("expected error from flush on impossible path")
 238  	}
 239  }
 240  
 241  func TestFileSubscriptionStore_EmptyDir(t *testing.T) {
 242  	dir := t.TempDir()
 243  
 244  	store, err := NewFileSubscriptionStore(dir)
 245  	if err != nil {
 246  		t.Fatalf("NewFileSubscriptionStore: %v", err)
 247  	}
 248  
 249  	subs, err := store.List()
 250  	if err != nil {
 251  		t.Fatalf("List empty: %v", err)
 252  	}
 253  	if len(subs) != 0 {
 254  		t.Errorf("List empty returned %d, want 0", len(subs))
 255  	}
 256  }
 257