Skip to content

Commit

Permalink
initial tests for create account tx
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasmenendez committed Sep 26, 2024
1 parent 987662a commit fdcda49
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 20 deletions.
30 changes: 30 additions & 0 deletions api/api_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package api

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strings"
"testing"
"time"

qt "github.com/frankban/quicktest"
"github.com/vocdoni/saas-backend/account"
"github.com/vocdoni/saas-backend/db"
"github.com/vocdoni/saas-backend/notifications/smtp"
Expand All @@ -17,8 +21,10 @@ import (
)

type apiTestCase struct {
name string
uri string
method string
headers map[string]string
body []byte
expectedStatus int
expectedBody []byte
Expand Down Expand Up @@ -62,6 +68,30 @@ func mustMarshal(i any) []byte {
return b
}

// runAPITestCase helper function runs the given API test case and checks the
// response status code and body against the expected values.
func runAPITestCase(c *qt.C, tc apiTestCase) {
c.Logf("running api test case: %s", tc.name)
req, err := http.NewRequest(tc.method, tc.uri, bytes.NewBuffer(tc.body))
c.Assert(err, qt.IsNil)
for k, v := range tc.headers {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
c.Assert(err, qt.IsNil)
defer func() {
if err := resp.Body.Close(); err != nil {
c.Errorf("error closing response body: %v", err)
}
}()
c.Assert(resp.StatusCode, qt.Equals, tc.expectedStatus)
if tc.expectedBody != nil {
body, err := io.ReadAll(resp.Body)
c.Assert(err, qt.IsNil)
c.Assert(strings.TrimSpace(string(body)), qt.Equals, string(tc.expectedBody))
}
}

// pingAPI helper function pings the API endpoint and retries the request
// if it fails until the retries limit is reached. It returns an error if the
// request fails or the status code is not 200 as many times as the retries
Expand Down
12 changes: 12 additions & 0 deletions api/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
},
}
}
default:
log.Warnw("transaction type not allowed", "user", user.Email, "type", fmt.Sprintf("%T", tx.Payload))
ErrTxTypeNotAllowed.Write(w)
return
}
case *models.Tx_NewProcess:
txNewProcess := tx.GetNewProcess()
Expand Down Expand Up @@ -148,6 +152,10 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
},
}
}
default:
log.Warnw("transaction type not allowed", "user", user.Email, "type", fmt.Sprintf("%T", tx.Payload))
ErrTxTypeNotAllowed.Write(w)
return
}
case *models.Tx_SetProcess:
txSetProcess := tx.GetSetProcess()
Expand Down Expand Up @@ -222,6 +230,10 @@ func (a *API) signTxHandler(w http.ResponseWriter, r *http.Request) {
AnonymousVotes: currentProcess.VoteMode.Anonymous,
MaxVoteOverwrite: currentProcess.TallyMode.MaxVoteOverwrites,
})
default:
log.Warnw("transaction type not allowed", "user", user.Email, "type", fmt.Sprintf("%T", tx.Payload))
ErrTxTypeNotAllowed.Write(w)
return
}
// include the faucet package in the tx if it's not present
if txSetProcess.FaucetPackage == nil {
Expand Down
235 changes: 235 additions & 0 deletions api/transaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package api

import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"regexp"
"testing"

ethcommon "github.com/ethereum/go-ethereum/common"
qt "github.com/frankban/quicktest"
"go.vocdoni.io/dvote/crypto/ethereum"
"go.vocdoni.io/proto/build/go/models"
"google.golang.org/protobuf/proto"
)

