diff --git a/src/ui/main.go b/src/ui/main.go index 499c55b6..1411ccdd 100644 --- a/src/ui/main.go +++ b/src/ui/main.go @@ -14,6 +14,7 @@ import ( "github.com/my-cloud/ruthenium/src/ui/server/index" "github.com/my-cloud/ruthenium/src/ui/server/transaction" "github.com/my-cloud/ruthenium/src/ui/server/transaction/info" + "github.com/my-cloud/ruthenium/src/ui/server/transaction/status" "github.com/my-cloud/ruthenium/src/ui/server/transactions" "github.com/my-cloud/ruthenium/src/ui/server/wallet/address" "github.com/my-cloud/ruthenium/src/ui/server/wallet/amount" @@ -46,8 +47,9 @@ func main() { watch := tick.NewWatch() http.Handle("/", index.NewHandler(*templatesPath, logger)) http.Handle("/transaction", transaction.NewHandler(host, logger)) - http.Handle("/transaction/info", info.NewHandler(host, settings, watch, logger)) http.Handle("/transactions", transactions.NewHandler(host, logger)) + http.Handle("/transaction/info", info.NewHandler(host, settings, watch, logger)) + http.Handle("/transaction/status", status.NewHandler(host, settings, watch, logger)) http.Handle("/wallet/address", address.NewHandler(logger)) http.Handle("/wallet/amount", amount.NewHandler(host, settings, watch, logger)) logger.Info("user interface server is running...") diff --git a/src/ui/server/transaction/handler.go b/src/ui/server/transaction/handler.go index a8bd8781..5f3af09a 100644 --- a/src/ui/server/transaction/handler.go +++ b/src/ui/server/transaction/handler.go @@ -28,9 +28,9 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) var transaction *verification.Transaction err := decoder.Decode(&transaction) if err != nil { - handler.logger.Error(fmt.Errorf("failed to decode transaction request: %w", err).Error()) + handler.logger.Error(fmt.Errorf("failed to decode transaction: %w", err).Error()) writer.WriteHeader(http.StatusBadRequest) - jsonWriter.Write("invalid transaction request") + jsonWriter.Write("invalid transaction") return } transactionRequest := validation.NewTransactionRequest(transaction, handler.host.Target()) @@ -42,7 +42,7 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) } err = handler.host.AddTransaction(marshaledTransaction) if err != nil { - handler.logger.Error(fmt.Errorf("failed to create transaction: %w", err).Error()) + handler.logger.Error(fmt.Errorf("failed to add transaction: %w", err).Error()) writer.WriteHeader(http.StatusInternalServerError) return } diff --git a/src/ui/server/transaction/info/handler.go b/src/ui/server/transaction/info/handler.go index 94d56be7..66375547 100644 --- a/src/ui/server/transaction/info/handler.go +++ b/src/ui/server/transaction/info/handler.go @@ -131,6 +131,7 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) return } writer.Header().Add("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) server.NewIoWriter(writer, handler.logger).Write(string(marshaledResponse[:])) default: handler.logger.Error("invalid HTTP method") diff --git a/src/ui/server/transaction/status/handler.go b/src/ui/server/transaction/status/handler.go new file mode 100644 index 00000000..35f9c688 --- /dev/null +++ b/src/ui/server/transaction/status/handler.go @@ -0,0 +1,142 @@ +package status + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/my-cloud/ruthenium/src/log" + "github.com/my-cloud/ruthenium/src/node/clock" + "github.com/my-cloud/ruthenium/src/node/network" + "github.com/my-cloud/ruthenium/src/node/protocol/verification" + "github.com/my-cloud/ruthenium/src/ui/server" + "net/http" +) + +type Handler struct { + host network.Neighbor + settings server.Settings + watch clock.Watch + logger log.Logger +} + +func NewHandler(host network.Neighbor, settings server.Settings, watch clock.Watch, logger log.Logger) *Handler { + return &Handler{host, settings, watch, logger} +} + +func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodPut: + jsonWriter := server.NewIoWriter(writer, handler.logger) + decoder := json.NewDecoder(req.Body) + var transaction *verification.Transaction + err := decoder.Decode(&transaction) + if err != nil { + handler.logger.Error(fmt.Errorf("failed to decode transaction: %w", err).Error()) + writer.WriteHeader(http.StatusBadRequest) + jsonWriter.Write("invalid transaction") + return + } else if len(transaction.Outputs()) == 0 { + handler.logger.Error(errors.New("transaction has no output").Error()) + writer.WriteHeader(http.StatusBadRequest) + jsonWriter.Write("invalid transaction") + return + } + lastOutputIndex := len(transaction.Outputs()) - 1 + lastOutput := transaction.Outputs()[lastOutputIndex] + utxosBytes, err := handler.host.GetUtxos(lastOutput.Address()) + if err != nil { + handler.logger.Error(fmt.Errorf("failed to get UTXOs: %w", err).Error()) + writer.WriteHeader(http.StatusInternalServerError) + return + } + var utxos []*verification.Utxo + err = json.Unmarshal(utxosBytes, &utxos) + if err != nil { + handler.logger.Error(fmt.Errorf("failed to unmarshal UTXOs: %w", err).Error()) + writer.WriteHeader(http.StatusInternalServerError) + return + } + genesisTimestamp, err := handler.host.GetFirstBlockTimestamp() + now := handler.watch.Now().UnixNano() + currentBlockHeight := (now - genesisTimestamp) / handler.settings.ValidationTimestamp() + currentBlockTimestamp := genesisTimestamp + currentBlockHeight*handler.settings.ValidationTimestamp() + progress := &Progress{ + CurrentBlockTimestamp: currentBlockTimestamp, + ValidationTimestamp: handler.settings.ValidationTimestamp(), + } + for _, utxo := range utxos { + if utxo.TransactionId() == transaction.Id() && utxo.OutputIndex() == uint16(lastOutputIndex) { + progress.TransactionStatus = "confirmed" + handler.sendResponse(writer, progress) + return + } + } + if err != nil { + handler.logger.Error(fmt.Errorf("failed to get genesis timestamp: %w", err).Error()) + writer.WriteHeader(http.StatusInternalServerError) + return + } + blocksBytes, err := handler.host.GetBlocks(uint64(currentBlockHeight)) + if err != nil { + handler.logger.Error("failed to get blocks") + writer.WriteHeader(http.StatusInternalServerError) + return + } + var blocks []*verification.Block + err = json.Unmarshal(blocksBytes, &blocks) + if err != nil { + handler.logger.Error(fmt.Errorf("failed to unmarshal blocks: %w", err).Error()) + writer.WriteHeader(http.StatusInternalServerError) + return + } + if len(blocks) == 0 { + handler.logger.Error("failed to get last block, get blocks returned an empty list") + writer.WriteHeader(http.StatusInternalServerError) + return + } + for _, validatedTransaction := range blocks[0].Transactions() { + if validatedTransaction.Id() == transaction.Id() { + progress.TransactionStatus = "validated" + handler.sendResponse(writer, progress) + return + } + } + transactionsBytes, err := handler.host.GetTransactions() + if err != nil { + handler.logger.Error(fmt.Errorf("failed to get transactions: %w", err).Error()) + writer.WriteHeader(http.StatusInternalServerError) + return + } + var transactions []*verification.Transaction + err = json.Unmarshal(transactionsBytes, &transactions) + if err != nil { + handler.logger.Error(fmt.Errorf("failed to unmarshal transactions: %w", err).Error()) + writer.WriteHeader(http.StatusInternalServerError) + return + } + for _, pendingTransaction := range transactions { + if pendingTransaction.Id() == transaction.Id() { + progress.TransactionStatus = "sent" + handler.sendResponse(writer, progress) + return + } + } + progress.TransactionStatus = "rejected" + handler.sendResponse(writer, progress) + default: + handler.logger.Error("invalid HTTP method") + writer.WriteHeader(http.StatusBadRequest) + } +} + +func (handler *Handler) sendResponse(writer http.ResponseWriter, progress *Progress) { + marshaledResponse, err := json.Marshal(progress) + if err != nil { + handler.logger.Error(fmt.Errorf("failed to marshal progress: %w", err).Error()) + writer.WriteHeader(http.StatusInternalServerError) + return + } + writer.Header().Add("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) + server.NewIoWriter(writer, handler.logger).Write(string(marshaledResponse[:])) +} diff --git a/src/ui/server/transaction/status/progress.go b/src/ui/server/transaction/status/progress.go new file mode 100644 index 00000000..c61d676e --- /dev/null +++ b/src/ui/server/transaction/status/progress.go @@ -0,0 +1,7 @@ +package status + +type Progress struct { + CurrentBlockTimestamp int64 `json:"current_block_timestamp"` + TransactionStatus string `json:"transaction_status"` + ValidationTimestamp int64 `json:"validation_timestamp"` +} diff --git a/src/ui/server/transactions/handler.go b/src/ui/server/transactions/handler.go index 7c360f5d..17450202 100644 --- a/src/ui/server/transactions/handler.go +++ b/src/ui/server/transactions/handler.go @@ -27,6 +27,7 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) return } writer.Header().Add("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) server.NewIoWriter(writer, handler.logger).Write(string(transactions[:])) default: handler.logger.Error("invalid HTTP method") diff --git a/src/ui/server/wallet/amount/handler.go b/src/ui/server/wallet/amount/handler.go index 28bfa16f..b2368f81 100644 --- a/src/ui/server/wallet/amount/handler.go +++ b/src/ui/server/wallet/amount/handler.go @@ -56,6 +56,7 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, req *http.Request) return } writer.Header().Add("Content-Type", "application/json") + writer.WriteHeader(http.StatusOK) server.NewIoWriter(writer, handler.logger).Write(string(marshaledAmount[:])) default: handler.logger.Error("invalid HTTP method") diff --git a/templates/index.html b/templates/index.html index 0840b40b..7ea515ca 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,6 +2,7 @@ + Wallet @@ -49,8 +50,8 @@

