-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(tests): Introduce AggregationPredicatesSpec
PR-URL: hasura/graphql-engine-mono#5686 GitOrigin-RevId: 85b39ad569180929e5620c45bf9a98ef6ee99d42
- Loading branch information
1 parent
8bc34e1
commit d43a30e
Showing
11 changed files
with
568 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
306 changes: 306 additions & 0 deletions
306
server/src-test/Hasura/GraphQL/Schema/BoolExp/AggregationPredicatesSpec.hs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,306 @@ | ||
{-# LANGUAGE QuasiQuotes #-} | ||
|
||
-- | This module contain unit tests of the schema of the default implementation | ||
-- of aggregation predicates. | ||
module Hasura.GraphQL.Schema.BoolExp.AggregationPredicatesSpec (spec) where | ||
|
||
import Data.Aeson.QQ (aesonQQ) | ||
import Data.HashMap.Strict qualified as HM | ||
import Data.Text.NonEmpty (nonEmptyTextQQ) | ||
import Hasura.Backends.Postgres.Instances.Schema () | ||
import Hasura.Backends.Postgres.SQL.Types | ||
( PGScalarType (PGInteger, PGText), | ||
QualifiedTable, | ||
) | ||
import Hasura.Backends.Postgres.SQL.Value (PGScalarValue (..)) | ||
import Hasura.GraphQL.Parser.Internal.Input (ifParser) | ||
import Hasura.GraphQL.Schema.BoolExp.AggregationPredicates | ||
( ArgumentsSignature (..), | ||
FunctionSignature (..), | ||
defaultAggregationPredicatesParser, | ||
) | ||
import Hasura.GraphQL.Schema.Introspection (queryInputFieldsParserIntrospection) | ||
import Hasura.Prelude | ||
import Hasura.RQL.IR.BoolExp (OpExpG (AEQ)) | ||
import Hasura.RQL.IR.BoolExp.AggregationPredicates | ||
import Hasura.RQL.IR.Value (UnpreparedValue (UVParameter)) | ||
import Hasura.RQL.Types.Column (ColumnType (ColumnScalar), ColumnValue (..)) | ||
import Hasura.RQL.Types.Common (InsertOrder (..), RelName (..), RelType (..), SourceName (..)) | ||
import Hasura.RQL.Types.Relationships.Local (RelInfo (..)) | ||
import Hasura.RQL.Types.Source (SourceInfo (..)) | ||
import Hasura.RQL.Types.SourceCustomization (SourceCustomization (SourceCustomization)) | ||
import Hasura.RQL.Types.Table | ||
( TableCoreInfoG (_tciName), | ||
TableInfo (_tiCoreInfo), | ||
) | ||
import Hasura.SQL.Backend (BackendType (Postgres), PostgresKind (Vanilla)) | ||
import Language.GraphQL.Draft.Syntax qualified as G | ||
import Language.GraphQL.Draft.Syntax.QQ qualified as G | ||
import Test.Aeson.Expectation (shouldBeSubsetOf) | ||
import Test.Hspec | ||
import Test.Hspec.Extended | ||
import Test.Parser.Field qualified as GQL | ||
import Test.Parser.Internal | ||
( ColumnInfoBuilder | ||
( ColumnInfoBuilder, | ||
cibIsPrimaryKey, | ||
cibName, | ||
cibNullable, | ||
cibType | ||
), | ||
TableInfoBuilder (columns, relations), | ||
buildTableInfo, | ||
mkTable, | ||
tableInfoBuilder, | ||
) | ||
import Test.Parser.Monad | ||
( ParserTest (runParserTest), | ||
notImplementedYet, | ||
runSchemaTest, | ||
) | ||
import Type.Reflection (Typeable, typeRep) | ||
|
||
{- Notes: | ||
AggregationPredicates are defined as a standalone feature. It should be possible | ||
to test them without reference to an existing backend. | ||
We cannot do that however, since backends have the closed datakind `BackendType`. | ||
-} | ||
|
||
newtype Unshowable a = Unshowable {unUnshowable :: a} | ||
deriving (Eq, Ord) | ||
|
||
instance Typeable a => Show (Unshowable a) where | ||
show _ = "Unshowable<" ++ show (typeRep @a) ++ ">" | ||
|
||
spec :: Spec | ||
spec = do | ||
describe "Aggregation Predicates Schema Parsers" do | ||
describe "When no aggregation functions are given" do | ||
it "Yields no parsers" do | ||
let maybeParser = | ||
runSchemaTest $ | ||
defaultAggregationPredicatesParser @('Postgres 'Vanilla) @_ @_ @ParserTest | ||
[] | ||
sourceInfo | ||
albumTableInfo | ||
(Unshowable maybeParser) `shouldSatisfy` (isNothing . unUnshowable) | ||
|
||
describe "When some aggregation functions are given" do | ||
let maybeParser = | ||
runSchemaTest $ | ||
defaultAggregationPredicatesParser @('Postgres 'Vanilla) @_ @_ @ParserTest | ||
[ FunctionSignature | ||
{ fnName = "count", | ||
fnGQLName = [G.name|count|], | ||
fnArguments = ArgumentsStar, | ||
fnReturnType = PGInteger | ||
} | ||
] | ||
sourceInfo | ||
albumTableInfo | ||
|
||
it "Positively yields a parser" do | ||
(Unshowable maybeParser) `shouldSatisfy` (isJust . unUnshowable) | ||
|
||
dependentSpec maybeParser $ do | ||
it "Defines the expected GraphQL types" \parser -> do | ||
introspectionResult <- | ||
queryInputFieldsParserIntrospection | ||
parser | ||
[GQL.field| | ||
__schema { | ||
types { | ||
name | ||
fields { | ||
name | ||
type { name } | ||
} | ||
inputFields { | ||
name | ||
type { name } | ||
} | ||
} | ||
} |] | ||
|
||
let expectedTopLevel = | ||
[aesonQQ| | ||
{ "types": [ | ||
{ | ||
"name": "album_tracks_aggregate", | ||
"fields": null, | ||
"inputFields": [ | ||
{ | ||
"name": "count", | ||
"type": { | ||
"name": "album_tracks_aggregate_count" | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
|] | ||
let expectedCountType = | ||
[aesonQQ| | ||
{ "types": [ | ||
{ | ||
"name": "album_tracks_aggregate_count", | ||
"fields": null, | ||
"inputFields": [ | ||
{ | ||
"name": "arguments", | ||
"type": { | ||
"name": null | ||
} | ||
}, | ||
{ | ||
"name": "distinct", | ||
"type": { | ||
"name": "Boolean" | ||
} | ||
}, | ||
{ | ||
"name": "filter", | ||
"type": { | ||
"name": "track_bool_exp" | ||
} | ||
}, | ||
{ | ||
"name": "predicate", | ||
"type": { | ||
"name": null | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} | ||
|] | ||
|
||
expectedTopLevel `shouldBeSubsetOf` introspectionResult | ||
expectedCountType `shouldBeSubsetOf` introspectionResult | ||
|
||
it "Parses an example field" \parser -> do | ||
let input = | ||
[GQL.inputfields| | ||
tracks_aggregate: { | ||
count: { | ||
arguments: [], | ||
predicate: {_eq : 42 }, | ||
distinct: true | ||
} | ||
} | ||
|] | ||
actual <- runParserTest $ ifParser parser input | ||
|
||
let expected :: [AggregationPredicatesImplementation ('Postgres 'Vanilla) (UnpreparedValue ('Postgres 'Vanilla))] | ||
expected = | ||
[ AggregationPredicatesImplementation | ||
{ aggRelation = tracksRel, | ||
aggPredicates = | ||
[ AggregationPredicate | ||
{ aggPredFunctionName = "count", | ||
aggPredArguments = AggregationPredicateArgumentsStar, | ||
aggPredDistinct = True, | ||
aggPredFilter = Nothing, | ||
aggPredPredicate = | ||
[ AEQ | ||
True | ||
( UVParameter | ||
Nothing | ||
( ColumnValue | ||
{ cvType = ColumnScalar PGInteger, | ||
cvValue = PGValInteger 42 | ||
} | ||
) | ||
) | ||
] | ||
} | ||
] | ||
} | ||
] | ||
|
||
actual `shouldBe` expected | ||
where | ||
albumTableInfo :: TableInfo ('Postgres 'Vanilla) | ||
albumTableInfo = | ||
buildTableInfo | ||
( (tableInfoBuilder (mkTable "album")) | ||
{ columns = | ||
[ ColumnInfoBuilder | ||
{ cibName = "id", | ||
cibType = ColumnScalar PGInteger, | ||
cibNullable = False, | ||
cibIsPrimaryKey = True | ||
}, | ||
ColumnInfoBuilder | ||
{ cibName = "title", | ||
cibType = ColumnScalar PGText, | ||
cibNullable = False, | ||
cibIsPrimaryKey = False | ||
} | ||
], | ||
relations = [tracksRel] | ||
} | ||
) | ||
|
||
trackTableInfo :: TableInfo ('Postgres 'Vanilla) | ||
trackTableInfo = | ||
buildTableInfo | ||
( (tableInfoBuilder (mkTable "track")) | ||
{ columns = | ||
[ ColumnInfoBuilder | ||
{ cibName = "id", | ||
cibType = ColumnScalar PGInteger, | ||
cibNullable = False, | ||
cibIsPrimaryKey = True | ||
}, | ||
ColumnInfoBuilder | ||
{ cibName = "title", | ||
cibType = ColumnScalar PGText, | ||
cibNullable = False, | ||
cibIsPrimaryKey = False | ||
}, | ||
ColumnInfoBuilder | ||
{ cibName = "duration_seconds", | ||
cibType = ColumnScalar PGInteger, | ||
cibNullable = False, | ||
cibIsPrimaryKey = False | ||
}, | ||
ColumnInfoBuilder | ||
{ cibName = "album_id", | ||
cibType = ColumnScalar PGInteger, | ||
cibNullable = False, | ||
cibIsPrimaryKey = False | ||
} | ||
] | ||
} | ||
) | ||
|
||
tracksRel :: RelInfo ('Postgres 'Vanilla) | ||
tracksRel = | ||
RelInfo | ||
{ riName = RelName [nonEmptyTextQQ|tracks|], | ||
riType = ArrRel, | ||
riMapping = HM.fromList [("id", "album_id")], | ||
riRTable = (mkTable "track"), | ||
riIsManual = False, | ||
riInsertOrder = AfterParent | ||
} | ||
|
||
sourceInfo :: SourceInfo ('Postgres 'Vanilla) | ||
sourceInfo = | ||
SourceInfo | ||
{ _siName = SNDefault, | ||
_siTables = makeTableCache [albumTableInfo, trackTableInfo], | ||
_siFunctions = mempty, | ||
_siConfiguration = notImplementedYet "SourceConfig", | ||
_siQueryTagsConfig = Nothing, | ||
_siCustomization = SourceCustomization Nothing Nothing Nothing | ||
} | ||
|
||
makeTableCache :: [TableInfo ('Postgres 'Vanilla)] -> HashMap QualifiedTable (TableInfo ('Postgres 'Vanilla)) | ||
makeTableCache tables = HM.fromList [(_tciName $ _tiCoreInfo ti, ti) | ti <- tables] |
Oops, something went wrong.