func TestSignTxHandler(t *testing.T) {
c := qt.New(t)
defer func() {
if err := testDB.Reset(); err != nil {
c.Logf("error resetting test database: %v", err)
}
}()
// create user and organization
userDataJson := mustMarshal(&UserInfo{
Email: testEmail,
Password: testPass,
FirstName: testFirstName,
LastName: testLastName,
})
signupReq, err := http.NewRequest(http.MethodPost, testURL(usersEndpoint), bytes.NewBuffer(userDataJson))
c.Assert(err, qt.IsNil)
signuoResp, err := http.DefaultClient.Do(signupReq)
c.Assert(err, qt.IsNil)
c.Assert(signuoResp.StatusCode, qt.Equals, http.StatusOK)
c.Assert(signuoResp.Body.Close(), qt.IsNil)
c.Assert(signuoResp.Body.Close(), qt.IsNil)
// get the verification code from the email
signupMailBody, err := testMailService.FindEmail(context.Background(), testEmail)
c.Assert(err, qt.IsNil)
// create a regex to find the verification code in the email
mailCodeRgx := regexp.MustCompile(fmt.Sprintf(`%s(.{%d})`, VerificationCodeTextBody, VerificationCodeLength*2))
mailCode := mailCodeRgx.FindStringSubmatch(signupMailBody)
// verify the user
verifyJson := mustMarshal(&UserVerification{
Email: testEmail,
Code: mailCode[1],
})
verifyReq, err := http.NewRequest(http.MethodPost, testURL(verifyUserEndpoint), bytes.NewBuffer(verifyJson))
c.Assert(err, qt.IsNil)
verifyResp, err := http.DefaultClient.Do(verifyReq)
c.Assert(err, qt.IsNil)
c.Assert(verifyResp.StatusCode, qt.Equals, http.StatusOK)
c.Assert(verifyResp.Body.Close(), qt.IsNil)
// request login
loginReq, err := http.NewRequest(http.MethodPost, testURL(authLoginEndpoint), bytes.NewBuffer(userDataJson))
c.Assert(err, qt.IsNil)
loginResp, err := http.DefaultClient.Do(loginReq)
c.Assert(err, qt.IsNil)
c.Assert(loginResp.StatusCode, qt.Equals, http.StatusOK)
// parse login response
var loginRespData *LoginResponse
c.Assert(json.NewDecoder(loginResp.Body).Decode(&loginRespData), qt.IsNil)
c.Assert(loginResp.Body.Close(), qt.IsNil)
// create an organization
orgDataJson := mustMarshal(&OrganizationInfo{
Name: "Test Organization",
Type: "community",
Description: "My amazing testing organization",
Size: "100",
Color: "#ff0000",
Logo: "https://placehold.co/128x128.png",
Subdomain: "mysubdomain",
Language: "ES",
Timezone: "GMT+2",
Country: "Spain",
Header: "https://placehold.co/600x400.png",
})
orgReq, err := http.NewRequest(http.MethodPost, testURL(organizationsEndpoint), bytes.NewBuffer(orgDataJson))
c.Assert(err, qt.IsNil)
// include the user token in the request headers
orgReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", loginRespData.Token))
orgResp, err := http.DefaultClient.Do(orgReq)
c.Assert(err, qt.IsNil)
c.Assert(orgResp.StatusCode, qt.Equals, http.StatusOK)
c.Assert(orgResp.Body.Close(), qt.IsNil)
// get the organization address
orgsReq, err := http.NewRequest(http.MethodGet, testURL(authAddressesEndpoint), nil)
c.Assert(err, qt.IsNil)
orgsReq.Header.Set("Authorization", fmt.Sprintf("Bearer %s", loginRespData.Token))
orgsResp, err := http.DefaultClient.Do(orgsReq)
c.Assert(err, qt.IsNil)
c.Assert(orgsResp.StatusCode, qt.Equals, http.StatusOK)
var orgsAddress *OrganizationAddresses
c.Assert(json.NewDecoder(orgsResp.Body).Decode(&orgsAddress), qt.IsNil)
c.Assert(orgsResp.Body.Close(), qt.IsNil)
// parse org address
strMainOrgAddress := orgsAddress.Addresses[0]
mainOrgAddress := ethcommon.HexToAddress(strMainOrgAddress)
c.Run("setAccountTx", func(c *qt.C) {
infoUri := "https://example.com"
authHeaders := map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", loginRespData.Token),
}
// generate random address
randSignKeys := ethereum.NewSignKeys()
c.Assert(randSignKeys.Generate(), qt.IsNil)
// different account set account tx
differentAccountTx := &models.Tx{
Payload: &models.Tx_SetAccount{
SetAccount: &models.SetAccountTx{
Account: randSignKeys.Address().Bytes(),
Txtype: models.TxType_CREATE_ACCOUNT,
InfoURI: &infoUri,
},
},
}
bDifferentAccountTx, err := proto.Marshal(differentAccountTx)
c.Assert(err, qt.IsNil)
// no info uri set account tx
noInfoUriTx := &models.Tx{
Payload: &models.Tx_SetAccount{
SetAccount: &models.SetAccountTx{
Account: mainOrgAddress.Bytes(),
Txtype: models.TxType_CREATE_ACCOUNT,
},
},
}
bNoInfoUriTx, err := proto.Marshal(noInfoUriTx)
c.Assert(err, qt.IsNil)
// no account set account tx
noAccountTx := &models.Tx{
Payload: &models.Tx_SetAccount{
SetAccount: &models.SetAccountTx{
Txtype: models.TxType_CREATE_ACCOUNT,
InfoURI: &infoUri,
},
},
}
bNoAccountTx, err := proto.Marshal(noAccountTx)
c.Assert(err, qt.IsNil)
// invalid tx type set account tx
invalidTxTypeTx := &models.Tx{
Payload: &models.Tx_SetAccount{
SetAccount: &models.SetAccountTx{
Account: mainOrgAddress.Bytes(),
Txtype: models.TxType(100),
InfoURI: &infoUri,
},
},
}
bInvalidTxTypeTx, err := proto.Marshal(invalidTxTypeTx)
c.Assert(err, qt.IsNil)
// valid set account tx
validSetAccountTx := &models.Tx{
Payload: &models.Tx_SetAccount{
SetAccount: &models.SetAccountTx{
Account: mainOrgAddress.Bytes(),
Txtype: models.TxType_CREATE_ACCOUNT,
InfoURI: &infoUri,
},
},
}
bValidSetAccountTx, err := proto.Marshal(validSetAccountTx)
c.Assert(err, qt.IsNil)
tests := []apiTestCase{
{
name: "differentAccount",
uri: testURL(signTxEndpoint),
method: http.MethodPost,
headers: authHeaders,
body: mustMarshal(&TransactionData{
Address: strMainOrgAddress,
TxPayload: base64.StdEncoding.EncodeToString(bDifferentAccountTx),
}),
expectedBody: mustMarshal(ErrUnauthorized.With("invalid account")),
expectedStatus: http.StatusUnauthorized,
},
{
name: "noInfoUri",
uri: testURL(signTxEndpoint),
method: http.MethodPost,
headers: authHeaders,
body: mustMarshal(&TransactionData{
Address: strMainOrgAddress,
TxPayload: base64.StdEncoding.EncodeToString(bNoInfoUriTx),
}),
expectedBody: mustMarshal(ErrInvalidTxFormat.With("missing fields")),
expectedStatus: http.StatusBadRequest,
},
{
name: "noAccount",
uri: testURL(signTxEndpoint),
method: http.MethodPost,
headers: authHeaders,
body: mustMarshal(&TransactionData{
Address: strMainOrgAddress,
TxPayload: base64.StdEncoding.EncodeToString(bNoAccountTx),
}),
expectedBody: mustMarshal(ErrInvalidTxFormat.With("missing fields")),
expectedStatus: http.StatusBadRequest,
},
{
name: "invalidTxType",
uri: testURL(signTxEndpoint),
method: http.MethodPost,
headers: authHeaders,
body: mustMarshal(&TransactionData{
Address: strMainOrgAddress,
TxPayload: base64.StdEncoding.EncodeToString(bInvalidTxTypeTx),
}),
expectedBody: mustMarshal(ErrTxTypeNotAllowed),
expectedStatus: http.StatusBadRequest,
},
{
name: "validSetAccount",
uri: testURL(signTxEndpoint),
method: http.MethodPost,
headers: authHeaders,
body: mustMarshal(&TransactionData{
Address: strMainOrgAddress,
TxPayload: base64.StdEncoding.EncodeToString(bValidSetAccountTx),
}),
expectedStatus: http.StatusOK,
},
}
// run the tests
for _, tt := range tests {
runAPITestCase(c, tt)
}
})
}
Loading

0 comments on commit fdcda49

Please sign in to comment.