Send Tokens

- - + +
@@ -61,7 +62,10 @@

Send Tokens

- + +
+
+
@@ -106,6 +110,8 @@

Transactions Pool

}); $(function () { + let pendingTransaction; + $("#send_tokens_button").click(function () { if (!keyPair) { alert("The private key must be provided to send tokens") @@ -113,7 +119,7 @@

Transactions Pool

} const senderAddress = $("#sender_address").val(); - const atoms = $("#send_amount").val(); + const atoms = $("#amount").val(); const result = atomsToParticles(atoms, 100000000); if (result.err) { alert(result.err); @@ -151,7 +157,6 @@

Transactions Pool

"is_registered": false, "value": value, } - console.log(isIncomeUpdateRequested) const rest = { "address": senderAddress, "is_registered": isIncomeUpdateRequested, @@ -172,6 +177,7 @@

Transactions Pool

success: function (response) { if (response === "success") { alert("Send success"); + pendingTransaction = transaction } else { alert("Send failed: " + response) } @@ -205,6 +211,7 @@

Transactions Pool

setInterval(refresh_amount, 100) setInterval(refresh_transactions, 100) + setInterval(refresh_progress, 100) function refresh_amount() { const $walletAmount = $("#wallet_amount"); @@ -239,6 +246,53 @@

Transactions Pool

} }) } + + function refresh_progress() { + const progressBar = document.querySelector('.progress-circle'); + if (pendingTransaction === undefined) { + progressBar.style.background = `conic-gradient(white 100%, white 0)`; + progressBar.textContent = ""; + } else { + $.ajax({ + url: "/transaction/status", + type: "PUT", + contentType: "application/json", + data: JSON.stringify(pendingTransaction), + success: function (response) { + const now = new Date().getTime() * 1000000 + let angle = (now - response.current_block_timestamp) / response.validation_timestamp * 100 + let color1; + let color2; + switch (response.transaction_status) { + case "sent": + color1 = "lightseagreen"; + color2 = "royalblue"; + break; + case "validated": + color1 = "seagreen"; + color2 = "lightseagreen"; + break; + case "confirmed": + color1 = "seagreen"; + color2 = "seagreen"; + break; + case "rejected": + color1 = "brown"; + color2 = "brown"; + break; + default: + color1 = "white"; + color2 = "white"; + } + progressBar.textContent = response.transaction_status[0] + progressBar.style.background = `conic-gradient(${color1} ${angle}%, ${color2} 0)`; + }, + error: function (response) { + console.error(response); + } + }) + } + } }) function atomsToParticles(atoms, particlesInOneAtom) { @@ -419,4 +473,26 @@

Transactions Pool

width: 50%; margin-left: 90px; } + + .progress { + width: 30px; + height: 30px; + margin-left: 30px; + margin-right: 30px; + border-radius: 50%; + position: absolute; + } + + .progress-circle { + width: 100%; + height: 100%; + border-radius: 50%; + background: conic-gradient(white 0, white 0); + animation: progress 5s 1 forwards; + text-align: center; + font-weight: bold; + font-size: 22px; + text-transform: capitalize; + color: lightgrey; + } diff --git a/test/ui/server/transaction/handler_test.go b/test/ui/server/transaction/handler_test.go index fc53f657..2beb70cf 100644 --- a/test/ui/server/transaction/handler_test.go +++ b/test/ui/server/transaction/handler_test.go @@ -47,32 +47,10 @@ func Test_ServeHTTP_UndecipherableTransaction_BadRequest(t *testing.T) { neighborMock := new(networktest.NeighborMock) logger := logtest.NewLoggerMock() handler := transaction.NewHandler(neighborMock, logger) - transactionRequest := "" - b, _ := json.Marshal(transactionRequest) - body := bytes.NewReader(b) + marshalledData, _ := json.Marshal("") + body := bytes.NewReader(marshalledData) recorder := httptest.NewRecorder() - request := httptest.NewRequest("POST", urlTarget, body) - - // Act - handler.ServeHTTP(recorder, request) - - // Assert - isNeighborMethodCalled := len(neighborMock.AddTransactionCalls()) != 0 - test.Assert(t, !isNeighborMethodCalled, "Neighbor method is called whereas it should not.") - expectedStatusCode := 400 - test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) -} - -func Test_ServeHTTP_InvalidTransaction_BadRequest(t *testing.T) { - // Arrange - neighborMock := new(networktest.NeighborMock) - neighborMock.TargetFunc = func() string { return "0.0.0.0:0" } - logger := logtest.NewLoggerMock() - handler := transaction.NewHandler(neighborMock, logger) - data, _ := json.Marshal("") - body := bytes.NewReader(data) - recorder := httptest.NewRecorder() - request := httptest.NewRequest("POST", urlTarget, body) + request := httptest.NewRequest(http.MethodPost, urlTarget, body) // Act handler.ServeHTTP(recorder, request) @@ -92,14 +70,11 @@ func Test_ServeHTTP_NodeError_InternalServerError(t *testing.T) { neighborMock.AddTransactionFunc = func([]byte) error { return errors.New("") } logger := logtest.NewLoggerMock() handler := transaction.NewHandler(neighborMock, logger) - address := "RecipientAddress" - var value uint64 = 0 - var timestamp int64 = 0 - transactionRequest, _ := verification.NewRewardTransaction(address, false, timestamp, value) + transactionRequest, _ := verification.NewRewardTransaction("", false, 0, 0) marshalledTransaction, _ := json.Marshal(transactionRequest) body := bytes.NewReader(marshalledTransaction) recorder := httptest.NewRecorder() - request := httptest.NewRequest("POST", urlTarget, body) + request := httptest.NewRequest(http.MethodPost, urlTarget, body) // Act handler.ServeHTTP(recorder, request) @@ -119,14 +94,11 @@ func Test_ServeHTTP_ValidTransaction_NeighborMethodCalled(t *testing.T) { neighborMock.AddTransactionFunc = func([]byte) error { return nil } logger := logtest.NewLoggerMock() handler := transaction.NewHandler(neighborMock, logger) - address := "RecipientAddress" - var value uint64 = 0 - var timestamp int64 = 0 - transactionRequest, _ := verification.NewRewardTransaction(address, false, timestamp, value) + transactionRequest, _ := verification.NewRewardTransaction("", false, 0, 0) marshalledTransaction, _ := json.Marshal(transactionRequest) body := bytes.NewReader(marshalledTransaction) recorder := httptest.NewRecorder() - request := httptest.NewRequest("POST", urlTarget, body) + request := httptest.NewRequest(http.MethodPost, urlTarget, body) // Act handler.ServeHTTP(recorder, request) diff --git a/test/ui/server/transaction/info/handler_test.go b/test/ui/server/transaction/info/handler_test.go index ab8e42ad..7445a01b 100644 --- a/test/ui/server/transaction/info/handler_test.go +++ b/test/ui/server/transaction/info/handler_test.go @@ -26,11 +26,6 @@ func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { neighborMock := new(networktest.NeighborMock) watchMock := new(clocktest.WatchMock) settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } - settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } - settings.ParticlesPerTokenFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } handler := info.NewHandler(neighborMock, settings, watchMock, logger) recorder := httptest.NewRecorder() invalidHttpMethods := []string{http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} @@ -143,7 +138,7 @@ func Test_ServeHTTP_GetUtxosError_ReturnsInternalServerError(t *testing.T) { test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) } -func Test_ServeHTTP_GetFirstBlockTimestampFuncError_ReturnsInternalServerError(t *testing.T) { +func Test_ServeHTTP_GetFirstBlockTimestampError_ReturnsInternalServerError(t *testing.T) { // Arrange logger := logtest.NewLoggerMock() neighborMock := new(networktest.NeighborMock) diff --git a/test/ui/server/transaction/status/handler_test.go b/test/ui/server/transaction/status/handler_test.go new file mode 100644 index 00000000..0a212fd3 --- /dev/null +++ b/test/ui/server/transaction/status/handler_test.go @@ -0,0 +1,374 @@ +package info + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "github.com/my-cloud/ruthenium/src/node/protocol/verification" + "github.com/my-cloud/ruthenium/src/ui/server/transaction/status" + "github.com/my-cloud/ruthenium/test" + "github.com/my-cloud/ruthenium/test/log/logtest" + "github.com/my-cloud/ruthenium/test/node/clock/clocktest" + "github.com/my-cloud/ruthenium/test/node/network/networktest" + "github.com/my-cloud/ruthenium/test/ui/server/servertest" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +const urlTarget = "/url-target" + +func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + watchMock := new(clocktest.WatchMock) + settings := new(servertest.SettingsMock) + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + invalidHttpMethods := []string{http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace} + for _, method := range invalidHttpMethods { + t.Run(method, func(t *testing.T) { + request := httptest.NewRequest(method, urlTarget, nil) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + }) + } +} + +func Test_ServeHTTP_UndecipherableTransaction_BadRequest(t *testing.T) { + // Arrange + neighborMock := new(networktest.NeighborMock) + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + watchMock := new(clocktest.WatchMock) + logger := logtest.NewLoggerMock() + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + data := "" + marshalledData, _ := json.Marshal(data) + body := bytes.NewReader(marshalledData) + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + isNeighborMethodCalled := len(neighborMock.AddTransactionCalls()) != 0 + test.Assert(t, !isNeighborMethodCalled, "Neighbor method is called whereas it should not.") + expectedStatusCode := 400 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_ServeHTTP_GetUtxosError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return nil, errors.New("") } + watchMock := new(clocktest.WatchMock) + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + transaction, _ := verification.NewRewardTransaction("", false, 0, 0) + marshalledTransaction, _ := json.Marshal(transaction) + body := bytes.NewReader(marshalledTransaction) + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + isNeighborMethodCalled := len(neighborMock.GetUtxosCalls()) == 1 + test.Assert(t, isNeighborMethodCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_ServeHTTP_GetFirstBlockTimestampError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + marshalledEmptyArray := []byte{91, 93} + neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, errors.New("") } + watchMock := new(clocktest.WatchMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + transaction, _ := verification.NewRewardTransaction("", false, 0, 0) + marshalledTransaction, _ := json.Marshal(transaction) + body := bytes.NewReader(marshalledTransaction) + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_ServeHTTP_GetBlocksError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + marshalledEmptyArray := []byte{91, 93} + neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return nil, errors.New("") } + watchMock := new(clocktest.WatchMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + transaction, _ := verification.NewRewardTransaction("", false, 0, 0) + marshalledTransaction, _ := json.Marshal(transaction) + body := bytes.NewReader(marshalledTransaction) + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_ServeHTTP_GetTransactionsError_ReturnsInternalServerError(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + marshalledEmptyArray := []byte{91, 93} + neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + blocks := []*verification.Block{verification.NewBlock(0, [32]byte{}, nil, nil, nil)} + marshalledBlocks, _ := json.Marshal(blocks) + neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } + neighborMock.GetTransactionsFunc = func() ([]byte, error) { return nil, errors.New("") } + watchMock := new(clocktest.WatchMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + transaction, _ := verification.NewRewardTransaction("", false, 0, 0) + marshalledTransaction, _ := json.Marshal(transaction) + body := bytes.NewReader(marshalledTransaction) + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 && len(neighborMock.GetTransactionsCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 500 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) +} + +func Test_ServeHTTP_TransactionNotFound_ReturnsRejected(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + marshalledEmptyArray := []byte{91, 93} + neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + blocks := []*verification.Block{verification.NewBlock(0, [32]byte{}, nil, nil, nil)} + marshalledBlocks, _ := json.Marshal(blocks) + neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } + neighborMock.GetTransactionsFunc = func() ([]byte, error) { return marshalledEmptyArray, nil } + watchMock := new(clocktest.WatchMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + transaction, _ := verification.NewRewardTransaction("", false, 0, 0) + marshalledTransaction, _ := json.Marshal(transaction) + body := bytes.NewReader(marshalledTransaction) + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 && len(neighborMock.GetTransactionsCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + expectedStatus := "rejected" + response := recorder.Body.Bytes() + var progress *status.Progress + err := json.Unmarshal(response, &progress) + fmt.Println(err) + actualStatus := progress.TransactionStatus + test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) +} + +func Test_ServeHTTP_UtxoFound_ReturnsConfirmed(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + transaction, _ := verification.NewRewardTransaction("", false, 0, 0) + transactionId := transaction.Id() + inputInfo := verification.NewInputInfo(0, transactionId) + utxo := verification.NewUtxo(inputInfo, &verification.Output{}, 0) + marshalledEmptyUtxos, _ := json.Marshal([]*verification.Utxo{utxo}) + neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyUtxos, nil } + neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + watchMock := new(clocktest.WatchMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + marshalledTransaction, _ := json.Marshal(transaction) + body := bytes.NewReader(marshalledTransaction) + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + expectedStatus := "confirmed" + response := recorder.Body.Bytes() + var progress *status.Progress + err := json.Unmarshal(response, &progress) + fmt.Println(err) + actualStatus := progress.TransactionStatus + test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) +} + +func Test_ServeHTTP_ValidatedTransactionFound_ReturnsValidated(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + marshalledEmptyArray := []byte{91, 93} + neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + transaction, _ := verification.NewRewardTransaction("", false, 0, 0) + blocks := []*verification.Block{verification.NewBlock(0, [32]byte{}, []*verification.Transaction{transaction}, nil, nil)} + marshalledBlocks, _ := json.Marshal(blocks) + neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } + watchMock := new(clocktest.WatchMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + marshalledTransaction, _ := json.Marshal(transaction) + body := bytes.NewReader(marshalledTransaction) + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + expectedStatus := "validated" + response := recorder.Body.Bytes() + var progress *status.Progress + err := json.Unmarshal(response, &progress) + fmt.Println(err) + actualStatus := progress.TransactionStatus + test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) +} + +func Test_ServeHTTP_PendingTransactionFound_ReturnsSent(t *testing.T) { + // Arrange + logger := logtest.NewLoggerMock() + neighborMock := new(networktest.NeighborMock) + marshalledEmptyArray := []byte{91, 93} + neighborMock.GetUtxosFunc = func(string) ([]byte, error) { return marshalledEmptyArray, nil } + neighborMock.GetFirstBlockTimestampFunc = func() (int64, error) { return 0, nil } + blocks := []*verification.Block{verification.NewBlock(0, [32]byte{}, nil, nil, nil)} + marshalledBlocks, _ := json.Marshal(blocks) + neighborMock.GetBlocksFunc = func(uint64) ([]byte, error) { return marshalledBlocks, nil } + transaction, _ := verification.NewRewardTransaction("", false, 0, 0) + transactions := []*verification.Transaction{transaction} + marshalledTransactions, _ := json.Marshal(transactions) + neighborMock.GetTransactionsFunc = func() ([]byte, error) { return marshalledTransactions, nil } + watchMock := new(clocktest.WatchMock) + watchMock.NowFunc = func() time.Time { return time.Unix(0, 0) } + settings := new(servertest.SettingsMock) + settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } + settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } + settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } + settings.ParticlesPerTokenFunc = func() uint64 { return 1 } + settings.ValidationTimestampFunc = func() int64 { return 1 } + handler := status.NewHandler(neighborMock, settings, watchMock, logger) + recorder := httptest.NewRecorder() + marshalledTransaction, _ := json.Marshal(transaction) + body := bytes.NewReader(marshalledTransaction) + request := httptest.NewRequest(http.MethodPut, urlTarget, body) + + // Act + handler.ServeHTTP(recorder, request) + + // Assert + areNeighborMethodsCalled := len(neighborMock.GetUtxosCalls()) == 1 && len(neighborMock.GetFirstBlockTimestampCalls()) == 1 && len(neighborMock.GetBlocksCalls()) == 1 && len(neighborMock.GetTransactionsCalls()) == 1 + test.Assert(t, areNeighborMethodsCalled, "Neighbor method is not called whereas it should be.") + expectedStatusCode := 200 + test.Assert(t, recorder.Code == expectedStatusCode, fmt.Sprintf("Wrong response status code. expected: %d actual: %d", expectedStatusCode, recorder.Code)) + expectedStatus := "sent" + response := recorder.Body.Bytes() + var progress *status.Progress + err := json.Unmarshal(response, &progress) + fmt.Println(err) + actualStatus := progress.TransactionStatus + test.Assert(t, actualStatus == expectedStatus, fmt.Sprintf("Wrong response. expected: %s actual: %s", expectedStatus, actualStatus)) +} diff --git a/test/ui/server/transactions/handler_test.go b/test/ui/server/transactions/handler_test.go index ac41d1c2..8b8cfaa0 100644 --- a/test/ui/server/transactions/handler_test.go +++ b/test/ui/server/transactions/handler_test.go @@ -44,7 +44,7 @@ func Test_ServeHTTP_NodeError_InternalServerError(t *testing.T) { logger := logtest.NewLoggerMock() handler := transactions.NewHandler(neighborMock, logger) recorder := httptest.NewRecorder() - request := httptest.NewRequest("GET", urlTarget, nil) + request := httptest.NewRequest(http.MethodGet, urlTarget, nil) // Act handler.ServeHTTP(recorder, request) @@ -63,7 +63,7 @@ func Test_ServeHTTP_ValidRequest_NeighborMethodCalled(t *testing.T) { logger := logtest.NewLoggerMock() handler := transactions.NewHandler(neighborMock, logger) recorder := httptest.NewRecorder() - request := httptest.NewRequest("GET", urlTarget, nil) + request := httptest.NewRequest(http.MethodGet, urlTarget, nil) // Act handler.ServeHTTP(recorder, request) diff --git a/test/ui/server/wallet/amount/handler_test.go b/test/ui/server/wallet/amount/handler_test.go index 346382d7..2b97b232 100644 --- a/test/ui/server/wallet/amount/handler_test.go +++ b/test/ui/server/wallet/amount/handler_test.go @@ -26,11 +26,6 @@ func Test_ServeHTTP_InvalidHttpMethod_BadRequest(t *testing.T) { neighborMock := new(networktest.NeighborMock) watchMock := new(clocktest.WatchMock) settings := new(servertest.SettingsMock) - settings.HalfLifeInNanosecondsFunc = func() float64 { return 0 } - settings.IncomeBaseInParticlesFunc = func() uint64 { return 0 } - settings.IncomeLimitInParticlesFunc = func() uint64 { return 0 } - settings.ParticlesPerTokenFunc = func() uint64 { return 1 } - settings.ValidationTimestampFunc = func() int64 { return 1 } handler := amount.NewHandler(neighborMock, settings, watchMock, logger) recorder := httptest.NewRecorder() invalidHttpMethods := []string{http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodConnect, http.MethodOptions, http.MethodTrace}