diff --git a/src/coin/mocks/ReadableTxn.go b/src/coin/mocks/ReadableTxn.go new file mode 100644 index 00000000..fbe33f1f --- /dev/null +++ b/src/coin/mocks/ReadableTxn.go @@ -0,0 +1,36 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import ( + api "github.com/SkycoinProject/skycoin/src/api" + mock "github.com/stretchr/testify/mock" +) + +// ReadableTxn is an autogenerated mock type for the ReadableTxn type +type ReadableTxn struct { + mock.Mock +} + +// ToCreatedTransaction provides a mock function with given fields: +func (_m *ReadableTxn) ToCreatedTransaction() (*api.CreatedTransaction, error) { + ret := _m.Called() + + var r0 *api.CreatedTransaction + if rf, ok := ret.Get(0).(func() *api.CreatedTransaction); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*api.CreatedTransaction) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/src/coin/skycoin/models/account.go b/src/coin/skycoin/models/account.go index ec278569..a97a57ea 100644 --- a/src/coin/skycoin/models/account.go +++ b/src/coin/skycoin/models/account.go @@ -187,7 +187,7 @@ func (wlt *RemoteWallet) ListPendingTransactions() (core.TransactionIterator, er } txns := make([]core.Transaction, 0) for _, ut := range response.Transactions { - txns = append(txns, &SkycoinPendingTransaction{Transaction: ut}) + txns = append(txns, &SkycoinPendingTransaction{Transaction: &ut}) } return NewSkycoinTransactionIterator(txns), nil } @@ -308,7 +308,7 @@ func (wlt *LocalWallet) ListPendingTransactions() (core.TransactionIterator, err } txns := make([]core.Transaction, 0) for _, ut := range response.Transactions { - txns = append(txns, &SkycoinPendingTransaction{Transaction: ut}) + txns = append(txns, &SkycoinPendingTransaction{Transaction: &ut}) } return NewSkycoinTransactionIterator(txns), nil } diff --git a/src/coin/skycoin/models/account_test.go b/src/coin/skycoin/models/account_test.go index 0156a145..40305d2d 100644 --- a/src/coin/skycoin/models/account_test.go +++ b/src/coin/skycoin/models/account_test.go @@ -58,10 +58,10 @@ func TestWalletListPendingTransactions(t *testing.T) { wlt := wallets.Value() account := wlt.GetCryptoAccount() global_mock.On(method, wlt.GetId()).Return(response, errors.New("failure")).Once() - txns, err := account.ListPendingTransactions() // nolint gosec + _, err := account.ListPendingTransactions() require.Error(t, err) global_mock.On(method, wlt.GetId()).Return(response, nil).Once() - txns, err = account.ListPendingTransactions() + txns, err := account.ListPendingTransactions() require.NoError(t, err) for txns.Next() { iter := NewSkycoinTransactionOutputIterator(txns.Value().GetOutputs()) @@ -95,10 +95,10 @@ func TestSkycoinAddressGetBalance(t *testing.T) { response.Confirmed = readable.Balance{Coins: uint64(42000000), Hours: uint64(200)} skyAddrs := addr.GetCryptoAccount() global_mock.On("Balance", []string{addr.String()}).Return(response, errors.New("failure")).Once() - val, err := skyAddrs.GetBalance(Sky) // nolint gosec + _, err = skyAddrs.GetBalance(Sky) require.Error(t, err) global_mock.On("Balance", []string{addr.String()}).Return(response, nil) - val, err = skyAddrs.GetBalance(Sky) + val, err := skyAddrs.GetBalance(Sky) require.NoError(t, err) require.Equal(t, val, uint64(42000000)) val, err = skyAddrs.GetBalance(CoinHour) @@ -216,22 +216,22 @@ func TestLocalWalletGetBalance(t *testing.T) { // wallet not found wlt := &LocalWallet{WalletDir: "./testdata", Id: "no_wallet.wlt"} - val, err := wlt.GetBalance(Sky) // nolint gosec + _, err := wlt.GetBalance(Sky) require.Error(t, err) // api interaction error wlt = &LocalWallet{WalletDir: "./testdata", Id: "test.wlt"} - val, err = wlt.GetBalance(Sky) // nolint gosec + _, err = wlt.GetBalance(Sky) require.Error(t, err) // invalid HeadOutputs wlt = &LocalWallet{WalletDir: "./testdata", Id: "test.wlt"} - val, err = wlt.GetBalance(Sky) // nolint gosec + _, err = wlt.GetBalance(Sky) require.Error(t, err) response.HeadOutputs = response.HeadOutputs[:len(response.HeadOutputs)-1] // all well - val, err = wlt.GetBalance(Sky) + val, err := wlt.GetBalance(Sky) require.NoError(t, err) require.Equal(t, uint64(84000000), val) val, err = wlt.GetBalance(CoinHour) @@ -239,7 +239,7 @@ func TestLocalWalletGetBalance(t *testing.T) { require.Equal(t, uint64(84), val) //invalid ticker - _, err = wlt.GetBalance("INVALID_TICKER") // nolint gosec + _, err = wlt.GetBalance("INVALID_TICKER") require.Error(t, err) } diff --git a/src/coin/skycoin/models/blockchain_test.go b/src/coin/skycoin/models/blockchain_test.go index 8f2a949b..60c9fb3d 100644 --- a/src/coin/skycoin/models/blockchain_test.go +++ b/src/coin/skycoin/models/blockchain_test.go @@ -154,10 +154,10 @@ func TestSkycoinBlockchainStatusGetLastBlock(t *testing.T) { status := &SkycoinBlockchain{CacheTime: 20} // api interaction error - block, err := status.GetLastBlock() // nolint gosec + _, err := status.GetLastBlock() require.Error(t, err) - block, err = status.GetLastBlock() + block, err := status.GetLastBlock() require.NoError(t, err) val, err2 := block.GetVersion() require.NoError(t, err2) diff --git a/src/coin/skycoin/models/coin.go b/src/coin/skycoin/models/coin.go index d16824da..5de9d906 100644 --- a/src/coin/skycoin/models/coin.go +++ b/src/coin/skycoin/models/coin.go @@ -25,7 +25,7 @@ var logCoin = logging.MustGetLogger("Skycoin coin") Implements Transaction interface */ type SkycoinPendingTransaction struct { - Transaction readable.UnconfirmedTransactionVerbose + Transaction *readable.UnconfirmedTransactionVerbose } func (txn *SkycoinPendingTransaction) SupportedAssets() []string { @@ -174,7 +174,7 @@ func (txn *SkycoinPendingTransaction) EncodeSkycoinTransaction() ([]byte, error) func verifyReadableTransaction(rTxn skytypes.ReadableTxn, checkSigned bool) error { var createdTxn *api.CreatedTransaction - if cTxn, err := rTxn.ToCreatedTransaction(); err != nil { + if cTxn, err := rTxn.ToCreatedTransaction(); err == nil { createdTxn = cTxn } else { return err diff --git a/src/coin/skycoin/models/coin_test.go b/src/coin/skycoin/models/coin_test.go index 8dcac029..18e46257 100644 --- a/src/coin/skycoin/models/coin_test.go +++ b/src/coin/skycoin/models/coin_test.go @@ -2,19 +2,26 @@ package skycoin import ( "encoding/hex" + goerrors "errors" + "fmt" + "strconv" "testing" + "time" "github.com/stretchr/testify/require" "github.com/SkycoinProject/skycoin/src/api" "github.com/SkycoinProject/skycoin/src/cipher" + "github.com/SkycoinProject/skycoin/src/coin" "github.com/SkycoinProject/skycoin/src/readable" - "github.com/SkycoinProject/skycoin/src/testutil" + "github.com/fibercrypto/fibercryptowallet/src/coin/mocks" + "github.com/fibercrypto/fibercryptowallet/src/coin/skycoin/skytypes" "github.com/fibercrypto/fibercryptowallet/src/core" + "github.com/fibercrypto/fibercryptowallet/src/errors" "github.com/fibercrypto/fibercryptowallet/src/util/requirethat" ) -func TestSkycoinTransactionGetStatus(t *testing.T) { +func TestTransactionGetStatus(t *testing.T) { global_mock.On("Transaction", "hash1").Return( &readable.TransactionWithStatus{ Status: readable.TransactionStatus{ @@ -22,7 +29,7 @@ func TestSkycoinTransactionGetStatus(t *testing.T) { }, }, nil, - ) + ).Once() global_mock.On("Transaction", "hash2").Return( &readable.TransactionWithStatus{ Status: readable.TransactionStatus{ @@ -30,56 +37,180 @@ func TestSkycoinTransactionGetStatus(t *testing.T) { }, }, nil, - ) + ).Once() + global_mock.On("Transaction", "hash3").Return(nil, goerrors.New("failure")).Once() - thx1 := &SkycoinTransaction{skyTxn: readable.TransactionVerbose{}} - thx1.skyTxn.Hash = "hash1" - thx2 := &SkycoinTransaction{skyTxn: readable.TransactionVerbose{}} - thx2.skyTxn.Hash = "hash2" + tests := []struct { + name string + txn core.Transaction + want core.TransactionStatus + }{ + { + name: "SkycoinCreatedTransaction-Created", + txn: new(SkycoinCreatedTransaction), + want: core.TXN_STATUS_CREATED, + }, + { + name: "SkycoinCreatedTransaction-Created", + txn: &SkycoinCreatedTransaction{ + skyTxn: api.CreatedTransaction{Length: 10}, + }, + want: core.TXN_STATUS_CREATED, + }, + { + name: "SkycoinTransaction-Confirmed", + txn: &SkycoinTransaction{ + skyTxn: readable.TransactionVerbose{ + BlockTransactionVerbose: readable.BlockTransactionVerbose{ + Hash: "hash1", + }, + }, + }, + want: core.TXN_STATUS_CONFIRMED, + }, + { + name: "SkycoinTransaction-Unconfirmed", + txn: &SkycoinTransaction{ + skyTxn: readable.TransactionVerbose{ + BlockTransactionVerbose: readable.BlockTransactionVerbose{ + Hash: "hash2", + }, + }, + }, + want: core.TXN_STATUS_PENDING, + }, + { + name: "SkycoinTransaction-ApiError", + txn: &SkycoinTransaction{ + skyTxn: readable.TransactionVerbose{ + BlockTransactionVerbose: readable.BlockTransactionVerbose{ + Hash: "hash3", + }, + }, + }, + want: core.TXN_STATUS_CREATED, + }, + { + name: "SkycoinPendingTransaction", + txn: new(SkycoinPendingTransaction), + want: core.TXN_STATUS_PENDING, + }, + { + name: "SkycoinUnjectedTransaction", + txn: new(SkycoinUninjectedTransaction), + want: core.TXN_STATUS_CREATED, + }, + } - require.Equal(t, thx1.GetStatus(), core.TXN_STATUS_CONFIRMED) - require.Equal(t, thx2.GetStatus(), core.TXN_STATUS_PENDING) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, tt.want, tt.txn.GetStatus()) + }) + } } func TestSkycoinTransactionGetInputs(t *testing.T) { - //set correct return value + skyAmount := uint64(20000000) + chAmount := uint64(20) + response := &readable.TransactionWithStatusVerbose{ - Transaction: readable.TransactionVerbose{}, + Transaction: readable.TransactionVerbose{ + BlockTransactionVerbose: readable.BlockTransactionVerbose{ + In: []readable.TransactionInput{ + readable.TransactionInput{ + Hash: "I1", + Coins: "20", + Hours: chAmount, + }, + readable.TransactionInput{ + Hash: "I2", + Coins: "20", + Hours: uint64(20), + }, + }, + }, + }, } - response.Transaction.In = []readable.TransactionInput{ - readable.TransactionInput{ - Hash: "I1", - Coins: "20", - Hours: uint64(20), - CalculatedHours: uint64(20), + global_mock.On("TransactionVerbose", "hash1").Return(nil, goerrors.New("failure")).Once() + global_mock.On("TransactionVerbose", "hash1").Return(response, nil).Once() + + st := new(SkycoinTransaction) + st.skyTxn.Hash = "hash1" + + tests := []struct { + name string + txn core.Transaction + ids []string + wantNil bool + }{ + { + name: "SkycoinCreatedTransaction", + txn: &SkycoinCreatedTransaction{ + skyTxn: api.CreatedTransaction{ + In: []api.CreatedTransactionInput{ + api.CreatedTransactionInput{ + UxID: "out1", Coins: "20", Hours: "20", + }, + api.CreatedTransactionInput{ + UxID: "out2", Coins: "20", Hours: "20", + }, + api.CreatedTransactionInput{ + UxID: "out3", Coins: "20", Hours: "20", + }, + }, + }, + }, + ids: []string{"out1", "out2", "out3"}, }, - readable.TransactionInput{ - Hash: "I2", - Coins: "20", - Hours: uint64(20), - CalculatedHours: uint64(20), + { + name: "SkycoinTransaction-ApiError", + txn: st, + wantNil: true, + }, + { + name: "SkycoinTransaction", + txn: st, + ids: []string{"I1", "I2"}, + }, + { + name: "SkycoinTransaction-InputsSaved", + txn: st, + ids: []string{"I1", "I2"}, }, } - global_mock.On("TransactionVerbose", "hash1").Return(response, nil) - - thx1 := &SkycoinTransaction{skyTxn: readable.TransactionVerbose{}} - thx1.skyTxn.Hash = "hash1" - inputs := thx1.GetInputs() - require.Equal(t, inputs[0].GetId(), "I1") - require.Equal(t, inputs[1].GetId(), "I2") - it := NewSkycoinTransactioninputIterator(inputs) - for it.Next() { - sky, err := it.Value().GetCoins(Sky) - require.NoError(t, err) - require.Equal(t, sky, uint64(20000000)) - hours, err1 := it.Value().GetCoins(CoinHour) - require.NoError(t, err1) - require.Equal(t, hours, uint64(20)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + inputs := tt.txn.GetInputs() + if tt.wantNil { + require.Nil(t, inputs) + } else { + ids := make([]string, 0) + it := NewSkycoinTransactioninputIterator(inputs) + for it.Next() { + input := it.Value() + ids = append(ids, input.GetId()) + sky, err := input.GetCoins(Sky) + require.NoError(t, err) + require.Equal(t, skyAmount, sky) + hours, err1 := input.GetCoins(CoinHour) + require.NoError(t, err1) + require.Equal(t, chAmount, hours) + } + requirethat.ElementsMatch(t, tt.ids, ids) + } + }) } } func TestSkycoinTransactionInputGetSpentOutput(t *testing.T) { + CleanGlobalMock() + + input := &SkycoinTransactionInput{skyIn: readable.TransactionInput{Hash: "in1"}} + global_mock.On("UxOut", "in1").Return(nil, goerrors.New("failure")).Once() + output := input.GetSpentOutput() + require.Nil(t, output) + global_mock.On("UxOut", "in1").Return( &readable.SpentOutput{ OwnerAddress: "2JJ8pgq8EDAnrzf9xxBJapE2qkYLefW4uF8", @@ -88,10 +219,9 @@ func TestSkycoinTransactionInputGetSpentOutput(t *testing.T) { Uxid: "out1", }, nil, - ) + ).Once() - input := &SkycoinTransactionInput{skyIn: readable.TransactionInput{Hash: "in1"}} - output := input.GetSpentOutput() + output = input.GetSpentOutput() t.Logf("%#v", output) require.Equal(t, output.GetId(), "out1") @@ -104,25 +234,33 @@ func TestSkycoinTransactionInputGetSpentOutput(t *testing.T) { require.Equal(t, hours, uint64(20)) } -func TestSkycoinTransactionOutputIsSpent(t *testing.T) { - global_mock.On("UxOut", "out1").Return( - &readable.SpentOutput{ - SpentTxnID: "0000000000000000000000000000000000000000000000000000000000000000", +func TestIsSpent(t *testing.T) { + badID := "0000000000000000000000000000000000000000000000000000000000000000" + tests := []struct { + name string + output core.TransactionOutput + }{ + { + name: "SkycoinTransactionOutput", + output: &SkycoinTransactionOutput{skyOut: readable.TransactionOutput{Hash: "out"}}, }, - nil, - ) - global_mock.On("UxOut", "out2").Return( - &readable.SpentOutput{ - SpentTxnID: "0", + { + name: "SkycoinCreatedTransactionOutput", + output: &SkycoinCreatedTransactionOutput{skyOut: api.CreatedTransactionOutput{UxID: "out"}}, }, - nil, - ) - - output1 := &SkycoinTransactionOutput{skyOut: readable.TransactionOutput{Hash: "out1"}} - output2 := &SkycoinTransactionOutput{skyOut: readable.TransactionOutput{Hash: "out2"}} + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + global_mock.On("UxOut", "out").Return(nil, goerrors.New("failure")).Once() + global_mock.On("UxOut", "out").Return(&readable.SpentOutput{SpentTxnID: badID}, nil).Once() + global_mock.On("UxOut", "out").Return(&readable.SpentOutput{SpentTxnID: "42"}, nil).Once() - require.Equal(t, output1.IsSpent(), false) - require.Equal(t, output2.IsSpent(), true) + require.Equal(t, tt.output.IsSpent(), false) + require.Equal(t, tt.output.IsSpent(), false) + require.Equal(t, tt.output.IsSpent(), true) + require.Equal(t, tt.output.IsSpent(), true) + }) + } } func TestUninjectedTransactionSignedUnsigned(t *testing.T) { @@ -167,6 +305,7 @@ func TestSkycoinUninjectedTransactionGetInputs(t *testing.T) { addr := makeAddress() + global_mock.On("UxOut", h.String()).Return(nil, goerrors.New("failure")).Once() global_mock.On("UxOut", h.String()).Return( &readable.SpentOutput{ OwnerAddress: addr.String(), @@ -179,6 +318,9 @@ func TestSkycoinUninjectedTransactionGetInputs(t *testing.T) { ) tiList := ut.GetInputs() + require.Nil(t, tiList) + + tiList = ut.GetInputs() ti := tiList[0] require.Equal(t, 1, len(tiList)) sky, err := ti.GetCoins(Sky) @@ -193,29 +335,6 @@ func TestSkycoinUninjectedTransactionGetInputs(t *testing.T) { require.Equal(t, uint64(0), val) } -func TestSkycoinCreatedTransactionOutputIsSpent(t *testing.T) { - global_mock.On("UxOut", "out1").Return( - &readable.SpentOutput{ - SpentTxnID: "0000000000000000000000000000000000000000000000000000000000000000", - }, - nil, - ) - global_mock.On("UxOut", "out2").Return( - &readable.SpentOutput{ - SpentTxnID: "0", - }, - nil, - ) - - output1 := &SkycoinCreatedTransactionOutput{skyOut: api.CreatedTransactionOutput{UxID: "out1"}} - output2 := &SkycoinCreatedTransactionOutput{skyOut: api.CreatedTransactionOutput{UxID: "out2"}} - - require.Equal(t, output1.IsSpent(), false) - require.Equal(t, output2.IsSpent(), true) - require.Equal(t, output2.IsSpent(), true) - -} - func TestSupportedAssets(t *testing.T) { pendTxn := new(SkycoinPendingTransaction) assets := pendTxn.SupportedAssets() @@ -250,78 +369,1551 @@ func TestSupportedAssets(t *testing.T) { requirethat.ElementsMatch(t, []string{Sky, CoinHour, CalculatedHour}, assets) } -func TestPendingTxnStatus(t *testing.T) { - pendTxn := new(SkycoinPendingTransaction) - require.Equal(t, core.TXN_STATUS_PENDING, pendTxn.GetStatus()) +func TestTransactionsGetTimestamp(t *testing.T) { + cur := time.Now() + tests := []struct { + name string + txn core.Transaction + want uint64 + }{ + { + name: "SkycoinUninjectTransaction", + txn: new(SkycoinUninjectedTransaction), + want: 0, + }, + { + name: "SkycoinCreatedTransaction-Created", + txn: &SkycoinCreatedTransaction{ + skyTxn: api.CreatedTransaction{Length: 10}, + }, + want: 0, + }, + { + name: "SkycoinTransaction", + txn: &SkycoinTransaction{ + skyTxn: readable.TransactionVerbose{ + Timestamp: 42, + }, + }, + want: 42, + }, + { + name: "SkycoinPendingTransaction", + txn: &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Received: cur, + }, + }, + want: uint64(cur.Unix()), + }, + { + name: "SkycoinCreatedTransaction", + txn: new(SkycoinCreatedTransaction), + want: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require.Equal(t, core.Timestamp(tt.want), tt.txn.GetTimestamp()) + }) + } } -func TestPendingTxnFee(t *testing.T) { - pendTxn := new(SkycoinPendingTransaction) +func TestPendingTxnGetInputs(t *testing.T) { + hashes := make([]string, 0) + for i := 0; i < 10; i++ { + hashes = append(hashes, fmt.Sprintf("hash%d", i)) + } + sTxn := new(SkycoinPendingTransaction) + inputs := make([]readable.TransactionInput, 0) + for _, hash := range hashes { + inputs = append(inputs, readable.TransactionInput{Hash: hash}) + } + sTxn.Transaction = &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + In: inputs, + }, + } + inHashes := make([]string, 0) + for _, input := range sTxn.GetInputs() { + inHashes = append(inHashes, input.GetId()) + } + requirethat.ElementsMatch(t, hashes, inHashes) +} - fee, err := pendTxn.ComputeFee(Sky) - require.NoError(t, err) - require.Equal(t, uint64(0), fee) +func TestPendingTxnGetOutputs(t *testing.T) { + hashes := make([]string, 0) + for i := 0; i < 10; i++ { + hashes = append(hashes, fmt.Sprintf("hash%d", i)) + } + sTxn := new(SkycoinPendingTransaction) + outputs := make([]readable.TransactionOutput, 0) + for _, hash := range hashes { + outputs = append(outputs, readable.TransactionOutput{Hash: hash}) + } + sTxn.Transaction = &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + Out: outputs, + }, + } + outHashes := make([]string, 0) + for _, output := range sTxn.GetOutputs() { + outHashes = append(outHashes, output.GetId()) + } + requirethat.ElementsMatch(t, hashes, outHashes) +} - _, err = pendTxn.ComputeFee(CalculatedHour) - testutil.RequireError(t, err, "Feature not implemented") +func Test_newCreatedTransactionOutput(t *testing.T) { + tests := []struct { + uxID string + address string + coins string + hours string + }{ + {uxID: "uxId1", address: "addr1", coins: "coins1", hours: "hours1"}, + {uxID: "uxId2", address: "addr2", coins: "coins2", hours: "hours2"}, + {uxID: "uxId3", address: "addr3", coins: "coins3", hours: "hours3"}, + {uxID: "uxId4", address: "addr4", coins: "coins4", hours: "hours4"}, + } - _, err = pendTxn.ComputeFee("NOCOINATALL") - testutil.RequireError(t, err, "Invalid ticker") + for i, tt := range tests { + t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) { + txn := newCreatedTransactionOutput(tt.uxID, tt.address, tt.coins, tt.hours) + require.Equal(t, tt.uxID, txn.UxID) + require.Equal(t, tt.address, txn.Address) + require.Equal(t, tt.coins, txn.Coins) + require.Equal(t, tt.hours, txn.Hours) + }) + } } -func TestUninjectedTxnTimestamp(t *testing.T) { - coreTxn := new(SkycoinUninjectedTransaction) - require.Equal(t, core.Timestamp(0), coreTxn.GetTimestamp()) +func Test_newCreatedTransactionInput(t *testing.T) { + tests := []struct { + uxID string + address string + coins string + hours string + calculatedHours string + time uint64 + block uint64 + txID string + }{ + { + uxID: "uxId1", + address: "addr1", + coins: "coins1", + hours: "hours1", + calculatedHours: "cH1", + txID: "id1", + time: 1, + block: 1, + }, + { + uxID: "uxId2", + address: "addr2", + coins: "coins2", + hours: "hours2", + calculatedHours: "cH2", + txID: "id2", + time: 2, + block: 2, + }, + { + uxID: "uxId3", + address: "addr3", + coins: "coins3", + hours: "hours3", + calculatedHours: "cH3", + txID: "id3", + time: 3, + block: 3, + }, + { + uxID: "uxId4", + address: "addr4", + coins: "coins4", + hours: "hours4", + calculatedHours: "cH4", + txID: "id4", + time: 4, + block: 4, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) { + txn := newCreatedTransactionInput( + tt.uxID, tt.address, tt.coins, tt.hours, tt.calculatedHours, tt.time, tt.block, tt.txID, + ) + require.Equal(t, tt.uxID, txn.UxID) + require.Equal(t, tt.address, txn.Address) + require.Equal(t, tt.coins, txn.Coins) + require.Equal(t, tt.hours, txn.Hours) + require.Equal(t, tt.calculatedHours, txn.CalculatedHours) + require.Equal(t, tt.time, txn.Time) + require.Equal(t, tt.block, txn.Block) + require.Equal(t, tt.txID, txn.TxID) + }) + } } -func TestUninjectedTxnStatus(t *testing.T) { - coreTxn := new(SkycoinUninjectedTransaction) - require.Equal(t, core.TXN_STATUS_CREATED, coreTxn.GetStatus()) +func Test_newCreatedTransaction(t *testing.T) { + tests := []struct { + length uint32 + txnType uint8 + txID string + innerHash string + fee string + ins []api.CreatedTransactionInput + outs []api.CreatedTransactionOutput + sigs []string + }{ + { + length: 1, + txnType: 1, + txID: "txID1", + innerHash: "hash1", + fee: "fee1", + ins: []api.CreatedTransactionInput{ + api.CreatedTransactionInput{UxID: "UxID1"}, + }, + outs: []api.CreatedTransactionOutput{ + api.CreatedTransactionOutput{UxID: "UxID1"}, + }, + sigs: []string{"first1", "second1"}, + }, + { + length: 2, + txnType: 2, + txID: "txID2", + innerHash: "hash2", + fee: "fee2", + ins: []api.CreatedTransactionInput{ + api.CreatedTransactionInput{UxID: "UxID2"}, + }, + outs: []api.CreatedTransactionOutput{ + api.CreatedTransactionOutput{UxID: "UxID2"}, + }, + sigs: []string{"first2", "second2"}, + }, + { + length: 3, + txnType: 3, + txID: "txID3", + innerHash: "hash3", + fee: "fee3", + ins: []api.CreatedTransactionInput{ + api.CreatedTransactionInput{UxID: "UxID3"}, + }, + outs: []api.CreatedTransactionOutput{ + api.CreatedTransactionOutput{UxID: "UxID3"}, + }, + sigs: []string{"first3", "second3"}, + }, + { + length: 4, + txnType: 4, + txID: "txID4", + innerHash: "hash4", + fee: "fee4", + ins: []api.CreatedTransactionInput{ + api.CreatedTransactionInput{UxID: "UxID4"}, + }, + outs: []api.CreatedTransactionOutput{ + api.CreatedTransactionOutput{UxID: "UxID4"}, + }, + sigs: []string{"first4", "second4"}, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) { + txn := newCreatedTransaction( + tt.length, + tt.txnType, + tt.txID, + tt.innerHash, + tt.fee, + tt.ins, + tt.outs, + tt.sigs, + ) + require.Equal(t, tt.length, txn.Length) + require.Equal(t, tt.txnType, txn.Type) + require.Equal(t, tt.txID, txn.TxID) + require.Equal(t, tt.innerHash, txn.InnerHash) + require.Equal(t, tt.fee, txn.Fee) + requirethat.ElementsMatch(t, tt.ins, txn.In) + requirethat.ElementsMatch(t, tt.outs, txn.Out) + requirethat.ElementsMatch(t, tt.sigs, txn.Sigs) + }) + } } -func TestUninjectedTxnFee(t *testing.T) { - coreTxn := new(SkycoinUninjectedTransaction) +func Test_blockTxnToCreatedTxn(t *testing.T) { + tests := []struct { + Address string + Coins string + Hours uint64 + CalculatedHours uint64 + Time uint64 + Block uint64 + Hash string + Length uint32 + Type uint8 + InnerHash string + Fee uint64 + sigs []string + }{ + { + Address: "addr1", + Coins: "coins1", + Hours: 1, + CalculatedHours: 1, + Time: 1, + Hash: "hash1", + Length: 1, + Type: 1, + InnerHash: "inner1", + Fee: 1, + sigs: []string{"first1", "second1"}, + }, + { + Address: "addr2", + Coins: "coins2", + Hours: 2, + CalculatedHours: 2, + Time: 2, + Hash: "hash2", + Length: 2, + Type: 2, + InnerHash: "inner2", + Fee: 2, + sigs: []string{"first2", "second2"}, + }, + { + Address: "addr3", + Coins: "coins3", + Hours: 3, + CalculatedHours: 3, + Time: 3, + Hash: "hash3", + Length: 3, + Type: 3, + InnerHash: "inner3", + Fee: 3, + sigs: []string{"first3", "second3"}, + }, + { + Address: "addr4", + Coins: "coins4", + Hours: 4, + CalculatedHours: 4, + Time: 4, + Hash: "hash4", + Length: 4, + Type: 4, + InnerHash: "inner4", + Fee: 4, + sigs: []string{"first4", "second4"}, + }, + } - fee, err := coreTxn.ComputeFee(Sky) - require.NoError(t, err) - require.Equal(t, uint64(0), fee) + for i, tt := range tests { + t.Run(fmt.Sprintf("case%d", i), func(*testing.T) { + block := readable.BlockTransactionVerbose{ + Length: tt.Length, + Type: tt.Type, + Hash: tt.Hash, + InnerHash: tt.InnerHash, + Fee: tt.Fee, + In: []readable.TransactionInput{ + readable.TransactionInput{ + Hash: tt.Hash, + Address: tt.Address, + Coins: tt.Coins, + Hours: tt.Hours, + CalculatedHours: tt.CalculatedHours, + }, + }, + Out: []readable.TransactionOutput{ + readable.TransactionOutput{ + Hash: tt.Hash, + Address: tt.Address, + Coins: tt.Coins, + Hours: tt.Hours, + }, + }, + Sigs: tt.sigs, + } + txn, err := blockTxnToCreatedTxn(block, tt.Time) + require.NoError(t, err) + require.Equal(t, tt.Length, txn.Length) + require.Equal(t, tt.Type, txn.Type) + require.Equal(t, tt.Hash, txn.TxID) + require.Equal(t, tt.InnerHash, txn.InnerHash) + require.Equal(t, fmt.Sprint(tt.Fee), txn.Fee) + require.Equal(t, 1, len(txn.In)) + require.Equal(t, 1, len(txn.Out)) + requirethat.ElementsMatch(t, + []api.CreatedTransactionInput{ + newCreatedTransactionInput( + tt.Hash, + tt.Address, + tt.Coins, + fmt.Sprint(tt.Hours), + fmt.Sprint(tt.CalculatedHours), + tt.Time, + tt.Block, + tt.Hash, + ), + }, + txn.In, + ) + requirethat.ElementsMatch(t, + []api.CreatedTransactionOutput{ + newCreatedTransactionOutput( + tt.Hash, tt.Address, tt.Coins, fmt.Sprint(tt.Hours), + ), + }, + txn.Out, + ) + requirethat.ElementsMatch(t, tt.sigs, txn.Sigs) + }) + } +} + +func TestPendingTxnToCreatedTransaction(t *testing.T) { + for i := 0; i < 5; i++ { + t.Run(fmt.Sprintf("case%d", i), func(t *testing.T) { + txn := &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + Hash: fmt.Sprintf("hash%d", i), + }, + Announced: time.Now(), + }, + } + expected, err := blockTxnToCreatedTxn( + txn.Transaction.Transaction, + uint64(txn.Transaction.Announced.UnixNano()), + ) + require.NoError(t, err) + created, err1 := txn.ToCreatedTransaction() + require.NoError(t, err1) + require.Equal(t, expected, created) + }) + } +} + +func Test_serializeCreatedTransaction(t *testing.T) { + mockTxn := new(mocks.ReadableTxn) + id := "0000000000000000000000000000000000000000000000000000000000000000" + created := &api.CreatedTransaction{ + TxID: "78877fa898f0b4c45c9c33ae941e40617ad7c8657a307db62bc5691f92f4f60e", + InnerHash: "No-match", + } + mockTxn.On("ToCreatedTransaction").Return(nil, goerrors.New("failure")).Once() + mockTxn.On("ToCreatedTransaction").Return(created, nil) + + _, err := serializeCreatedTransaction(mockTxn) + require.Error(t, err) - _, err = coreTxn.ComputeFee(CalculatedHour) - testutil.RequireError(t, err, "Feature not implemented") + _, err = serializeCreatedTransaction(mockTxn) + require.Error(t, err) - coreTxn.fee = 64 - fee, err = coreTxn.ComputeFee(CoinHour) + created.InnerHash = id + ser, err := serializeCreatedTransaction(mockTxn) + require.NoError(t, err) + txn, err := created.ToTransaction() + require.NoError(t, err) + expected, err := txn.Serialize() require.NoError(t, err) - require.Equal(t, uint64(64), fee) + require.Equal(t, expected, ser) +} - _, err = coreTxn.ComputeFee("NOCOINATALL") - testutil.RequireError(t, err, "Invalid ticker") +func TestSkycoinPendingTransactionEncodeSkycoinTransaction(t *testing.T) { + date, err := time.Parse(time.RFC3339, "2012-11-01T22:08:41+00:00") + require.NoError(t, err) + sTxn := &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + InnerHash: "0000000000000000000000000000000000000000000000000000000000000000", + Hash: "78877fa898f0b4c45c9c33ae941e40617ad7c8657a307db62bc5691f92f4f60e", + }, + Announced: date, + }, + } + ser, err := sTxn.EncodeSkycoinTransaction() + require.NoError(t, err) + exp, err := serializeCreatedTransaction(sTxn) + require.NoError(t, err) + require.Equal(t, exp, ser) } -func TestSkycoinTxnFee(t *testing.T) { - skyTxn := new(SkycoinTransaction) +func Test_verifyReadableTransaction(t *testing.T) { + mockTxn := new(mocks.ReadableTxn) + id := "0000000000000000000000000000000000000000000000000000000000000000" + created := &api.CreatedTransaction{ + TxID: "78877fa898f0b4c45c9c33ae941e40617ad7c8657a307db62bc5691f92f4f60e", + InnerHash: "No-match", + } + mockTxn.On("ToCreatedTransaction").Return(nil, goerrors.New("failure")).Once() + mockTxn.On("ToCreatedTransaction").Return(created, nil) + + // ReadableTxn error + err := verifyReadableTransaction(mockTxn, false) + require.Error(t, err) - fee, err := skyTxn.ComputeFee(Sky) + // transaction hash error + err = verifyReadableTransaction(mockTxn, false) + require.Error(t, err) + + // Verify fail + created.InnerHash = id + err = verifyReadableTransaction(mockTxn, false) + require.Error(t, err) + + // VerifyUnsigned fail + err = verifyReadableTransaction(mockTxn, true) + require.Error(t, err) + + //TODO: add a case that not raise an error +} + +func TestPendingTxnVerifySignature(t *testing.T) { + tests := []struct { + name string + sTxn *SkycoinPendingTransaction + }{ + { + name: "empty transaction", + sTxn: &SkycoinPendingTransaction{Transaction: new(readable.UnconfirmedTransactionVerbose)}, + }, + { + name: "cero outputs", + sTxn: &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + In: []readable.TransactionInput{readable.TransactionInput{}}, + }, + }, + }, + }, + //TODO: add valid tests + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if !tt.sTxn.Transaction.IsValid { + require.Error(t, tt.sTxn.VerifySigned()) + require.Error(t, tt.sTxn.VerifyUnsigned()) + tt.sTxn.Transaction.IsValid = true + } + require.Equal(t, verifyReadableTransaction(tt.sTxn, false), tt.sTxn.VerifyUnsigned()) + require.Equal(t, verifyReadableTransaction(tt.sTxn, true), tt.sTxn.VerifySigned()) + }) + } +} + +func Test_checkFullySigned(t *testing.T) { + mockTxn := new(mocks.ReadableTxn) + id := "0000000000000000000000000000000000000000000000000000000000000000" + created := &api.CreatedTransaction{ + TxID: "78877fa898f0b4c45c9c33ae941e40617ad7c8657a307db62bc5691f92f4f60e", + InnerHash: "No-match", + } + mockTxn.On("ToCreatedTransaction").Return(nil, goerrors.New("failure")).Once() + mockTxn.On("ToCreatedTransaction").Return(created, nil) + + _, err := checkFullySigned(mockTxn) + require.Error(t, err) + + _, err = checkFullySigned(mockTxn) + require.Error(t, err) + + // false + created.InnerHash = id + val, err := checkFullySigned(mockTxn) require.NoError(t, err) - require.Equal(t, uint64(0), fee) + require.False(t, val) + + //TODO: add valid test +} - _, err = skyTxn.ComputeFee(CalculatedHour) - testutil.RequireError(t, err, "Feature not implemented") +func TestTransactionIsFullySigned(t *testing.T) { + type skyTxn interface { + skytypes.ReadableTxn + core.Transaction + } + tests := []struct { + name string + txn skyTxn + }{ + { + name: "empty PendingTxn", + txn: &SkycoinPendingTransaction{Transaction: new(readable.UnconfirmedTransactionVerbose)}, + }, + { + name: "cero outputs in PendingTxn", + txn: &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + In: []readable.TransactionInput{readable.TransactionInput{}}, + }, + }, + }, + }, + { + name: "empty Txn", + txn: new(SkycoinTransaction), + }, + //TODO: add valid tests + } - _, err = skyTxn.ComputeFee("NOCOINATALL") - testutil.RequireError(t, err, "Invalid ticker") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fully, err := checkFullySigned(tt.txn) + fully1, err1 := tt.txn.IsFullySigned() + require.Equal(t, err, err1) + require.Equal(t, fully, fully1) + }) + } } -func TestSkycoinCreatedTxnFee(t *testing.T) { - cTxn := new(SkycoinCreatedTransaction) +func TestSkycoinUninjectedTransactionGetOutputs(t *testing.T) { + addr := makeAddress() + addr1 := makeAddress() + tests := []struct { + name string + ujTxn *SkycoinUninjectedTransaction + addrs []string + wantNil bool + }{ + { + name: "empty transaction", + ujTxn: &SkycoinUninjectedTransaction{ + outputs: []core.TransactionOutput{}, + }, + addrs: make([]string, 0), + }, + { + name: "nil outputs", + ujTxn: &SkycoinUninjectedTransaction{ + txn: &coin.Transaction{}, + }, + addrs: make([]string, 0), + }, + { + name: "incorrect output", + ujTxn: &SkycoinUninjectedTransaction{ + txn: &coin.Transaction{ + Out: []coin.TransactionOutput{ + coin.TransactionOutput{ + Coins: 9223372036854775808, + }, + }, + }, + }, + wantNil: true, + }, + { + name: "some outputs", + ujTxn: &SkycoinUninjectedTransaction{ + txn: &coin.Transaction{ + Out: []coin.TransactionOutput{ + coin.TransactionOutput{ + Address: addr, + }, + coin.TransactionOutput{ + Address: addr1, + }, + }, + }, + }, + addrs: []string{addr.String(), addr1.String()}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + outputs := tt.ujTxn.GetOutputs() + if tt.wantNil { + require.Nil(t, outputs) + } else { + require.Equal(t, len(tt.addrs), len(outputs)) + hashes := make([]string, len(tt.addrs)) + for i, out := range outputs { + hashes[i] = out.GetAddress().String() + } + requirethat.ElementsMatch(t, tt.addrs, hashes) + } + }) + } +} + +func TestGetId(t *testing.T) { + type ObjectWithID interface { + GetId() string + } + tests := []struct { + obj ObjectWithID + want string + }{ + { + obj: &SkycoinCreatedTransactionOutput{ + skyOut: api.CreatedTransactionOutput{ + UxID: "uxid", + }, + }, + want: "uxid", + }, + { + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + UxID: "uxid", + }, + }, + want: "uxid", + }, + { + obj: &SkycoinUninjectedTransaction{ + txn: new(coin.Transaction), + }, + want: "78877fa898f0b4c45c9c33ae941e40617ad7c8657a307db62bc5691f92f4f60e", + }, + { + obj: &SkycoinUninjectedTransaction{ + txn: &coin.Transaction{ + Length: 5, + }, + }, + want: "03e228f59704bc30de09f76fe9db0981ca77b6421aaa997e227d39bcc317174e", + }, + { + obj: &SkycoinUninjectedTransaction{ + txn: &coin.Transaction{ + Type: 2, + }, + }, + want: "bb5e828965130b51e627725f6fea3247124da6799d28ccac81c247fd78b34621", + }, + { + obj: &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + Hash: "hash1", + }, + }, + }, + want: "hash1", + }, + { + obj: &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + Hash: "hash2", + }, + }, + }, + want: "hash2", + }, + { + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + UxID: "uxid1", + }, + }, + want: "uxid1", + }, + { + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + UxID: "uxid2", + }, + }, + want: "uxid2", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("ID%d", i), func(t *testing.T) { + require.Equal(t, tt.want, tt.obj.GetId()) + }) + } +} + +func TestSkycoinTransactionGetOutputs(t *testing.T) { + skyAmount := uint64(20000000) + chAmount := uint64(20) + + st := new(SkycoinTransaction) + outs := []readable.TransactionOutput{ + readable.TransactionOutput{ + Hash: "O1", + Coins: "20", + Hours: chAmount, + }, + readable.TransactionOutput{ + Hash: "O2", + Coins: "20", + Hours: chAmount, + }, + } + st.skyTxn.Out = outs + + tests := []struct { + name string + txn core.Transaction + ids []string + wantNil bool + }{ + { + name: "SkycoinCreatedTransaction", + txn: &SkycoinCreatedTransaction{ + skyTxn: api.CreatedTransaction{ + Out: []api.CreatedTransactionOutput{ + api.CreatedTransactionOutput{ + UxID: "out1", Coins: "20", Hours: "20", + }, + api.CreatedTransactionOutput{ + UxID: "out2", Coins: "20", Hours: "20", + }, + api.CreatedTransactionOutput{ + UxID: "out3", Coins: "20", Hours: "20", + }, + }, + }, + }, + ids: []string{"out1", "out2", "out3"}, + }, + { + name: "SkycoinTransaction1", + txn: st, + ids: []string{"O1", "O2"}, + }, + { + name: "SkycoinTransaction1-OutputsSaved", + txn: st, + ids: []string{"O1", "O2"}, + }, + { + name: "SkycoinTransaction1-OutputsSaved", + txn: &SkycoinTransaction{ + skyTxn: readable.TransactionVerbose{ + BlockTransactionVerbose: readable.BlockTransactionVerbose{ + Out: outs[:1], + }, + }, + }, + ids: []string{"O1"}, + }, + { + name: "SkycoinTransaction1-NoOutputs", + txn: new(SkycoinTransaction), + ids: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + outputs := tt.txn.GetOutputs() + if tt.wantNil { + require.Nil(t, outputs) + } else { + ids := make([]string, 0) + it := NewSkycoinTransactionOutputIterator(outputs) + for it.Next() { + output := it.Value() + ids = append(ids, output.GetId()) + sky, err := output.GetCoins(Sky) + require.NoError(t, err) + require.Equal(t, skyAmount, sky) + hours, err1 := output.GetCoins(CoinHour) + require.NoError(t, err1) + require.Equal(t, chAmount, hours) + } + requirethat.ElementsMatch(t, tt.ids, ids) + } + }) + } +} + +func TestTransactionComputeFee(t *testing.T) { + expectedError := func(ticker string) error { + if ticker == CalculatedHour { + return errors.ErrNotImplemented + } + if ticker == Sky || ticker == CoinHour { + return nil + } + return errors.ErrInvalidAltcoinTicker + } + expectedAmount := func(ticker string, amount uint64) uint64 { + if ticker == CoinHour { + return amount + } + return 0 + } + pendingTxnWithFee := func(ticker string, fee string) (core.Transaction, uint64, bool, error) { + val, err := strconv.ParseUint(fee, 10, 64) + if err != nil { + return nil, 0, false, nil + } + return &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + Fee: val, + }, + }, + }, expectedAmount(ticker, val), true, expectedError(ticker) + } + uninjectedTxnWithFee := func(ticker string, fee string) (core.Transaction, uint64, bool, error) { + val, err := strconv.ParseUint(fee, 10, 64) + if err != nil { + return nil, 0, false, nil + } + return &SkycoinUninjectedTransaction{fee: val}, expectedAmount(ticker, val), true, expectedError(ticker) + } + skycoinTxnWithFee := func(ticker string, fee string) (core.Transaction, uint64, bool, error) { + val, err := strconv.ParseUint(fee, 10, 64) + if err != nil { + return nil, 0, false, nil + } + return &SkycoinTransaction{ + skyTxn: readable.TransactionVerbose{ + BlockTransactionVerbose: readable.BlockTransactionVerbose{ + Fee: val, + }, + }, + }, expectedAmount(ticker, val), true, expectedError(ticker) + } + createdTxnWithFee := func(ticker string, fee string) (core.Transaction, uint64, bool, error) { + val, err := strconv.ParseInt(fee, 10, 64) + var expError error + if err != nil && ticker == CoinHour { + expError = err + } else { + expError = expectedError(ticker) + } + return &SkycoinCreatedTransaction{ + skyTxn: api.CreatedTransaction{ + Fee: fee, + }, + }, expectedAmount(ticker, uint64(val)), true, expError + } + + tests := []struct { + name string + generator func(string, string) (core.Transaction, uint64, bool, error) + }{ + { + name: "SkycoinPendingTransaction", + generator: pendingTxnWithFee, + }, + { + name: "SkycoinUninjectedTransaction", + generator: uninjectedTxnWithFee, + }, + { + name: "SkycoinTransaction", + generator: skycoinTxnWithFee, + }, + { + name: "SkycoinCreatedTransaction", + generator: createdTxnWithFee, + }, + } + tickers := []string{Sky, CoinHour, CalculatedHour, "INVALIDTICKER"} + amounts := []string{"1", "2", "42", "100", "42,42", "1,1"} + + for _, tt := range tests { + for _, ticker := range tickers { + for _, amount := range amounts { + t.Run(tt.name+"-"+ticker, func(t *testing.T) { + thx, expected, valid, err := tt.generator(ticker, amount) + if valid { + val, err1 := thx.ComputeFee(ticker) + if err != nil { + require.Equal(t, err, err1) + } else { + require.NoError(t, err1) + require.Equal(t, expected, val) + } + } + }) + } + } + } +} + +func TestTransactionVerifyUnsigned(t *testing.T) { + type skyTxn interface { + skytypes.ReadableTxn + core.Transaction + } + tests := []struct { + name string + txn skyTxn + }{ + { + name: "empty Txn", + txn: new(SkycoinTransaction), + }, + { + name: "empty Txn", + txn: new(SkycoinCreatedTransaction), + }, + //TODO: add better tests + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := verifyReadableTransaction(tt.txn, false) + err1 := tt.txn.VerifyUnsigned() + require.Equal(t, err, err1) + }) + } +} + +func TestTransactionVerifySigned(t *testing.T) { + type skyTxn interface { + skytypes.ReadableTxn + core.Transaction + } + tests := []struct { + name string + txn skyTxn + }{ + { + name: "empty Txn", + txn: new(SkycoinTransaction), + }, + { + name: "empty Txn", + txn: new(SkycoinCreatedTransaction), + }, + //TODO: add better tests + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := verifyReadableTransaction(tt.txn, true) + err1 := tt.txn.VerifySigned() + require.Equal(t, err, err1) + }) + } +} + +func Test_getSkycoinTransactionInputsFromTxnHash(t *testing.T) { + CleanGlobalMock() + + inputs := []readable.TransactionInput{ + readable.TransactionInput{ + Hash: "hash1", + }, + readable.TransactionInput{ + Hash: "hash2", + }, + readable.TransactionInput{ + Hash: "hash3", + }, + readable.TransactionInput{ + Hash: "hash4", + }, + } + + for len(inputs) > 0 { + global_mock.On("TransactionVerbose", "hash").Return( + &readable.TransactionWithStatusVerbose{ + Transaction: readable.TransactionVerbose{ + BlockTransactionVerbose: readable.BlockTransactionVerbose{ + In: inputs, + }, + }, + }, nil, + ).Once() + t.Run("InputsFromTxnHash", func(t *testing.T) { + txnInputs, err := getSkycoinTransactionInputsFromTxnHash("hash") + require.NoError(t, err) + rawInputs := make([]readable.TransactionInput, len(txnInputs)) + for i, in := range txnInputs { + skyIn, valid := in.(*SkycoinTransactionInput) + require.True(t, valid) + require.Nil(t, skyIn.spentOutput) + rawInputs[i] = skyIn.skyIn + } + requirethat.ElementsMatch(t, inputs, rawInputs) + }) + inputs = inputs[:len(inputs)-1] + } + global_mock.On("TransactionVerbose", "hash").Return(nil, goerrors.New("failure")) + _, err := getSkycoinTransactionInputsFromTxnHash("hash") + require.Error(t, err) +} + +func TestGetCoins(t *testing.T) { + type ObjectWithCoins interface { + GetCoins(string) (uint64, error) + } - fee, err := cTxn.ComputeFee(Sky) + invalidTicker := "INVALIDTICKER" + tests := []struct { + name string + obj ObjectWithCoins + ticker string + want uint64 + err bool + }{ + // SkycoinCreatedTransactionOutput + { + name: "SkycoinCreatedTransactionOutput", + ticker: invalidTicker, + obj: new(SkycoinCreatedTransactionOutput), + err: true, + }, + { + name: "SkycoinCreatedTransactionOutput", + ticker: Sky, + obj: &SkycoinCreatedTransactionOutput{ + skyOut: api.CreatedTransactionOutput{ + Coins: "20", + }, + }, + want: 20000000, + }, + { + name: "SkycoinCreatedTransactionOutput", + ticker: Sky, + obj: &SkycoinCreatedTransactionOutput{ + skyOut: api.CreatedTransactionOutput{ + Coins: "20.1", + }, + }, + want: 20100000, + }, + { + name: "SkycoinCreatedTransactionOutput", + ticker: Sky, + obj: &SkycoinCreatedTransactionOutput{ + skyOut: api.CreatedTransactionOutput{ + Coins: "20,1a", + }, + }, + err: true, + }, + { + name: "SkycoinCreatedTransactionOutput", + ticker: CoinHour, + obj: &SkycoinCreatedTransactionOutput{ + skyOut: api.CreatedTransactionOutput{ + Hours: "42", + }, + }, + want: 42, + }, + { + name: "SkycoinCreatedTransactionOutput", + ticker: CoinHour, + obj: &SkycoinCreatedTransactionOutput{ + skyOut: api.CreatedTransactionOutput{ + Hours: "42.1", + }, + }, + err: true, + }, + { + name: "SkycoinCreatedTransactionOutput", + ticker: CalculatedHour, + obj: &SkycoinCreatedTransactionOutput{ + calculatedHours: 42, + }, + want: 42, + }, + { + name: "SkycoinCreatedTransactionOutput", + ticker: CalculatedHour, + obj: &SkycoinCreatedTransactionOutput{ + calculatedHours: 50, + }, + want: 50, + }, + // SkycoinCreatedTransactionInput + { + name: "SkycoinCreatedTransactionInput", + ticker: invalidTicker, + obj: new(SkycoinCreatedTransactionInput), + err: true, + }, + { + name: "SkycoinCreatedTransactionInput", + ticker: Sky, + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + Coins: "20", + }, + }, + want: 20000000, + }, + { + name: "SkycoinCreatedTransactionInput", + ticker: Sky, + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + Coins: "20.1", + }, + }, + want: 20100000, + }, + { + name: "SkycoinCreatedTransactionInput", + ticker: Sky, + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + Coins: "20,1a", + }, + }, + err: true, + }, + { + name: "SkycoinCreatedTransactionInput", + ticker: CoinHour, + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + Hours: "42", + }, + }, + want: 42, + }, + { + name: "SkycoinCreatedTransactionInput", + ticker: CoinHour, + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + Hours: "42.1", + }, + }, + err: true, + }, + { + name: "SkycoinCreatedTransactionInput", + ticker: CalculatedHour, + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + CalculatedHours: "42", + }, + }, + want: 42, + }, + { + name: "SkycoinCreatedTransactionInput", + ticker: CalculatedHour, + obj: &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + CalculatedHours: "42.1", + }, + }, + err: true, + }, + // SkycoinTransactionInput + { + name: "SkycoinTransactionInput", + ticker: invalidTicker, + obj: new(SkycoinTransactionInput), + err: true, + }, + { + name: "SkycoinTransactionInput", + ticker: Sky, + obj: &SkycoinTransactionInput{ + skyIn: readable.TransactionInput{ + Coins: "20", + }, + }, + want: 20000000, + }, + { + name: "SkycoinTransactionInput", + ticker: Sky, + obj: &SkycoinTransactionInput{ + skyIn: readable.TransactionInput{ + Coins: "20.1", + }, + }, + want: 20100000, + }, + { + name: "SkycoinTransactionInput", + ticker: Sky, + obj: &SkycoinTransactionInput{ + skyIn: readable.TransactionInput{ + Coins: "20,1a", + }, + }, + err: true, + }, + { + name: "SkycoinTransactionInput", + ticker: CoinHour, + obj: &SkycoinTransactionInput{ + skyIn: readable.TransactionInput{ + Hours: 42, + }, + }, + want: 42, + }, + { + name: "SkycoinTransactionInput", + ticker: CalculatedHour, + obj: &SkycoinTransactionInput{ + skyIn: readable.TransactionInput{ + CalculatedHours: 42, + }, + }, + want: 42, + }, + // TransactionOutput + { + name: "SkycoinTransactionOutput", + ticker: invalidTicker, + obj: new(SkycoinTransactionOutput), + err: true, + }, + { + name: "SkycoinTransactionOutput", + ticker: Sky, + obj: &SkycoinTransactionOutput{ + skyOut: readable.TransactionOutput{ + Coins: "20", + }, + }, + want: 20000000, + }, + { + name: "SkycoinTransactionOutput", + ticker: Sky, + obj: &SkycoinTransactionOutput{ + skyOut: readable.TransactionOutput{ + Coins: "20.1", + }, + }, + want: 20100000, + }, + { + name: "SkycoinTransactionOutput", + ticker: Sky, + obj: &SkycoinTransactionOutput{ + skyOut: readable.TransactionOutput{ + Coins: "20,1a", + }, + }, + err: true, + }, + { + name: "SkycoinTransactionOutput", + ticker: CoinHour, + obj: &SkycoinTransactionOutput{ + skyOut: readable.TransactionOutput{ + Hours: 42, + }, + }, + want: 42, + }, + { + name: "SkycoinTransactionOutput", + ticker: CalculatedHour, + obj: &SkycoinTransactionOutput{ + calculatedHours: 42, + }, + want: 42, + }, + } + + for _, tt := range tests { + t.Run(tt.name+"-"+tt.ticker, func(t *testing.T) { + amount, err := tt.obj.GetCoins(tt.ticker) + if tt.err { + require.Error(t, err) + } else { + require.NoError(t, err) + require.Equal(t, tt.want, amount) + } + }) + } +} + +func TestGetAddress(t *testing.T) { + strAddr := makeAddress().String() + + tests := []struct { + name string + output core.TransactionOutput + err bool + want string + isNil bool + }{ + { + name: "SkycoinCreatedTransactionOutput-empty", + output: new(SkycoinCreatedTransactionOutput), + isNil: true, + }, + { + name: "SkycoinCreatedTransactionOutput-valid", + output: &SkycoinCreatedTransactionOutput{ + skyOut: api.CreatedTransactionOutput{ + Address: strAddr, + }, + }, + want: strAddr, + }, + { + name: "SkycoinTransactionOutput-empty", + output: new(SkycoinTransactionOutput), + isNil: true, + }, + { + name: "SkycoinTransactionOutput-valid", + output: &SkycoinTransactionOutput{ + skyOut: readable.TransactionOutput{ + Address: strAddr, + }, + }, + want: strAddr, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + addr := tt.output.GetAddress() + if tt.isNil { + require.Nil(t, addr) + } else { + require.NotNil(t, addr) + require.Equal(t, tt.want, addr.String()) + } + }) + } +} + +func TestSkycoinCreatedTransactionInputGetSpentOutput(t *testing.T) { + tests := []struct { + addr string + coins string + hours string + uxid string + ccHours string + }{ + {addr: makeAddress().String(), coins: "1", hours: "1", uxid: "uxid1", ccHours: "1"}, + {addr: makeAddress().String(), coins: "2", hours: "2", uxid: "uxid2", ccHours: "2"}, + {addr: makeAddress().String(), coins: "3", hours: "3", uxid: "uxid3", ccHours: "3"}, + {addr: makeAddress().String(), coins: "4", hours: "4", uxid: "uxid4", ccHours: "4"}, + {addr: makeAddress().String(), coins: "5", hours: "5", uxid: "uxid5", ccHours: "5"}, + {addr: makeAddress().String(), coins: "6", hours: "6", uxid: "uxid6", ccHours: "6"}, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("GetSepent%d", i), func(t *testing.T) { + createdIn := &SkycoinCreatedTransactionInput{ + skyIn: api.CreatedTransactionInput{ + Address: tt.addr, + Coins: tt.coins, + Hours: tt.hours, + UxID: tt.uxid, + CalculatedHours: tt.ccHours, + }, + } + output := createdIn.GetSpentOutput() + createdOut, valid := output.(*SkycoinCreatedTransactionOutput) + require.True(t, valid) + for _, asset := range createdIn.SupportedAssets() { + cur, err := createdIn.GetCoins(asset) + require.NoError(t, err) + val, err := createdOut.GetCoins(asset) + require.NoError(t, err) + require.Equal(t, cur, val) + } + require.Equal(t, createdIn.GetId(), createdOut.GetId()) + require.Equal(t, tt.addr, createdOut.GetAddress().String()) + }) + } +} + +func Test_newCreatedTransactionOutputs(t *testing.T) { + outputs := []api.CreatedTransactionOutput{ + api.CreatedTransactionOutput{ + Address: "addr1", + }, + api.CreatedTransactionOutput{ + Address: "addr2", + }, + api.CreatedTransactionOutput{ + Address: "addr3", + }, + api.CreatedTransactionOutput{ + Address: "addr4", + }, + api.CreatedTransactionOutput{ + Address: "addr5", + }, + } + + for len(outputs) > 0 { + outs := newCreatedTransactionOutputs(outputs) + rawOutputs := make([]api.CreatedTransactionOutput, len(outputs)) + for i, out := range outs { + createdOut, valid := out.(*SkycoinCreatedTransactionOutput) + require.True(t, valid) + rawOutputs[i] = createdOut.skyOut + } + requirethat.ElementsMatch(t, outputs, rawOutputs) + outputs = outputs[:len(outputs)-1] + } +} + +func TestEncodeSkycoinTransaction(t *testing.T) { + type SkycoinReadableTxn interface { + skytypes.SkycoinTxn + skytypes.ReadableTxn + } + + date, err := time.Parse(time.RFC3339, "2012-11-01T22:08:41+00:00") require.NoError(t, err) - require.Equal(t, uint64(0), fee) - _, err = cTxn.ComputeFee(CalculatedHour) - testutil.RequireError(t, err, "Feature not implemented") + tests := []struct { + name string + txn SkycoinReadableTxn + }{ + { + name: "SkycoinPendingTransaction", + txn: &SkycoinPendingTransaction{ + Transaction: &readable.UnconfirmedTransactionVerbose{ + Transaction: readable.BlockTransactionVerbose{ + InnerHash: "0000000000000000000000000000000000000000000000000000000000000000", + Hash: "78877fa898f0b4c45c9c33ae941e40617ad7c8657a307db62bc5691f92f4f60e", + }, + Announced: date, + }, + }, + }, + { + name: "SkycoinCreatedTransaction", + txn: &SkycoinCreatedTransaction{ + skyTxn: api.CreatedTransaction{ + InnerHash: "0000000000000000000000000000000000000000000000000000000000000000", + TxID: "78877fa898f0b4c45c9c33ae941e40617ad7c8657a307db62bc5691f92f4f60e", + }, + }, + }, + } - _, err = cTxn.ComputeFee("NOCOINATALL") - testutil.RequireError(t, err, "Invalid ticker") + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ser, err := tt.txn.EncodeSkycoinTransaction() + require.NoError(t, err) + exp, err := serializeCreatedTransaction(tt.txn) + require.NoError(t, err) + require.Equal(t, exp, ser) + }) + } } diff --git a/src/coin/skycoin/models/network.go b/src/coin/skycoin/models/network.go index 78a2e788..8829bd77 100644 --- a/src/coin/skycoin/models/network.go +++ b/src/coin/skycoin/models/network.go @@ -133,7 +133,7 @@ func (spex *SkycoinPEX) GetTxnPool() (core.TransactionIterator, error) { } skycoinTxns := make([]core.Transaction, 0) for _, txn := range txns { - skycoinTxns = append(skycoinTxns, &SkycoinPendingTransaction{Transaction: txn}) + skycoinTxns = append(skycoinTxns, &SkycoinPendingTransaction{Transaction: &txn}) } return NewSkycoinTransactionIterator(skycoinTxns), nil } diff --git a/src/coin/skycoin/models/wallet_test.go b/src/coin/skycoin/models/wallet_test.go index db096eca..a59a93d5 100644 --- a/src/coin/skycoin/models/wallet_test.go +++ b/src/coin/skycoin/models/wallet_test.go @@ -1704,9 +1704,13 @@ func TestSkycoinLocalWalletEncrypt(t *testing.T) { clean := err == nil tt.srv.Encrypt(tt.name, tt.pwd) - encrypted, _ := tt.srv.IsEncrypted(tt.name) // nolint gosec + encrypted, err := tt.srv.IsEncrypted(tt.name) if clean { - _ = wallet.Save(wlt, tt.srv.walletDir) // nolint gosec + require.NoError(t, err) + err = wallet.Save(wlt, tt.srv.walletDir) + require.NoError(t, err) + } else { + require.Error(t, err) } require.Equal(t, tt.valid, encrypted) }) @@ -1739,9 +1743,13 @@ func TestSkycoinLocalWalletDecrypt(t *testing.T) { clean := err == nil tt.srv.Decrypt(tt.name, tt.pwd) - encrypted, _ := tt.srv.IsEncrypted(tt.name) // nolint gosec + encrypted, err := tt.srv.IsEncrypted(tt.name) if clean { - _ = wallet.Save(wlt, tt.srv.walletDir) // nolint gosec + require.NoError(t, err) + err = wallet.Save(wlt, tt.srv.walletDir) + require.NoError(t, err) + } else { + require.Error(t, err) } require.Equal(t, tt.valid, encrypted) }) @@ -1786,7 +1794,8 @@ func TestWalletsReadyForTxn(t *testing.T) { mock.AnythingOfType("*mocks.Transaction"), ).Return( func(w core.Wallet, txn core.Transaction) bool { - ok, _ := checkTxnSupported(mockWlt, w, txn) // nolint gosec + ok, err := checkTxnSupported(mockWlt, w, txn) + require.NoError(t, err) return ok }, nil, @@ -1948,8 +1957,10 @@ func TestSeedServiceGenerateMnemonic(t *testing.T) { func TestSeedServiceVerifyMnemonic(t *testing.T) { srv := new(SeedService) - mnc128, _ := srv.GenerateMnemonic(128) // nolint gosec - mnc256, _ := srv.GenerateMnemonic(256) // nolint gosec + mnc128, err := srv.GenerateMnemonic(128) + require.NoError(t, err) + mnc256, err := srv.GenerateMnemonic(256) + require.NoError(t, err) tests := []struct { name string mnemonic string diff --git a/src/util/main_test.go b/src/util/main_test.go index f6425f9d..7e03ab57 100644 --- a/src/util/main_test.go +++ b/src/util/main_test.go @@ -35,7 +35,7 @@ func TestPubKeyFromBytes(t *testing.T) { require.Nil(t, err) require.Equal(t, pubKey, mockPubKey) - _, err = PubKeyFromBytes(`CUSTOMTICKER`, bytes) // nolint gosec + _, err = PubKeyFromBytes(`CUSTOMTICKER`, bytes) require.NotNil(t, err) } @@ -65,6 +65,6 @@ func TestSecKeyFromBytes(t *testing.T) { require.Nil(t, err) require.Equal(t, secKey, mockSecKey) - _, err = SecKeyFromBytes(`CUSTOMTICKER`, bytes) // nolint gosec + _, err = SecKeyFromBytes(`CUSTOMTICKER`, bytes) require.NotNil(t, err) }