184 lines
5.4 KiB
Go
184 lines
5.4 KiB
Go
package nip60
|
|
|
|
import (
|
|
"bytes"
|
|
"cmp"
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"fiatjaf.com/nostr"
|
|
"fiatjaf.com/nostr/keyer"
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/elnosh/gonuts/cashu"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/exp/rand"
|
|
)
|
|
|
|
func TestWallet(t *testing.T) {
|
|
ctx := context.Background()
|
|
kr := keyer.NewPlainKeySigner(nostr.MustSecretKeyFromHex("040cbf11f24b080ad9d8669d7514d9f3b7b1f58e5a6dcb75549352b041656537"))
|
|
|
|
privateKey, _ := btcec.NewPrivateKey()
|
|
|
|
w := &Wallet{
|
|
kr: kr,
|
|
PrivateKey: privateKey,
|
|
PublicKey: privateKey.PubKey(),
|
|
Mints: []string{"https://mint1.com", "https://mint2.com"},
|
|
Tokens: []Token{
|
|
{
|
|
Mint: "https://mint1.com",
|
|
Proofs: cashu.Proofs{{Amount: 100}},
|
|
mintedAt: nostr.Timestamp(time.Now().Add(-3 * time.Hour).Unix()),
|
|
},
|
|
{
|
|
Mint: "https://mint2.com",
|
|
Proofs: cashu.Proofs{{Amount: 200}},
|
|
mintedAt: nostr.Timestamp(time.Now().Add(-2 * time.Hour).Unix()),
|
|
},
|
|
{
|
|
Mint: "https://mint1.com",
|
|
Proofs: cashu.Proofs{{Amount: 300}},
|
|
mintedAt: nostr.Timestamp(time.Now().Add(-1 * time.Hour).Unix()),
|
|
},
|
|
},
|
|
History: []HistoryEntry{
|
|
{
|
|
In: true,
|
|
Amount: 100,
|
|
createdAt: nostr.Timestamp(time.Now().Add(-3 * time.Hour).Unix()),
|
|
TokenReferences: []TokenRef{
|
|
{Created: true, EventID: nostr.MustIDFromHex("645babb9051f46ddc97d960e68f82934e627f136dde7b860bf87c9213d937b58")},
|
|
},
|
|
},
|
|
{
|
|
In: true,
|
|
Amount: 200,
|
|
createdAt: nostr.Timestamp(time.Now().Add(-2 * time.Hour).Unix()),
|
|
TokenReferences: []TokenRef{
|
|
{Created: false, EventID: nostr.MustIDFromHex("add072ae7d7a027748e03024267a1c073f3fbc26cca468ba8630d039a7f5df72")},
|
|
{Created: true, EventID: nostr.MustIDFromHex("b8460b5589b68a0d9a017ac3784d17a0729046206aa631f7f4b763b738e36cf8")},
|
|
},
|
|
},
|
|
{
|
|
In: true,
|
|
Amount: 300,
|
|
createdAt: nostr.Timestamp(time.Now().Add(-1 * time.Hour).Unix()),
|
|
TokenReferences: []TokenRef{
|
|
{Created: false, EventID: nostr.MustIDFromHex("61f86031d0ab95e9134a3ab955e96104cb1f4d610172838d28aa7ae9dc1cc924")},
|
|
{Created: true, EventID: nostr.MustIDFromHex("588b78e4af06e960434239e7367a0bedf84747d4c52ff943f5e8b7daa3e1b601"), IsNutzap: true},
|
|
{Created: false, EventID: nostr.MustIDFromHex("8f14c0a4ff1bf85ccc26bf0125b9a289552f9b59bbb310b163d6a88a7bbd4ebc")},
|
|
{Created: true, EventID: nostr.MustIDFromHex("41a6f442b7c3c9e2f1e8c4835c00f17c56b3e3be4c9f7cf7bc4cdd705b1b61db"), IsNutzap: true},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// turn everything into events
|
|
events := make([]nostr.Event, 0, 7)
|
|
|
|
// wallet metadata event
|
|
metaEvent := nostr.Event{}
|
|
err := w.toEvent(ctx, kr, &metaEvent)
|
|
require.NoError(t, err)
|
|
events = append(events, metaEvent)
|
|
|
|
// token events
|
|
for i := range w.Tokens {
|
|
evt := nostr.Event{}
|
|
evt.Tags = nostr.Tags{}
|
|
err := w.Tokens[i].toEvent(ctx, kr, &evt)
|
|
require.NoError(t, err)
|
|
w.Tokens[i].event = &evt
|
|
events = append(events, evt)
|
|
}
|
|
|
|
// history events
|
|
for i := range w.History {
|
|
evt := nostr.Event{}
|
|
evt.Tags = nostr.Tags{}
|
|
err := w.History[i].toEvent(ctx, kr, &evt)
|
|
require.NoError(t, err)
|
|
w.History[i].event = &evt
|
|
events = append(events, evt)
|
|
}
|
|
|
|
// test different orderings
|
|
testCases := []struct {
|
|
name string
|
|
sort func([]nostr.Event)
|
|
}{
|
|
{
|
|
name: "random order",
|
|
sort: func(evts []nostr.Event) {
|
|
r := rand.New(rand.NewSource(42)) // deterministic
|
|
r.Shuffle(len(evts), func(i, j int) {
|
|
evts[i], evts[j] = evts[j], evts[i]
|
|
})
|
|
},
|
|
},
|
|
{
|
|
name: "most recent first",
|
|
sort: func(evts []nostr.Event) {
|
|
slices.SortFunc(evts, func(a, b nostr.Event) int {
|
|
return int(b.CreatedAt - a.CreatedAt)
|
|
})
|
|
},
|
|
},
|
|
{
|
|
name: "least recent first",
|
|
sort: func(evts []nostr.Event) {
|
|
slices.SortFunc(evts, func(a, b nostr.Event) int {
|
|
return int(a.CreatedAt - b.CreatedAt)
|
|
})
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// make a copy and sort it
|
|
eventsCopy := make([]nostr.Event, len(events))
|
|
copy(eventsCopy, events)
|
|
tc.sort(eventsCopy)
|
|
|
|
// create relay event channel
|
|
evtChan := make(chan nostr.RelayEvent)
|
|
eoseChan := make(chan struct{})
|
|
|
|
// send events in a goroutine
|
|
go func() {
|
|
for _, evt := range eventsCopy {
|
|
evtChan <- nostr.RelayEvent{Event: evt}
|
|
}
|
|
close(eoseChan)
|
|
close(evtChan)
|
|
}()
|
|
|
|
// load wallet from events
|
|
loaded := loadWallet(ctx, kr, evtChan, make(chan nostr.RelayEvent), eoseChan)
|
|
loaded.Processed = func(evt nostr.Event, err error) {
|
|
fmt.Println("processed", evt.Kind, err)
|
|
}
|
|
|
|
<-loaded.Stable
|
|
|
|
require.Len(t, loaded.Tokens, len(w.Tokens))
|
|
require.ElementsMatch(t, loaded.Tokens, w.Tokens)
|
|
require.Len(t, loaded.History, len(w.History))
|
|
slices.SortFunc(loaded.History, func(a, b HistoryEntry) int { return cmp.Compare(a.createdAt, b.createdAt) })
|
|
slices.SortFunc(w.History, func(a, b HistoryEntry) int { return cmp.Compare(a.createdAt, b.createdAt) })
|
|
for i := range w.History {
|
|
slices.SortFunc(loaded.History[i].TokenReferences, func(a, b TokenRef) int { return bytes.Compare(a.EventID[:], b.EventID[:]) })
|
|
slices.SortFunc(w.History[i].TokenReferences, func(a, b TokenRef) int { return bytes.Compare(a.EventID[:], b.EventID[:]) })
|
|
require.Equal(t, loaded.History[i], w.History[i])
|
|
}
|
|
require.ElementsMatch(t, loaded.Mints, w.Mints)
|
|
require.Equal(t, loaded.PublicKey.SerializeCompressed(), w.PublicKey.SerializeCompressed())
|
|
})
|
|
}
|
|
}
|