freckle-http 0.0.0.0 → 0.1.0.0
raw patch · 8 files changed
+195/−69 lines, 8 filesdep +freckle-preludedep ~Blammodep ~freckle-memcacheddep ~hspecPVP ok
version bump matches the API change (PVP)
Dependencies added: freckle-prelude
Dependency ranges changed: Blammo, freckle-memcached, hspec
API changes (from Hackage documentation)
- Freckle.App.HttpSpec: spec :: Spec
Files
- CHANGELOG.md +5/−1
- README.md +6/−0
- freckle-http.cabal +10/−9
- library/Freckle/App/HttpSpec.hs +0/−51
- library/Freckle/App/Test/Http.hs +1/−1
- package.yaml +7/−7
- tests/Freckle/App/HttpSpec.hs +51/−0
- tests/Freckle/App/Test/Http/MatchRequestSpec.hs +115/−0
CHANGELOG.md view
@@ -1,4 +1,8 @@-## [_Unreleased_](https://github.com/freckle/freckle-app/compare/freckle-http-v0.0.0.0...main)+## [_Unreleased_](https://github.com/freckle/freckle-app/compare/freckle-http-v0.1.0.0...main)++## [v0.1.0.0](https://github.com/freckle/freckle-app/compare/freckle-http-v0.0.0.0...freckle-http-v0.1.0.0)++Removes `Freckle.App.HttpSpec` which had been included by mistake. ## [v0.0.0.0](https://github.com/freckle/freckle-app/tree/freckle-http-v0.0.0.0/freckle-http)
README.md view
@@ -1,1 +1,7 @@ # freckle-http++`freckle-http` is a general-purpose toolkit for making HTTP requests.++---++[CHANGELOG](./CHANGELOG.md) | [LICENSE](./LICENSE)
freckle-http.cabal view
@@ -1,14 +1,14 @@ cabal-version: 1.18 name: freckle-http-version: 0.0.0.0+version: 0.1.0.0 license: MIT license-file: LICENSE maintainer: Freckle Education homepage: https://github.com/freckle/freckle-app#readme bug-reports: https://github.com/freckle/freckle-app/issues-synopsis: ...+synopsis: Toolkit for making HTTP requests description: Please see README.md-category: Utils+category: HTTP build-type: Simple extra-source-files: package.yaml extra-doc-files:@@ -29,7 +29,6 @@ Freckle.App.Http.Header Freckle.App.Http.Paginate Freckle.App.Http.Retry- Freckle.App.HttpSpec Freckle.App.Test.Http Freckle.App.Test.Http.MatchRequest @@ -51,7 +50,7 @@ -Wno-safe -Wno-unsafe build-depends:- Blammo >=2.0.0.0,+ Blammo >=2.1.0.0, Glob >=0.10.2, aeson >=2.0.3.0, annotated-exception >=0.2.0.4,@@ -63,15 +62,13 @@ errors >=2.3.0, extra >=1.7.13, filepath >=1.4.2.2,- freckle-memcached >=0.0.0.1,+ freckle-memcached >=0.0.0.2, hs-opentelemetry-api >=0.1.0.0,- hspec >=2.8.1, http-client >=0.7.13.1, http-conduit >=2.3.5, http-link-header >=1.2.1, http-types >=0.12.3, lens >=5.1.1,- lens-aeson >=1.2.2, memcache >=0.3.0.1, monad-logger >=0.3.40, monad-validate >=1.3.0.0,@@ -97,6 +94,8 @@ hs-source-dirs: tests other-modules: Freckle.App.Http.CacheSpec+ Freckle.App.HttpSpec+ Freckle.App.Test.Http.MatchRequestSpec Paths_freckle_http default-language: GHC2021@@ -119,11 +118,13 @@ base >=4.16.4.0 && <5, bytestring >=0.11.4.0, freckle-http,- hspec >=2.10.10,+ freckle-prelude >=0.0.1.1,+ hspec >=2.8.1, hspec-expectations-json >=1.0.0.7, hspec-expectations-lifted >=0.10.0, http-types >=0.12.3, lens >=5.1.1,+ lens-aeson >=1.2.2, mtl >=2.2.2, time >=1.11.1.1, unordered-containers >=0.2.19.1,
− library/Freckle/App/HttpSpec.hs
@@ -1,51 +0,0 @@-module Freckle.App.HttpSpec- ( spec- ) where--import Prelude--import Control.Lens (to, (^?), _Left, _Right)-import Data.Aeson-import Data.Aeson.Lens-import Data.List.NonEmpty qualified as NE-import Freckle.App.Http-import Freckle.App.Test.Http-import Network.HTTP.Types.Status (status200)-import Test.Hspec--spec :: Spec-spec = do- describe "httpJson" $ do- stubs <- runIO $ loadHttpStubsDirectory "tests/files"-- it "fetches JSON via HTTP" $ do- resp <-- flip runHttpStubsT stubs- . httpJson @_ @Value- $ parseRequest_ "https://www.stackage.org/lts-17.10"-- getResponseStatus resp `shouldBe` status200- getResponseBody resp- ^? _Right- . key "snapshot"- . key "ghc"- . _String- `shouldBe` Just "8.10.4"-- it "places JSON parse errors in a Left body" $ do- resp <-- flip runHttpStubsT stubs- . httpJson @_ @[()]- $ parseRequest_ "https://www.stackage.org/lts-17.10"-- let expectedErrorMessages =- [ "Error in $: expected [a], encountered Object"- , "Error in $: parsing [] failed, expected Array, but encountered Object"- ]-- getResponseStatus resp `shouldBe` status200- getResponseBody resp- ^? _Left- . to hdeErrors- . to NE.head- `shouldSatisfy` maybe False (`elem` expectedErrorMessages)
library/Freckle/App/Test/Http.hs view
@@ -191,7 +191,7 @@ -- | Load stubs from the filesystem -- -- Within the given directory, files are expected to be named for scheme, then--- host, then path/port/query.+-- host, then path\/port\/query. -- -- Given, --
package.yaml view
@@ -1,9 +1,9 @@ name: freckle-http-version: 0.0.0.0+version: 0.1.0.0 maintainer: Freckle Education-category: Utils+category: HTTP github: freckle/freckle-app-synopsis: ...+synopsis: Toolkit for making HTTP requests description: Please see README.md extra-doc-files:@@ -58,7 +58,7 @@ source-dirs: library dependencies: - Glob- - Blammo >= 2.0.0.0+ - Blammo - aeson - annotated-exception - bytestring@@ -70,13 +70,11 @@ - filepath - freckle-memcached - hs-opentelemetry-api- - hspec >= 2.8.1 - http-client - http-conduit >= 2.3.5 # addToRequestQueryString - http-link-header - http-types - lens- - lens-aeson - memcache - monad-logger - monad-validate@@ -101,11 +99,13 @@ - aeson - bytestring - freckle-http- - hspec+ - freckle-prelude+ - hspec >= 2.8.1 - hspec-expectations-json - hspec-expectations-lifted - http-types - lens+ - lens-aeson - mtl - time - unordered-containers
+ tests/Freckle/App/HttpSpec.hs view
@@ -0,0 +1,51 @@+module Freckle.App.HttpSpec+ ( spec+ ) where++import Prelude++import Control.Lens (to, (^?), _Left, _Right)+import Data.Aeson+import Data.Aeson.Lens+import Data.List.NonEmpty qualified as NE+import Freckle.App.Http+import Freckle.App.Test.Http+import Network.HTTP.Types.Status (status200)+import Test.Hspec++spec :: Spec+spec = do+ describe "httpJson" $ do+ stubs <- runIO $ loadHttpStubsDirectory "tests/files"++ it "fetches JSON via HTTP" $ do+ resp <-+ flip runHttpStubsT stubs+ . httpJson @_ @Value+ $ parseRequest_ "https://www.stackage.org/lts-17.10"++ getResponseStatus resp `shouldBe` status200+ getResponseBody resp+ ^? _Right+ . key "snapshot"+ . key "ghc"+ . _String+ `shouldBe` Just "8.10.4"++ it "places JSON parse errors in a Left body" $ do+ resp <-+ flip runHttpStubsT stubs+ . httpJson @_ @[()]+ $ parseRequest_ "https://www.stackage.org/lts-17.10"++ let expectedErrorMessages =+ [ "Error in $: expected [a], encountered Object"+ , "Error in $: parsing [] failed, expected Array, but encountered Object"+ ]++ getResponseStatus resp `shouldBe` status200+ getResponseBody resp+ ^? _Left+ . to hdeErrors+ . to NE.head+ `shouldSatisfy` maybe False (`elem` expectedErrorMessages)
+ tests/Freckle/App/Test/Http/MatchRequestSpec.hs view
@@ -0,0 +1,115 @@+module Freckle.App.Test.Http.MatchRequestSpec+ ( spec+ ) where++import Freckle.App.Prelude++import Freckle.App.Http+ ( Request+ , addAcceptHeader+ , addRequestHeader+ , parseRequest_+ , setRequestPath+ )+import Freckle.App.Test.Http.MatchRequest+import Network.HTTP.Types.Header (hAccept)+import Test.Hspec++spec :: Spec+spec = do+ describe "matchRequestFromUrl" $ do+ context "matching complete requests" $ do+ let+ url = "https://localhost:3000/hello?world"+ mr = matchRequestFromUrl url++ it "matches the same request" $ do+ mr `shouldMatchRequest` parseRequest_ url++ it "matches on path, even if built relative" $ do+ let req = parseRequest_ "https://localhost:3000/world?world"+ mr `shouldMatchRequest` setRequestPath "hello" req++ it "matches on method" $ do+ mr `shouldNotMatchRequest` parseRequest_ ("POST " <> url)++ it "matches on scheme" $ do+ mr `shouldNotMatchRequest` parseRequest_ "http://localhost:3000/hello?world"++ it "matches on host" $ do+ mr `shouldNotMatchRequest` parseRequest_ "https://localhost2:3000/hello?world"++ it "matches on port" $ do+ mr `shouldNotMatchRequest` parseRequest_ "https://localhost:3001/hello?world"++ it "matches on path" $ do+ mr `shouldNotMatchRequest` parseRequest_ "https://localhost:3000/world?world"++ it "matches on query" $ do+ mr `shouldNotMatchRequest` parseRequest_ "https://localhost:3000/hello?wut"++ it "can match on everything unspecified" $ do+ let mr = matchRequestFromUrl "https://"++ mr `shouldMatchRequest` parseRequest_ "https://example.com"+ mr `shouldMatchRequest` parseRequest_ "https://example.com/"+ mr `shouldMatchRequest` parseRequest_ "https://example.com/foo"+ mr `shouldMatchRequest` parseRequest_ "https://example.com/bar"++ it "can match any path" $ do+ let mr = matchRequestFromUrl "https://example.com"++ mr `shouldMatchRequest` parseRequest_ "https://example.com"+ mr `shouldMatchRequest` parseRequest_ "https://example.com/"+ mr `shouldMatchRequest` parseRequest_ "https://example.com/foo"+ mr `shouldMatchRequest` parseRequest_ "https://example.com/bar"++ it "can match the root path explicitly" $ do+ let mr = matchRequestFromUrl "https://example.com/"++ mr `shouldMatchRequest` parseRequest_ "https://example.com/"+ mr `shouldMatchRequest` parseRequest_ "https://example.com"+ mr `shouldNotMatchRequest` parseRequest_ "https://example.com/foo"+ mr `shouldNotMatchRequest` parseRequest_ "https://example.com/bar"++ context "matching headers" $ do+ let+ url = "https://example.com/"+ accept = "text/plain"+ hasNoHeaders = parseRequest_ url+ hasOnlyTheHeader = addAcceptHeader accept hasNoHeaders+ hasExtraHeaders = addRequestHeader "User-Agent" "me" hasOnlyTheHeader++ it "can match headers exactly" $ do+ let mr = matchRequestFromUrl url <> MatchHeaders [(hAccept, accept)]++ mr `shouldMatchRequest` hasOnlyTheHeader+ mr `shouldNotMatchRequest` hasExtraHeaders+ mr `shouldNotMatchRequest` hasNoHeaders++ it "can match headers-include" $ do+ let mr = matchRequestFromUrl url <> MatchHeader (hAccept, accept)++ mr `shouldMatchRequest` hasOnlyTheHeader+ mr `shouldMatchRequest` hasExtraHeaders+ mr `shouldNotMatchRequest` hasNoHeaders++shouldMatchRequest :: HasCallStack => MatchRequest -> Request -> IO ()+mr `shouldMatchRequest` req = do+ case matchRequest req mr of+ Left err -> do+ let preamble = unlines ["Expected to match request, but did not", "", show req]+ expectationFailure $ preamble <> err+ Right () -> pure ()++infix 1 `shouldMatchRequest`++shouldNotMatchRequest :: HasCallStack => MatchRequest -> Request -> IO ()+mr `shouldNotMatchRequest` req = do+ case matchRequest req mr of+ Left {} -> pure ()+ Right () -> do+ let preamble = unlines ["Expected to NOT match request, but did", "", show req]+ expectationFailure $ preamble <> showMatchRequest mr++infix 1 `shouldNotMatchRequest`