vspd/webapi/setaltsignaddr_test.go
Jamie Holdstock ed1fac1a2a
Add types module for API requests/responses/errors. (#356)
* Remove helper func getCommitmentAddress.

This function is only used in one place so removing it does not introduce any duplication. Removing the func also removes the need for errors.Is comparison, which will be very useful in upcoming changes.

* Rename apiError to ErrorCode.

* Don't use inline type for API errors.

* Export webapi errors.

* Export webapi request/responses.

* Add types module for API requests/responses/errors
2022-11-18 15:06:47 -05:00

291 lines
7.0 KiB
Go

// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package webapi
import (
"bytes"
"crypto/ed25519"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"github.com/decred/dcrd/chaincfg/v3"
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v3"
"github.com/decred/slog"
"github.com/decred/vspd/database"
"github.com/decred/vspd/types"
"github.com/gin-gonic/gin"
)
const (
// hexCharset is a list of all valid hexadecimal characters.
hexCharset = "1234567890abcdef"
// sigCharset is a list of all valid request/response signature characters
// (base64 encoding).
sigCharset = "0123456789ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/="
testDb = "test.db"
)
var (
seededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
feeXPub = "feexpub"
maxVoteChangeRecords = 3
api *Server
)
// randBytes returns a byte slice of size n filled with random bytes.
func randBytes(n int) []byte {
slice := make([]byte, n)
if _, err := seededRand.Read(slice); err != nil {
panic(err)
}
return slice
}
func stdoutLogger() slog.Logger {
backend := slog.NewBackend(os.Stdout)
log := backend.Logger("test")
log.SetLevel(slog.LevelTrace)
return log
}
func TestMain(m *testing.M) {
log := stdoutLogger()
// Set up some global params.
cfg := Config{
NetParams: chaincfg.MainNetParams(),
}
_, signPrivKey, _ := ed25519.GenerateKey(seededRand)
// Create a database to use.
// Ensure we are starting with a clean environment.
os.Remove(testDb)
// Create a new blank database for all tests.
err := database.CreateNew(testDb, feeXPub, log)
if err != nil {
panic(fmt.Errorf("error creating test database: %w", err))
}
// Open the newly created database so it is ready to use.
db, err := database.Open(testDb, log, maxVoteChangeRecords)
if err != nil {
panic(fmt.Errorf("error opening test database: %w", err))
}
api = &Server{
cfg: cfg,
signPrivKey: signPrivKey,
db: db,
log: log,
}
// Run tests.
exitCode := m.Run()
writeBackup := false
db.Close(writeBackup)
os.Remove(testDb)
os.Exit(exitCode)
}
// randString randomly generates a string of the requested length, using only
// characters from the provided charset.
func randString(length int, charset string) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
// Ensure that testNode satisfies Node.
var _ Node = (*testNode)(nil)
type testNode struct {
getRawTransaction *dcrdtypes.TxRawResult
getRawTransactionErr error
existsLiveTicket bool
existsLiveTicketErr error
}
func (n *testNode) ExistsLiveTicket(ticketHash string) (bool, error) {
return n.existsLiveTicket, n.existsLiveTicketErr
}
func (n *testNode) GetRawTransaction(txHash string) (*dcrdtypes.TxRawResult, error) {
return n.getRawTransaction, n.getRawTransactionErr
}
func TestSetAltSignAddress(t *testing.T) {
const testAddr = "DsVoDXNQqyF3V83PJJ5zMdnB4pQuJHBAh15"
tests := []struct {
name string
dcrdClientErr bool
vspClosed bool
deformReq int
addr string
node *testNode
isExistingAltSignAddr bool
wantCode int
}{{
name: "ok",
addr: testAddr,
node: &testNode{
getRawTransaction: &dcrdtypes.TxRawResult{
Confirmations: 1000,
},
getRawTransactionErr: nil,
existsLiveTicket: true,
},
wantCode: http.StatusOK,
}, {
name: "vsp closed",
vspClosed: true,
wantCode: http.StatusBadRequest,
}, {
name: "dcrd client error",
dcrdClientErr: true,
wantCode: http.StatusInternalServerError,
}, {
name: "bad request",
deformReq: 1,
wantCode: http.StatusBadRequest,
}, {
name: "bad addr",
addr: "xxx",
wantCode: http.StatusBadRequest,
}, {
name: "addr wrong type",
addr: "DkM3ZigNyiwHrsXRjkDQ8t8tW6uKGW9g61qEkG3bMqQPQWYEf5X3J",
wantCode: http.StatusBadRequest,
}, {
name: "getRawTransaction error from dcrd client",
addr: testAddr,
node: &testNode{
getRawTransactionErr: errors.New("getRawTransaction error"),
},
wantCode: http.StatusInternalServerError,
}, {
name: "existsLiveTicket error from dcrd client",
addr: testAddr,
node: &testNode{
getRawTransaction: &dcrdtypes.TxRawResult{
Confirmations: 1000,
},
existsLiveTicketErr: errors.New("existsLiveTicket error"),
},
wantCode: http.StatusInternalServerError,
}, {
name: "ticket can't vote",
addr: testAddr,
node: &testNode{
getRawTransaction: &dcrdtypes.TxRawResult{
Confirmations: 1000,
},
existsLiveTicket: false,
},
wantCode: http.StatusBadRequest,
}, {
name: "only one alt sign addr allowed",
addr: testAddr,
node: &testNode{
getRawTransaction: &dcrdtypes.TxRawResult{},
existsLiveTicket: true,
},
isExistingAltSignAddr: true,
wantCode: http.StatusBadRequest,
}}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ticketHash := randString(64, hexCharset)
req := &types.SetAltSignAddrRequest{
Timestamp: time.Now().Unix(),
TicketHash: ticketHash,
TicketHex: randString(504, hexCharset),
ParentHex: randString(504, hexCharset),
AltSignAddress: test.addr,
}
reqSig := randString(504, hexCharset)
b, err := json.Marshal(req)
if err != nil {
t.Fatal(err)
}
if test.isExistingAltSignAddr {
data := &database.AltSignAddrData{
AltSignAddr: test.addr,
Req: string(b),
ReqSig: reqSig,
Resp: string(randBytes(1000)),
RespSig: randString(96, sigCharset),
}
if err := api.db.InsertAltSignAddr(ticketHash, data); err != nil {
t.Fatalf("unable to insert alt sign addr: %v", err)
}
}
api.cfg.VspClosed = test.vspClosed
w := httptest.NewRecorder()
c, r := gin.CreateTestContext(w)
var dcrdErr error
if test.dcrdClientErr {
dcrdErr = errors.New("error")
}
handle := func(c *gin.Context) {
c.Set(dcrdKey, test.node)
c.Set(dcrdErrorKey, dcrdErr)
c.Set(requestBytesKey, b[test.deformReq:])
api.setAltSignAddr(c)
}
r.POST("/", handle)
c.Request, err = http.NewRequest(http.MethodPost, "/", nil)
if err != nil {
t.Fatal(err)
}
c.Request.Header.Set("VSP-Client-Signature", reqSig)
r.ServeHTTP(w, c.Request)
if test.wantCode != w.Code {
t.Fatalf("expected status %d, got %d", test.wantCode, w.Code)
}
altsig, err := api.db.AltSignAddrData(ticketHash)
if err != nil {
t.Fatalf("unable to get alt sign addr data: %v", err)
}
if test.wantCode != http.StatusOK && !test.isExistingAltSignAddr {
if altsig != nil {
t.Fatalf("expected no alt sign addr saved for errored state")
}
return
}
if !bytes.Equal(b, []byte(altsig.Req)) || altsig.ReqSig != reqSig {
t.Fatalf("expected alt sign addr data different than actual")
}
})
}
}