user_labels_test.go raw

   1  package transactions
   2  
   3  import (
   4  	"context"
   5  	"encoding/json"
   6  	"fmt"
   7  	"strings"
   8  	"testing"
   9  
  10  	"github.com/stretchr/testify/assert"
  11  	"github.com/stretchr/testify/require"
  12  	"gorm.io/datatypes"
  13  
  14  	"github.com/getAlby/hub/constants"
  15  	"github.com/getAlby/hub/db"
  16  	"github.com/getAlby/hub/tests"
  17  )
  18  
  19  func setupTransactionForUserLabels(t *testing.T, transactionType string, initialMetadata map[string]interface{}) (*transactionsService, *tests.TestService, *db.Transaction) {
  20  	t.Helper()
  21  
  22  	svc, err := tests.CreateTestService(t)
  23  	require.NoError(t, err)
  24  
  25  	preimage := tests.MockLNClientTransaction.Preimage
  26  	dbTransaction := &db.Transaction{
  27  		State:          constants.TRANSACTION_STATE_SETTLED,
  28  		Type:           transactionType,
  29  		PaymentRequest: tests.MockLNClientTransaction.Invoice,
  30  		PaymentHash:    tests.MockLNClientTransaction.PaymentHash,
  31  		Preimage:       &preimage,
  32  		AmountMsat:     123000,
  33  	}
  34  	if initialMetadata != nil {
  35  		metadataBytes, err := json.Marshal(initialMetadata)
  36  		require.NoError(t, err)
  37  		dbTransaction.Metadata = datatypes.JSON(metadataBytes)
  38  	}
  39  
  40  	require.NoError(t, svc.DB.Create(dbTransaction).Error)
  41  
  42  	return NewTransactionsService(svc.DB, svc.EventPublisher), svc, dbTransaction
  43  }
  44  
  45  func loadTransactionMetadata(t *testing.T, svc *tests.TestService, id uint) map[string]interface{} {
  46  	t.Helper()
  47  
  48  	var dbTransaction db.Transaction
  49  	require.NoError(t, svc.DB.First(&dbTransaction, id).Error)
  50  	if dbTransaction.Metadata == nil {
  51  		return nil
  52  	}
  53  
  54  	metadata := map[string]interface{}{}
  55  	require.NoError(t, json.Unmarshal(dbTransaction.Metadata, &metadata))
  56  	return metadata
  57  }
  58  
  59  func TestSetTransactionUserLabels_ValidLabels(t *testing.T) {
  60  	transactionsService, svc, dbTransaction := setupTransactionForUserLabels(t, constants.TRANSACTION_TYPE_OUTGOING, nil)
  61  	defer svc.Remove()
  62  
  63  	err := transactionsService.SetTransactionUserLabels(context.TODO(), dbTransaction.ID, map[string]string{
  64  		"description":  "top up PPQ.AI",
  65  		"counterparty": "PPQ.AI",
  66  	})
  67  	require.NoError(t, err)
  68  
  69  	metadata := loadTransactionMetadata(t, svc, dbTransaction.ID)
  70  	labels, ok := metadata["user_labels"].(map[string]interface{})
  71  	require.True(t, ok)
  72  	assert.Equal(t, "top up PPQ.AI", labels["description"])
  73  	assert.Equal(t, "PPQ.AI", labels["counterparty"])
  74  }
  75  
  76  func TestSetTransactionUserLabels_IncomingTransaction(t *testing.T) {
  77  	transactionsService, svc, dbTransaction := setupTransactionForUserLabels(t, constants.TRANSACTION_TYPE_INCOMING, nil)
  78  	defer svc.Remove()
  79  
  80  	err := transactionsService.SetTransactionUserLabels(context.TODO(), dbTransaction.ID, map[string]string{
  81  		"source": "customer",
  82  	})
  83  	require.NoError(t, err)
  84  
  85  	metadata := loadTransactionMetadata(t, svc, dbTransaction.ID)
  86  	labels, ok := metadata["user_labels"].(map[string]interface{})
  87  	require.True(t, ok)
  88  	assert.Equal(t, "customer", labels["source"])
  89  }
  90  
  91  func TestSetTransactionUserLabels_PreservesExistingMetadata(t *testing.T) {
  92  	transactionsService, svc, dbTransaction := setupTransactionForUserLabels(t, constants.TRANSACTION_TYPE_OUTGOING, map[string]interface{}{
  93  		"comment": "hello",
  94  		"nostr":   map[string]interface{}{"pubkey": "abcdef"},
  95  	})
  96  	defer svc.Remove()
  97  
  98  	err := transactionsService.SetTransactionUserLabels(context.TODO(), dbTransaction.ID, map[string]string{
  99  		"account": "sponsoring",
 100  	})
 101  	require.NoError(t, err)
 102  
 103  	metadata := loadTransactionMetadata(t, svc, dbTransaction.ID)
 104  	assert.Equal(t, "hello", metadata["comment"])
 105  	assert.NotNil(t, metadata["nostr"])
 106  	labels, ok := metadata["user_labels"].(map[string]interface{})
 107  	require.True(t, ok)
 108  	assert.Equal(t, "sponsoring", labels["account"])
 109  }
 110  
 111  func TestSetTransactionUserLabels_ClearsLabels(t *testing.T) {
 112  	transactionsService, svc, dbTransaction := setupTransactionForUserLabels(t, constants.TRANSACTION_TYPE_OUTGOING, map[string]interface{}{
 113  		"comment":     "hello",
 114  		"user_labels": map[string]interface{}{"description": "old"},
 115  	})
 116  	defer svc.Remove()
 117  
 118  	err := transactionsService.SetTransactionUserLabels(context.TODO(), dbTransaction.ID, map[string]string{})
 119  	require.NoError(t, err)
 120  
 121  	metadata := loadTransactionMetadata(t, svc, dbTransaction.ID)
 122  	_, exists := metadata["user_labels"]
 123  	assert.False(t, exists)
 124  	assert.Equal(t, "hello", metadata["comment"])
 125  }
 126  
 127  func TestSetTransactionUserLabels_TrimsAndDropsBlankLabels(t *testing.T) {
 128  	transactionsService, svc, dbTransaction := setupTransactionForUserLabels(t, constants.TRANSACTION_TYPE_OUTGOING, nil)
 129  	defer svc.Remove()
 130  
 131  	err := transactionsService.SetTransactionUserLabels(context.TODO(), dbTransaction.ID, map[string]string{
 132  		"  account  ": "  sponsoring  ",
 133  		"empty":       "",
 134  		"":            "orphan",
 135  	})
 136  	require.NoError(t, err)
 137  
 138  	metadata := loadTransactionMetadata(t, svc, dbTransaction.ID)
 139  	labels, ok := metadata["user_labels"].(map[string]interface{})
 140  	require.True(t, ok)
 141  	assert.Len(t, labels, 1)
 142  	assert.Equal(t, "sponsoring", labels["account"])
 143  }
 144  
 145  func TestSetTransactionUserLabels_RejectsOversizedMetadata(t *testing.T) {
 146  	transactionsService, svc, dbTransaction := setupTransactionForUserLabels(t, constants.TRANSACTION_TYPE_OUTGOING, nil)
 147  	defer svc.Remove()
 148  
 149  	labels := map[string]string{
 150  		"description": strings.Repeat("a", constants.INVOICE_METADATA_MAX_LENGTH),
 151  	}
 152  	err := transactionsService.SetTransactionUserLabels(context.TODO(), dbTransaction.ID, labels)
 153  	require.Error(t, err)
 154  
 155  	encodedMetadata, marshalErr := json.Marshal(map[string]interface{}{
 156  		"user_labels": labels,
 157  	})
 158  	require.NoError(t, marshalErr)
 159  
 160  	assert.Equal(t,
 161  		fmt.Sprintf("encoded invoice metadata provided is too large. Limit: %d Received: %d", constants.INVOICE_METADATA_MAX_LENGTH, len(encodedMetadata)),
 162  		err.Error(),
 163  	)
 164  }
 165