1// Copyright (c) 2014-2017 The btcsuite developers
2// Copyright (c) 2015-2017 The Decred developers
3// Use of this source code is governed by an ISC
4// license that can be found in the LICENSE file.
5
6package btcjson_test
7
8import (
9	"bytes"
10	"encoding/json"
11	"fmt"
12	"reflect"
13	"testing"
14
15	"github.com/btcsuite/btcd/btcjson"
16)
17
18// TestChainSvrWsNtfns tests all of the chain server websocket-specific
19// notifications marshal and unmarshal into valid results include handling of
20// optional fields being omitted in the marshalled command, while optional
21// fields with defaults have the default assigned on unmarshalled commands.
22func TestChainSvrWsNtfns(t *testing.T) {
23	t.Parallel()
24
25	tests := []struct {
26		name         string
27		newNtfn      func() (interface{}, error)
28		staticNtfn   func() interface{}
29		marshalled   string
30		unmarshalled interface{}
31	}{
32		{
33			name: "blockconnected",
34			newNtfn: func() (interface{}, error) {
35				return btcjson.NewCmd("blockconnected", "123", 100000, 123456789)
36			},
37			staticNtfn: func() interface{} {
38				return btcjson.NewBlockConnectedNtfn("123", 100000, 123456789)
39			},
40			marshalled: `{"jsonrpc":"1.0","method":"blockconnected","params":["123",100000,123456789],"id":null}`,
41			unmarshalled: &btcjson.BlockConnectedNtfn{
42				Hash:   "123",
43				Height: 100000,
44				Time:   123456789,
45			},
46		},
47		{
48			name: "blockdisconnected",
49			newNtfn: func() (interface{}, error) {
50				return btcjson.NewCmd("blockdisconnected", "123", 100000, 123456789)
51			},
52			staticNtfn: func() interface{} {
53				return btcjson.NewBlockDisconnectedNtfn("123", 100000, 123456789)
54			},
55			marshalled: `{"jsonrpc":"1.0","method":"blockdisconnected","params":["123",100000,123456789],"id":null}`,
56			unmarshalled: &btcjson.BlockDisconnectedNtfn{
57				Hash:   "123",
58				Height: 100000,
59				Time:   123456789,
60			},
61		},
62		{
63			name: "filteredblockconnected",
64			newNtfn: func() (interface{}, error) {
65				return btcjson.NewCmd("filteredblockconnected", 100000, "header", []string{"tx0", "tx1"})
66			},
67			staticNtfn: func() interface{} {
68				return btcjson.NewFilteredBlockConnectedNtfn(100000, "header", []string{"tx0", "tx1"})
69			},
70			marshalled: `{"jsonrpc":"1.0","method":"filteredblockconnected","params":[100000,"header",["tx0","tx1"]],"id":null}`,
71			unmarshalled: &btcjson.FilteredBlockConnectedNtfn{
72				Height:        100000,
73				Header:        "header",
74				SubscribedTxs: []string{"tx0", "tx1"},
75			},
76		},
77		{
78			name: "filteredblockdisconnected",
79			newNtfn: func() (interface{}, error) {
80				return btcjson.NewCmd("filteredblockdisconnected", 100000, "header")
81			},
82			staticNtfn: func() interface{} {
83				return btcjson.NewFilteredBlockDisconnectedNtfn(100000, "header")
84			},
85			marshalled: `{"jsonrpc":"1.0","method":"filteredblockdisconnected","params":[100000,"header"],"id":null}`,
86			unmarshalled: &btcjson.FilteredBlockDisconnectedNtfn{
87				Height: 100000,
88				Header: "header",
89			},
90		},
91		{
92			name: "recvtx",
93			newNtfn: func() (interface{}, error) {
94				return btcjson.NewCmd("recvtx", "001122", `{"height":100000,"hash":"123","index":0,"time":12345678}`)
95			},
96			staticNtfn: func() interface{} {
97				blockDetails := btcjson.BlockDetails{
98					Height: 100000,
99					Hash:   "123",
100					Index:  0,
101					Time:   12345678,
102				}
103				return btcjson.NewRecvTxNtfn("001122", &blockDetails)
104			},
105			marshalled: `{"jsonrpc":"1.0","method":"recvtx","params":["001122",{"height":100000,"hash":"123","index":0,"time":12345678}],"id":null}`,
106			unmarshalled: &btcjson.RecvTxNtfn{
107				HexTx: "001122",
108				Block: &btcjson.BlockDetails{
109					Height: 100000,
110					Hash:   "123",
111					Index:  0,
112					Time:   12345678,
113				},
114			},
115		},
116		{
117			name: "redeemingtx",
118			newNtfn: func() (interface{}, error) {
119				return btcjson.NewCmd("redeemingtx", "001122", `{"height":100000,"hash":"123","index":0,"time":12345678}`)
120			},
121			staticNtfn: func() interface{} {
122				blockDetails := btcjson.BlockDetails{
123					Height: 100000,
124					Hash:   "123",
125					Index:  0,
126					Time:   12345678,
127				}
128				return btcjson.NewRedeemingTxNtfn("001122", &blockDetails)
129			},
130			marshalled: `{"jsonrpc":"1.0","method":"redeemingtx","params":["001122",{"height":100000,"hash":"123","index":0,"time":12345678}],"id":null}`,
131			unmarshalled: &btcjson.RedeemingTxNtfn{
132				HexTx: "001122",
133				Block: &btcjson.BlockDetails{
134					Height: 100000,
135					Hash:   "123",
136					Index:  0,
137					Time:   12345678,
138				},
139			},
140		},
141		{
142			name: "rescanfinished",
143			newNtfn: func() (interface{}, error) {
144				return btcjson.NewCmd("rescanfinished", "123", 100000, 12345678)
145			},
146			staticNtfn: func() interface{} {
147				return btcjson.NewRescanFinishedNtfn("123", 100000, 12345678)
148			},
149			marshalled: `{"jsonrpc":"1.0","method":"rescanfinished","params":["123",100000,12345678],"id":null}`,
150			unmarshalled: &btcjson.RescanFinishedNtfn{
151				Hash:   "123",
152				Height: 100000,
153				Time:   12345678,
154			},
155		},
156		{
157			name: "rescanprogress",
158			newNtfn: func() (interface{}, error) {
159				return btcjson.NewCmd("rescanprogress", "123", 100000, 12345678)
160			},
161			staticNtfn: func() interface{} {
162				return btcjson.NewRescanProgressNtfn("123", 100000, 12345678)
163			},
164			marshalled: `{"jsonrpc":"1.0","method":"rescanprogress","params":["123",100000,12345678],"id":null}`,
165			unmarshalled: &btcjson.RescanProgressNtfn{
166				Hash:   "123",
167				Height: 100000,
168				Time:   12345678,
169			},
170		},
171		{
172			name: "txaccepted",
173			newNtfn: func() (interface{}, error) {
174				return btcjson.NewCmd("txaccepted", "123", 1.5)
175			},
176			staticNtfn: func() interface{} {
177				return btcjson.NewTxAcceptedNtfn("123", 1.5)
178			},
179			marshalled: `{"jsonrpc":"1.0","method":"txaccepted","params":["123",1.5],"id":null}`,
180			unmarshalled: &btcjson.TxAcceptedNtfn{
181				TxID:   "123",
182				Amount: 1.5,
183			},
184		},
185		{
186			name: "txacceptedverbose",
187			newNtfn: func() (interface{}, error) {
188				return btcjson.NewCmd("txacceptedverbose", `{"hex":"001122","txid":"123","version":1,"locktime":4294967295,"vin":null,"vout":null,"confirmations":0}`)
189			},
190			staticNtfn: func() interface{} {
191				txResult := btcjson.TxRawResult{
192					Hex:           "001122",
193					Txid:          "123",
194					Version:       1,
195					LockTime:      4294967295,
196					Vin:           nil,
197					Vout:          nil,
198					Confirmations: 0,
199				}
200				return btcjson.NewTxAcceptedVerboseNtfn(txResult)
201			},
202			marshalled: `{"jsonrpc":"1.0","method":"txacceptedverbose","params":[{"hex":"001122","txid":"123","version":1,"locktime":4294967295,"vin":null,"vout":null}],"id":null}`,
203			unmarshalled: &btcjson.TxAcceptedVerboseNtfn{
204				RawTx: btcjson.TxRawResult{
205					Hex:           "001122",
206					Txid:          "123",
207					Version:       1,
208					LockTime:      4294967295,
209					Vin:           nil,
210					Vout:          nil,
211					Confirmations: 0,
212				},
213			},
214		},
215		{
216			name: "relevanttxaccepted",
217			newNtfn: func() (interface{}, error) {
218				return btcjson.NewCmd("relevanttxaccepted", "001122")
219			},
220			staticNtfn: func() interface{} {
221				return btcjson.NewRelevantTxAcceptedNtfn("001122")
222			},
223			marshalled: `{"jsonrpc":"1.0","method":"relevanttxaccepted","params":["001122"],"id":null}`,
224			unmarshalled: &btcjson.RelevantTxAcceptedNtfn{
225				Transaction: "001122",
226			},
227		},
228	}
229
230	t.Logf("Running %d tests", len(tests))
231	for i, test := range tests {
232		// Marshal the notification as created by the new static
233		// creation function.  The ID is nil for notifications.
234		marshalled, err := btcjson.MarshalCmd(nil, test.staticNtfn())
235		if err != nil {
236			t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
237				test.name, err)
238			continue
239		}
240
241		if !bytes.Equal(marshalled, []byte(test.marshalled)) {
242			t.Errorf("Test #%d (%s) unexpected marshalled data - "+
243				"got %s, want %s", i, test.name, marshalled,
244				test.marshalled)
245			continue
246		}
247
248		// Ensure the notification is created without error via the
249		// generic new notification creation function.
250		cmd, err := test.newNtfn()
251		if err != nil {
252			t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
253				i, test.name, err)
254		}
255
256		// Marshal the notification as created by the generic new
257		// notification creation function.    The ID is nil for
258		// notifications.
259		marshalled, err = btcjson.MarshalCmd(nil, cmd)
260		if err != nil {
261			t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
262				test.name, err)
263			continue
264		}
265
266		if !bytes.Equal(marshalled, []byte(test.marshalled)) {
267			t.Errorf("Test #%d (%s) unexpected marshalled data - "+
268				"got %s, want %s", i, test.name, marshalled,
269				test.marshalled)
270			continue
271		}
272
273		var request btcjson.Request
274		if err := json.Unmarshal(marshalled, &request); err != nil {
275			t.Errorf("Test #%d (%s) unexpected error while "+
276				"unmarshalling JSON-RPC request: %v", i,
277				test.name, err)
278			continue
279		}
280
281		cmd, err = btcjson.UnmarshalCmd(&request)
282		if err != nil {
283			t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
284				test.name, err)
285			continue
286		}
287
288		if !reflect.DeepEqual(cmd, test.unmarshalled) {
289			t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
290				"- got %s, want %s", i, test.name,
291				fmt.Sprintf("(%T) %+[1]v", cmd),
292				fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
293			continue
294		}
295	}
296}
297