packages feed

graphql-0.4.0.0: tests/Test/StarWars/QuerySpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Test.StarWars.QuerySpec
    ( spec
    ) where

import qualified Data.Aeson as Aeson
import Data.Aeson ( object
                  , (.=)
                  )
import Data.Text (Text)
import Language.GraphQL
import Language.GraphQL.Schema (Subs)
import Text.RawString.QQ (r)
import Test.Hspec.Expectations ( Expectation
                               , shouldBe
                               )
import Test.Hspec ( Spec
                  , describe
                  , it
                  )
import Test.StarWars.Schema

-- * Test
-- See https://github.com/graphql/graphql-js/blob/master/src/__tests__/starWarsQueryTests.js

spec :: Spec
spec = describe "Star Wars Query Tests" $ do
    describe "Basic Queries" $ do
      it "R2-D2 hero" $ testQuery
        [r| query HeroNameQuery {
            hero {
                id
            }
            }
        |]
        $ object [ "data" .= object ["hero" .= object ["id" .= ("2001" :: Text)]]]
      it "R2-D2 ID and friends" $ testQuery
        [r| query HeroNameAndFriendsQuery {
            hero {
                id
                name
                friends {
                name
                }
            }
            }
        |]
        $ object [ "data" .= object [
            "hero" .= object
                [ "id" .= ("2001" :: Text)
                , r2d2Name
                , "friends" .=
                    [ object [lukeName]
                    , object [hanName]
                    , object [leiaName]
                    ]
                ]
        ]]

    describe "Nested Queries" $ do
      it "R2-D2 friends" $ testQuery
        [r| query NestedQuery {
              hero {
                name
                friends {
                  name
                  appearsIn
                    friends {
                      name
                    }
                }
              }
            }
        |]
        $ object [ "data" .= object [
          "hero" .= object [
              "name" .= ("R2-D2" :: Text)
            , "friends" .= [
                  object [
                      "name" .= ("Luke Skywalker" :: Text)
                    , "appearsIn" .= ["NEWHOPE","EMPIRE","JEDI" :: Text]
                    , "friends" .= [
                          object [hanName]
                        , object [leiaName]
                        , object [c3poName]
                        , object [r2d2Name]
                        ]
                    ]
                , object [
                      hanName
                    , "appearsIn" .= [ "NEWHOPE","EMPIRE","JEDI" :: Text]
                    , "friends" .= [
                          object [lukeName]
                        , object [leiaName]
                        , object [r2d2Name]
                        ]
                    ]
                , object [
                      leiaName
                    , "appearsIn" .= [ "NEWHOPE","EMPIRE","JEDI" :: Text]
                    , "friends" .= [
                          object [lukeName]
                        , object [hanName]
                        , object [c3poName]
                        , object [r2d2Name]
                        ]
                    ]
                ]
            ]
        ]]
      it "Luke ID" $ testQuery
        [r| query FetchLukeQuery {
              human(id: "1000") {
                name
              }
            }
        |]
        $ object [ "data" .= object [
          "human" .= object [lukeName]
        ]]

    it "Luke ID with variable" $ testQueryParams
      (\v -> if v == "someId" then Just "1000" else Nothing)
      [r| query FetchSomeIDQuery($someId: String!) {
            human(id: $someId) {
              name
            }
          }
      |]
      $ object [ "data" .= object [
        "human" .= object [lukeName]
      ]]
    it "Han ID with variable" $ testQueryParams
      (\v -> if v == "someId" then Just "1002" else Nothing)
      [r| query FetchSomeIDQuery($someId: String!) {
            human(id: $someId) {
              name
            }
          }
      |]
      $ object [ "data" .= object [
        "human" .= object [hanName]
      ]]
    it "Invalid ID" $ testQueryParams
      (\v -> if v == "id" then Just "Not a valid ID" else Nothing)
      [r| query humanQuery($id: String!) {
            human(id: $id) {
              name
            }
          }
      |] $ object ["data" .= object ["human" .= Aeson.Null]]
    it "Luke aliased" $ testQuery
      [r| query FetchLukeAliased {
            luke: human(id: "1000") {
              name
            }
          }
      |]
      $ object [ "data" .= object [
       "luke" .= object [lukeName]
      ]]
    it "R2-D2 ID and friends aliased" $ testQuery
      [r| query HeroNameAndFriendsQuery {
            hero {
              id
              name
              friends {
                friendName: name
              }
            }
          }
      |]
      $ object [ "data" .= object [
        "hero" .= object [
            "id" .= ("2001" :: Text)
          , r2d2Name
          , "friends" .= [
                object ["friendName" .= ("Luke Skywalker" :: Text)]
              , object ["friendName" .= ("Han Solo" :: Text)]
              , object ["friendName" .= ("Leia Organa" :: Text)]
              ]
          ]
      ]]
    it "Luke and Leia aliased" $ testQuery
      [r| query FetchLukeAndLeiaAliased {
            luke: human(id: "1000") {
              name
            }
            leia: human(id: "1003") {
              name
            }
          }
      |]
      $ object [ "data" .= object [
        "luke" .= object [lukeName]
      , "leia" .= object [leiaName]
      ]]

    describe "Fragments for complex queries" $ do
      it "Aliases to query for duplicate content" $ testQuery
        [r| query DuplicateFields {
              luke: human(id: "1000") {
                name
                homePlanet
              }
              leia: human(id: "1003") {
                name
                homePlanet
              }
            }
        |]
        $ object [ "data" .= object [
          "luke" .= object [lukeName, tatooine]
        , "leia" .= object [leiaName, alderaan]
        ]]
      it "Fragment for duplicate content" $ testQuery
        [r|  query UseFragment {
              luke: human(id: "1000") {
                ...HumanFragment
              }
              leia: human(id: "1003") {
                ...HumanFragment
              }
            }
            fragment HumanFragment on Human {
              name
              homePlanet
            }
        |]
        $ object [ "data" .= object [
          "luke" .= object [lukeName, tatooine]
        , "leia" .= object [leiaName, alderaan]
        ]]

    describe "__typename" $ do
      it "R2D2 is a Droid" $ testQuery
        [r| query CheckTypeOfR2 {
              hero {
                __typename
                name
              }
            }
          |]
        $ object ["data" .= object [
            "hero" .= object ["__typename" .= ("Droid" :: Text), r2d2Name]
        ]]
      it "Luke is a human" $ testQuery
        [r| query CheckTypeOfLuke {
              hero(episode: EMPIRE) {
                __typename
                name
              }
            }
          |]
        $ object ["data" .= object [
            "hero" .= object ["__typename" .= ("Human" :: Text), lukeName]
        ]]

    describe "Errors in resolvers" $ do
        it "error on secretBackstory" $ testQuery
          [r|
            query HeroNameQuery {
              hero {
                name
                  secretBackstory
              }
            }
          |]
          $ object
              [ "data" .= object
                  [ "hero" .= object
                      [ "name" .= ("R2-D2" :: Text)
                      , "secretBackstory" .= Aeson.Null
                      ]
                  ]
              , "errors" .=
                  [ object
                      ["message" .= ("secretBackstory is secret." :: Text)]
                  ]
              ]
        it "Error in a list" $ testQuery
          [r| query HeroNameQuery {
                hero {
                  name
                  friends {
                    name
                    secretBackstory
                  }
                }
              }
            |]
          $ object ["data" .= object
            [ "hero" .= object
                [ "name" .= ("R2-D2" :: Text)
                , "friends" .=
                    [ object
                        [ "name" .= ("Luke Skywalker" :: Text)
                        , "secretBackstory" .= Aeson.Null
                        ]
                    , object
                        [ "name" .= ("Han Solo" :: Text)
                        , "secretBackstory" .= Aeson.Null
                        ]
                    , object
                        [ "name" .= ("Leia Organa" :: Text)
                        , "secretBackstory" .= Aeson.Null
                        ]
                    ]
                ]
            ]
            , "errors" .=
                [ object ["message" .= ("secretBackstory is secret." :: Text)]
                , object ["message" .= ("secretBackstory is secret." :: Text)]
                , object ["message" .= ("secretBackstory is secret." :: Text)]
                ]
            ]
        it "error on secretBackstory with alias" $ testQuery
          [r| query HeroNameQuery {
                mainHero: hero {
                  name
                  story: secretBackstory
                }
              }
            |]
          $ object
              [ "data" .= object
                  [ "mainHero" .= object
                      [ "name" .= ("R2-D2" :: Text)
                      , "story" .= Aeson.Null
                      ]
                  ]
              , "errors" .=
                  [ object ["message" .= ("secretBackstory is secret." :: Text)]
                  ]
              ]

  where
    lukeName = "name" .= ("Luke Skywalker" :: Text)
    leiaName = "name" .= ("Leia Organa" :: Text)
    hanName = "name" .= ("Han Solo" :: Text)
    r2d2Name = "name" .= ("R2-D2" :: Text)
    c3poName = "name" .= ("C-3PO" :: Text)
    tatooine = "homePlanet" .= ("Tatooine" :: Text)
    alderaan = "homePlanet" .= ("Alderaan" :: Text)

testQuery :: Text -> Aeson.Value -> Expectation
testQuery q expected = graphql schema q >>= flip shouldBe expected

testQueryParams :: Subs -> Text -> Aeson.Value -> Expectation
testQueryParams f q expected = graphqlSubs schema f q >>= flip shouldBe expected