taler-mailbox

Service for asynchronous wallet-to-wallet payment messages
Log | Files | Refs | Submodules | README | LICENSE

commit 6678d6a01acd7c130d546a3a3ecc8250b7d316d9
parent ccc069e14056a9562725f1f68aefc35371ec46b0
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Thu, 24 Apr 2025 13:14:01 +0200

add tests for API

Diffstat:
Acmd/mailbox-server/main_test.go | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acmd/mailbox-server/test-mailbox.conf | 12++++++++++++
Mpkg/rest/mailbox.go | 28+++++++++++++---------------
3 files changed, 178 insertions(+), 15 deletions(-)

diff --git a/cmd/mailbox-server/main_test.go b/cmd/mailbox-server/main_test.go @@ -0,0 +1,153 @@ +package main_test + +import ( + "bytes" + "crypto/ed25519" + "crypto/rand" + "crypto/sha512" + "encoding/binary" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "testing" + + gnunetutil "git.gnunet.org/gnunet-go.git/pkg/util" + "taler.net/taler-mailbox/internal/gana" + "taler.net/taler-mailbox/pkg/rest" +) + +var a mailbox.Mailbox + +var testWalletAlicePriv ed25519.PrivateKey +var testWalletAlice ed25519.PublicKey +var testWalletAliceString string + +func executeRequest(req *http.Request) *httptest.ResponseRecorder { + rr := httptest.NewRecorder() + a.Router.ServeHTTP(rr, req) + return rr +} + +func checkResponseCode(t *testing.T, expected, actual int) { + if expected != actual { + t.Errorf("Expected response code %d, Got %d\n", expected, actual) + } +} + +func TestMain(m *testing.M) { + a.Initialize("test-mailbox.conf") + testWalletAlice, testWalletAlicePriv, _ = ed25519.GenerateKey(nil) + h := sha512.New() + h.Write(testWalletAlice) + testWalletAliceString = gnunetutil.EncodeBinaryToString(h.Sum(nil)) + code := m.Run() + // Purge DB + a.Db.Where("1 = 1").Delete(&mailbox.InboxEntry{}) + os.Exit(code) +} + +func TestEmptyMailbox(t *testing.T) { + a.Db.Where("1 = 1").Delete(&mailbox.InboxEntry{}) + req, _ := http.NewRequest("GET", "/" + testWalletAliceString, nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusNoContent, response.Code) + + body := response.Body.String() + if body != "" { + t.Errorf("Expected empty response, Got %s", body) + } +} + +func TestPostMessage(t *testing.T) { + testMessage := make([]byte, 256) + a.Db.Where("1 = 1").Delete(&mailbox.InboxEntry{}) + req, _ := http.NewRequest("POST", "/" + testWalletAliceString, bytes.NewReader(testMessage)) + response := executeRequest(req) + + checkResponseCode(t, http.StatusNoContent, response.Code) + + body := response.Body.String() + if body != "" { + t.Errorf("Expected empty response, Got %s", body) + } +} + +func TestPostThenDeleteMessage(t *testing.T) { + // testMessage := make([]byte, 256) + var deletionReq mailbox.MessageDeletionRequest + testMessages := make([]byte, 25600) + _, _ = rand.Read(testMessages) + a.Db.Where("1 = 1").Delete(&mailbox.InboxEntry{}) + + for i := 0; i < 100; i++ { + testMessage := testMessages[i*256:(i+1)*256] + req, _ := http.NewRequest("POST", "/" + testWalletAliceString, bytes.NewReader(testMessage)) + response := executeRequest(req) + + checkResponseCode(t, http.StatusNoContent, response.Code) + + body := response.Body.String() + if body != "" { + t.Errorf("Expected empty response, Got %s", body) + } + } + + req, _ := http.NewRequest("GET", "/" + testWalletAliceString, nil) + response := executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + if response.Body.Len() != 25600 { + t.Errorf("Expected response of 25600 bytes, Got %d", response.Body.Len()) + } + + etag := response.Result().Header.Get("ETag") + + if etag == "" { + t.Errorf("ETag missing!\n") + } + + // Now delete 10 messages + h := sha512.New() + for i := 0; i < 10; i++ { + h.Write(testMessages[i*256:(i+1)*256]) + } + var signed_msg [64+4+4]byte + size := signed_msg[0:4] + binary.BigEndian.PutUint32(size, 64+4+4) + purp := signed_msg[4:8] + binary.BigEndian.PutUint32(purp, gana.TALER_SIGNATURE_PURPOSE_MAILBOX_MESSAGES_DELETE) + checksum := h.Sum(nil) + copy(signed_msg[8:], checksum) + sig := ed25519.Sign(testWalletAlicePriv, signed_msg[0:]) + if !ed25519.Verify(testWalletAlice, signed_msg[0:], sig) { + t.Errorf("Signature invalid!") + } + deletionReq.WalletSig = gnunetutil.EncodeBinaryToString(sig) + deletionReq.Count = 10 + deletionReq.Checksum = gnunetutil.EncodeBinaryToString(checksum) + reqJson, _ := json.Marshal(deletionReq) + + hAddress := gnunetutil.EncodeBinaryToString(testWalletAlice) + req, _ = http.NewRequest("DELETE", "/" + hAddress, bytes.NewBuffer(reqJson)) + req.Header.Add("If-Match", etag) + response = executeRequest(req) + + checkResponseCode(t, http.StatusNoContent, response.Code) + + body := response.Body.String() + if body != "" { + t.Errorf("Expected empty response, Got %s", body) + } + + req, _ = http.NewRequest("GET", "/" + testWalletAliceString, nil) + response = executeRequest(req) + + checkResponseCode(t, http.StatusOK, response.Code) + + if response.Body.Len() != 25600 - 2560 { + t.Errorf("Expected response of 25600 - 2560 bytes, Got %d", response.Body.Len()) + } +} diff --git a/cmd/mailbox-server/test-mailbox.conf b/cmd/mailbox-server/test-mailbox.conf @@ -0,0 +1,12 @@ +[mailbox] +bind_to = localhost:11000 +production = false +message_body_bytes = 256 +message_fee = KUDOS:0 + +[mailbox-pq] +host = localhost +port = 5432 +user = mar33597 +password = secret +db_name = taler-mailbox-test diff --git a/pkg/rest/mailbox.go b/pkg/rest/mailbox.go @@ -277,43 +277,41 @@ func (m *Mailbox) deleteMessagesResponse(w http.ResponseWriter, r *http.Request) } checksum, err := gnunetutil.DecodeStringToBinary(msg.Checksum, 64) if err != nil { - w.WriteHeader(http.StatusInternalServerError) + w.WriteHeader(http.StatusBadRequest) return } pk := ed25519.PublicKey(pkey) sig, err := gnunetutil.DecodeStringToBinary(msg.WalletSig, 64) if nil != err { - w.WriteHeader(http.StatusForbidden) + w.WriteHeader(http.StatusBadRequest) return } - var signed_msg bytes.Buffer - size := make([]byte, 4) + var signed_msg [72]byte + size := signed_msg[0:4] binary.BigEndian.PutUint32(size, 64+4+4) - purp := make([]byte, 4) + purp := signed_msg[4:8] binary.BigEndian.PutUint32(purp, gana.TALER_SIGNATURE_PURPOSE_MAILBOX_MESSAGES_DELETE) - signed_msg.Write(size) - signed_msg.Write(purp) - signed_msg.Write(checksum) - if !ed25519.Verify(pk, signed_msg.Bytes(), sig) { + copy(signed_msg[8:], checksum) + if !ed25519.Verify(pk, signed_msg[0:], sig) { w.WriteHeader(http.StatusForbidden) return } h := sha512.New() h.Write(pkey) h_mailbox := gnunetutil.EncodeBinaryToString(h.Sum(nil)) - err = m.Db.Where("h_mailbox = ? AND ID >= ? LIMIT ?", h_mailbox, etag_expected, msg.Count).Find(&entries).Error + err = m.Db.Where("h_mailbox = ? AND id >= ?", h_mailbox, etag_expected).Limit(msg.Count).Find(&entries).Error if err != nil { w.WriteHeader(http.StatusInternalServerError) return } - if entries[0].ID != uint(etag_expected) { - w.WriteHeader(http.StatusPreconditionFailed) - return - } if len(entries) != msg.Count { w.WriteHeader(http.StatusNotFound) return } + if entries[0].ID != uint(etag_expected) { + w.WriteHeader(http.StatusPreconditionFailed) + return + } h_all := sha512.New() for _, entry := range entries { h_all.Write(entry.Body) @@ -347,7 +345,7 @@ func (m *Mailbox) setupHandlers() { /* Mailbox API */ m.Router.HandleFunc("/{h_mailbox}", m.sendMessageResponse).Methods("POST") m.Router.HandleFunc("/{h_mailbox}", m.getMessagesResponse).Methods("GET") - m.Router.HandleFunc("/{h_mailbox}", m.deleteMessagesResponse).Methods("DELETE") + m.Router.HandleFunc("/{mailbox}", m.deleteMessagesResponse).Methods("DELETE") } // Initialize the Mailbox instance with cfgfile