github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/server/public_test.go (about)

     1  // +build unittest
     2  
     3  package server
     4  
     5  import (
     6  	"blockbook/bchain"
     7  	"blockbook/bchain/coins/btc"
     8  	"blockbook/common"
     9  	"blockbook/db"
    10  	"blockbook/tests/dbtestdata"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"net/url"
    15  	"os"
    16  	"strings"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/golang/glog"
    21  	"github.com/jakm/btcutil/chaincfg"
    22  	"github.com/martinboehm/golang-socketio"
    23  	"github.com/martinboehm/golang-socketio/transport"
    24  )
    25  
    26  func TestMain(m *testing.M) {
    27  	// set the current directory to blockbook root so that ./static/ works
    28  	if err := os.Chdir(".."); err != nil {
    29  		glog.Fatal("Chdir error:", err)
    30  	}
    31  	c := m.Run()
    32  	chaincfg.ResetParams()
    33  	os.Exit(c)
    34  }
    35  
    36  func setupRocksDB(t *testing.T, parser bchain.BlockChainParser) (*db.RocksDB, *common.InternalState, string) {
    37  	tmp, err := ioutil.TempDir("", "testdb")
    38  	if err != nil {
    39  		t.Fatal(err)
    40  	}
    41  	d, err := db.NewRocksDB(tmp, 100000, -1, parser, nil)
    42  	if err != nil {
    43  		t.Fatal(err)
    44  	}
    45  	is, err := d.LoadInternalState("fakecoin")
    46  	if err != nil {
    47  		t.Fatal(err)
    48  	}
    49  	d.SetInternalState(is)
    50  	// import data
    51  	if err := d.ConnectBlock(dbtestdata.GetTestUTXOBlock1(parser)); err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	if err := d.ConnectBlock(dbtestdata.GetTestUTXOBlock2(parser)); err != nil {
    55  		t.Fatal(err)
    56  	}
    57  	return d, is, tmp
    58  }
    59  
    60  func setupPublicHTTPServer(t *testing.T) (*PublicServer, string) {
    61  	parser := btc.NewBitcoinParser(
    62  		btc.GetChainParams("test"),
    63  		&btc.Configuration{BlockAddressesToKeep: 1})
    64  
    65  	d, is, path := setupRocksDB(t, parser)
    66  	// setup internal state and match BestHeight to test data
    67  	is.Coin = "Fakecoin"
    68  	is.CoinLabel = "Fake Coin"
    69  	is.CoinShortcut = "FAKE"
    70  	is.BestHeight = 225494
    71  
    72  	metrics, err := common.GetMetrics("Fakecoin")
    73  	if err != nil {
    74  		glog.Fatal("metrics: ", err)
    75  	}
    76  
    77  	chain, err := dbtestdata.NewFakeBlockChain(parser)
    78  	if err != nil {
    79  		glog.Fatal("fakechain: ", err)
    80  	}
    81  
    82  	// caching is switched off because test transactions do not have hex data
    83  	txCache, err := db.NewTxCache(d, chain, metrics, is, false)
    84  	if err != nil {
    85  		glog.Fatal("txCache: ", err)
    86  	}
    87  
    88  	// s.Run is never called, binding can be to any port
    89  	s, err := NewPublicServer("localhost:12345", "", d, chain, txCache, "", metrics, is, false)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	return s, path
    94  }
    95  
    96  func closeAndDestroyPublicServer(t *testing.T, s *PublicServer, dbpath string) {
    97  	// destroy db
    98  	if err := s.db.Close(); err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	os.RemoveAll(dbpath)
   102  }
   103  
   104  func newGetRequest(u string) *http.Request {
   105  	r, err := http.NewRequest("GET", u, nil)
   106  	if err != nil {
   107  		glog.Fatal(err)
   108  	}
   109  	return r
   110  }
   111  
   112  func newPostFormRequest(u string, formdata ...string) *http.Request {
   113  	form := url.Values{}
   114  	for i := 0; i < len(formdata)-1; i += 2 {
   115  		form.Add(formdata[i], formdata[i+1])
   116  	}
   117  	r, err := http.NewRequest("POST", u, strings.NewReader(form.Encode()))
   118  	if err != nil {
   119  		glog.Fatal(err)
   120  	}
   121  	r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
   122  	return r
   123  }
   124  
   125  func newPostRequest(u string, body string) *http.Request {
   126  	r, err := http.NewRequest("POST", u, strings.NewReader(body))
   127  	if err != nil {
   128  		glog.Fatal(err)
   129  	}
   130  	r.Header.Add("Content-Type", "application/octet-stream")
   131  	return r
   132  }
   133  
   134  func httpTests(t *testing.T, ts *httptest.Server) {
   135  	tests := []struct {
   136  		name        string
   137  		r           *http.Request
   138  		status      int
   139  		contentType string
   140  		body        []string
   141  	}{
   142  		{
   143  			name:        "explorerTx",
   144  			r:           newGetRequest(ts.URL + "/tx/fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"),
   145  			status:      http.StatusOK,
   146  			contentType: "text/html; charset=utf-8",
   147  			body: []string{
   148  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   149  				`<h1>Transaction</h1>`,
   150  				`<span class="data">fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db</span>`,
   151  				`td class="data">0 FAKE</td>`,
   152  				`<a href="/address/mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj">mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj</a>`,
   153  				`13.60030331 FAKE`,
   154  				`<td><span class="float-left">No Inputs (Newly Generated Coins)</span></td>`,
   155  				`</html>`,
   156  			},
   157  		},
   158  		{
   159  			name:        "explorerAddress",
   160  			r:           newGetRequest(ts.URL + "/address/mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"),
   161  			status:      http.StatusOK,
   162  			contentType: "text/html; charset=utf-8",
   163  			body: []string{
   164  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   165  				`<h1>Address`,
   166  				`<small class="text-muted">0 FAKE</small>`,
   167  				`<span class="data">mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz</span>`,
   168  				`<td class="data">0.00012345 FAKE</td>`,
   169  				`<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`,
   170  				`3172.83951061 FAKE <a class="text-danger" href="/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0" title="Spent">➡</a></span>`,
   171  				`<td><span class="ellipsis float-left"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="float-right">`,
   172  				`td><span class="ellipsis float-left"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="float-right">`,
   173  				`9172.83951061 FAKE <span class="text-success" title="Unspent"> <b>×</b></span></span>`,
   174  				`<a href="/tx/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840">00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840</a>`,
   175  				`</html>`,
   176  			},
   177  		},
   178  		{
   179  			name:        "explorerSpendingTx",
   180  			r:           newGetRequest(ts.URL + "/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0"),
   181  			status:      http.StatusOK,
   182  			contentType: "text/html; charset=utf-8",
   183  			body: []string{
   184  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   185  				`<h1>Transaction</h1>`,
   186  				`<span class="data">3d90d15ed026dc45e19ffb52875ed18fa9e8012ad123d7f7212176e2b0ebdb71</span>`,
   187  				`<td class="data">0.00000062 FAKE</td>`,
   188  				`</html>`,
   189  			},
   190  		},
   191  		{
   192  			name:        "explorerSpendingTx - not found",
   193  			r:           newGetRequest(ts.URL + "/spending/123be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0"),
   194  			status:      http.StatusOK,
   195  			contentType: "text/html; charset=utf-8",
   196  			body: []string{
   197  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   198  				`<h1>Error</h1>`,
   199  				`<h4>Transaction not found</h4>`,
   200  				`</html>`,
   201  			},
   202  		},
   203  		{
   204  			name:        "explorerBlocks",
   205  			r:           newGetRequest(ts.URL + "/blocks"),
   206  			status:      http.StatusOK,
   207  			contentType: "text/html; charset=utf-8",
   208  			body: []string{
   209  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   210  				`<h1>Blocks`,
   211  				`<td><a href="/block/225494">225494</a></td>`,
   212  				`<td class="ellipsis">00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6</td>`,
   213  				`<td class="ellipsis">0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997</td>`,
   214  				`<td class="text-right">2</td>`,
   215  				`<td class="text-right">1234567</td>`,
   216  				`</html>`,
   217  			},
   218  		},
   219  		{
   220  			name:        "explorerBlock",
   221  			r:           newGetRequest(ts.URL + "/block/225494"),
   222  			status:      http.StatusOK,
   223  			contentType: "text/html; charset=utf-8",
   224  			body: []string{
   225  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   226  				`<h1>Block 225494</h1>`,
   227  				`<span class="data">00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6</span>`,
   228  				`<td class="data">4</td>`, // number of transactions
   229  				`<a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a>`,
   230  				`9172.83951061 FAKE`,
   231  				`<a href="/tx/fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db">fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db</a>`,
   232  				`</html>`,
   233  			},
   234  		},
   235  		{
   236  			name:        "explorerIndex",
   237  			r:           newGetRequest(ts.URL + "/"),
   238  			status:      http.StatusOK,
   239  			contentType: "text/html; charset=utf-8",
   240  			body: []string{
   241  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   242  				`<h1>Application status</h1>`,
   243  				`<h3 class="bg-warning text-white" style="padding: 20px;">Synchronization with backend is disabled, the state of index is not up to date.</h3>`,
   244  				`<a href="/block/225494">225494</a>`,
   245  				`<td class="data">/Fakecoin:0.0.1/</td>`,
   246  				`</html>`,
   247  			},
   248  		},
   249  		{
   250  			name:        "explorerSearch block height",
   251  			r:           newGetRequest(ts.URL + "/search?q=225494"),
   252  			status:      http.StatusOK,
   253  			contentType: "text/html; charset=utf-8",
   254  			body: []string{
   255  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   256  				`<h1>Block 225494</h1>`,
   257  				`<span class="data">00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6</span>`,
   258  				`<td class="data">4</td>`, // number of transactions
   259  				`<a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a>`,
   260  				`9172.83951061 FAKE`,
   261  				`<a href="/tx/fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db">fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db</a>`,
   262  				`</html>`,
   263  			},
   264  		},
   265  		{
   266  			name:        "explorerSearch block hash",
   267  			r:           newGetRequest(ts.URL + "/search?q=00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6"),
   268  			status:      http.StatusOK,
   269  			contentType: "text/html; charset=utf-8",
   270  			body: []string{
   271  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   272  				`<h1>Block 225494</h1>`,
   273  				`<span class="data">00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6</span>`,
   274  				`<td class="data">4</td>`, // number of transactions
   275  				`<a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a>`,
   276  				`9172.83951061 FAKE`,
   277  				`<a href="/tx/fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db">fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db</a>`,
   278  				`</html>`,
   279  			},
   280  		},
   281  		{
   282  			name:        "explorerSearch tx",
   283  			r:           newGetRequest(ts.URL + "/search?q=fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"),
   284  			status:      http.StatusOK,
   285  			contentType: "text/html; charset=utf-8",
   286  			body: []string{
   287  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   288  				`<h1>Transaction</h1>`,
   289  				`<span class="data">fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db</span>`,
   290  				`td class="data">0 FAKE</td>`,
   291  				`<a href="/address/mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj">mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj</a>`,
   292  				`13.60030331 FAKE`,
   293  				`<td><span class="float-left">No Inputs (Newly Generated Coins)</span></td>`,
   294  				`</html>`,
   295  			},
   296  		},
   297  		{
   298  			name:        "explorerSearch address",
   299  			r:           newGetRequest(ts.URL + "/search?q=mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"),
   300  			status:      http.StatusOK,
   301  			contentType: "text/html; charset=utf-8",
   302  			body: []string{
   303  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   304  				`<h1>Address`,
   305  				`<small class="text-muted">0 FAKE</small>`,
   306  				`<span class="data">mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz</span>`,
   307  				`<td class="data">0.00012345 FAKE</td>`,
   308  				`<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`,
   309  				`3172.83951061 FAKE <a class="text-danger" href="/spending/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25/0" title="Spent">➡</a></span>`,
   310  				`<td><span class="ellipsis float-left"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="float-right">`,
   311  				`td><span class="ellipsis float-left"><a href="/address/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL">mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL</a></span><span class="float-right">`,
   312  				`9172.83951061 FAKE <span class="text-success" title="Unspent"> <b>×</b></span></span>`,
   313  				`<a href="/tx/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840">00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840</a>`,
   314  				`</html>`,
   315  			},
   316  		},
   317  		{
   318  			name:        "explorerSearch not found",
   319  			r:           newGetRequest(ts.URL + "/search?q=1234"),
   320  			status:      http.StatusOK,
   321  			contentType: "text/html; charset=utf-8",
   322  			body: []string{
   323  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   324  				`<h1>Error</h1>`,
   325  				`<h4>No matching records found for &#39;1234&#39;</h4>`,
   326  				`</html>`,
   327  			},
   328  		},
   329  		{
   330  			name:        "explorerSendTx",
   331  			r:           newGetRequest(ts.URL + "/sendtx"),
   332  			status:      http.StatusOK,
   333  			contentType: "text/html; charset=utf-8",
   334  			body: []string{
   335  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   336  				`<h1>Send Raw Transaction</h1>`,
   337  				`<textarea class="form-control" rows="8" name="hex"></textarea>`,
   338  				`</html>`,
   339  			},
   340  		},
   341  		{
   342  			name:        "explorerSendTx POST",
   343  			r:           newPostFormRequest(ts.URL+"/sendtx", "hex", "12341234"),
   344  			status:      http.StatusOK,
   345  			contentType: "text/html; charset=utf-8",
   346  			body: []string{
   347  				`<a href="/" class="nav-link">Fake Coin Explorer</a>`,
   348  				`<h1>Send Raw Transaction</h1>`,
   349  				`<textarea class="form-control" rows="8" name="hex">12341234</textarea>`,
   350  				`<div class="alert alert-danger">Invalid data</div>`,
   351  				`</html>`,
   352  			},
   353  		},
   354  		{
   355  			name:        "apiIndex",
   356  			r:           newGetRequest(ts.URL + "/api"),
   357  			status:      http.StatusOK,
   358  			contentType: "application/json; charset=utf-8",
   359  			body: []string{
   360  				`{"blockbook":{"coin":"Fakecoin"`,
   361  				`"bestHeight":225494`,
   362  				`"backend":{"chain":"fakecoin","blocks":2,"headers":2,"bestblockhash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6"`,
   363  				`"version":"001001","subversion":"/Fakecoin:0.0.1/"`,
   364  			},
   365  		},
   366  		{
   367  			name:        "apiBlockIndex",
   368  			r:           newGetRequest(ts.URL + "/api/block-index/"),
   369  			status:      http.StatusOK,
   370  			contentType: "application/json; charset=utf-8",
   371  			body: []string{
   372  				`{"blockHash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6"}`,
   373  			},
   374  		},
   375  		{
   376  			name:        "apiTx",
   377  			r:           newGetRequest(ts.URL + "/api/tx/05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"),
   378  			status:      http.StatusOK,
   379  			contentType: "application/json; charset=utf-8",
   380  			body: []string{
   381  				`{"txid":"05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07","vin":[{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","vout":2,"n":0,"scriptSig":{"hex":""},"addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"],"value":"0.00009876"}],"vout":[{"value":"0.00009","n":0,"scriptPubKey":{"hex":"a914e921fc4912a315078f370d959f2c4f7b6d2a683c87","addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"]},"spent":false}],"blockhash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6","blockheight":225494,"confirmations":1,"time":22549400002,"blocktime":22549400002,"valueOut":"0.00009","valueIn":"0.00009876","fees":"0.00000876","hex":""}`,
   382  			},
   383  		},
   384  		{
   385  			name:        "apiTx - not found",
   386  			r:           newGetRequest(ts.URL + "/api/tx/1232e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"),
   387  			status:      http.StatusBadRequest,
   388  			contentType: "application/json; charset=utf-8",
   389  			body: []string{
   390  				`{"error":"Tx not found, Not found"}`,
   391  			},
   392  		},
   393  		{
   394  			name:        "apiTxSpecific",
   395  			r:           newGetRequest(ts.URL + "/api/tx-specific/00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840"),
   396  			status:      http.StatusOK,
   397  			contentType: "application/json; charset=utf-8",
   398  			body: []string{
   399  				`{"hex":"","txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","version":0,"locktime":0,"vin":null,"vout":[{"ValueSat":100000000,"value":0,"n":0,"scriptPubKey":{"hex":"76a914010d39800f86122416e28f485029acf77507169288ac","addresses":null}},{"ValueSat":12345,"value":0,"n":1,"scriptPubKey":{"hex":"76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac","addresses":null}}],"confirmations":2,"time":22549300000,"blocktime":22549300000}`,
   400  			},
   401  		},
   402  		{
   403  			name:        "apiAddress",
   404  			r:           newGetRequest(ts.URL + "/api/address/mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw"),
   405  			status:      http.StatusOK,
   406  			contentType: "application/json; charset=utf-8",
   407  			body: []string{
   408  				`{"page":1,"totalPages":1,"itemsOnPage":1000,"addrStr":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","balance":"0","totalReceived":"12345.67890123","totalSent":"12345.67890123","unconfirmedBalance":"0","unconfirmedTxApperances":0,"txApperances":2,"transactions":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75"]}`,
   409  			},
   410  		},
   411  		{
   412  			name:        "apiAddressUtxo",
   413  			r:           newGetRequest(ts.URL + "/api/utxo/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL"),
   414  			status:      http.StatusOK,
   415  			contentType: "application/json; charset=utf-8",
   416  			body: []string{
   417  				`[{"txid":"7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","vout":1,"amount":"9172.83951061","satoshis":917283951061,"height":225494,"confirmations":1}]`,
   418  			},
   419  		},
   420  		{
   421  			name:        "apiSendTx",
   422  			r:           newGetRequest(ts.URL + "/api/sendtx/1234567890"),
   423  			status:      http.StatusBadRequest,
   424  			contentType: "application/json; charset=utf-8",
   425  			body: []string{
   426  				`{"error":"Invalid data"}`,
   427  			},
   428  		},
   429  		{
   430  			name:        "apiSendTx POST",
   431  			r:           newPostRequest(ts.URL+"/api/sendtx/", "123456"),
   432  			status:      http.StatusOK,
   433  			contentType: "application/json; charset=utf-8",
   434  			body: []string{
   435  				`{"result":"9876"}`,
   436  			},
   437  		},
   438  		{
   439  			name:        "apiSendTx POST empty",
   440  			r:           newPostRequest(ts.URL+"/api/sendtx", ""),
   441  			status:      http.StatusBadRequest,
   442  			contentType: "application/json; charset=utf-8",
   443  			body: []string{
   444  				`{"error":"Missing tx blob"}`,
   445  			},
   446  		},
   447  		{
   448  			name:        "apiEstimateFee",
   449  			r:           newGetRequest(ts.URL + "/api/estimatefee/123?conservative=false"),
   450  			status:      http.StatusOK,
   451  			contentType: "application/json; charset=utf-8",
   452  			body: []string{
   453  				`{"result":"0.00012299"}`,
   454  			},
   455  		},
   456  	}
   457  
   458  	for _, tt := range tests {
   459  		t.Run(tt.name, func(t *testing.T) {
   460  			resp, err := http.DefaultClient.Do(tt.r)
   461  			if err != nil {
   462  				t.Fatal(err)
   463  			}
   464  			defer resp.Body.Close()
   465  			if resp.StatusCode != tt.status {
   466  				t.Errorf("StatusCode = %v, want %v", resp.StatusCode, tt.status)
   467  			}
   468  			if resp.Header["Content-Type"][0] != tt.contentType {
   469  				t.Errorf("Content-Type = %v, want %v", resp.Header["Content-Type"][0], tt.contentType)
   470  			}
   471  			bb, err := ioutil.ReadAll(resp.Body)
   472  			if err != nil {
   473  				t.Fatal(err)
   474  			}
   475  			b := string(bb)
   476  			for _, c := range tt.body {
   477  				if !strings.Contains(b, c) {
   478  					t.Errorf("Page body does not contain %v, body %v", c, b)
   479  					break
   480  				}
   481  			}
   482  		})
   483  	}
   484  }
   485  
   486  func socketioTests(t *testing.T, ts *httptest.Server) {
   487  	type socketioReq struct {
   488  		Method string        `json:"method"`
   489  		Params []interface{} `json:"params"`
   490  	}
   491  
   492  	url := strings.Replace(ts.URL, "http://", "ws://", 1) + "/socket.io/"
   493  	s, err := gosocketio.Dial(url, transport.GetDefaultWebsocketTransport())
   494  	if err != nil {
   495  		t.Fatal(err)
   496  	}
   497  	defer s.Close()
   498  
   499  	tests := []struct {
   500  		name string
   501  		req  socketioReq
   502  		want string
   503  	}{
   504  		{
   505  			name: "getInfo",
   506  			req:  socketioReq{"getInfo", []interface{}{}},
   507  			want: `{"result":{"blocks":225494,"testnet":true,"network":"fakecoin","subversion":"/Fakecoin:0.0.1/","coin_name":"Fakecoin","about":"Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose."}}`,
   508  		},
   509  		{
   510  			name: "estimateFee",
   511  			req:  socketioReq{"estimateFee", []interface{}{17}},
   512  			want: `{"result":0.000034}`,
   513  		},
   514  		{
   515  			name: "estimateSmartFee",
   516  			req:  socketioReq{"estimateSmartFee", []interface{}{19, true}},
   517  			want: `{"result":0.000019}`,
   518  		},
   519  		{
   520  			name: "getAddressTxids",
   521  			req: socketioReq{"getAddressTxids", []interface{}{
   522  				[]string{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"},
   523  				map[string]interface{}{
   524  					"start":        2000000,
   525  					"end":          0,
   526  					"queryMempool": false,
   527  				},
   528  			}},
   529  			want: `{"result":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840"]}`,
   530  		},
   531  		{
   532  			name: "getAddressTxids limited range",
   533  			req: socketioReq{"getAddressTxids", []interface{}{
   534  				[]string{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"},
   535  				map[string]interface{}{
   536  					"start":        225494,
   537  					"end":          225494,
   538  					"queryMempool": false,
   539  				},
   540  			}},
   541  			want: `{"result":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25"]}`,
   542  		},
   543  		{
   544  			name: "getAddressHistory",
   545  			req: socketioReq{"getAddressHistory", []interface{}{
   546  				[]string{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"},
   547  				map[string]interface{}{
   548  					"start":        2000000,
   549  					"end":          0,
   550  					"queryMempool": false,
   551  					"from":         0,
   552  					"to":           5,
   553  				},
   554  			}},
   555  			want: `{"result":{"totalCount":2,"items":[{"addresses":{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz":{"inputIndexes":[1],"outputIndexes":[]}},"satoshis":-12345,"confirmations":1,"tx":{"hex":"","height":225494,"blockTimestamp":22549400000,"version":0,"hash":"7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","inputs":[{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","outputIndex":0,"script":"","sequence":0,"address":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","satoshis":1234567890123},{"txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","outputIndex":1,"script":"","sequence":0,"address":"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz","satoshis":12345}],"inputSatoshis":1234567902468,"outputs":[{"satoshis":317283951061,"script":"76a914ccaaaf374e1b06cb83118453d102587b4273d09588ac","address":"mzB8cYrfRwFRFAGTDzV8LkUQy5BQicxGhX"},{"satoshis":917283951061,"script":"76a9148d802c045445df49613f6a70ddd2e48526f3701f88ac","address":"mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL"}],"outputSatoshis":1234567902122,"feeSatoshis":346}},{"addresses":{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz":{"inputIndexes":[],"outputIndexes":[1]}},"satoshis":12345,"confirmations":2,"tx":{"hex":"","height":225493,"blockTimestamp":22549300000,"version":0,"hash":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","inputs":[],"outputs":[{"satoshis":100000000,"script":"76a914010d39800f86122416e28f485029acf77507169288ac","address":"mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti"},{"satoshis":12345,"script":"76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac","address":"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"}],"outputSatoshis":100012345}}]}}`,
   556  		},
   557  		{
   558  			name: "getBlockHeader",
   559  			req:  socketioReq{"getBlockHeader", []interface{}{225493}},
   560  			want: `{"result":{"hash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","version":0,"confirmations":0,"height":0,"chainWork":"","nextHash":"","merkleRoot":"","time":0,"medianTime":0,"nonce":0,"bits":"","difficulty":0}}`,
   561  		},
   562  		{
   563  			name: "getDetailedTransaction",
   564  			req:  socketioReq{"getDetailedTransaction", []interface{}{"3d90d15ed026dc45e19ffb52875ed18fa9e8012ad123d7f7212176e2b0ebdb71"}},
   565  			want: `{"result":{"hex":"","height":225494,"blockTimestamp":22549400001,"version":0,"hash":"3d90d15ed026dc45e19ffb52875ed18fa9e8012ad123d7f7212176e2b0ebdb71","inputs":[{"txid":"7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","outputIndex":0,"script":"","sequence":0,"address":"mzB8cYrfRwFRFAGTDzV8LkUQy5BQicxGhX","satoshis":317283951061},{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","outputIndex":1,"script":"","sequence":0,"address":"2Mz1CYoppGGsLNUGF2YDhTif6J661JitALS","satoshis":1}],"inputSatoshis":317283951062,"outputs":[{"satoshis":118641975500,"script":"76a914b434eb0c1a3b7a02e8a29cc616e791ef1e0bf51f88ac","address":"mwwoKQE5Lb1G4picHSHDQKg8jw424PF9SC"},{"satoshis":198641975500,"script":"76a9143f8ba3fda3ba7b69f5818086e12223c6dd25e3c888ac","address":"mmJx9Y8ayz9h14yd9fgCW1bUKoEpkBAquP"}],"outputSatoshis":317283951000,"feeSatoshis":62}}`,
   566  		},
   567  		{
   568  			name: "sendTransaction",
   569  			req:  socketioReq{"sendTransaction", []interface{}{"010000000001019d64f0c72a0d206001decbffaa722eb1044534c"}},
   570  			want: `{"error":{"message":"Invalid data"}}`,
   571  		},
   572  	}
   573  
   574  	for _, tt := range tests {
   575  		t.Run(tt.name, func(t *testing.T) {
   576  			resp, err := s.Ack("message", tt.req, time.Second*3)
   577  			if err != nil {
   578  				t.Errorf("Socketio error %v", err)
   579  			}
   580  			if resp != tt.want {
   581  				t.Errorf("Socketio resp %v, want %v", resp, tt.want)
   582  			}
   583  		})
   584  	}
   585  }
   586  
   587  func Test_PublicServer_UTXO(t *testing.T) {
   588  	s, dbpath := setupPublicHTTPServer(t)
   589  	defer closeAndDestroyPublicServer(t, s, dbpath)
   590  	s.ConnectFullPublicInterface()
   591  	// take the handler of the public server and pass it to the test server
   592  	ts := httptest.NewServer(s.https.Handler)
   593  	defer ts.Close()
   594  
   595  	httpTests(t, ts)
   596  	socketioTests(t, ts)
   597  
   598  }