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