Commit 39138557 authored by Yuanle Song's avatar Yuanle Song
Browse files

add unit test for rd-api

parent 2b51fd08
Loading
Loading
Loading
Loading
+4 −19
Original line number Diff line number Diff line
{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Control.Monad.IO.Class (liftIO)
import GHC.Exts (fromList)

import Web.Scotty
import Data.Aeson (Value(..), toJSON, object, (.=))

import RD.Lib (sha1sum)
import App (app)

main :: IO ()
main = scotty 8082 $ do
  get "/" $ do
    json $ Object $ fromList [("ok", Bool True)
                             ,("app", "reliable-download api")]
  get "/debug/t1" $ do
    sha1 <- liftIO $ sha1sum "/home/sylecn/persist/cache/ideaIC-2018.1.tar.gz"
    json $ object ["ok" .= True
                  ,"sha1sum" .= sha1]
  get "/:word" $ do
    beam <- param "word"
    sha1 <- liftIO $ sha1sum beam
    html $ mconcat ["sha1sum for ", beam, " is ", sha1, "."]
main = do
  putStrLn "listening on 0.0.0.0:8082"
  scotty 8082 app
+95 −0
Original line number Diff line number Diff line
@@ -3,8 +3,29 @@
Time-stamp: <2018-05-05>
#+STARTUP: content
* notes                                                               :entry:
** 2018-05-05 writing tests in hspec
- Test WAI application using hspec
  hspec/hspec-wai: Helpers to test WAI application with Hspec
  https://github.com/hspec/hspec-wai
  Here has example. good.
  Hspec: A Testing Framework for Haskell
  http://hspec.github.io/

- Test.Hspec.Expectations
  https://hackage.haskell.org/package/hspec-expectations-0.8.2/docs/Test-Hspec-Expectations.html#v:Expectation
  shouldBe
  shouldStartWith
  shouldEndWith
  shouldContain
  etc

** 2018-05-05 Data.Aeson
http://hackage.haskell.org/package/aeson-1.3.1.0/docs/Data-Aeson.html
// this seems more readable than lts haskell's doc.

Aeson: the tutorial
https://artyom.me/aeson

** 2018-05-05 sol/hpack: hpack: An alternative format for Haskell packages
https://github.com/sol/hpack
** 2018-05-04 make hoogle work in current project
@@ -36,9 +57,42 @@ scotty/examples at master · scotty-web/scotty
https://github.com/scotty-web/scotty/tree/master/examples

** 2018-05-04 for project notes, see GTD.org id002
** 2018-05-05 API spec
- POST /rd
  json body
  {"path": "/path/to/resource"}
- 

* later                                                               :entry:
** 2018-05-05 should rd-api serve the static file itself?
that way you don't need to tell rd-server the web root dir.
- normally
  nginx serve static files under $WEB_ROOT

  rd-server --web-root $WEB_ROOT
  this serve /rd/.*, provides metadata for files.

- it may be faster if you just do
  rd-server --web-root $WEB_ROOT
  This serves both /rd/.* and /.*

* current                                                             :entry:
** 
** 2018-05-05 utf-8 character not working well.
curl http://localhost:8082/rd/%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.rar
{"ok":true,"path":"中"}
only first character is in path key.

- a scotty bug?
  full_path is also wrong. not regexp problem.
- well, since this encoding doesn't work well. I think I will pass the path in
  json body instead of in the URL.

  I don't need it in the URL anyway. download must be handled by a rd client.
- search: haskell scotty path utf-8 character

  check source code.

** 2018-05-04 check whether haskell is viable for this project.
In static file hosting nginx reverse proxy /rd/ to rd application server.

@@ -63,4 +117,45 @@ location /rd/ {
- 

* done                                                                :entry:
** 2018-05-05 how to test scotty web api
wai-test: Unit test framework (built on HUnit) for WAI applications. (deprecated)
http://hackage.haskell.org/package/wai-test
merged to wai-extra

wai-extra: Provides some basic WAI handlers and middleware.
http://hackage.haskell.org/package/wai-extra

hspec/hspec-wai: Helpers to test WAI application with Hspec
https://github.com/hspec/hspec-wai
Here has example. good.
Hspec: A Testing Framework for Haskell
http://hspec.github.io/

- can I write test without those describe and it strings? no.
- how to check some part of the response body?
  requires a WaiExpectation in the do block.

  type WaiExpectation = WaiSession ()

  shouldRespondWith (get "/rd/") 200

  source code
  https://hackage.haskell.org/package/hspec-wai-0.9.0/docs/src/Test-Hspec-Wai.html#shouldRespondWith

  shouldBe :: (HasCallStack, Show a, Eq a) => a -> a -> Expectation

  I can't use shouldBe there. because I need a WaiExpectation.

  This worked:
  resp <- get "/rd/"
  liftIO (simpleStatus resp `shouldBe` status200)

** 2018-05-05 can I start scotty web server and stop it in unit test?
nope.
it returns IO (), there is no PID or something.
I can use haskell fork though.

** 2018-05-05 can I pattern match on URL with / in them?
yes. see Route Patterns.

* wontfix                                                             :entry:
+17 −1
Original line number Diff line number Diff line
@@ -14,9 +14,11 @@ extra-source-files:

default-extensions:
  - OverloadedStrings
  - ScopedTypeVariables

dependencies:
  - base >= 4.7 && < 5
  - wai
  - scotty
  - cryptohash
  - bytestring
@@ -38,8 +40,22 @@ executables:
    main:             Main.hs

tests:
  api-test:
    main: TestApi.hs
    source-dirs: test
    dependencies:
    - reliable-download
    - HUnit
    - hspec
    - hspec-wai
    - http-types
    - wai-extra
    - unordered-containers
    - binary
  lib-test:
    main: TestLib.hs
    main: test.hs
    other-modules:
      - TestLib
    source-dirs: test
    dependencies:
    - reliable-download

src/App.hs

0 → 100644
+29 −0
Original line number Diff line number Diff line
module App (app, waiApp) where

import Control.Monad.IO.Class (liftIO)

import Network.Wai (Application)
import Web.Scotty
import Data.Aeson (Value(..), toJSON, object, (.=))
import qualified Data.Text.Lazy as LT

import RD.Lib (sha1sum)

app :: ScottyM ()
app = do
  get (literal "/rd/") $ do
    json $ object [("ok" .= True)
                  ,("app" .= ("reliable-download api" :: String))]
  get (regex "^/rd/(.*)") $ do
    path :: LT.Text <- param "1"
    fullPath :: LT.Text <- param "0"
    json $ object [("ok" .= True)
                  ,("full_path" .= fullPath)
                  ,("path" .= path)]
  get "/rd/debug/t1" $ do
    sha1 <- liftIO $ sha1sum "/home/sylecn/persist/cache/ideaIC-2018.1.tar.gz"
    json $ object ["ok" .= True
                  ,"sha1sum" .= sha1]

waiApp :: IO Application
waiApp = scottyApp app

test/TestApi.hs

0 → 100644
+105 −0
Original line number Diff line number Diff line
-- module TestApi (main) where

-- GET /rd/some_complicated_url_safe_text

import Test.Hspec
import Test.Hspec.Wai
import Network.Wai.Test
import Network.HTTP.Types.Status
import Network.HTTP.Types.URI (encodePathSegments, decodePathSegments)
import Data.Binary.Builder (toLazyByteString)

import qualified Data.Aeson as J
import qualified Data.ByteString.Lazy as LB
import qualified Data.Text as T
import qualified Data.HashMap.Strict as H

import App (waiApp)

-- | decode response as json object.
jsonObject :: SResponse -> Maybe J.Object
jsonObject resp = J.decode $ simpleBody resp

-- | decode response as json object, then retrieve a key.
jsonKey :: SResponse -> T.Text -> Maybe J.Value
jsonKey resp key = do
  map <- jsonObject resp
  H.lookup key map

jsonKeyAsBool :: SResponse -> T.Text -> Maybe Bool
jsonKeyAsBool resp key = do
  v <- jsonKey resp key
  case v of
    J.Bool b -> Just b
    _ -> Nothing

jsonKeyAsText :: SResponse -> T.Text -> Maybe T.Text
jsonKeyAsText resp key = do
  v <- jsonKey resp key
  case v of
    J.String b -> Just b
    _ -> Nothing

spec :: Spec
spec = do
  describe "dumb" $ do
    it "should be that" $ do
      True `shouldBe` True
  describe "3rd party libs" $ do
    it "should encode and decode utf-8 characters in URL" $ do
      ((decodePathSegments . LB.toStrict . toLazyByteString . encodePathSegments) ["中文1", "路径2"]) `shouldBe` ["中文1", "路径2"]

-- | like get, but accept path in [T.Text] format and do url safe encoding.
getPath :: [T.Text] -> WaiSession SResponse
getPath = get . LB.toStrict . toLazyByteString . encodePathSegments

apiSpec :: Spec
apiSpec = with waiApp $ do
  describe "rd api" $ do
    it "has health check" $ do
      resp <- get "/rd/"
      liftIO (simpleStatus resp `shouldBe` status200)
      liftIO ((jsonKeyAsBool resp "ok") `shouldBe` Just True)
      -- liftIO (shouldSatisfy 1 (\x -> True))
      liftIO ((fmap (T.isInfixOf "reliable-download")
                    (jsonKeyAsText resp "app"))
              `shouldBe` Just True)

    it "only respond with /rd/ prefix" $ do
      get "/" `shouldRespondWith` 404
      get "/abc" `shouldRespondWith` 404

    it "should parse basic path correctly" $ do
      resp <- get "/rd/abc"
      liftIO (simpleStatus resp `shouldBe` status200)
      liftIO ((jsonKeyAsBool resp "ok") `shouldBe` Just True)
      liftIO ((jsonKeyAsText resp "path") `shouldBe` Just "abc")
      resp <- get "/rd/abc/def"
      liftIO (simpleStatus resp `shouldBe` status200)
      liftIO ((jsonKeyAsBool resp "ok") `shouldBe` Just True)
      liftIO ((jsonKeyAsText resp "path") `shouldBe` Just "abc/def")
      resp <- get "/rd/abc/def/"
      liftIO (simpleStatus resp `shouldBe` status200)
      liftIO ((jsonKeyAsBool resp "ok") `shouldBe` Just True)
      liftIO ((jsonKeyAsText resp "path") `shouldBe` Just "abc/def/")

    it "should parse complex path correctly" $ do
      resp <- getPath ["rd", "abc def # ? ghi"]
      liftIO (simpleStatus resp `shouldBe` status200)
      liftIO ((jsonKeyAsBool resp "ok") `shouldBe` Just True)
      liftIO ((jsonKeyAsText resp "path") `shouldBe` Just "abc def # ? ghi")

      resp <- getPath ["rd", "abc/def.jpg"]
      liftIO (simpleStatus resp `shouldBe` status200)
      liftIO ((jsonKeyAsBool resp "ok") `shouldBe` Just True)
      liftIO ((jsonKeyAsText resp "path") `shouldBe` Just "abc/def.jpg")

      -- resp <- getPath ["rd", "中文文件名.rar"]
      -- liftIO (simpleStatus resp `shouldBe` status200)
      -- liftIO ((jsonKeyAsBool resp "ok") `shouldBe` Just True)
      -- liftIO ((jsonKeyAsText resp "path") `shouldBe` Just "中文文件名.rar")

main :: IO ()
main = do
  hspec spec
  hspec apiSpec
Loading