packages feed

servant-routes-golden (empty) → 0.1.0.0

raw patch · 7 files changed

+348/−0 lines, 7 filesdep +QuickCheckdep +aesondep +aeson-pretty

Dependencies added: QuickCheck, aeson, aeson-pretty, base, hspec, hspec-core, hspec-golden, servant, servant-routes, servant-routes-golden, text

Files

+ CHANGELOG.md view
@@ -0,0 +1,16 @@+# Changelog++All notable changes to `servant-routes-golden` will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),+and this project adheres to [Haskell Package Versioning Policy](https://pvp.haskell.org).++## [Unreleased]++## [0.1.0.0] - 10.06.2025++- Use the `ToJSON` instance of `Routes` to generate golden tests for an API+- Test suite for the package uses these golden tests on a dummy API++[unreleased]: https://github.com/fpringle/servant-routes/compare/golden-v0.1.0.0...HEAD+[0.1.0.0]: https://github.com/fpringle/servant-routes/releases/tag/golden-v0.1.0.0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2025, Frederick Pringle++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Frederick Pringle nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,107 @@+# servant-routes-golden++This package lets us define [Golden tests](https://ro-che.info/articles/2017-12-04-golden-tests) using the `HasRoutes` class from [servant-routes](https://github.com/fpringle/servant-routes/blob/main/servant-routes#readme) and the [hspec-golden](https://hackage.haskell.org/package/hspec-golden) library.++See `Servant.API.Routes.Golden` for reference documentation.++## Example++```haskell+-- file src/API.hs+module API where++import Servant.API++type UserAPI =+  "users"+    :> ( "list" :> Get '[JSON] [User]+          :<|> "create" :> ReqBody '[JSON] UserCreateData :> Post '[JSON] UserID+          :<|> "detail" :> QueryParam' '[Required] "id" UserID :> Get '[JSON] User+       )++-- file test/APISpec.hs++module APISpec where++import API+import Servant.API.Routes.Golden+import Hspec++spec :: Spec+spec =+  it "UserAPI" $ goldenRoutes @UserAPI (show ''UserAPI)+```++We can run `cabal test` to generate the starting "golden file":++```bash+$ cabal test+API+  UserAPI [✔]+    First time execution. Golden file created.+```++Of course, if we run the test again, it should pass:++```bash+$ cabal test+API+  UserAPI [✔]+    Golden and Actual output didn't change+```++But let's say we change the API definition slightly:++```diff+type UserAPI =+  "users"+    :> ( "list" :> Get '[JSON] [User]+-          :<|> "create" :> ReqBody '[JSON] UserCreateData :> Post '[JSON] UserID++          :<|> "create-new" :> ReqBody '[JSON] UserCreateData :> Post '[JSON] UserID+          :<|> "detail" :> QueryParam' '[Required] "id" UserID :> Get '[JSON] User+       )+```++Then when we run the tests again:++```bash+$ cabal test+API+  UserAPI [✘]+    Files golden and actual not match++Failures:++  test/APISpec.hs:9:3: +  1) Servant.API.Routes.Golden UserAPI+       expected: {+                     "/users/create": {+                         "POST": {+                             "auths": [],+                             "description": null,+                             "method": "POST",+                             "params": [],+                             "path": "/users/create",+                             "request_body": "UserCreateData",+                             "request_headers": [],+                             "response": {+                 @@ 45 lines omitted @@+                 +        but got: {+                     "/users/create-new": {+                         "POST": {+                             "auths": [],+                             "description": null,+                             "method": "POST",+                             "params": [],+                             "path": "/users/create-new",+                             "request_body": "UserCreateData",+                             "request_headers": [],+                             "response": {+                 @@ 45 lines omitted @@+```++This forces us to either:++- acknowledge that the golden files should be updated, and do so by running the [hgold CLI](https://github.com/stackbuilders/hspec-golden?tab=readme-ov-file#install-cli), or+- realise that our changes resulted in a change to the API which we didn't anticipate, so we have to fix them.
+ servant-routes-golden.cabal view
@@ -0,0 +1,99 @@+cabal-version:      3.0+name:               servant-routes-golden+version:            0.1.0.0+synopsis:           Golden test your Servant APIs using `servant-routes`+description:+    See the documentation of 'Servant.API.Routes.Golden'.+license:            BSD-3-Clause+license-file:       LICENSE+author:             Frederick Pringle+maintainer:         freddyjepringle@gmail.com+copyright:          Copyright(c) Frederick Pringle 2025+homepage:           https://github.com/fpringle/servant-routes+bug-reports:        https://github.com/fpringle/servant-routes/issues+category:           Servant, Web+build-type:         Simple+extra-doc-files:    CHANGELOG.md+                    README.md++tested-with:+    GHC == 8.8.4+  , GHC == 8.10.7+  , GHC == 9.0.2+  , GHC == 9.2.4+  , GHC == 9.2.8+  , GHC == 9.4.2+  , GHC == 9.4.5+  , GHC == 9.6.1+  , GHC == 9.6.7+  , GHC == 9.8.2+  , GHC == 9.10.2+  , GHC == 9.12.2++source-repository head+  type:           git+  location:       https://github.com/fpringle/servant-routes+  subdir:         servant-routes-golden++common warnings+  ghc-options: -Wall -Wno-unused-do-bind++common deps+  build-depends:+    , base >= 4.13 && < 5+    , aeson >= 2.0 && < 2.3+    , text >= 1.2 && < 2.2+    , aeson-pretty >= 0.8.8 && < 0.9+    , hspec-core >= 2.10.0 && < 2.12+    , hspec-golden >= 0.2 && < 0.3+    , servant-routes >= 0.1.0.0 && < 0.2++common extensions+  default-extensions:+    DeriveGeneric+    DerivingVia +    FlexibleContexts+    FlexibleInstances+    LambdaCase+    NamedFieldPuns+    OverloadedStrings+    PackageImports+    RecordWildCards+    ScopedTypeVariables+    TypeApplications+    ViewPatterns+    DataKinds+    AllowAmbiguousTypes+    TypeFamilies+    TypeOperators++library+  import:+      warnings+    , deps+    , extensions+  ghc-options: -Wunused-packages+  exposed-modules:+    Servant.API.Routes.Golden+  hs-source-dirs:   src+  default-language: Haskell2010++test-suite servant-routes-golden-test+  import:+      warnings+    , deps+    , extensions+  default-language: Haskell2010+  type:             exitcode-stdio-1.0+  hs-source-dirs:   test+  main-is:          Spec.hs+  other-modules:+    Servant.API.Routes.GoldenSpec+  build-tool-depends:+      hspec-discover:hspec-discover+  ghc-options:      -Wno-orphans+  build-depends:+    , servant-routes-golden+    , servant >= 0.17 && < 0.21+    , hspec >= 2.9 && < 2.12+    , QuickCheck >= 2.14 && < 2.16
+ src/Servant/API/Routes/Golden.hs view
@@ -0,0 +1,54 @@+{- |+Module      : Servant.API.Routes.Golden+Copyright   : (c) Frederick Pringle, 2025+License     : BSD-3-Clause+Maintainer  : frederick.pringle@fpringle.com++The 'HasRoutes' class allows us to generate a list of 'Route's from a Servant+API type. Using "hspec-golden", we can generate automatic 'G.Golden' tests from+these APIs. If such a test fails, we know that the shape of our API has changed.+Therefore we must either:++- decide that the shape change is correct, and acknowledge that the golden files+  should be updated, by running the+  [hgold CLI](https://github.com/stackbuilders/hspec-golden?tab=readme-ov-file#install-cli), or+- realise that our changes resulted in a change to the API which we didn't+  intend/anticipate, so we have to fix them.+-}+module Servant.API.Routes.Golden+  ( -- * Generating golden tests using HasRoutes+    goldenRoutes+  , goldenRoutesSpec+  )+where++import Control.Monad ((>=>))+import qualified Data.Aeson as A+import qualified Data.Aeson.Encode.Pretty as P+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Encoding as TLE+import qualified Data.Text.Lazy.IO as TL+import Servant.API.Routes+import qualified Test.Hspec.Core.Spec as H+import qualified Test.Hspec.Golden as G++{- | Given an API type with a 'HasRoutes' instance, we can create a 'G.Golden' test+on the t'Routes' representation of that API. This can be used to automatically track+changes to the API shape during testing. For a concrete example see the+[README](https://github.com/fpringle/servant-routes/blob/main/servant-routes-golden/README.md).+-}+goldenRoutes :: forall api. (HasRoutes api) => String -> G.Golden A.Value+goldenRoutes name =+  (G.defaultGolden name "")+    { G.output = A.toJSON . Routes $ getRoutes @api+    , G.encodePretty = T.unpack . TL.toStrict . pretty+    , G.writeToFile = \fp -> TL.writeFile fp . pretty+    , G.readFromFile = A.eitherDecodeFileStrict @A.Value >=> either fail pure+    }+  where+    pretty = TLE.decodeUtf8 . P.encodePretty' (P.defConfig {P.confCompare = compare})++-- | Generate a 'H.Spec' for your API type.+goldenRoutesSpec :: forall api. (HasRoutes api) => String -> H.Spec+goldenRoutesSpec = H.it "Generates the correct Routes" . goldenRoutes @api
+ test/Servant/API/Routes/GoldenSpec.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE TemplateHaskellQuotes #-}++module Servant.API.Routes.GoldenSpec+  ( spec+  )+where++import qualified Data.Text as T+import GHC.Generics+import Servant.API+import Servant.API.Routes.Golden+import Test.Hspec as H++type SubAPI = ReqBody '[JSON] String :> Post '[JSON] Int++type SubAPI2 = Header "h1" T.Text :> "x" :> ("y" :> Put '[JSON] String :<|> SubAPI)++type SubAPI3 =+  Header "h2" T.Text+    :> "x2"+    :> ( "y1" :> Description "desc" :> Put '[JSON] String+          :<|> "z1" :> Header' '[Optional] "h3" Int :> Get '[JSON] [Integer]+       )++#if MIN_VERSION_servant(0,19,0)+data API mode = API+  { ep1 :: mode :- "sub1" :> SubAPI+  , ep2 :: mode :- "sub2" :> SubAPI2+  , ep3 :: mode :- "sub3" :> SubAPI3+  } deriving Generic+#endif++spec :: H.Spec+spec = do+  it "SubAPI" $ goldenRoutes @SubAPI (show ''SubAPI)+  it "SubAPI2" $ goldenRoutes @SubAPI2 (show ''SubAPI2)+  it "SubAPI3" $ goldenRoutes @SubAPI3 (show ''SubAPI3)+#if MIN_VERSION_servant(0,19,0)+  it "NamedRoutes API" $ goldenRoutes @(NamedRoutes API) (show ''API)+#endif
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}