|
| 1 | +-- | Running Plutip test plans with Blockfrost |
| 2 | +module Contract.Test.Blockfrost |
| 3 | + ( BlockfrostKeySetup |
| 4 | + , runPlutipTestsWithBlockfrost |
| 5 | + , executePlutipTestsWithBlockfrost |
| 6 | + ) where |
| 7 | + |
| 8 | +import Prelude |
| 9 | + |
| 10 | +import Contract.Config |
| 11 | + ( BlockfrostBackendParams |
| 12 | + , ContractParams |
| 13 | + , CtlBackendParams |
| 14 | + , PrivatePaymentKeySource(PrivatePaymentKeyFile) |
| 15 | + , PrivateStakeKeySource(PrivateStakeKeyFile) |
| 16 | + , QueryBackendParams(BlockfrostBackendParams) |
| 17 | + , ServerConfig |
| 18 | + , WalletSpec(UseKeys) |
| 19 | + ) |
| 20 | +import Contract.Test.Mote (TestPlanM, interpretWithConfig) |
| 21 | +import Contract.Test.Plutip (PlutipTest, runPlutipTestsWithKeyDir) |
| 22 | +import Control.Monad.Error.Class (liftMaybe) |
| 23 | +import Ctl.Internal.Test.E2E.Runner (readBoolean) |
| 24 | +import Data.Maybe (Maybe(Just, Nothing), isNothing, maybe) |
| 25 | +import Data.Number as Number |
| 26 | +import Data.Time.Duration (Seconds(Seconds)) |
| 27 | +import Data.UInt as UInt |
| 28 | +import Effect (Effect) |
| 29 | +import Effect.Aff (Aff) |
| 30 | +import Effect.Class (liftEffect) |
| 31 | +import Effect.Console as Console |
| 32 | +import Effect.Exception (error, throw) |
| 33 | +import Node.Process (lookupEnv) |
| 34 | +import Test.Spec.Runner (Config) |
| 35 | + |
| 36 | +-- | All parameters that are needed to run Plutip-style tests using |
| 37 | +-- | Blockfrost API. |
| 38 | +-- | |
| 39 | +-- | Includes: |
| 40 | +-- | |
| 41 | +-- | - Private payment and (optionally) stake keys |
| 42 | +-- | - A directory to store temporary private keys that will be used in tests. |
| 43 | +-- | In case of a suddent test interruption, funds will not be lost since |
| 44 | +-- | the private keys will be saved to files. |
| 45 | +type BlockfrostKeySetup = |
| 46 | + { privateKeySources :: |
| 47 | + { payment :: PrivatePaymentKeySource |
| 48 | + , stake :: Maybe PrivateStakeKeySource |
| 49 | + } |
| 50 | + , testKeysDirectory :: String |
| 51 | + } |
| 52 | + |
| 53 | +-- | A function that interprets a Plutip test plan into an `Aff`, given a |
| 54 | +-- | pre-funded address and a Blockfrost API endpoint. |
| 55 | +-- | |
| 56 | +-- | Accepts: |
| 57 | +-- | |
| 58 | +-- | 1. Runtime parameters for `Contract` |
| 59 | +-- | 2. Parameters for Blockfrost backend |
| 60 | +-- | 3. Optional parameters for CTL backend if it should be used |
| 61 | +-- | 4. Key setup parameters - keys are used to provide funds to the test suite. |
| 62 | +-- | Create the keys using [this guide](https://developers.cardano.org/docs/stake-pool-course/handbook/keys-addresses/) |
| 63 | +-- | and fund them using the test ADA faucet: https://docs.cardano.org/cardano-testnet/tools/faucet |
| 64 | +-- | 5. A test suite to run. |
| 65 | +-- | |
| 66 | +-- | Note that this function does not start a Plutip cluster. Instead, it |
| 67 | +-- | substitutes it with Blockfrost. |
| 68 | +-- | |
| 69 | +-- | **If you are using a paid Blockfrost plan**, be careful with what you run with |
| 70 | +-- | this function. |
| 71 | +-- | |
| 72 | +-- | Avoid moving the funds around too much using `withWallets` |
| 73 | +-- | in the tests to save on both time and costs. |
| 74 | +runPlutipTestsWithBlockfrost |
| 75 | + :: ContractParams |
| 76 | + -> BlockfrostBackendParams |
| 77 | + -> Maybe CtlBackendParams |
| 78 | + -> BlockfrostKeySetup |
| 79 | + -> TestPlanM PlutipTest Unit |
| 80 | + -> TestPlanM (Aff Unit) Unit |
| 81 | +runPlutipTestsWithBlockfrost |
| 82 | + contractParams |
| 83 | + backendParams |
| 84 | + mbCtlBackendParams |
| 85 | + { privateKeySources, testKeysDirectory } |
| 86 | + suite = |
| 87 | + runPlutipTestsWithKeyDir |
| 88 | + config |
| 89 | + testKeysDirectory |
| 90 | + suite |
| 91 | + where |
| 92 | + config = |
| 93 | + contractParams |
| 94 | + { backendParams = BlockfrostBackendParams backendParams mbCtlBackendParams |
| 95 | + , walletSpec = Just $ UseKeys privateKeySources.payment |
| 96 | + privateKeySources.stake |
| 97 | + } |
| 98 | + |
| 99 | +-- | Reads environment variables containing Blockfrost test suite configuration |
| 100 | +-- | and runs a given test suite using `runPlutipTestsWithBlockfrost`. |
| 101 | +executePlutipTestsWithBlockfrost |
| 102 | + :: Config |
| 103 | + -> ContractParams |
| 104 | + -> Maybe CtlBackendParams |
| 105 | + -> TestPlanM PlutipTest Unit |
| 106 | + -> Aff Unit |
| 107 | +executePlutipTestsWithBlockfrost |
| 108 | + testConfig |
| 109 | + contractParams |
| 110 | + mbCtlBackendParams |
| 111 | + suite = do |
| 112 | + blockfrostApiKey <- liftEffect $ |
| 113 | + lookupEnv "BLOCKFROST_API_KEY" <#> notEmptyString |
| 114 | + when (isNothing blockfrostApiKey) do |
| 115 | + liftEffect $ Console.warn $ |
| 116 | + "Warning: BLOCKFROST_API_KEY is not set. " <> |
| 117 | + "If you are using a public instance, the tests will fail" |
| 118 | + privatePaymentKeyFile <- |
| 119 | + getEnvVariable "PRIVATE_PAYMENT_KEY_FILE" |
| 120 | + "Please specify a payment key file" |
| 121 | + mbPrivateStakeKeyFile <- liftEffect $ |
| 122 | + lookupEnv "PRIVATE_STAKE_KEY_FILE" <#> notEmptyString |
| 123 | + confirmTxDelay <- liftEffect $ |
| 124 | + lookupEnv "TX_CONFIRMATION_DELAY_SECONDS" >>= parseConfirmationDelay |
| 125 | + when (confirmTxDelay < Just (Seconds 20.0)) do |
| 126 | + liftEffect $ Console.warn $ |
| 127 | + "Warning: It is recommended to set TX_CONFIRMATION_DELAY_SECONDS to at " |
| 128 | + <> "least 20 seconds to let the changes propagate after transaction " |
| 129 | + <> "submission." |
| 130 | + testKeysDirectory <- getEnvVariable "BACKUP_KEYS_DIR" |
| 131 | + "Please specify a directory to store temporary private keys in" |
| 132 | + blockfrostConfig <- liftEffect $ readBlockfrostServerConfig |
| 133 | + let |
| 134 | + backendParams = |
| 135 | + { blockfrostConfig |
| 136 | + , blockfrostApiKey |
| 137 | + , confirmTxDelay |
| 138 | + } |
| 139 | + interpretWithConfig testConfig $ |
| 140 | + runPlutipTestsWithBlockfrost contractParams backendParams mbCtlBackendParams |
| 141 | + { privateKeySources: |
| 142 | + { payment: PrivatePaymentKeyFile privatePaymentKeyFile |
| 143 | + , stake: PrivateStakeKeyFile <$> mbPrivateStakeKeyFile |
| 144 | + } |
| 145 | + , testKeysDirectory |
| 146 | + } |
| 147 | + suite |
| 148 | + where |
| 149 | + getEnvVariable :: String -> String -> Aff String |
| 150 | + getEnvVariable variable text = liftEffect do |
| 151 | + res <- notEmptyString <$> lookupEnv variable >>= case _ of |
| 152 | + Nothing -> throw $ text <> " (" <> variable <> ")" |
| 153 | + Just result -> pure result |
| 154 | + pure res |
| 155 | + |
| 156 | + -- Treat env variables set to "" as empty |
| 157 | + notEmptyString :: Maybe String -> Maybe String |
| 158 | + notEmptyString = |
| 159 | + case _ of |
| 160 | + Just "" -> Nothing |
| 161 | + other -> other |
| 162 | + |
| 163 | + parseConfirmationDelay :: Maybe String -> Effect (Maybe Seconds) |
| 164 | + parseConfirmationDelay = |
| 165 | + notEmptyString >>> maybe (pure Nothing) \str -> |
| 166 | + case Number.fromString str of |
| 167 | + Nothing -> liftEffect $ throw |
| 168 | + "TX_CONFIRMATION_DELAY_SECONDS must be set to a valid number" |
| 169 | + Just number -> pure $ Just $ Seconds number |
| 170 | + |
| 171 | +readBlockfrostServerConfig :: Effect ServerConfig |
| 172 | +readBlockfrostServerConfig = do |
| 173 | + port <- lookupEnv "BLOCKFROST_PORT" >>= \mbPort -> |
| 174 | + liftMaybe (error "Unable to read BLOCKFROST_PORT environment variable") |
| 175 | + (mbPort >>= UInt.fromString) |
| 176 | + host <- lookupEnv "BLOCKFROST_HOST" >>= |
| 177 | + liftMaybe (error "Unable to read BLOCKFROST_HOST") |
| 178 | + secure <- lookupEnv "BLOCKFROST_SECURE" >>= \mbSecure -> |
| 179 | + liftMaybe |
| 180 | + ( error |
| 181 | + "Unable to read BLOCKFROST_SECURE ('true' - use HTTPS, 'false' - use HTTP)" |
| 182 | + ) |
| 183 | + (mbSecure >>= readBoolean) |
| 184 | + path <- lookupEnv "BLOCKFROST_PATH" |
| 185 | + pure { port, host, secure, path } |
0 commit comments