Используйте функции, которые возвращают «Текст обработчика» в yesod-tests

#haskell #yesod

#haskell #yesod

Вопрос:

Вот (упрощенный) тест, который у меня есть

 it "asserts route access for valid arguments" $ do
    -- ... 
    token <- getFlagTokenFromCsrf "1234"

    get $ FlagMentorR flaggedId Flag token
    statusIs 200
  

где у меня есть getFlagTokenFromCsrf :: Text -> Handler Text

Ошибка, которую я получаю при попытке stack test :

 Couldn't match typeYesod.Core.Types.HandlerT App IO Text
               with ‘Control.Monad.Trans.State.Lazy.StateT
                       (YesodExampleData App) IO TextExpected type: Control.Monad.Trans.State.Lazy.StateT
                 (YesodExampleData App) IO Text
  Actual type: Handler Text
  

(Вот полный пример)

Ответ №1:

Кажется, я пытаюсь сделать что-то довольно похожее. То есть: попробуйте запустить произвольные Handler a функции в модульных тестах.

Некоторые подходы, которые я пробовал и обдумывал:

  1. Самое близкое, что я получил, действительно сработало для меня:

     import Application (handler)
    
    spec = withApp $ do
      it "" $ do
        liftIO $ handler $ someHandler
      

    Хотя кажется, что это вызывает makeFoundation скрытый вызов, и я бы предпочел, чтобы мой обработчик выполнялся в контексте основного веб-приложения, а не отдельного экземпляра.

  2. Чтобы выполнить обработчик в контексте основного приложения, я также попытался:

     import Foundation (unsafeHandler)
    import qualified Control.Monad.Trans.State as ST
    
    spec = withApp $ do
        it "" $ do
          (YesodExampleData app _ _ _) <- ST.get
          liftIO $ unsafeHandler app $ someHandler
      

    Что привело меня к ошибке типа, подобной этой:

     [34 of 35] Compiling Handler.FooSpec ( /path/to/test/Handler/FooSpec.hs, /path/to/.stack-work/odir/Handler/FooSpec.o )
    
    /path/to/test/Handler/FooSpec.hs:42:31:
        Couldn't match typeNetwork.Wai.Internal.Request
                             -> (Network.Wai.Internal.Response
                                 -> IO Network.Wai.Internal.ResponseReceived)
                             -> IO Network.Wai.Internal.ResponseReceived’
                       with ‘AppExpected type: App
          Actual type: Network.Wai.Application
        Probable cause: ‘app’ is applied to too few arguments
        In the first argument of ‘unsafeHandler’, namely ‘app’
        In the expression: unsafeHandler app
    Failed, modules loaded: Import, Utils, Foundation, [...]
      

    Я не совсем понимаю, в чем может быть разница между Network.Wai.Application и App , но это может быть ключом к выполнению этой работы.

  3. Я также подумал о том, чтобы указать маршрут к обработчику, и тогда он будет доступен через get SomeRouteR . Хотя я бы предпочел не делать этого, если смогу помочь.

  4. Я также рассматриваю возможность рефакторинга некоторых моих Handler a функций IO a , а затем я могу использовать liftIO их для выполнения не только в обработчиках, но и в тестах.

Это все, что я получил. Если я выясню что-то еще, я намерен обновить этот ответ. Я также задал этот вопрос, поэтому я получаю уведомление, если кто-то еще выяснит что-то большее или лучшее.

Редактировать: я также задавал этот вопрос на GitHub.

Редактировать:

Я нашел что-то довольно многообещающее, пытаясь копать дальше в направлении варианта # 2:

 import Foundation (unsafeHandler)

runHandler :: Handler a -> ST.StateT (YesodExampleData App) IO a
runHandler handler = do
  foundation <- getTestYesod
  liftIO $ unsafeHandler foundation handler
  

На котором я смог запустить тест следующим образом:

 it "runHandler" $ do
  let
    testHandler :: Handler Int
    testHandler = do
      return 2
  runHandler testHandler >>= (==? 2)

  let
    testHandler2 :: Handler String
    testHandler2 = do
      fmap show $ (Import.runDB) $ insert (def :: User)
  runHandler testHandler2 >>= (==? ("UserKey {unUserKey = SqlBackendKey {unSqlBackendKey = 1}}"))
  

Ответ №2:

Я не эксперт по Yesod, но то, что вы пытаетесь сделать, вероятно, не имеет смысла: getFlagTokenFromCsrf это серверная функция, которая извлекает некоторую информацию из запроса, следовательно, она живет в Handler a монаде, потому что это монада для серверного кода.

В отличие от этого, тесты Yesod предназначены для интеграционных тестов, утверждающих поведение на стороне клиента в отношении вашего Application (см. https://hackage.haskell.org/package/yesod-test-1.5.3/docs/Yesod-Test.html ). Функции там живут в другой монаде, а именно YesodExample site a , которая управляет запросами от вашего имени. Эта монада позволяет вам повторно использовать часть вашего серверного кода (маршруты, типы данных) и управлять состоянием вашей БД, но в остальном она совершенно не связана с операциями на стороне сервера.

Я не уверен, что вы пытаетесь сделать, но если вам нужна какая-то информация, сгенерированная на стороне сервера, вам нужно найти другой способ сделать ее доступной на стороне клиента.

Комментарии:

1. Я понимаю. Я думал, что можно было бы также использовать модульные тесты.

2. ДА. Я думаю, что модульные тесты в этом конкретном случае имели бы большой смысл. Или найдите способ ввести известный токен на стороне клиента, чтобы вы могли проверить из ответа сервера, действительно ли он проверил токен