migrate_test.go raw

   1  package main
   2  
   3  import (
   4  	"fmt"
   5  	"os"
   6  	"strconv"
   7  	"testing"
   8  	"time"
   9  
  10  	"github.com/sirupsen/logrus"
  11  	"github.com/stretchr/testify/require"
  12  	"gorm.io/datatypes"
  13  	"gorm.io/gorm"
  14  
  15  	"github.com/getAlby/hub/db"
  16  	"github.com/getAlby/hub/logger"
  17  	test_db "github.com/getAlby/hub/tests/db"
  18  )
  19  
  20  type testEnvironment struct {
  21  	source *gorm.DB
  22  	dest   *gorm.DB
  23  }
  24  
  25  func (e *testEnvironment) cleanup(t *testing.T) {
  26  	err := db.Stop(e.source)
  27  	require.NoError(t, err)
  28  
  29  	err = db.Stop(e.dest)
  30  	require.NoError(t, err)
  31  }
  32  
  33  func TestMigrate(t *testing.T) {
  34  	type testCase struct {
  35  		name      string
  36  		sourceURI string
  37  		destURI   string
  38  	}
  39  
  40  	// Test migration between sqlite instances for basic sanity checking.
  41  	tc := []testCase{
  42  		{
  43  			name:      "sqlite to sqlite",
  44  			sourceURI: getTestSqliteURI(0),
  45  			destURI:   getTestSqliteURI(1),
  46  		},
  47  	}
  48  
  49  	// Only run Postgres tests if Postgres is configured and its URI is set.
  50  	if getTestPostgresURI() != "" {
  51  		tcPg := []testCase{
  52  			{
  53  				name:      "sqlite to postgres",
  54  				sourceURI: getTestSqliteURI(0),
  55  				destURI:   getTestPostgresURI(),
  56  			},
  57  			{
  58  				name:      "postgres to sqlite",
  59  				sourceURI: getTestPostgresURI(),
  60  				destURI:   getTestSqliteURI(0),
  61  			},
  62  		}
  63  
  64  		tc = append(tc, tcPg...)
  65  	}
  66  
  67  	for _, tt := range tc {
  68  		t.Run(tt.name, func(t *testing.T) {
  69  			env, err := setupTest(t, tt.sourceURI, tt.destURI)
  70  			require.NoError(t, err)
  71  			defer env.cleanup(t)
  72  
  73  			err = db.MigrateDB(env.source, env.dest)
  74  			require.NoError(t, err)
  75  
  76  			requireCount[db.App](t, env.dest, 2)
  77  			requireCount[db.AppPermission](t, env.dest, 2)
  78  			requireCount[db.RequestEvent](t, env.dest, 1)
  79  			requireCount[db.ResponseEvent](t, env.dest, 1)
  80  			requireCount[db.Transaction](t, env.dest, 1)
  81  			requireCount[db.Swap](t, env.dest, 1)
  82  			requireCount[db.Forward](t, env.dest, 1)
  83  			requireCount[db.UserConfig](t, env.dest, 1)
  84  		})
  85  	}
  86  }
  87  
  88  func getTestSqliteURI(dbIndex int) string {
  89  	if uri := os.Getenv("TEST_DB_MIGRATE_SQLITE_URI"); uri != "" {
  90  		return uri
  91  	}
  92  
  93  	return fmt.Sprintf("file:testmemdb%d?mode=memory&cache=shared&_txlock=immediate&_foreign_keys=1", dbIndex)
  94  }
  95  
  96  func getTestPostgresURI() string {
  97  	return os.Getenv("TEST_DB_MIGRATE_POSTGRES_URI")
  98  }
  99  
 100  func setupTest(t *testing.T, sourceURI string, destURI string) (*testEnvironment, error) {
 101  	logger.Init(strconv.Itoa(int(logrus.DebugLevel)))
 102  
 103  	source, err := test_db.NewDBWithURI(t, sourceURI)
 104  	if err != nil {
 105  		t.Fatalf("failed to open source database: %v", err)
 106  	}
 107  
 108  	dest, err := test_db.NewDBWithURI(t, destURI)
 109  	if err != nil {
 110  		t.Fatalf("failed to open destination database: %v", err)
 111  	}
 112  
 113  	insertMockData(t, source)
 114  
 115  	return &testEnvironment{
 116  		source: source,
 117  		dest:   dest,
 118  	}, nil
 119  }
 120  
 121  func insertMockData(t *testing.T, tx *gorm.DB) {
 122  	baseTime := time.Date(2025, 01, 15, 8, 0, 0, 0, time.UTC)
 123  
 124  	userCfg1 := &db.UserConfig{
 125  		Key:       "Relay",
 126  		Value:     "wss://relay.getalby.com",
 127  		Encrypted: false,
 128  		CreatedAt: baseTime,
 129  		UpdatedAt: baseTime,
 130  	}
 131  	create(t, tx, userCfg1)
 132  
 133  	app1 := &db.App{
 134  		Name:         "test1",
 135  		Description:  "test1 description",
 136  		AppPubkey:    "2b7dea2866958f17c568cf024e113db7a3baa9c253a9016889196b8d0b11c7ae",
 137  		WalletPubkey: ptr("f766024546ddbdc45db6016714047e34117d5e0d68e51fae06ffca9687783995"),
 138  		CreatedAt:    baseTime,
 139  		UpdatedAt:    baseTime,
 140  		Isolated:     false,
 141  		Metadata:     datatypes.JSON("{}"),
 142  	}
 143  	create(t, tx, app1)
 144  
 145  	app1Perm := &db.AppPermission{
 146  		App:           *app1,
 147  		Scope:         "pay_invoice",
 148  		MaxAmountSat:  0,
 149  		BudgetRenewal: "monthly",
 150  		ExpiresAt:     nil,
 151  		CreatedAt:     baseTime,
 152  		UpdatedAt:     baseTime,
 153  	}
 154  	create(t, tx, app1Perm)
 155  
 156  	app2 := &db.App{
 157  		Name:         "test2",
 158  		Description:  "test2 description",
 159  		AppPubkey:    "560f31e764f7af64719aba1dfdc0bcb3e681d48bb76265ca939622e1a719fe2a",
 160  		WalletPubkey: ptr("b44c5b3e9c3105b9347cce9f4bbfc899df13c591976fe0f706c1aacd4358020b"),
 161  		CreatedAt:    baseTime,
 162  		UpdatedAt:    baseTime,
 163  		Isolated:     false,
 164  		Metadata:     datatypes.JSON("{}"),
 165  	}
 166  	create(t, tx, app2)
 167  
 168  	app2Perm := &db.AppPermission{
 169  		App:           *app2,
 170  		Scope:         "get_info",
 171  		MaxAmountSat:  0,
 172  		BudgetRenewal: "monthly",
 173  		ExpiresAt:     nil,
 174  		CreatedAt:     baseTime,
 175  		UpdatedAt:     baseTime,
 176  	}
 177  	create(t, tx, app2Perm)
 178  
 179  	requestEvent1 := &db.RequestEvent{
 180  		AppId:       &app1.ID,
 181  		NostrId:     "a35a1ca6d1a06e08a509f2c8fe3edb2ba10811d030e2f6f3239e9f21203ac954",
 182  		ContentData: "{}",
 183  		Method:      "pay_invoice",
 184  		State:       "executed",
 185  		CreatedAt:   baseTime,
 186  		UpdatedAt:   baseTime,
 187  	}
 188  	create(t, tx, requestEvent1)
 189  
 190  	responseEvent1 := &db.ResponseEvent{
 191  		NostrId:   "e30d55d0e4f0d5391a1a1379f1d8b7d38ad02b3554b06ca993aa8790a3153f61",
 192  		RequestId: requestEvent1.ID,
 193  		State:     "confirmed",
 194  		RepliedAt: baseTime,
 195  		CreatedAt: baseTime,
 196  		UpdatedAt: baseTime,
 197  	}
 198  	create(t, tx, responseEvent1)
 199  
 200  	transaction1 := &db.Transaction{
 201  		AppId:          &app1.ID,
 202  		RequestEventId: &requestEvent1.ID,
 203  		Type:           "outgoing",
 204  		State:          "settled",
 205  		AmountMsat:     21000,
 206  		FeeMsat:        1000,
 207  		PaymentRequest: "lnbc210n1invoice",
 208  		PaymentHash:    "13d9764a54269fa4d5f4e7c410f4ffdbc839bbeaa2fcbb96343ca502f0c86e34",
 209  		Description:    "test transaction",
 210  		Preimage:       ptr("2c1ee1b464b1a1a147debe0ac0c8ce4b615f9bfa64d12a25c1c4d10ea45a5b02"),
 211  		CreatedAt:      baseTime,
 212  		UpdatedAt:      baseTime,
 213  		SettledAt:      &baseTime,
 214  		Metadata:       datatypes.JSON("{}"),
 215  		Boostagram:     datatypes.JSON("{}"),
 216  	}
 217  	create(t, tx, transaction1)
 218  
 219  	swap1 := &db.Swap{
 220  		SwapId:             "swap1",
 221  		Type:               "out",
 222  		State:              "success",
 223  		Invoice:            "lnbc210n1swapinvoice",
 224  		SendAmountSat:      21000,
 225  		ReceiveAmountSat:   20000,
 226  		Preimage:           "35a3f1a7a06a41b9ba3a1b1a8ff852e5085b3b593f8ba4677a35a1ca6d1a06e0",
 227  		PaymentHash:        "e6b1a1379f1d8b7d38ad02b3554b06ca993aa8790a3153f61e30d55d0e4f0d53",
 228  		DestinationAddress: "bc1qtest",
 229  		LockupAddress:      "bc1qlockup",
 230  		LockupTxId:         "lockuptx",
 231  		ClaimTxId:          "claimtx",
 232  		AutoSwap:           false,
 233  		TimeoutBlockHeight: 900000,
 234  		BoltzPubkey:        "02d1a06e08a509f2c8fe3edb2ba10811d030e2f6f3239e9f21203ac954a35a1c",
 235  		SwapTree:           datatypes.JSON("{}"),
 236  		CreatedAt:          baseTime,
 237  		UpdatedAt:          baseTime,
 238  	}
 239  	create(t, tx, swap1)
 240  
 241  	forward1 := &db.Forward{
 242  		OutboundAmountForwardedMsat: 1000000,
 243  		TotalFeeEarnedMsat:          1000,
 244  		CreatedAt:                   baseTime,
 245  		UpdatedAt:                   baseTime,
 246  	}
 247  	create(t, tx, forward1)
 248  }
 249  
 250  func requireCount[T any](t *testing.T, tx *gorm.DB, expected int64) {
 251  	var count int64
 252  	var model T
 253  	require.NoError(t, tx.Model(&model).Count(&count).Error)
 254  	require.Equal(t, expected, count)
 255  }
 256  
 257  func create[T any](t *testing.T, tx *gorm.DB, v T) *gorm.DB {
 258  	tx.Create(v)
 259  	require.NoError(t, tx.Error)
 260  	return tx
 261  }
 262  
 263  func ptr[T any](v T) *T {
 264  	return &v
 265  }
 266