mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
187 lines
5.1 KiB
Go
187 lines
5.1 KiB
Go
package mchain
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// keygenStub returns a fake mpcd /keygen endpoint that hands back a
|
|
// pre-canned response and counts the number of times it was hit.
|
|
// Wallet IDs are derived from the count so a test can prove that
|
|
// GetOrCreate isn't minting twice.
|
|
func keygenStub(t *testing.T) (*httptest.Server, *int64) {
|
|
t.Helper()
|
|
var calls int64
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/keygen" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
n := atomic.AddInt64(&calls, 1)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"wallet_id": "release-test-" + itoa(int(n)),
|
|
"eth_address": "0xE000000000000000000000000000000000000000",
|
|
"result_type": "success",
|
|
})
|
|
}))
|
|
t.Cleanup(srv.Close)
|
|
return srv, &calls
|
|
}
|
|
|
|
func itoa(n int) string {
|
|
const digits = "0123456789"
|
|
if n == 0 {
|
|
return "0"
|
|
}
|
|
var buf [20]byte
|
|
i := len(buf)
|
|
for n > 0 {
|
|
i--
|
|
buf[i] = digits[n%10]
|
|
n /= 10
|
|
}
|
|
return string(buf[i:])
|
|
}
|
|
|
|
func newClientForStub(srv *httptest.Server) *Client {
|
|
return &Client{
|
|
APIURL: srv.URL,
|
|
OrgID: "test-org",
|
|
Timeout: 2 * time.Second,
|
|
}
|
|
}
|
|
|
|
func TestFileReleaseStore_GetOrCreate_IsIdempotent(t *testing.T) {
|
|
srv, calls := keygenStub(t)
|
|
client := newClientForStub(srv)
|
|
|
|
store, err := NewFileReleaseStore(client, "")
|
|
if err != nil {
|
|
t.Fatalf("new store: %v", err)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
w1, err := store.GetOrCreate(ctx, "LUX_TESTNET")
|
|
if err != nil {
|
|
t.Fatalf("first GetOrCreate: %v", err)
|
|
}
|
|
w2, err := store.GetOrCreate(ctx, "LUX_TESTNET")
|
|
if err != nil {
|
|
t.Fatalf("second GetOrCreate: %v", err)
|
|
}
|
|
if w1.Name != w2.Name || w1.Address != w2.Address {
|
|
t.Errorf("expected same wallet on second call, got %+v vs %+v", w1, w2)
|
|
}
|
|
if got := atomic.LoadInt64(calls); got != 1 {
|
|
t.Errorf("expected exactly 1 keygen call, got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestFileReleaseStore_DistinctNetworks_DistinctWallets(t *testing.T) {
|
|
srv, _ := keygenStub(t)
|
|
store, _ := NewFileReleaseStore(newClientForStub(srv), "")
|
|
|
|
ctx := context.Background()
|
|
lux, _ := store.GetOrCreate(ctx, "LUX_TESTNET")
|
|
base, _ := store.GetOrCreate(ctx, "BASE_SEPOLIA")
|
|
if lux.Name == base.Name {
|
|
t.Errorf("expected distinct wallet IDs across networks, both = %q", lux.Name)
|
|
}
|
|
}
|
|
|
|
func TestFileReleaseStore_PersistsAcrossInstances(t *testing.T) {
|
|
srv, calls := keygenStub(t)
|
|
client := newClientForStub(srv)
|
|
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "release-wallets.json")
|
|
|
|
first, err := NewFileReleaseStore(client, path)
|
|
if err != nil {
|
|
t.Fatalf("new first store: %v", err)
|
|
}
|
|
w1, err := first.GetOrCreate(context.Background(), "LUX_TESTNET")
|
|
if err != nil {
|
|
t.Fatalf("first mint: %v", err)
|
|
}
|
|
|
|
// New store at the same path simulates a process restart.
|
|
second, err := NewFileReleaseStore(client, path)
|
|
if err != nil {
|
|
t.Fatalf("new second store: %v", err)
|
|
}
|
|
w2, err := second.GetOrCreate(context.Background(), "LUX_TESTNET")
|
|
if err != nil {
|
|
t.Fatalf("second mint: %v", err)
|
|
}
|
|
if w1.Name != w2.Name || w1.Address != w2.Address {
|
|
t.Errorf("wallet did not survive restart: %+v vs %+v", w1, w2)
|
|
}
|
|
if got := atomic.LoadInt64(calls); got != 1 {
|
|
t.Errorf("expected the persisted wallet to be reused (1 keygen call), got %d", got)
|
|
}
|
|
}
|
|
|
|
func TestFileReleaseStore_RejectsEmptyNetwork(t *testing.T) {
|
|
srv, _ := keygenStub(t)
|
|
store, _ := NewFileReleaseStore(newClientForStub(srv), "")
|
|
_, err := store.GetOrCreate(context.Background(), "")
|
|
if err == nil {
|
|
t.Fatal("expected an error for empty network, got nil")
|
|
}
|
|
}
|
|
|
|
func TestFileReleaseStore_LoadIgnoresMissingFile(t *testing.T) {
|
|
srv, _ := keygenStub(t)
|
|
// Point at a path that doesn't exist yet — constructor should
|
|
// succeed (missing file = no wallets minted yet).
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "does-not-exist.json")
|
|
_, err := NewFileReleaseStore(newClientForStub(srv), path)
|
|
if err != nil {
|
|
t.Fatalf("expected nil err for missing file, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFileReleaseStore_PersistFileShape(t *testing.T) {
|
|
srv, _ := keygenStub(t)
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "release-wallets.json")
|
|
store, _ := NewFileReleaseStore(newClientForStub(srv), path)
|
|
if _, err := store.GetOrCreate(context.Background(), "LUX_TESTNET"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatalf("read persisted file: %v", err)
|
|
}
|
|
var doc struct {
|
|
Version int `json:"version"`
|
|
Wallets map[string]*Wallet `json:"wallets"`
|
|
}
|
|
if err := json.Unmarshal(data, &doc); err != nil {
|
|
t.Fatalf("decode persisted file: %v\n%s", err, data)
|
|
}
|
|
if doc.Version != releaseStoreVersion {
|
|
t.Errorf("file version = %d, want %d", doc.Version, releaseStoreVersion)
|
|
}
|
|
if w := doc.Wallets["LUX_TESTNET"]; w == nil || w.Address == "" {
|
|
t.Errorf("LUX_TESTNET wallet missing/empty in persisted file: %+v", doc.Wallets)
|
|
}
|
|
}
|
|
|
|
func TestFileReleaseStore_NilClientRejected(t *testing.T) {
|
|
_, err := NewFileReleaseStore(nil, "")
|
|
if err == nil {
|
|
t.Fatal("expected an error for nil client, got nil")
|
|
}
|
|
}
|