2022-04-10 07:47:15 +03:00
{- # LANGUAGE TemplateHaskell # -}
module Command
( Command ( .. ) ,
TestOptions ( .. ) ,
AgentCapabilities ( .. ) ,
parseCommandLine ,
)
where
import Control.Arrow ( left )
import Control.Lens ( contains , modifying , use , ( ^. ) , _2 )
import Control.Lens.TH ( makeLenses )
import Control.Monad ( when )
import Control.Monad.State ( State , runState )
2022-05-02 02:01:11 +03:00
import Data.Aeson ( eitherDecodeStrict' )
2022-04-10 07:47:15 +03:00
import Data.HashSet ( HashSet )
import Data.HashSet qualified as HashSet
import Data.Text ( Text )
import Data.Text qualified as Text
2022-05-02 02:01:11 +03:00
import Data.Text.Encoding qualified as Text
2022-04-10 07:47:15 +03:00
import Data.Version ( showVersion )
2022-05-02 08:03:12 +03:00
import Hasura.Backends.DataConnector.API qualified as API
2022-04-10 07:47:15 +03:00
import Options.Applicative
import Paths_graphql_engine qualified as PackageInfo
import Servant.Client ( BaseUrl , parseBaseUrl )
import Prelude
data Command
= Test TestOptions
| ExportOpenAPISpec
data TestOptions = TestOptions
{ _toAgentBaseUrl :: BaseUrl ,
2022-05-02 02:01:11 +03:00
_toAgentConfig :: API . Config ,
2022-04-10 07:47:15 +03:00
_toAgentCapabilities :: AgentCapabilities ,
_toParallelDegree :: Maybe Int ,
_toMatch :: Maybe String ,
_toSkip :: Maybe String
}
data AgentCapabilities
= AutoDetect
| Explicit API . Capabilities
data CapabilitiesState = CapabilitiesState
{ _csRemainingCapabilities :: HashSet Text ,
_csCapabilitiesEnquired :: HashSet Text
}
$ ( makeLenses ''CapabilitiesState )
parseCommandLine :: IO Command
parseCommandLine =
execParser $
info
( helper <*> version <*> commandParser )
( fullDesc
2022-05-02 08:03:12 +03:00
<> header " Hasura Data Connector Agent Test Utility "
2022-04-10 07:47:15 +03:00
)
version :: Parser ( a -> a )
version =
infoOption
displayText
( long " version "
<> short 'v'
<> help " Prints the version of the application and quits "
<> hidden
)
where
displayText = " Version " <> showVersion PackageInfo . version
commandParser :: Parser Command
commandParser =
subparser
( testCommand <> exportOpenApiSpecCommand )
where
testCommand =
command
" test "
( info
( helper <*> testCommandParser )
( progDesc " Executes a suite of tests against an agent to ensure its correct function " )
)
exportOpenApiSpecCommand =
command
" export-openapi-spec "
( info
( helper <*> pure ExportOpenAPISpec )
2022-05-02 08:03:12 +03:00
( progDesc " Exports the OpenAPI specification of the Data Connector API that agents must implement " )
2022-04-10 07:47:15 +03:00
)
testOptionsParser :: Parser TestOptions
testOptionsParser =
TestOptions
<$> option
baseUrl
( long " agent-base-url "
<> short 'u'
<> metavar " URL "
2022-05-02 08:03:12 +03:00
<> help " The base URL of the Data Connector agent to be tested "
2022-04-10 07:47:15 +03:00
)
2022-05-02 02:01:11 +03:00
<*> option
configValue
( long " agent-config "
<> short 's'
<> metavar " JSON "
<> help " The configuration JSON to be sent to the agent via the X-Hasura-DataConnector-Config header "
)
2022-04-10 07:47:15 +03:00
<*> agentCapabilitiesParser
<*> optional
( option
positiveNonZeroInt
( long " jobs "
<> short 'j'
<> metavar " INT "
<> help " Run at most N parallelizable tests simultaneously (default: number of available processors) "
)
)
<*> optional
( option
auto
( long " match "
<> short 'm'
<> metavar " PATTERN "
<> help " Only run tests that match given PATTERN "
)
)
<*> optional
( option
auto
( long " skip "
<> short 's'
<> metavar " PATTERN "
<> help " Skip tests that match given PATTERN "
)
)
testCommandParser :: Parser Command
testCommandParser = Test <$> testOptionsParser
baseUrl :: ReadM BaseUrl
baseUrl = eitherReader $ left show . parseBaseUrl
positiveNonZeroInt :: ReadM Int
positiveNonZeroInt =
auto >>= \ int ->
if int <= 0 then readerError " Must be a positive, non-zero integer " else pure int
2022-05-02 02:01:11 +03:00
configValue :: ReadM API . Config
configValue = eitherReader $ ( fmap API . Config . eitherDecodeStrict' . Text . encodeUtf8 . Text . pack )
2022-04-10 07:47:15 +03:00
agentCapabilitiesParser :: Parser AgentCapabilities
agentCapabilitiesParser =
option
agentCapabilities
( long " capabilities "
<> short 'c'
<> metavar " CAPABILITIES "
<> value AutoDetect
<> help ( Text . unpack helpText )
)
where
helpText =
" The capabilities that the agent has, to determine what tests to run. By default, they will be autodetected. The valid capabilities are: " <> allCapabilitiesText
allCapabilitiesText =
" [autodetect | none | " <> Text . intercalate " , " ( HashSet . toList allPossibleCapabilities ) <> " ] "
agentCapabilities :: ReadM AgentCapabilities
agentCapabilities =
str >>= \ text -> do
let capabilities = HashSet . fromList $ Text . strip <$> Text . split ( == ',' ) text
if HashSet . member " autodetect " capabilities
then
if HashSet . size capabilities == 1
then pure AutoDetect
else readerError " You can either autodetect capabilities or specify them manually, not both "
else
if HashSet . member " none " capabilities
then
if HashSet . size capabilities == 1
then pure . Explicit . fst $ readCapabilities mempty
else readerError " You cannot specify other capabilities when specifying none "
else Explicit <$> readExplicitCapabilities capabilities
where
readExplicitCapabilities :: HashSet Text -> ReadM API . Capabilities
readExplicitCapabilities providedCapabilities =
let ( capabilities , CapabilitiesState { .. } ) = readCapabilities providedCapabilities
in if _csRemainingCapabilities /= mempty
then readerError . Text . unpack $ " Unknown capabilities: " <> Text . intercalate " , " ( HashSet . toList _csRemainingCapabilities )
else pure capabilities
readCapabilities :: HashSet Text -> ( API . Capabilities , CapabilitiesState )
readCapabilities providedCapabilities =
flip runState ( CapabilitiesState providedCapabilities mempty ) $
API . Capabilities
<$> readCapability " relationships "
readCapability :: Text -> State CapabilitiesState Bool
readCapability capability = do
modifying csCapabilitiesEnquired $ HashSet . insert capability
hasCapability <- use $ csRemainingCapabilities . contains capability
when hasCapability $
modifying csRemainingCapabilities $ HashSet . delete capability
pure hasCapability
allPossibleCapabilities :: HashSet Text
allPossibleCapabilities =
readCapabilities mempty ^. _2 . csCapabilitiesEnquired