// Copyright (c) 2013 Conformal Systems LLC. // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package btcjson_test import ( "bytes" "encoding/json" "github.com/conformal/btcjson" "io" "io/ioutil" "testing" ) // cmdtests is a table of all the possible commands and a list of inputs, // some of which should work, some of which should not (indicated by the // pass variable). This mainly checks the type and number of the arguments, // it does not actually check to make sure the values are correct (i.e., that // addresses are reasonable) as the bitcoin client must be able to deal with // that. var cmdtests = []struct { cmd string args []interface{} pass bool }{ {"getinfo", nil, true}, {"getinfo", []interface{}{1}, false}, {"listaccounts", nil, true}, {"listaccounts", []interface{}{1}, true}, {"listaccounts", []interface{}{"test"}, false}, {"listaccounts", []interface{}{1, 2}, false}, {"getblockhash", nil, false}, {"getblockhash", []interface{}{1}, true}, {"getblockhash", []interface{}{1, 2}, false}, {"getblockhash", []interface{}{1.1}, false}, {"settxfee", nil, false}, {"settxfee", []interface{}{1.0}, true}, {"settxfee", []interface{}{1.0, 2.0}, false}, {"settxfee", []interface{}{1}, false}, {"getmemorypool", nil, true}, {"getmemorypool", []interface{}{"test"}, true}, {"getmemorypool", []interface{}{1}, false}, {"getmemorypool", []interface{}{"test", 2}, false}, {"backupwallet", nil, false}, {"backupwallet", []interface{}{1, 2}, false}, {"backupwallet", []interface{}{1}, false}, {"backupwallet", []interface{}{"testpath"}, true}, {"setaccount", nil, false}, {"setaccount", []interface{}{1}, false}, {"setaccount", []interface{}{1, 2, 3}, false}, {"setaccount", []interface{}{1, "test"}, false}, {"setaccount", []interface{}{"test", "test"}, true}, {"verifymessage", nil, false}, {"verifymessage", []interface{}{1}, false}, {"verifymessage", []interface{}{1, 2}, false}, {"verifymessage", []interface{}{1, 2, 3, 4}, false}, {"verifymessage", []interface{}{"test", "test", "test"}, true}, {"verifymessage", []interface{}{"test", "test", 1}, false}, {"getaddednodeinfo", nil, false}, {"getaddednodeinfo", []interface{}{1}, false}, {"getaddednodeinfo", []interface{}{true}, true}, {"getaddednodeinfo", []interface{}{true, 1}, false}, {"getaddednodeinfo", []interface{}{true, "test"}, true}, {"setgenerate", nil, false}, {"setgenerate", []interface{}{1, 2, 3}, false}, {"setgenerate", []interface{}{true}, true}, {"setgenerate", []interface{}{true, 1}, true}, {"setgenerate", []interface{}{true, 1.1}, false}, {"setgenerate", []interface{}{"true", 1}, false}, {"getbalance", nil, true}, {"getbalance", []interface{}{"test"}, true}, {"getbalance", []interface{}{"test", 1}, true}, {"getbalance", []interface{}{"test", 1.0}, false}, {"getbalance", []interface{}{1, 1}, false}, {"getbalance", []interface{}{"test", 1, 2}, false}, {"getbalance", []interface{}{1}, false}, {"addnode", nil, false}, {"addnode", []interface{}{1, 2, 3}, false}, {"addnode", []interface{}{"test"}, true}, {"addnode", []interface{}{1}, false}, {"addnode", []interface{}{"test", 1}, true}, {"addnode", []interface{}{"test", 1.0}, false}, {"listreceivedbyaccount", nil, true}, {"listreceivedbyaccount", []interface{}{1, 2, 3}, false}, {"listreceivedbyaccount", []interface{}{1}, true}, {"listreceivedbyaccount", []interface{}{1.0}, false}, {"listreceivedbyaccount", []interface{}{1, false}, true}, {"listreceivedbyaccount", []interface{}{1, "false"}, false}, {"listtransactions", nil, true}, {"listtransactions", []interface{}{"test"}, true}, {"listtransactions", []interface{}{"test", 1}, true}, {"listtransactions", []interface{}{"test", 1, 2}, true}, {"listtransactions", []interface{}{"test", 1, 2, 3}, false}, {"listtransactions", []interface{}{1}, false}, {"listtransactions", []interface{}{"test", 1.0}, false}, {"listtransactions", []interface{}{"test", 1, "test"}, false}, {"importprivkey", nil, false}, {"importprivkey", []interface{}{"test"}, true}, {"importprivkey", []interface{}{1}, false}, {"importprivkey", []interface{}{"test", "test"}, true}, {"importprivkey", []interface{}{"test", "test", true}, true}, {"importprivkey", []interface{}{"test", "test", true, 1}, false}, {"importprivkey", []interface{}{"test", 1.0, true}, false}, {"importprivkey", []interface{}{"test", "test", "true"}, false}, {"listunspent", nil, true}, {"listunspent", []interface{}{1}, true}, {"listunspent", []interface{}{1, 2}, true}, {"listunspent", []interface{}{1, 2, 3}, false}, {"listunspent", []interface{}{1.0}, false}, {"listunspent", []interface{}{1, 2.0}, false}, {"sendfrom", nil, false}, {"sendfrom", []interface{}{"test"}, false}, {"sendfrom", []interface{}{"test", "test"}, false}, {"sendfrom", []interface{}{"test", "test", 1.0}, true}, {"sendfrom", []interface{}{"test", 1, 1.0}, false}, {"sendfrom", []interface{}{1, "test", 1.0}, false}, {"sendfrom", []interface{}{"test", "test", 1}, false}, {"sendfrom", []interface{}{"test", "test", 1.0, 1}, true}, {"sendfrom", []interface{}{"test", "test", 1.0, 1, "test"}, true}, {"sendfrom", []interface{}{"test", "test", 1.0, 1, "test", "test"}, true}, {"move", nil, false}, {"move", []interface{}{1, 2, 3, 4, 5, 6}, false}, {"move", []interface{}{1, 2}, false}, {"move", []interface{}{"test", "test", 1.0}, true}, {"move", []interface{}{"test", "test", 1.0, 1, "test"}, true}, {"move", []interface{}{"test", "test", 1.0, 1}, true}, {"move", []interface{}{1, "test", 1.0}, false}, {"move", []interface{}{"test", 1, 1.0}, false}, {"move", []interface{}{"test", "test", 1}, false}, {"move", []interface{}{"test", "test", 1.0, 1.0, "test"}, false}, {"move", []interface{}{"test", "test", 1.0, 1, true}, false}, {"sendtoaddress", nil, false}, {"sendtoaddress", []interface{}{"test"}, false}, {"sendtoaddress", []interface{}{"test", 1.0}, true}, {"sendtoaddress", []interface{}{"test", 1.0, "test"}, true}, {"sendtoaddress", []interface{}{"test", 1.0, "test", "test"}, true}, {"sendtoaddress", []interface{}{1, 1.0, "test", "test"}, false}, {"sendtoaddress", []interface{}{"test", 1, "test", "test"}, false}, {"sendtoaddress", []interface{}{"test", 1.0, 1.0, "test"}, false}, {"sendtoaddress", []interface{}{"test", 1.0, "test", 1.0}, false}, {"sendtoaddress", []interface{}{"test", 1.0, "test", "test", 1}, false}, {"addmultisignaddress", []interface{}{1, "test", "test"}, true}, {"addmultisignaddress", []interface{}{1, "test"}, false}, {"addmultisignaddress", []interface{}{1, 1.0, "test"}, false}, {"addmultisignaddress", []interface{}{1, "test", "test", "test"}, true}, {"createrawtransaction", []interface{}{"in1", 0, "a1", 1.0}, true}, {"createrawtransaction", []interface{}{"in1", "out1", "a1", 1.0, "test"}, false}, {"createrawtransaction", []interface{}{}, false}, {"createrawtransaction", []interface{}{"in1", 1.0, "a1", 1.0}, false}, {"sendmany", []interface{}{"in1", "out1", 1.0, 1, "comment"}, true}, {"sendmany", []interface{}{"in1", "out1", 1.0, "comment"}, true}, {"sendmany", []interface{}{"in1", "out1"}, false}, {"sendmany", []interface{}{true, "out1", 1.0, 1, "comment"}, false}, {"sendmany", []interface{}{"in1", "out1", "test", 1, "comment"}, false}, {"lockunspent", []interface{}{true, "something"}, true}, {"lockunspent", []interface{}{true}, false}, {"lockunspent", []interface{}{1.0, "something"}, false}, {"signrawtransaction", []interface{}{"hexstring"}, true}, {"signrawtransaction", []interface{}{"hexstring", "test", "test2", "test3", "test4"}, true}, {"signrawtransaction", []interface{}{"hexstring", "test", "test2", "test3"}, false}, {"signrawtransaction", []interface{}{1.2, "test", "test2", "test3", "test4"}, false}, {"signrawtransaction", []interface{}{"hexstring", 1, "test2", "test3", "test4"}, false}, {"signrawtransaction", []interface{}{"hexstring", "test", 2, "test3", "test4"}, false}, {"signrawtransaction", []interface{}{"hexstring", "test", "test2", 3, "test4"}, false}, {"listsinceblock", []interface{}{"test", "test"}, true}, {"listsinceblock", []interface{}{"test", "test", "test"}, false}, {"listsinceblock", []interface{}{"test"}, true}, {"listsinceblock", []interface{}{}, true}, {"listsinceblock", []interface{}{1, "test"}, false}, {"fakecommand", nil, false}, } // TestRpcCreateMessage tests CreateMessage using the table of messages // in cmdtests. func TestRpcCreateMessage(t *testing.T) { var err error for i, tt := range cmdtests { if tt.args == nil { _, err = btcjson.CreateMessage(tt.cmd) } else { _, err = btcjson.CreateMessage(tt.cmd, tt.args...) } if tt.pass { if err != nil { t.Errorf("Could not create command %d: %s %v.", i, tt.cmd, err) } } else { if err == nil { t.Errorf("Should create command. %d: %s", i, tt.cmd) } } } return } // TestRpcCommand tests RpcCommand by generating some commands and // trying to send them off. func TestRpcCommand(t *testing.T) { user := "something" pass := "something" server := "invalid" var msg []byte _, err := btcjson.RpcCommand(user, pass, server, msg) if err == nil { t.Errorf("Should fail.") } msg, err = btcjson.CreateMessage("getinfo") if err != nil { t.Errorf("Cannot create valid json message") } _, err = btcjson.RpcCommand(user, pass, server, msg) if err == nil { t.Errorf("Should not connect to server.") } badMsg := []byte("{\"jsonrpc\":\"1.0\",\"id\":\"btcd\",\"method\":\"\"}") _, err = btcjson.RpcCommand(user, pass, server, badMsg) if err == nil { t.Errorf("Cannot have no method in msg..") } return } // FailingReadClose is a type used for testing so we can get something that // fails past Go's type system. type FailingReadCloser struct{} func (f *FailingReadCloser) Close() error { return io.ErrUnexpectedEOF } func (f *FailingReadCloser) Read(p []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } // TestRpcReply tests JsonGetRaw by sending both a good and a bad buffer // to it. func TestRpcReply(t *testing.T) { buffer := new(bytes.Buffer) buffer2 := ioutil.NopCloser(buffer) _, err := btcjson.GetRaw(buffer2) if err != nil { t.Errorf("Error reading rpc reply.") } failBuf := &FailingReadCloser{} _, err = btcjson.GetRaw(failBuf) if err == nil { t.Errorf("Error, this should fail.") } return } var idtests = []struct { testId []interface{} pass bool }{ {[]interface{}{"string test"}, true}, {[]interface{}{1}, true}, {[]interface{}{1.0}, true}, {[]interface{}{nil}, true}, {[]interface{}{make(chan int)}, false}, } // TestIsValidIdType tests that IsValidIdType allows (and disallows the correct // types). func TestIsValidIdType(t *testing.T) { for _, tt := range idtests { res := btcjson.IsValidIdType(tt.testId[0]) if res != tt.pass { t.Errorf("Incorrect type result %v.", tt) } } return } var floattests = []struct { in float64 out int64 pass bool }{ {1.0, 100000000, true}, {-1.0, -100000000, true}, {0.0, 0, true}, {0.00000001, 1, true}, {-0.00000001, -1, true}, {-1.0e307, 0, false}, {1.0e307, 0, false}, } // TestJSONtoAmount tests that JSONtoAmount returns the proper values. func TestJSONtoAmount(t *testing.T) { for _, tt := range floattests { res, err := btcjson.JSONToAmount(tt.in) if tt.pass { if res != tt.out || err != nil { t.Errorf("Should not fail: %v", tt.in) } } else { if err == nil { t.Errorf("Should not pass: %v", tt.in) } } } return } var resulttests = []struct { cmd string msg []byte comp bool pass bool }{ // Generate a fake message to make sure we can encode and decode it and // get the same thing back. {"getblockcount", []byte(`{"result":226790,"error":{"code":1,"message":"No Error"},"id":"btcd"}`), true, true}, // Generate a fake message to make sure we don't make a command from it. {"anycommand", []byte(`{"result":"test","id":1}`), false, false}, {"anycommand", []byte(`{some junk}`), false, false}, {"anycommand", []byte(`{"error":null,"result":null,"id":"test"}`), false, true}, {"getinfo", []byte(`{"error":null,"result":null,"id":"test"}`), false, true}, {"getinfo", []byte(`{"error":null,"result":null}`), false, false}, {"getinfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false}, {"getblock", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false}, {"getblock", []byte(`{"result":{"hash":"000000","confirmations":16007,"size":325648},"error":null,"id":1}`), false, true}, {"getrawtransaction", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false}, {"getrawtransaction", []byte(`{"error":null,"id":1,"result":{"hex":"somejunk","version":1}}`), false, true}, {"decoderawtransaction", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false}, {"decoderawtransaction", []byte(`{"error":null,"id":1,"result":{"Txid":"something"}}`), false, true}, {"getaddressesbyaccount", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false}, {"getaddressesbyaccount", []byte(`{"error":null,"id":1,"result":["test"]}`), false, true}, {"getmininginfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false}, {"getmininginfo", []byte(`{"error":null,"id":1,"result":{"generate":true}}`), false, true}, {"getrawmempool", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false}, {"getrawmempool", []byte(`{"error":null,"id":1,"result":["test"]}`), false, true}, {"validateaddress", []byte(`{"error":null,"id":1,"result":{"isvalid":false}}`), false, true}, {"validateaddress", []byte(`{"error":null,"id":1,"result":{false}}`), false, false}, {"signrawtransaction", []byte(`{"error":null,"id":1,"result":{"hex":"something","complete":false}}`), false, true}, {"signrawtransaction", []byte(`{"error":null,"id":1,"result":{false}}`), false, false}, {"listunspent", []byte(`{"error":null,"id":1,"result":[{"txid":"something"}]}`), false, true}, {"listunspent", []byte(`{"error":null,"id":1,"result":[{"txid"}]}`), false, false}, } // TestReadResultCmd tests that readResultCmd can properly unmarshall the // returned []byte that contains a json reply for both known and unknown // messages. func TestReadResultCmd(t *testing.T) { for i, tt := range resulttests { result, err := btcjson.ReadResultCmd(tt.cmd, tt.msg) if tt.pass { if err != nil { t.Errorf("Should read result: %d %v", i, err) } // Due to the pointer for the Error and other structs, // we can't always guarantee byte for byte comparison. if tt.comp { msg2, err := json.Marshal(result) if err != nil { t.Errorf("Should unmarshal result: %d %v", i, err) } if bytes.Compare(tt.msg, msg2) != 0 { t.Errorf("json byte arrays differ. %d %v %v", i, tt.msg, msg2) } } } else { if err == nil { t.Errorf("Should fail: %d, %s", i, tt.msg) } } } return }