-
Notifications
You must be signed in to change notification settings - Fork 631
[CBR-371] unit tests for listing addresses #3563
Changes from all commits
2d31a87
b4f382e
e8597b5
de5d4db
e7fbe72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import Control.Monad.Except (runExceptT) | |
import Data.Acid (update) | ||
import qualified Data.ByteString as B | ||
import qualified Data.Map.Strict as M | ||
import qualified Data.Set as S | ||
import Formatting (build, sformat) | ||
import Servant.Server | ||
|
||
|
@@ -16,7 +17,7 @@ import Test.QuickCheck (arbitrary, choose, elements, withMaxSuccess, | |
(===)) | ||
import Test.QuickCheck.Monadic (PropertyM, monadicIO, pick) | ||
|
||
import Pos.Core (Address) | ||
import Pos.Core (Address, addrRoot) | ||
import Pos.Crypto (EncryptedSecretKey, emptyPassphrase, firstHardened, | ||
safeDeterministicKeyGen) | ||
|
||
|
@@ -37,13 +38,15 @@ import Cardano.Wallet.Kernel.DB.HdWallet.Create (initHdRoot) | |
import Cardano.Wallet.Kernel.DB.HdWallet.Derivation | ||
(HardeningMode (..), deriveIndex) | ||
import Cardano.Wallet.Kernel.DB.InDb (InDb (..), fromDb) | ||
import qualified Cardano.Wallet.Kernel.DB.Util.IxSet as IxSet | ||
import Cardano.Wallet.Kernel.Internal (PassiveWallet, wallets) | ||
import qualified Cardano.Wallet.Kernel.Keystore as Keystore | ||
import qualified Cardano.Wallet.Kernel.Read as Kernel | ||
import Cardano.Wallet.Kernel.Types (AccountId (..), WalletId (..)) | ||
import qualified Cardano.Wallet.Kernel.Wallets as Kernel | ||
import Cardano.Wallet.WalletLayer (PassiveWalletLayer) | ||
import qualified Cardano.Wallet.WalletLayer as WalletLayer | ||
import qualified Cardano.Wallet.WalletLayer.Kernel.Accounts as Accounts | ||
import qualified Cardano.Wallet.WalletLayer.Kernel.Addresses as Addresses | ||
import qualified Cardano.Wallet.WalletLayer.Kernel.Conv as Kernel.Conv | ||
import qualified Cardano.Wallet.WalletLayer.Kernel.Wallets as Wallets | ||
|
@@ -123,6 +126,38 @@ prepareAddressFixture n = do | |
let SliceOf{..} = Addresses.getAddresses (RequestParams pp) db' | ||
return . map AddressFixture $ paginatedSlice | ||
|
||
prepareAddressesFixture | ||
:: Int -- ^ Number of Accounts to create. | ||
-> Int -- ^ Number of 'Address per account to create. | ||
-> Fixture.GenPassiveWalletFixture (M.Map V1.AccountIndex [V1.WalletAddress]) | ||
prepareAddressesFixture acn adn = do | ||
spendingPassword <- Fixture.genSpendingPassword | ||
newWalletRq <- WalletLayer.CreateWallet <$> Wallets.genNewWalletRq spendingPassword | ||
return $ \pw -> do | ||
let newAcc (n :: Int) = (V1.NewAccount spendingPassword ("My Account " <> show n)) | ||
Right v1Wallet <- Wallets.createWallet pw newWalletRq | ||
forM_ [1..acn] $ \n -> | ||
Accounts.createAccount pw (V1.walId v1Wallet) (newAcc n) | ||
-- Get all the available accounts | ||
db <- Kernel.getWalletSnapshot pw | ||
let Right accs = Accounts.getAccounts (V1.walId v1Wallet) db | ||
let accounts = IxSet.toList accs | ||
length accounts `shouldBe` (acn + 1) | ||
let insertAddresses :: V1.Account -> IO (V1.AccountIndex, [V1.WalletAddress]) | ||
insertAddresses acc = do | ||
let accId = V1.accIndex acc | ||
let newAddressRq = V1.NewAddress spendingPassword accId (V1.walId v1Wallet) | ||
res <- replicateM adn (Addresses.createAddress pw newAddressRq) | ||
case sequence res of | ||
Left e -> error (show e) | ||
Right addr -> return (accId, addr) | ||
res <- mapM insertAddresses accounts | ||
return $ M.fromList res | ||
|
||
expectedNumber :: Int -> Int -> Int | ||
expectedNumber acc adr = (acc + 1)*(adr + 1) - acc | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think if you bake the logic inside the fixture, you might be able to remove this. |
||
|
||
|
||
withFixture :: ( Keystore.Keystore | ||
-> PassiveWalletLayer IO | ||
-> PassiveWallet | ||
|
@@ -145,6 +180,18 @@ withAddressFixtures n = | |
Fixture.withPassiveWalletFixture $ do | ||
prepareAddressFixture n | ||
|
||
withAddressesFixtures :: Int -> Int -> | ||
( Keystore.Keystore | ||
-> PassiveWalletLayer IO | ||
-> PassiveWallet | ||
-> M.Map V1.AccountIndex [V1.WalletAddress] | ||
-> IO a | ||
) | ||
-> PropertyM IO a | ||
withAddressesFixtures n m = | ||
Fixture.withPassiveWalletFixture $ do | ||
prepareAddressesFixture n m | ||
|
||
spec :: Spec | ||
spec = describe "Addresses" $ do | ||
describe "CreateAddress" $ do | ||
|
@@ -348,6 +395,137 @@ spec = describe "Addresses" $ do | |
slice rNumOfPages rNumPerPage fixtureAddresses | ||
pure (toBeCheckedAddresses === correctAddresses) | ||
|
||
describe "Address listing with multiple Accounts (Servant)" $ do | ||
prop "page 0, per page 0" $ withMaxSuccess 20 $ do | ||
monadicIO $ | ||
withAddressesFixtures 4 4 $ \_ layer _ _ -> do | ||
let pp = PaginationParams (Page 0) (PerPage 0) | ||
res <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp) | ||
case res of | ||
Right wr | null (wrData wr) -> pure () | ||
_ -> fail ("Got " ++ show res) | ||
|
||
prop "it yields the correct number of results" $ withMaxSuccess 20 $ do | ||
monadicIO $ | ||
withAddressesFixtures 3 4 $ \_ layer _ _ -> do | ||
let pp = PaginationParams (Page 1) (PerPage 40) | ||
res <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp) | ||
case res of | ||
Right wr -> do | ||
-- this takes into account that there is an initial account | ||
-- and each account has an initial address (but not the initial | ||
-- account thus the -1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, unfortunately if you create multiple accounts for the same wallet those won't come with an extra associated address. But ideally, if you can bake this inside the fixture creation, test logic should hopefully simplify 😉 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should delete this comment. |
||
length (wrData wr) `shouldBe` expectedNumber 3 4 | ||
_ -> fail ("Got " ++ show res) | ||
|
||
prop "is deterministic" $ withMaxSuccess 20 $ do | ||
monadicIO $ | ||
withAddressesFixtures 3 8 $ \_ layer _ _ -> do | ||
let (expectedTotal :: Int) = expectedNumber 3 8 | ||
let pp = PaginationParams (Page 1) (PerPage 40) | ||
let pp1 = PaginationParams (Page 1) (PerPage (quot expectedTotal 3 + 1)) | ||
let pp2 = PaginationParams (Page 2) (PerPage (quot expectedTotal 3 + 1)) | ||
let pp3 = PaginationParams (Page 3) (PerPage (quot expectedTotal 3 + 1)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a bit dense for the reader to try to figure out why you do the |
||
res <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp) | ||
res' <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp) | ||
res1 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp1) | ||
res1' <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp1) | ||
res2 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp2) | ||
res2' <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp2) | ||
res3 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp3) | ||
res3' <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp3) | ||
res `shouldBe` res' | ||
res1 `shouldBe` res1' | ||
res2 `shouldBe` res2' | ||
res3 `shouldBe` res3' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Idea: considering is very error prone to go for this style of res <- runExceptT $ runHandler' $ do
r1 <- Handlers.listAddresses layer (RequestParams pp)
r2 <- Handlers.listAddresses layer (RequestParams pp)
pure (r1 === r2)
res1 <- runExceptT $ runHandler' $ do
r1 <- Handlers.listAddresses layer (RequestParams pp1)
r2 <- Handlers.listAddresses layer (RequestParams pp1)
pure (r1 === r2)
assert $ conjoin [res, res1] Even better, I think you can simplify this even further by noticing how this is just iterating over [pp,pp1,pp2,pp3] but executing mechanical actions, so you can write something which runs a |
||
|
||
prop "yields the correct set of resutls" $ withMaxSuccess 20 $ do | ||
monadicIO $ | ||
withAddressesFixtures 4 8 $ \_ layer _ _ -> do | ||
let (expectedTotal :: Int) = expectedNumber 4 8 | ||
let pp = PaginationParams (Page 1) (PerPage 50) | ||
let pp1 = PaginationParams (Page 1) (PerPage (quot expectedTotal 3 + 1)) | ||
let pp2 = PaginationParams (Page 2) (PerPage (quot expectedTotal 3 + 1)) | ||
let pp3 = PaginationParams (Page 3) (PerPage (quot expectedTotal 3 + 1)) | ||
res <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp) | ||
res1 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp1) | ||
res2 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp2) | ||
res3 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp3) | ||
case (res, res1, res2, res3) of | ||
(Right wr, Right wr1, Right wr2, Right wr3) -> do | ||
length (wrData wr) `shouldBe` expectedTotal | ||
let con = wrData wr1 <> wrData wr2 <> wrData wr3 | ||
length con `shouldBe` expectedTotal | ||
S.fromList con `shouldBe` S.fromList (wrData wr) | ||
(addrRoot . V1.unV1 . V1.addrId <$> con) | ||
`shouldBe` (addrRoot . V1.unV1 . V1.addrId <$> wrData wr) | ||
_ -> fail ("Got " ++ show res) | ||
|
||
prop "yields the correct ordered resutls when there is one account" $ withMaxSuccess 20 $ do | ||
monadicIO $ | ||
withAddressesFixtures 0 15 $ \_ layer _ _ -> do | ||
let (expectedTotal :: Int) = expectedNumber 0 15 | ||
let pp = PaginationParams (Page 1) (PerPage 50) | ||
let pp1 = PaginationParams (Page 1) (PerPage (quot expectedTotal 3 + 1)) | ||
let pp2 = PaginationParams (Page 2) (PerPage (quot expectedTotal 3 + 1)) | ||
let pp3 = PaginationParams (Page 3) (PerPage (quot expectedTotal 3 + 1)) | ||
res <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp) | ||
res1 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp1) | ||
res2 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp2) | ||
res3 <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp3) | ||
case (res, res1, res2, res3) of | ||
(Right wr, Right wr1, Right wr2, Right wr3) -> do | ||
length (wrData wr) `shouldBe` expectedTotal | ||
let con = wrData wr1 <> wrData wr2 <> wrData wr3 | ||
length con `shouldBe` expectedTotal | ||
S.fromList con `shouldBe` S.fromList (wrData wr) | ||
(addrRoot . V1.unV1 . V1.addrId <$> con) | ||
`shouldBe` (addrRoot . V1.unV1 . V1.addrId <$> wrData wr) | ||
_ -> fail ("Got " ++ show res) | ||
|
||
|
||
prop "yields the correct ordered resutls" $ withMaxSuccess 20 $ do | ||
monadicIO $ do | ||
forM_ [(4,8), (6,6), (5,7)] $ \(acc,adr) -> | ||
withAddressesFixtures acc adr $ \_ layer _ _ -> do | ||
forM_ [2..10] $ \k -> do | ||
let indexes = [1..k] | ||
let (expectedTotal :: Int) = expectedNumber acc adr | ||
let pagesParams = map (\i -> PaginationParams (Page i) (PerPage (quot expectedTotal k + 1))) | ||
indexes | ||
let pp = PaginationParams (Page 1) (PerPage 50) | ||
res <- runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams pp) | ||
eiResultsArray <- forM pagesParams $ \ppi -> runExceptT $ runHandler' $ do | ||
Handlers.listAddresses layer (RequestParams ppi) | ||
let resultsArray = sequence eiResultsArray | ||
case (res, resultsArray) of | ||
(Right wr, Right wrList) -> do | ||
let con = mconcat $ map wrData wrList | ||
length (wrData wr) `shouldBe` expectedTotal | ||
length con `shouldBe` expectedTotal | ||
S.fromList con `shouldBe` S.fromList (wrData wr) | ||
(addrRoot . V1.unV1 . V1.addrId <$> con) | ||
`shouldBe` (addrRoot . V1.unV1 . V1.addrId <$> wrData wr) | ||
_ -> fail ("Got " ++ show res) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a minor thing, but these tests feels very imperative rather than declarative. I don't know what we can do about it, but I suspect we might come up with some combinators which factor away duplication and mechanical reshuffling of the data and leave the logic of the tests exposes. I guess what we aim is something like:
Which is exactly what your tests are also doing, but each of these steps takes a lot of lines of code and is hard to track down the spirit of the test 😉 |
||
|
||
describe "ValidateAddress" $ do | ||
describe "Address validation (wallet layer)" $ do | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend flipping the logic and doing something like:
Then, you can use those two let bindings all over the place without having to worry about +1/-1 arithmetic, both in the fixtures and in the tests. Furthermore, you segregate the comment on why we do this exactly in one point in the code, without this assumption being scattered all over the place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand what are the semantics of
requestedAddresses
. Some accounts will have requestedAddresses addresses, while the first account will have requestedAddresses+1. Also requestedAccounts should never be 0, as one account is created by default.