diff --git a/server/html_templates.go b/server/html_templates.go
new file mode 100644
index 00000000..4d044c3b
--- /dev/null
+++ b/server/html_templates.go
@@ -0,0 +1,299 @@
+package server
+
+import (
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "math/big"
+ "net/http"
+ "runtime/debug"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/golang/glog"
+ "github.com/trezor/blockbook/api"
+ "github.com/trezor/blockbook/common"
+)
+
+type tpl int
+
+const (
+ noTpl = tpl(iota)
+ errorTpl
+ errorInternalTpl
+)
+
+// htmlTemplateHandler is a handle to public http server
+type htmlTemplates[TD any] struct {
+ metrics *common.Metrics
+ templates []*template.Template
+ debug bool
+ newTemplateData func(r *http.Request) *TD
+ newTemplateDataWithError func(error *api.APIError, r *http.Request) *TD
+ parseTemplates func() []*template.Template
+ postHtmlTemplateHandler func(data *TD, w http.ResponseWriter, r *http.Request)
+}
+
+func (s *htmlTemplates[TD]) htmlTemplateHandler(handler func(w http.ResponseWriter, r *http.Request) (tpl, *TD, error)) func(w http.ResponseWriter, r *http.Request) {
+ handlerName := getFunctionName(handler)
+ return func(w http.ResponseWriter, r *http.Request) {
+ var t tpl
+ var data *TD
+ var err error
+ defer func() {
+ if e := recover(); e != nil {
+ glog.Error(handlerName, " recovered from panic: ", e)
+ debug.PrintStack()
+ t = errorInternalTpl
+ if s.debug {
+ data = s.newTemplateDataWithError(&api.APIError{Text: fmt.Sprint("Internal server error: recovered from panic ", e)}, r)
+ } else {
+ data = s.newTemplateDataWithError(&api.APIError{Text: "Internal server error"}, r)
+ }
+ }
+ // noTpl means the handler completely handled the request
+ if t != noTpl {
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ // return 500 Internal Server Error with errorInternalTpl
+ if t == errorInternalTpl {
+ w.WriteHeader(http.StatusInternalServerError)
+ }
+ if err := s.templates[t].ExecuteTemplate(w, "base.html", data); err != nil {
+ glog.Error(err)
+ }
+ }
+ if s.metrics != nil {
+ s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Dec()
+ }
+ }()
+ if s.metrics != nil {
+ s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Inc()
+ }
+ if s.debug {
+ // reload templates on each request
+ // to reflect changes during development
+ s.templates = s.parseTemplates()
+ }
+ t, data, err = handler(w, r)
+ if err != nil || (data == nil && t != noTpl) {
+ t = errorInternalTpl
+ if apiErr, ok := err.(*api.APIError); ok {
+ data = s.newTemplateDataWithError(apiErr, r)
+ if apiErr.Public {
+ t = errorTpl
+ }
+ } else {
+ if err != nil {
+ glog.Error(handlerName, " error: ", err)
+ }
+ if s.debug {
+ data = s.newTemplateDataWithError(&api.APIError{Text: fmt.Sprintf("Internal server error: %v, data %+v", err, data)}, r)
+ } else {
+ data = s.newTemplateDataWithError(&api.APIError{Text: "Internal server error"}, r)
+ }
+ }
+ }
+ if s.postHtmlTemplateHandler != nil {
+ s.postHtmlTemplateHandler(data, w, r)
+ }
+
+ }
+}
+
+func relativeTimeUnit(d int64) string {
+ var u string
+ if d < 60 {
+ if d == 1 {
+ u = " sec"
+ } else {
+ u = " secs"
+ }
+ } else if d < 3600 {
+ d /= 60
+ if d == 1 {
+ u = " min"
+ } else {
+ u = " mins"
+ }
+ } else if d < 3600*24 {
+ d /= 3600
+ if d == 1 {
+ u = " hour"
+ } else {
+ u = " hours"
+ }
+ } else {
+ d /= 3600 * 24
+ if d == 1 {
+ u = " day"
+ } else {
+ u = " days"
+ }
+ }
+ return strconv.FormatInt(d, 10) + u
+}
+
+func relativeTime(d int64) string {
+ r := relativeTimeUnit(d)
+ if d > 3600*24 {
+ d = d % (3600 * 24)
+ if d >= 3600 {
+ r += " " + relativeTimeUnit(d)
+ }
+ } else if d > 3600 {
+ d = d % 3600
+ if d >= 60 {
+ r += " " + relativeTimeUnit(d)
+ }
+ }
+ return r
+}
+
+func unixTimeSpan(ut int64) template.HTML {
+ t := time.Unix(ut, 0)
+ return timeSpan(&t)
+}
+
+var timeNow = time.Now
+
+func timeSpan(t *time.Time) template.HTML {
+ if t == nil {
+ return ""
+ }
+ u := t.Unix()
+ if u <= 0 {
+ return ""
+ }
+ d := timeNow().Unix() - u
+ f := t.UTC().Format("2006-01-02 15:04:05")
+ if d < 0 {
+ return template.HTML(f)
+ }
+ r := relativeTime(d)
+ return template.HTML(`` + r + " ago")
+}
+
+func toJSON(data interface{}) string {
+ json, err := json.Marshal(data)
+ if err != nil {
+ return ""
+ }
+ return string(json)
+}
+
+func formatAmountWithDecimals(a *api.Amount, d int) string {
+ if a == nil {
+ return "0"
+ }
+ return a.DecimalString(d)
+}
+
+func appendAmountSpan(rv *strings.Builder, class, amount, shortcut, txDate string) {
+ rv.WriteString(`")
+ i := strings.IndexByte(amount, '.')
+ if i < 0 {
+ appendSeparatedNumberSpans(rv, amount, "nc")
+ } else {
+ appendSeparatedNumberSpans(rv, amount[:i], "nc")
+ rv.WriteString(`.`)
+ rv.WriteString(``)
+ appendLeftSeparatedNumberSpans(rv, amount[i+1:], "ns")
+ rv.WriteString("")
+ }
+ if shortcut != "" {
+ rv.WriteString(" ")
+ rv.WriteString(shortcut)
+ }
+ rv.WriteString("")
+}
+
+func appendAmountWrapperSpan(rv *strings.Builder, primary, symbol, classes string) {
+ rv.WriteString(``)
+}
+
+func formatInt(i int) template.HTML {
+ return formatInt64(int64(i))
+}
+
+func formatUint32(i uint32) template.HTML {
+ return formatInt64(int64(i))
+}
+
+func appendSeparatedNumberSpans(rv *strings.Builder, s, separatorClass string) {
+ if len(s) > 0 && s[0] == '-' {
+ s = s[1:]
+ rv.WriteByte('-')
+ }
+ t := (len(s) - 1) / 3
+ if t <= 0 {
+ rv.WriteString(s)
+ } else {
+ t *= 3
+ rv.WriteString(s[:len(s)-t])
+ for i := len(s) - t; i < len(s); i += 3 {
+ rv.WriteString(``)
+ rv.WriteString(s[i : i+3])
+ rv.WriteString("")
+ }
+ }
+}
+
+func appendLeftSeparatedNumberSpans(rv *strings.Builder, s, separatorClass string) {
+ l := len(s)
+ if l <= 3 {
+ rv.WriteString(s)
+ } else {
+ rv.WriteString(s[:3])
+ for i := 3; i < len(s); i += 3 {
+ rv.WriteString(``)
+ e := i + 3
+ if e > l {
+ e = l
+ }
+ rv.WriteString(s[i:e])
+ rv.WriteString("")
+ }
+ }
+}
+
+func formatInt64(i int64) template.HTML {
+ s := strconv.FormatInt(i, 10)
+ var rv strings.Builder
+ appendSeparatedNumberSpans(&rv, s, "ns")
+ return template.HTML(rv.String())
+}
+
+func formatBigInt(i *big.Int) template.HTML {
+ if i == nil {
+ return ""
+ }
+ s := i.String()
+ var rv strings.Builder
+ appendSeparatedNumberSpans(&rv, s, "ns")
+ return template.HTML(rv.String())
+}
diff --git a/server/html_templates_test.go b/server/html_templates_test.go
new file mode 100644
index 00000000..99c6c705
--- /dev/null
+++ b/server/html_templates_test.go
@@ -0,0 +1,173 @@
+//go:build unittest
+
+package server
+
+import (
+ "html/template"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+)
+
+func Test_formatInt64(t *testing.T) {
+ tests := []struct {
+ name string
+ n int64
+ want template.HTML
+ }{
+ {"1", 1, "1"},
+ {"13", 13, "13"},
+ {"123", 123, "123"},
+ {"1234", 1234, `1234`},
+ {"91234", 91234, `91234`},
+ {"891234", 891234, `891234`},
+ {"7891234", 7891234, `7891234`},
+ {"67891234", 67891234, `67891234`},
+ {"567891234", 567891234, `567891234`},
+ {"4567891234", 4567891234, `4567891234`},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := formatInt64(tt.n); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("formatInt64() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_formatTime(t *testing.T) {
+ timeNow = fixedTimeNow
+ tests := []struct {
+ name string
+ want template.HTML
+ }{
+ {
+ name: "2020-12-23 15:16:17",
+ want: `630 days 21 hours ago`,
+ },
+ {
+ name: "2022-08-23 11:12:13",
+ want: `23 days 1 hour ago`,
+ },
+ {
+ name: "2022-09-14 11:12:13",
+ want: `1 day 1 hour ago`,
+ },
+ {
+ name: "2022-09-14 14:12:13",
+ want: `22 hours 31 mins ago`,
+ },
+ {
+ name: "2022-09-15 09:33:26",
+ want: `3 hours 10 mins ago`,
+ },
+ {
+ name: "2022-09-15 12:23:56",
+ want: `20 mins ago`,
+ },
+ {
+ name: "2022-09-15 12:24:07",
+ want: `19 mins ago`,
+ },
+ {
+ name: "2022-09-15 12:43:21",
+ want: `35 secs ago`,
+ },
+ {
+ name: "2022-09-15 12:43:56",
+ want: `0 secs ago`,
+ },
+ {
+ name: "2022-09-16 12:43:56",
+ want: `2022-09-16 12:43:56`,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tm, _ := time.Parse("2006-01-02 15:04:05", tt.name)
+ if got := timeSpan(&tm); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("formatTime() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_appendAmountSpan(t *testing.T) {
+ tests := []struct {
+ name string
+ class string
+ amount string
+ shortcut string
+ txDate string
+ want string
+ }{
+ {
+ name: "prim-amt 1.23456789 BTC",
+ class: "prim-amt",
+ amount: "1.23456789",
+ shortcut: "BTC",
+ want: `1.23456789 BTC`,
+ },
+ {
+ name: "prim-amt 1432134.23456 BTC",
+ class: "prim-amt",
+ amount: "1432134.23456",
+ shortcut: "BTC",
+ want: `1432134.23456 BTC`,
+ },
+ {
+ name: "sec-amt 1 EUR",
+ class: "sec-amt",
+ amount: "1",
+ shortcut: "EUR",
+ want: `1 EUR`,
+ },
+ {
+ name: "sec-amt -1 EUR",
+ class: "sec-amt",
+ amount: "-1",
+ shortcut: "EUR",
+ want: `-1 EUR`,
+ },
+ {
+ name: "sec-amt 432109.23 EUR",
+ class: "sec-amt",
+ amount: "432109.23",
+ shortcut: "EUR",
+ want: `432109.23 EUR`,
+ },
+ {
+ name: "sec-amt -432109.23 EUR",
+ class: "sec-amt",
+ amount: "-432109.23",
+ shortcut: "EUR",
+ want: `-432109.23 EUR`,
+ },
+ {
+ name: "sec-amt 43141.29 EUR",
+ class: "sec-amt",
+ amount: "43141.29",
+ shortcut: "EUR",
+ txDate: "2022-03-14",
+ want: `43141.29 EUR`,
+ },
+ {
+ name: "sec-amt -43141.29 EUR",
+ class: "sec-amt",
+ amount: "-43141.29",
+ shortcut: "EUR",
+ txDate: "2022-03-14",
+ want: `-43141.29 EUR`,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ var rv strings.Builder
+ appendAmountSpan(&rv, tt.class, tt.amount, tt.shortcut, tt.txDate)
+ if got := rv.String(); !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("formatTime() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/server/public.go b/server/public.go
index 73936c74..86d90251 100644
--- a/server/public.go
+++ b/server/public.go
@@ -40,8 +40,9 @@ const (
apiV2
)
-// PublicServer is a handle to public http server
+// PublicServer provides public http server functionality
type PublicServer struct {
+ htmlTemplates[TemplateData]
binding string
certFiles string
socketio *SocketIoServer
@@ -55,10 +56,7 @@ type PublicServer struct {
api *api.Worker
explorerURL string
internalExplorer bool
- metrics *common.Metrics
is *common.InternalState
- templates []*template.Template
- debug bool
}
// NewPublicServer creates new public server http interface to blockbook and returns its handle
@@ -88,6 +86,10 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
}
s := &PublicServer{
+ htmlTemplates: htmlTemplates[TemplateData]{
+ metrics: metrics,
+ debug: debugMode,
+ },
binding: binding,
certFiles: certFiles,
https: https,
@@ -101,10 +103,12 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
mempool: mempool,
explorerURL: explorerURL,
internalExplorer: explorerURL == "",
- metrics: metrics,
is: is,
- debug: debugMode,
}
+ s.htmlTemplates.newTemplateData = s.newTemplateData
+ s.htmlTemplates.newTemplateDataWithError = s.newTemplateDataWithError
+ s.htmlTemplates.parseTemplates = s.parseTemplates
+ s.htmlTemplates.postHtmlTemplateHandler = s.postHtmlTemplateHandler
s.templates = s.parseTemplates()
// map only basic functions, the rest is enabled by method MapFullPublicInterface
@@ -396,82 +400,14 @@ func (s *PublicServer) newTemplateData(r *http.Request) *TemplateData {
return t
}
-func (s *PublicServer) newTemplateDataWithError(text string, r *http.Request) *TemplateData {
+func (s *PublicServer) newTemplateDataWithError(error *api.APIError, r *http.Request) *TemplateData {
td := s.newTemplateData(r)
- td.Error = &api.APIError{Text: text}
+ td.Error = error
return td
}
-func (s *PublicServer) htmlTemplateHandler(handler func(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error)) func(w http.ResponseWriter, r *http.Request) {
- handlerName := getFunctionName(handler)
- return func(w http.ResponseWriter, r *http.Request) {
- var t tpl
- var data *TemplateData
- var err error
- defer func() {
- if e := recover(); e != nil {
- glog.Error(handlerName, " recovered from panic: ", e)
- debug.PrintStack()
- t = errorInternalTpl
- if s.debug {
- data = s.newTemplateDataWithError(fmt.Sprint("Internal server error: recovered from panic ", e), r)
- } else {
- data = s.newTemplateDataWithError("Internal server error", r)
- }
- }
- // noTpl means the handler completely handled the request
- if t != noTpl {
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- // return 500 Internal Server Error with errorInternalTpl
- if t == errorInternalTpl {
- w.WriteHeader(http.StatusInternalServerError)
- }
- if err := s.templates[t].ExecuteTemplate(w, "base.html", data); err != nil {
- glog.Error(err)
- }
- }
- s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Dec()
- }()
- s.metrics.ExplorerPendingRequests.With((common.Labels{"method": handlerName})).Inc()
- if s.debug {
- // reload templates on each request
- // to reflect changes during development
- s.templates = s.parseTemplates()
- }
- t, data, err = handler(w, r)
- if err != nil || (data == nil && t != noTpl) {
- t = errorInternalTpl
- if apiErr, ok := err.(*api.APIError); ok {
- data = s.newTemplateData(r)
- data.Error = apiErr
- if apiErr.Public {
- t = errorTpl
- }
- } else {
- if err != nil {
- glog.Error(handlerName, " error: ", err)
- }
- if s.debug {
- data = s.newTemplateDataWithError(fmt.Sprintf("Internal server error: %v, data %+v", err, data), r)
- } else {
- data = s.newTemplateDataWithError("Internal server error", r)
- }
- }
- }
- // if SecondaryCoin is specified, set secondary_coin cookie
- if data != nil && data.SecondaryCoin != "" {
- http.SetCookie(w, &http.Cookie{Name: secondaryCoinCookieName, Value: data.SecondaryCoin + "=" + strconv.FormatBool(data.UseSecondaryCoin), Path: "/"})
- }
- }
-}
-
-type tpl int
-
const (
- noTpl = tpl(iota)
- errorTpl
- errorInternalTpl
- indexTpl
+ indexTpl = iota + errorInternalTpl + 1
txTpl
addressTpl
xpubTpl
@@ -481,7 +417,7 @@ const (
mempoolTpl
nftDetailTpl
- tplCount
+ publicTplCount
)
// TemplateData is used to transfer data to the templates
@@ -590,7 +526,7 @@ func (s *PublicServer) parseTemplates() []*template.Template {
return t
}
}
- t := make([]*template.Template, tplCount)
+ t := make([]*template.Template, publicTplCount)
t[errorTpl] = createTemplate("./static/templates/error.html", "./static/templates/base.html")
t[errorInternalTpl] = createTemplate("./static/templates/error.html", "./static/templates/base.html")
t[indexTpl] = createTemplate("./static/templates/index.html", "./static/templates/base.html")
@@ -611,85 +547,12 @@ func (s *PublicServer) parseTemplates() []*template.Template {
return t
}
-func relativeTimeUnit(d int64) string {
- var u string
- if d < 60 {
- if d == 1 {
- u = " sec"
- } else {
- u = " secs"
- }
- } else if d < 3600 {
- d /= 60
- if d == 1 {
- u = " min"
- } else {
- u = " mins"
- }
- } else if d < 3600*24 {
- d /= 3600
- if d == 1 {
- u = " hour"
- } else {
- u = " hours"
- }
- } else {
- d /= 3600 * 24
- if d == 1 {
- u = " day"
- } else {
- u = " days"
- }
+func (s *PublicServer) postHtmlTemplateHandler(data *TemplateData, w http.ResponseWriter, r *http.Request) {
+ // // if SecondaryCoin is specified, set secondary_coin cookie
+ if data != nil && data.SecondaryCoin != "" {
+ http.SetCookie(w, &http.Cookie{Name: secondaryCoinCookieName, Value: data.SecondaryCoin + "=" + strconv.FormatBool(data.UseSecondaryCoin), Path: "/"})
}
- return strconv.FormatInt(d, 10) + u
-}
-func relativeTime(d int64) string {
- r := relativeTimeUnit(d)
- if d > 3600*24 {
- d = d % (3600 * 24)
- if d >= 3600 {
- r += " " + relativeTimeUnit(d)
- }
- } else if d > 3600 {
- d = d % 3600
- if d >= 60 {
- r += " " + relativeTimeUnit(d)
- }
- }
- return r
-}
-
-func unixTimeSpan(ut int64) template.HTML {
- t := time.Unix(ut, 0)
- return timeSpan(&t)
-}
-
-var timeNow = time.Now
-
-func timeSpan(t *time.Time) template.HTML {
- if t == nil {
- return ""
- }
- u := t.Unix()
- if u <= 0 {
- return ""
- }
- d := timeNow().Unix() - u
- f := t.UTC().Format("2006-01-02 15:04:05")
- if d < 0 {
- return template.HTML(f)
- }
- r := relativeTime(d)
- return template.HTML(`` + r + " ago")
-}
-
-func toJSON(data interface{}) string {
- json, err := json.Marshal(data)
- if err != nil {
- return ""
- }
- return string(json)
}
func (s *PublicServer) formatAmount(a *api.Amount) string {
@@ -699,63 +562,6 @@ func (s *PublicServer) formatAmount(a *api.Amount) string {
return s.chainParser.AmountToDecimalString((*big.Int)(a))
}
-func formatAmountWithDecimals(a *api.Amount, d int) string {
- if a == nil {
- return "0"
- }
- return a.DecimalString(d)
-}
-
-func appendAmountSpan(rv *strings.Builder, class, amount, shortcut, txDate string) {
- rv.WriteString(`")
- i := strings.IndexByte(amount, '.')
- if i < 0 {
- appendSeparatedNumberSpans(rv, amount, "nc")
- } else {
- appendSeparatedNumberSpans(rv, amount[:i], "nc")
- rv.WriteString(`.`)
- rv.WriteString(``)
- appendLeftSeparatedNumberSpans(rv, amount[i+1:], "ns")
- rv.WriteString("")
- }
- if shortcut != "" {
- rv.WriteString(" ")
- rv.WriteString(shortcut)
- }
- rv.WriteString("")
-}
-
-func appendAmountWrapperSpan(rv *strings.Builder, primary, symbol, classes string) {
- rv.WriteString(``)
-}
-
-func formatSecondaryAmount(a float64, td *TemplateData) string {
- if td.SecondaryCoin == "BTC" || td.SecondaryCoin == "ETH" {
- return strconv.FormatFloat(a, 'f', 6, 64)
- }
- return strconv.FormatFloat(a, 'f', 2, 64)
-}
-
func (s *PublicServer) amountSpan(a *api.Amount, td *TemplateData, classes string) template.HTML {
primary := s.formatAmount(a)
var rv strings.Builder
@@ -898,70 +704,11 @@ func (s *PublicServer) summaryValuesSpan(baseValue float64, secondaryValue float
return template.HTML(rv.String())
}
-func formatInt(i int) template.HTML {
- return formatInt64(int64(i))
-}
-
-func formatUint32(i uint32) template.HTML {
- return formatInt64(int64(i))
-}
-
-func appendSeparatedNumberSpans(rv *strings.Builder, s, separatorClass string) {
- if len(s) > 0 && s[0] == '-' {
- s = s[1:]
- rv.WriteByte('-')
+func formatSecondaryAmount(a float64, td *TemplateData) string {
+ if td.SecondaryCoin == "BTC" || td.SecondaryCoin == "ETH" {
+ return strconv.FormatFloat(a, 'f', 6, 64)
}
- t := (len(s) - 1) / 3
- if t <= 0 {
- rv.WriteString(s)
- } else {
- t *= 3
- rv.WriteString(s[:len(s)-t])
- for i := len(s) - t; i < len(s); i += 3 {
- rv.WriteString(``)
- rv.WriteString(s[i : i+3])
- rv.WriteString("")
- }
- }
-}
-
-func appendLeftSeparatedNumberSpans(rv *strings.Builder, s, separatorClass string) {
- l := len(s)
- if l <= 3 {
- rv.WriteString(s)
- } else {
- rv.WriteString(s[:3])
- for i := 3; i < len(s); i += 3 {
- rv.WriteString(``)
- e := i + 3
- if e > l {
- e = l
- }
- rv.WriteString(s[i:e])
- rv.WriteString("")
- }
- }
-}
-
-func formatInt64(i int64) template.HTML {
- s := strconv.FormatInt(i, 10)
- var rv strings.Builder
- appendSeparatedNumberSpans(&rv, s, "ns")
- return template.HTML(rv.String())
-}
-
-func formatBigInt(i *big.Int) template.HTML {
- if i == nil {
- return ""
- }
- s := i.String()
- var rv strings.Builder
- appendSeparatedNumberSpans(&rv, s, "ns")
- return template.HTML(rv.String())
+ return strconv.FormatFloat(a, 'f', 2, 64)
}
func getAddressAlias(a string, td *TemplateData) *api.AddressAlias {
diff --git a/server/public_test.go b/server/public_test.go
index eba62945..3f862e34 100644
--- a/server/public_test.go
+++ b/server/public_test.go
@@ -4,13 +4,11 @@ package server
import (
"encoding/json"
- "html/template"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
- "reflect"
"strconv"
"strings"
"testing"
@@ -1609,165 +1607,3 @@ func Test_PublicServer_BitcoinType_ExtendedIndex(t *testing.T) {
httpTestsExtendedIndex(t, ts)
}
-
-func Test_formatInt64(t *testing.T) {
- tests := []struct {
- name string
- n int64
- want template.HTML
- }{
- {"1", 1, "1"},
- {"13", 13, "13"},
- {"123", 123, "123"},
- {"1234", 1234, `1234`},
- {"91234", 91234, `91234`},
- {"891234", 891234, `891234`},
- {"7891234", 7891234, `7891234`},
- {"67891234", 67891234, `67891234`},
- {"567891234", 567891234, `567891234`},
- {"4567891234", 4567891234, `4567891234`},
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- if got := formatInt64(tt.n); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("formatInt64() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func Test_formatTime(t *testing.T) {
- timeNow = fixedTimeNow
- tests := []struct {
- name string
- want template.HTML
- }{
- {
- name: "2020-12-23 15:16:17",
- want: `630 days 21 hours ago`,
- },
- {
- name: "2022-08-23 11:12:13",
- want: `23 days 1 hour ago`,
- },
- {
- name: "2022-09-14 11:12:13",
- want: `1 day 1 hour ago`,
- },
- {
- name: "2022-09-14 14:12:13",
- want: `22 hours 31 mins ago`,
- },
- {
- name: "2022-09-15 09:33:26",
- want: `3 hours 10 mins ago`,
- },
- {
- name: "2022-09-15 12:23:56",
- want: `20 mins ago`,
- },
- {
- name: "2022-09-15 12:24:07",
- want: `19 mins ago`,
- },
- {
- name: "2022-09-15 12:43:21",
- want: `35 secs ago`,
- },
- {
- name: "2022-09-15 12:43:56",
- want: `0 secs ago`,
- },
- {
- name: "2022-09-16 12:43:56",
- want: `2022-09-16 12:43:56`,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- tm, _ := time.Parse("2006-01-02 15:04:05", tt.name)
- if got := timeSpan(&tm); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("formatTime() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-func Test_appendAmountSpan(t *testing.T) {
- tests := []struct {
- name string
- class string
- amount string
- shortcut string
- txDate string
- want string
- }{
- {
- name: "prim-amt 1.23456789 BTC",
- class: "prim-amt",
- amount: "1.23456789",
- shortcut: "BTC",
- want: `1.23456789 BTC`,
- },
- {
- name: "prim-amt 1432134.23456 BTC",
- class: "prim-amt",
- amount: "1432134.23456",
- shortcut: "BTC",
- want: `1432134.23456 BTC`,
- },
- {
- name: "sec-amt 1 EUR",
- class: "sec-amt",
- amount: "1",
- shortcut: "EUR",
- want: `1 EUR`,
- },
- {
- name: "sec-amt -1 EUR",
- class: "sec-amt",
- amount: "-1",
- shortcut: "EUR",
- want: `-1 EUR`,
- },
- {
- name: "sec-amt 432109.23 EUR",
- class: "sec-amt",
- amount: "432109.23",
- shortcut: "EUR",
- want: `432109.23 EUR`,
- },
- {
- name: "sec-amt -432109.23 EUR",
- class: "sec-amt",
- amount: "-432109.23",
- shortcut: "EUR",
- want: `-432109.23 EUR`,
- },
- {
- name: "sec-amt 43141.29 EUR",
- class: "sec-amt",
- amount: "43141.29",
- shortcut: "EUR",
- txDate: "2022-03-14",
- want: `43141.29 EUR`,
- },
- {
- name: "sec-amt -43141.29 EUR",
- class: "sec-amt",
- amount: "-43141.29",
- shortcut: "EUR",
- txDate: "2022-03-14",
- want: `-43141.29 EUR`,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- var rv strings.Builder
- appendAmountSpan(&rv, tt.class, tt.amount, tt.shortcut, tt.txDate)
- if got := rv.String(); !reflect.DeepEqual(got, tt.want) {
- t.Errorf("formatTime() = %v, want %v", got, tt.want)
- }
- })
- }
-}