packages feed

reddit 0.2.2.2 → 0.2.3.0

raw patch · 24 files changed

+1071/−150 lines, 24 filesdep +yamldep ~api-builderdep ~freedep ~http-typesPVP ok

version bump matches the API change (PVP)

Dependencies added: yaml

Dependency ranges changed: api-builder, free, http-types

API changes (from Hackage documentation)

Files

reddit.cabal view
@@ -1,175 +1,199 @@-name:                reddit-version:             0.2.2.2-synopsis:            Library for interfacing with Reddit's API-description:-  A library for interfacing with Reddit's API in Haskell. Handles-  logins, rate-limiting and converting to and from JSON responses.--  Supports most user-facing Reddit API functions, as well as some-  moderator endpoints.+-- This file has been generated from package.yaml by hpack version 0.20.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 6d0965ca6f31dbbd95562eb8028178c6142b8dc8d08ddaf93ef114725e63d3fd -  Check out the readme at <https://github.com/intolerable/reddit>.-  Contributions are welcome.+name:           reddit+version:        0.2.3.0+synopsis:       Library for interfacing with Reddit's API+description:    A library for interfacing with Reddit''s API in Haskell. Handles logins, rate-limiting and converting to and from JSON responses. Supports most user-facing Reddit API functions, as well as some moderator endpoints. Check out the readme at <https://github.com/intolerable/reddit>.+                Contributions are welcome.+category:       Network+homepage:       https://github.com/intolerable/reddit#readme+bug-reports:    https://github.com/intolerable/reddit/issues+author:         Fraser Murray+maintainer:     fraser.m.murray@gmail.com+copyright:      Copyright (c) Fraser Murray, 2013-2018+license:        BSD2+license-file:   LICENSE+build-type:     Simple+cabal-version:  >= 1.10 -license:             BSD2-license-file:        LICENSE-author:              Fraser Murray-maintainer:          fraser.m.murray@gmail.com-homepage:            https://github.com/intolerable/reddit-copyright:           Copyright (c) Fraser Murray, 2013-2018-category:            Network-build-type:          Simple-cabal-version:       >= 1.10 extra-source-files:-  README.md-  test/data/aboutMe_example.json-  test/data/getUserInfo_example.json-  test/data/getUserComments_example.json+    README.md+    test/data/aboutMe_example.json+    test/data/getUserComments_example.json+    test/data/getUserInfo_example.json  source-repository head   type: git   location: https://github.com/intolerable/reddit  library+  hs-source-dirs:+      src+  default-extensions: FlexibleInstances GADTs GeneralizedNewtypeDeriving LambdaCase OverloadedStrings+  ghc-options: -Wall -fno-warn-dodgy-imports+  build-depends:+      aeson >=0.9 && <1.3+    , api-builder >=0.10 && <0.16+    , base >=4.6 && <4.12+    , bytestring ==0.10.*+    , data-default-class >=0.0.1 && <0.2+    , free >=4 && <6+    , http-client >=0.4.30 && <0.6+    , http-client-tls >=0.2 && <0.4+    , http-types >=0.8 && <0.13+    , network ==2.6.*+    , text ==1.*+    , time >=1.5 && <1.9+    , transformers >=0.4 && <0.6+    , unordered-containers >=0.2.5 && <0.3+    , vector >=0.10 && <0.13   exposed-modules:-    Reddit-    Reddit.Actions-    Reddit.Actions.Captcha-    Reddit.Actions.Comment-    Reddit.Actions.Flair-    Reddit.Actions.Message-    Reddit.Actions.Moderation-    Reddit.Actions.Post-    Reddit.Actions.Search-    Reddit.Actions.Subreddit-    Reddit.Actions.Thing-    Reddit.Actions.User-    Reddit.Actions.Voting-    Reddit.Actions.Wiki-    Reddit.Login-    Reddit.Types-    Reddit.Types.Captcha-    Reddit.Types.Comment-    Reddit.Types.Error-    Reddit.Types.Flair-    Reddit.Types.Listing-    Reddit.Types.Message-    Reddit.Types.Moderation-    Reddit.Types.Options-    Reddit.Types.Post-    Reddit.Types.Reddit-    Reddit.Types.SearchOptions-    Reddit.Types.Subreddit-    Reddit.Types.SubredditSettings-    Reddit.Types.Thing-    Reddit.Types.User-    Reddit.Types.Wiki+      Reddit+      Reddit.Actions+      Reddit.Actions.Captcha+      Reddit.Actions.Comment+      Reddit.Actions.Flair+      Reddit.Actions.Message+      Reddit.Actions.Moderation+      Reddit.Actions.Post+      Reddit.Actions.Search+      Reddit.Actions.Subreddit+      Reddit.Actions.Thing+      Reddit.Actions.User+      Reddit.Actions.Voting+      Reddit.Actions.Wiki+      Reddit.Login+      Reddit.Types+      Reddit.Types.Captcha+      Reddit.Types.Comment+      Reddit.Types.Error+      Reddit.Types.Flair+      Reddit.Types.Listing+      Reddit.Types.Message+      Reddit.Types.Moderation+      Reddit.Types.Options+      Reddit.Types.Post+      Reddit.Types.Reddit+      Reddit.Types.SearchOptions+      Reddit.Types.Subreddit+      Reddit.Types.SubredditSettings+      Reddit.Types.Thing+      Reddit.Types.User+      Reddit.Types.Wiki   other-modules:-    Paths_reddit-    Reddit.Parser-    Reddit.Routes-    Reddit.Routes.Captcha-    Reddit.Routes.Comment-    Reddit.Routes.Flair-    Reddit.Routes.Message-    Reddit.Routes.Moderation-    Reddit.Routes.Post-    Reddit.Routes.Search-    Reddit.Routes.Subreddit-    Reddit.Routes.Thing-    Reddit.Routes.User-    Reddit.Routes.Vote-    Reddit.Routes.Wiki-    Reddit.Types.Empty-    Reddit.Utilities-  default-extensions:-    FlexibleInstances-    GADTs-    GeneralizedNewtypeDeriving-    LambdaCase-    OverloadedStrings+      Reddit.Parser+      Reddit.Routes+      Reddit.Routes.Captcha+      Reddit.Routes.Comment+      Reddit.Routes.Flair+      Reddit.Routes.Message+      Reddit.Routes.Moderation+      Reddit.Routes.Post+      Reddit.Routes.Search+      Reddit.Routes.Subreddit+      Reddit.Routes.Thing+      Reddit.Routes.User+      Reddit.Routes.Vote+      Reddit.Routes.Wiki+      Reddit.Types.Empty+      Reddit.Utilities+      Paths_reddit   default-language: Haskell2010-  hs-source-dirs: src/-  build-depends:-    base >= 4.6 && < 4.12,-    aeson >= 0.9 && < 1.3,-    api-builder >= 0.10 && < 0.15,-    bytestring == 0.10.*,-    data-default-class >= 0.0.1 && < 0.2,-    free >= 4 && < 5,-    http-client >= 0.4.30 && < 0.6,-    http-client-tls >= 0.2 && < 0.4,-    http-types >= 0.8 && < 0.11,-    network == 2.6.*,-    text == 1.*,-    time >= 1.5 && < 1.9,-    transformers >= 0.4 && < 0.6,-    unordered-containers >= 0.2.5 && < 0.3,-    vector >= 0.10 && < 0.13-  ghc-options: -Wall -fno-warn-dodgy-imports  test-suite test-  hs-source-dirs: test-  main-is: Spec.hs-  default-extensions: OverloadedStrings-  default-language: Haskell2010   type: exitcode-stdio-1.0-  build-depends:-    base == 4.*,-    Cabal >= 1.16.0,-    aeson,-    api-builder,-    bytestring,-    reddit,-    hspec,-    text,-    time,-    transformers-  ghc-options: -Wall--test-suite test-io-  hs-source-dirs: test-io, test-utils   main-is: Spec.hs-  default-extensions:-    OverloadedStrings-    Rank2Types-  default-language: Haskell2010-  type: exitcode-stdio-1.0-  build-depends:-    base == 4.*,-    Cabal >= 1.16.0,-    api-builder,-    bytestring,-    data-default-class,-    http-client,-    http-client-tls,-    reddit,-    hspec,-    text,-    time,-    transformers+  hs-source-dirs:+      test+  default-extensions: FlexibleInstances GADTs GeneralizedNewtypeDeriving LambdaCase OverloadedStrings   ghc-options: -Wall+  build-depends:+      Cabal >=1.16.0+    , aeson+    , api-builder+    , base ==4.*+    , bytestring+    , hspec+    , reddit+    , text+    , time+    , transformers   other-modules:-    Utils+      Reddit.Types.CommentSpec+      Reddit.Types.ListingSpec+      Reddit.Types.PostSpec+      Reddit.Types.SubredditSpec+      Reddit.Types.UserSpec+      Reddit.Types.WikiSpec+      Paths_reddit+  default-language: Haskell2010  test-suite test-anon-  hs-source-dirs: test-anon, test-utils+  type: exitcode-stdio-1.0   main-is: Spec.hs-  default-extensions:-    OverloadedStrings-    Rank2Types+  hs-source-dirs:+      test-anon+      test-utils+  default-extensions: FlexibleInstances GADTs GeneralizedNewtypeDeriving LambdaCase OverloadedStrings Rank2Types+  ghc-options: -Wall+  build-depends:+      Cabal >=1.16.0+    , api-builder+    , base ==4.*+    , data-default-class+    , hspec+    , reddit+    , text+    , time+    , transformers+  other-modules:+      Reddit.Actions.CaptchaSpec+      Reddit.Actions.CommentSpec+      Reddit.Actions.FlairSpec+      Reddit.Actions.MessageSpec+      Reddit.Actions.PostSpec+      Reddit.Actions.SearchSpec+      Reddit.Actions.SubredditSpec+      Reddit.Actions.UserSpec+      Utils+      Paths_reddit   default-language: Haskell2010++test-suite test-io   type: exitcode-stdio-1.0-  build-depends:-    base == 4.*,-    Cabal >= 1.16.0,-    api-builder,-    data-default-class,-    reddit,-    hspec,-    text,-    time,-    transformers+  main-is: Spec.hs+  hs-source-dirs:+      test-io+      test-utils+  default-extensions: FlexibleInstances GADTs GeneralizedNewtypeDeriving LambdaCase OverloadedStrings Rank2Types   ghc-options: -Wall+  build-depends:+      Cabal >=1.16.0+    , api-builder+    , base ==4.*+    , bytestring+    , data-default-class+    , hspec+    , http-client+    , http-client-tls+    , reddit+    , text+    , time+    , transformers+    , yaml   other-modules:-    Utils+      ConfigLoad+      Reddit.Actions.CaptchaSpec+      Reddit.Actions.CommentSpec+      Reddit.Actions.FlairSpec+      Reddit.Actions.MessageSpec+      Reddit.Actions.PostSpec+      Reddit.Actions.SubredditSpec+      Reddit.Actions.UserSpec+      Utils+      Paths_reddit+  default-language: Haskell2010
src/Reddit/Types/Error.hs view
@@ -34,6 +34,7 @@       Just (Array e) -> case V.toList e of         String "WRONG_PASSWORD" : _ -> return CredentialsError         String "USER_REQUIRED" : _ -> return CredentialsError+        String "INCORRECT_USERNAME_PASSWORD" : _ -> return CredentialsError         String "RATELIMIT" : String d : _ ->             RateLimitError <$> ((o .: "json") >>= (.: "ratelimit")) <*> pure d         String "SUBREDDIT_REQUIRED" : _ -> return NoSubredditSpecified
+ test-anon/Reddit/Actions/CaptchaSpec.hs view
@@ -0,0 +1,26 @@+module Reddit.Actions.CaptchaSpec where++import Reddit+import Utils++import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++main :: IO ()+main = hspec spec++spec :: Spec+spec = skip $ describe "Reddit.Actions.Captcha" $ do++  it "should be able to get a new captcha" $ do+    res <- runAnon newCaptcha+    res `shouldSatisfy` isRight++  it "should be able to check if we need captchas" $ do+    res <- runAnon needsCaptcha+    res `shouldSatisfy` isRight++skip :: Monad m => m a -> m ()+skip _ = return ()
+ test-anon/Reddit/Actions/CommentSpec.hs view
@@ -0,0 +1,51 @@+module Reddit.Actions.CommentSpec where++import Reddit+import Utils++import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Comment" $ do++  it "should be able to get info for a comment" $ do+    res <- runAnon $ getCommentInfo (CommentID "c60o0iw")+    res `shouldSatisfy` isRight++  it "should be able to get info for multiple comments" $ do+    res <- runAnon $ getCommentsInfo [CommentID "c60o0iw", CommentID "c4zrenn"]+    res `shouldSatisfy` isRight+    case res of+      Right (Listing _ _ cs) -> length cs `shouldBe` 2+      Left _ -> expectationFailure "something failed"++  it "should fail if we try to get an invalid comment" $ do+    res <- runAnon $ getCommentInfo (CommentID "garbage")+    res `shouldSatisfy` isLeft++  it "shouldn't be able to get a list of comment IDs where some are invalid" $ do+    res <- runAnon $ getCommentsInfo [CommentID "c60o0iw", CommentID "garbage"]+    res `shouldSatisfy` isLeft++  it "should be able to get mass comment IDs" $ do+    let a = replicate 100 $ CommentID "c60o0iw"+    res <- runAnon $ getCommentsInfo a+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right (Listing _ _ ps) ->+        length ps `shouldBe` length a++  it "should fail if it tries to get TOO many comment IDs" $ do+    let a = replicate 101 $ CommentID "c60o0iw"+    res <- runAnon $ getCommentsInfo a+    res `shouldSatisfy` isLeft
+ test-anon/Reddit/Actions/FlairSpec.hs view
@@ -0,0 +1,19 @@+module Reddit.Actions.FlairSpec where++import Reddit+import Utils++import Test.Hspec++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Flair" $ do++  it "shouldn't be able to get the flair list anonymously" $ do+    res <- runAnon $ getFlairList $ R "gaming"+    res `shouldSatisfy` isLeft
+ test-anon/Reddit/Actions/MessageSpec.hs view
@@ -0,0 +1,19 @@+module Reddit.Actions.MessageSpec where++import Reddit+import Utils++import Test.Hspec++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Message" $ do++  it "shouldn't have an inbox for an anonymous user" $ do+    res <- runAnon getInbox+    res `shouldSatisfy` isLeft
+ test-anon/Reddit/Actions/PostSpec.hs view
@@ -0,0 +1,62 @@+module Reddit.Actions.PostSpec where++import Reddit+import Reddit.Types.Post+import Reddit.Types.Subreddit (SubredditID(..))+import Utils++import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Post" $ do++  it "should be able to get info for a post" $ do+    res <- runAnon $ getPostInfo (PostID "z1c9z")+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right post -> do+        author post `shouldBe` Username "PresidentObama"+        title post `shouldBe` "I am Barack Obama, President of the United States -- AMA"+        subredditID post `shouldBe` SubredditID "2qzb6"+        subreddit post `shouldBe` R "IAMA"+        nsfw post `shouldBe` False++  it "should be able to get info for multiple posts" $ do+    res <- runAnon $ getPostsInfo [PostID "z1c9z", PostID "t0ynr"]+    res `shouldSatisfy` isRight++  it "should cope with getting info for no posts" $ do+    res <- runAnon $ getPostsInfo []+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right (Listing _ _ ps) ->+        ps `shouldBe` []++  it "shouldn't be able to get a list of posts from invalid post IDs" $ do+    res <- runAnon $ getPostsInfo [PostID "z1c9z", PostID "nonsense"]+    res `shouldSatisfy` isLeft++  it "should be able to get mass PostIDs" $ do+    let a = replicate 100 $ PostID "z1c9z"+    res <- runAnon $ getPostsInfo a+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right (Listing _ _ ps) ->+        length ps `shouldBe` length a++  it "should fail if it tries to get TOO many PostIDs" $ do+    let a = replicate 101 $ PostID "z1c9z"+    res <- runAnon $ getPostsInfo a+    res `shouldSatisfy` isLeft
+ test-anon/Reddit/Actions/SearchSpec.hs view
@@ -0,0 +1,29 @@+module Reddit.Actions.SearchSpec where++import Reddit+import Reddit.Types.SearchOptions+import Utils++import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Search" $ do++  it "should be able to search for something" $ do+    res <- runAnon $ search Nothing (Options Nothing Nothing) Hot "reddit"+    res `shouldSatisfy` isRight++  it "should be able to limit the search results" $  do+    res <- runAnon $ search (Just $ R "programming") (Options Nothing (Just 5)) Hot "haskell"+    case res of+      Left _ -> expectationFailure "search failed"+      Right (Listing _ _ rs) -> length rs `shouldBe` 5
+ test-anon/Reddit/Actions/SubredditSpec.hs view
@@ -0,0 +1,36 @@+module Reddit.Actions.SubredditSpec where++import Reddit+import Reddit.Types.Subreddit+import Utils++import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Subreddit" $ do++  it "should be able to get the info for a subreddit anonymously" $ do+    let sub = R "gaming"+    res <- runAnon $ getSubredditInfo sub+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "json parse failed"+      Right info -> do+        name info `shouldBe` sub+        subredditID info `shouldBe` SubredditID "2qh03"+        subscribers info `shouldSatisfy` (> 0)+        userIsBanned info `shouldBe` Nothing++  it "shouldn't be able to anonymously get the settings for the moderated subreddit" $ do+    res <- runAnon $ getSubredditSettings $ R "gaming"+    res `shouldSatisfy` isLeft+
+ test-anon/Reddit/Actions/UserSpec.hs view
@@ -0,0 +1,62 @@+module Reddit.Actions.UserSpec where++import Reddit+import Reddit.Types.Comment+import Utils++import Control.Monad+import Data.Time.Clock+import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.User" $ do+  time <- runIO getCurrentTime++  it "should be able to get the user's most recent comments" $ do+    res <- runAnon $ getUserComments $ Username "Intolerable"+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right (Listing _ _ cs) ->+        forM_ cs $ \c -> do+          author c `shouldBe` Username "Intolerable"+          replies c `shouldSatisfy` (\(Listing _ _ x) -> null x)+          created c `shouldSatisfy` (< time)++  it "should be able to get multiple pages of user comments" $ do+    res <- runAnon $ getUserComments' (Options Nothing (Just 1)) $ Username "Intolerable"+    res `shouldSatisfy` isRight+    case res of+      Right (Listing _ (Just a) (c:[])) -> do+        next <- runAnon $ getUserComments' (Options (Just $ After a) (Just 1)) $ Username "Intolerable"+        next `shouldSatisfy` isRight+        case next of+          Right (Listing _ _ (d:[])) ->+            c `shouldSatisfy` (/= d)+          _ -> expectationFailure "something failed"+      _ -> expectationFailure "something failed"++  it "shouldn't be able to get about me info for an anonymous user" $ do+    res <- runAnon aboutMe+    res `shouldSatisfy` isLeft++  it "should be able to anonymously get the user info for a user" $ do+    res <- runAnon $ getUserInfo $ Username "Intolerable"+    res `shouldSatisfy` isRight++  it "should be able to check if a username is available" $ do+    res <- runAnon $ isUsernameAvailable $ Username "Intolerable"+    res `shouldSatisfy` isRight+    case res of+      Left _ -> return ()+      Right avail -> avail `shouldBe` False+
+ test-io/ConfigLoad.hs view
@@ -0,0 +1,54 @@+module ConfigLoad where++import Data.Text (Text)+import Data.Yaml+import Network.HTTP.Client+import Network.HTTP.Client.TLS+import Reddit+import Reddit.Login+import System.Exit+import Utils++newtype RunReddit = RunReddit+  { run :: forall a. Reddit a -> IO (Either (APIError RedditError) a) }++newtype TestConfig = TestConfig (Maybe Config)+  deriving (Show, Eq, Ord)++instance FromJSON TestConfig where+  parseJSON = withObject "TestConfig" $ \ o ->+    TestConfig <$> o .:? "config"++data Config =+  Config { tcUsername :: Text+         , tcPassword :: Text+         , tcSubreddit :: Text+         }+  deriving (Show, Eq, Ord)++instance FromJSON Config where+  parseJSON = withObject "Config" $ \ o ->+    Config <$> o .: "username"+           <*> o .: "password"+           <*> o .: "subreddit"++loadConfig :: IO (RunReddit, Username, SubredditName)+loadConfig = do+  decodeFileEither "test_config.yaml" >>= \case+    Left err -> do+      print err+      exitFailure+    Right (TestConfig Nothing) ->+      putStrLn "Warning: missing config section, skipping authorized tests"+      exitSuccess+    Right (TestConfig (Just (Config user pass sub))) -> do+      manager <- newManager tlsManagerSettings+      res <- runAnon $ login user pass+      case res of+        Left err -> do+          print err+          exitFailure+        Right details ->+          return ( RunReddit $ runRedditWith $ RedditOptions True (Just manager) (StoredDetails details) (Just "reddit-haskell test suite")+                 , Username user+                 , R sub)
+ test-io/Reddit/Actions/CaptchaSpec.hs view
@@ -0,0 +1,20 @@+module Reddit.Actions.CaptchaSpec where++import Reddit.Actions.Captcha++import ConfigLoad+import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Captcha" $ do+  (reddit, _, _) <- runIO loadConfig++  it "should be able to check if we need captchas" $ do+    res <- run reddit needsCaptcha+    res `shouldSatisfy` isRight
+ test-io/Reddit/Actions/CommentSpec.hs view
@@ -0,0 +1,54 @@+module Reddit.Actions.CommentSpec where++import Reddit.Actions.Comment+import Reddit.Types.Comment+import Reddit.Types.Listing++import ConfigLoad+import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Comment" $ do+  (reddit, _, _) <- runIO loadConfig++  it "should be able to get info for a comment" $ do+    res <- run reddit $ getCommentInfo (CommentID "c60o0iw")+    res `shouldSatisfy` isRight++  it "should be able to get info for multiple comments" $ do+    res <- run reddit $ getCommentsInfo [CommentID "c60o0iw", CommentID "c4zrenn"]+    res `shouldSatisfy` isRight+    case res of+      Right (Listing _ _ cs) -> length cs `shouldBe` 2+      Left _ -> expectationFailure "something failed"++  it "should fail if we try to get an invalid comment" $ do+    res <- run reddit $ getCommentInfo (CommentID "garbage")+    res `shouldSatisfy` isLeft++  it "shouldn't be able to get a list of comment IDs where some are invalid" $ do+    res <- run reddit $ getCommentsInfo [CommentID "c60o0iw", CommentID "garbage"]+    res `shouldSatisfy` isLeft++  it "should be able to get mass comment IDs" $ do+    let a = replicate 100 $ CommentID "c60o0iw"+    res <- run reddit $ getCommentsInfo a+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right (Listing _ _ ps) ->+        length ps `shouldBe` length a++  it "should fail if it tries to get TOO many comment IDs" $ do+    let a = replicate 101 $ CommentID "c60o0iw"+    res <- run reddit $ getCommentsInfo a+    res `shouldSatisfy` isLeft
+ test-io/Reddit/Actions/FlairSpec.hs view
@@ -0,0 +1,21 @@+module Reddit.Actions.FlairSpec where++import Reddit.Actions.Flair++import ConfigLoad+import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Flair" $ do+  (reddit, _, subreddit) <- runIO loadConfig++  it "should be able to get the flair list" $ do+    res <- run reddit $ getFlairList subreddit+    res `shouldSatisfy` isRight+
+ test-io/Reddit/Actions/MessageSpec.hs view
@@ -0,0 +1,24 @@+module Reddit.Actions.MessageSpec where++import Reddit.Actions.Message++import ConfigLoad+import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Message" $ do+  (reddit, _, _) <- runIO loadConfig++  it "should be able to check inbox" $ do+    res <- run reddit $ getInbox+    res `shouldSatisfy` isRight++  it "should be able to check unread messages" $ do+    res <- run reddit $ getUnread+    res `shouldSatisfy` isRight
+ test-io/Reddit/Actions/PostSpec.hs view
@@ -0,0 +1,65 @@+module Reddit.Actions.PostSpec where++import Reddit.Actions.Post+import Reddit.Types.Listing+import Reddit.Types.Post+import Reddit.Types.Subreddit (SubredditID(..), SubredditName(..))+import Reddit.Types.User++import ConfigLoad+import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Post" $ do+  (reddit, _, _) <- runIO loadConfig++  it "should be able to get info for a post" $ do+    res <- run reddit $ getPostInfo (PostID "z1c9z")+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right post -> do+        author post `shouldBe` Username "PresidentObama"+        title post `shouldBe` "I am Barack Obama, President of the United States -- AMA"+        subredditID post `shouldBe` SubredditID "2qzb6"+        subreddit post `shouldBe` R "IAMA"+        nsfw post `shouldBe` False++  it "should be able to get info for multiple posts" $ do+    res <- run reddit $ getPostsInfo [PostID "z1c9z", PostID "t0ynr"]+    res `shouldSatisfy` isRight++  it "should cope with getting info for no posts" $ do+    res <- run reddit $ getPostsInfo []+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right (Listing _ _ ps) ->+        ps `shouldBe` []++  it "shouldn't be able to get a list of posts from invalid post IDs" $ do+    res <- run reddit $ getPostsInfo [PostID "z1c9z", PostID "nonsense"]+    res `shouldSatisfy` isLeft++  it "should be able to get mass PostIDs" $ do+    let a = replicate 100 $ PostID "z1c9z"+    res <- run reddit $ getPostsInfo a+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right (Listing _ _ ps) ->+        length ps `shouldBe` length a++  it "should fail if it tries to get TOO many PostIDs" $ do+    let a = replicate 101 $ PostID "z1c9z"+    res <- run reddit $ getPostsInfo a+    res `shouldSatisfy` isLeft
+ test-io/Reddit/Actions/SubredditSpec.hs view
@@ -0,0 +1,32 @@+module Reddit.Actions.SubredditSpec where++import Reddit.Actions.Subreddit+import Reddit.Types.Subreddit++import ConfigLoad+import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.Subreddit" $ do+  (reddit, _, subreddit) <- runIO loadConfig++  it "should be able to get the info for a subreddit" $ do+    let sub = R "gaming"+    res <- run reddit $ getSubredditInfo sub+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "json parse failed"+      Right info -> do+        name info `shouldBe` sub+        subredditID info `shouldBe` SubredditID "2qh03"+        subscribers info `shouldSatisfy` (> 0)++  it "should be able to get the settings for a moderated subreddit" $ do+    res <- run reddit $ getSubredditSettings subreddit+    res `shouldSatisfy` isRight
+ test-io/Reddit/Actions/UserSpec.hs view
@@ -0,0 +1,70 @@+module Reddit.Actions.UserSpec where++import Reddit.Actions.User+import Reddit.Types.Comment+import Reddit.Types.Listing+import Reddit.Types.Options+import Reddit.Types.User++import ConfigLoad+import Control.Monad+import Data.Maybe+import Data.Time.Clock+import Test.Hspec++isRight :: Either a b -> Bool+isRight = const False `either` const True++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Actions.User" $ do+  (reddit, username, _) <- runIO loadConfig+  time <- runIO getCurrentTime++  it "should be able to get the user's most recent comments" $ do+    res <- run reddit $ getUserComments username+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right (Listing _ _ cs) ->+        forM_ cs $ \c -> do+          author c `shouldBe` username+          replies c `shouldSatisfy` (\(Listing _ _ x) -> null x)+          created c `shouldSatisfy` (< time)++  it "should be able to get multiple pages of user comments" $ do+    res <- run reddit $ getUserComments' (Options Nothing (Just 1)) username+    res `shouldSatisfy` isRight+    case res of+      Right (Listing _ (Just a) (c:[])) -> do+        next <- run reddit $ getUserComments' (Options (Just $ After a) (Just 1)) username+        next `shouldSatisfy` isRight+        case next of+          Right (Listing _ _ (d:[])) ->+            c `shouldSatisfy` (/= d)+          _ -> expectationFailure "something failed"+      _ -> expectationFailure "something failed"++  it "should be able to get the user's about me info" $ do+    res <- run reddit aboutMe+    res `shouldSatisfy` isRight+    case res of+      Left _ -> expectationFailure "something failed"+      Right user -> do+        userName user `shouldBe` username+        userCreated user `shouldSatisfy` (< time)+        hasMail user `shouldSatisfy` isJust+        isFriend user `shouldBe` False++  it "should be able to get the user info for a user" $ do+    res <- run reddit $ getUserInfo username+    res `shouldSatisfy` isRight++  it "should be able to check if a username is available" $ do+    res <- run reddit $ isUsernameAvailable username+    res `shouldSatisfy` isRight+    case res of+      Left _ -> return ()+      Right avail -> avail `shouldBe` False
+ test/Reddit/Types/CommentSpec.hs view
@@ -0,0 +1,62 @@+module Reddit.Types.CommentSpec where++import Reddit.Types.Comment+import Reddit.Types.Listing+import Reddit.Types.Subreddit hiding (subredditID)+import Reddit.Types.User++import Control.Monad+import Data.ByteString.Lazy (ByteString)+import Data.Maybe+import Data.Time.Clock+import Data.Aeson (eitherDecode)+import Test.Hspec+import qualified Data.ByteString.Lazy.Char8 as ByteString+import qualified Data.Text as Text++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Types.Comment" $ do+  let decode' = eitherDecode :: ByteString -> Either String CommentID+  getUserCommentsExample <- runIO $ ByteString.readFile "test/data/getUserComments_example.json"+  time <- runIO getCurrentTime++  it "can read the example" $+    getUserCommentsExample `shouldSatisfy` not . ByteString.null++  it "can parse a list of comments from json" $ do+    let decoded = eitherDecode getUserCommentsExample :: Either String CommentListing+    decoded `shouldSatisfy` isRight++    case decoded of+      Left _ -> expectationFailure "json parse failed"+      Right (Listing b a cs) -> do+        cs `shouldSatisfy` not . null+        b `shouldBe` Nothing+        a `shouldSatisfy` isJust++        length cs `shouldBe` 5+        forM_ cs $ \c -> do+          author c `shouldBe` Username "Intolerable"+          score c `shouldSatisfy` isJust+          subreddit c `shouldBe` R "DotA2"+          subredditID c `shouldBe` SubredditID "2s580"+          authorFlairText c `shouldBe` Just "sneep sneep"+          authorFlairCSSClass c `shouldBe` Just "rapier"+          body c `shouldSatisfy` not . Text.null+          bodyHTML c `shouldSatisfy` not . Text.null+          replies c `shouldBe` Listing Nothing Nothing []+          created c `shouldSatisfy` (< time)++  it "can parse a CommentID" $ do+    decode' "\"t1_cl1royq\"" `shouldBe` Right (CommentID "cl1royq")+    decode' "\"cl1royq\"" `shouldBe` Right (CommentID "cl1royq")+    decode' "\"t5_2s580\"" `shouldSatisfy` isLeft
+ test/Reddit/Types/ListingSpec.hs view
@@ -0,0 +1,66 @@+module Reddit.Types.ListingSpec where++import Reddit.Types.Comment+import Reddit.Types.Listing++import Control.Monad+import Data.Aeson (eitherDecode)+import Network.API.Builder+import Test.Hspec+import Test.Hspec.QuickCheck+import qualified Data.ByteString.Lazy.Char8 as ByteString++isRight :: Either a b -> Bool+isRight = const False `either` const True++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Types.Listing" $ do+  describe "Listing" $ do+    getUserCommentsExample <- runIO $ ByteString.readFile "test/data/getUserComments_example.json"++    it "can read the example" $+      getUserCommentsExample `shouldSatisfy` not . ByteString.null++    it "can parse a listing from json" $ do+      let decoded = eitherDecode getUserCommentsExample :: Either String CommentListing+      decoded `shouldSatisfy` isRight++      case decoded of+        Left _ -> expectationFailure "json parse failed"+        Right (Listing b a _) -> do+          a `shouldBe` Just (CommentID "cl1royq")+          b `shouldBe` Nothing++    describe "has a valid Functor instance" $ do+      prop "length l = length (fmap f l)" $ \xs ->+        let l = Listing Nothing Nothing (xs :: [()]) in+        length (contents (l :: Listing () ())) == length (contents (void l))++      prop "fmap id x == x" $ \xs ->+        let l = Listing Nothing Nothing (xs :: [String]) in+        fmap id l == (l :: Listing () String)++    it "can parse listings from empty strings" $ do+      let decoded :: Either String CommentListing+          decoded = eitherDecode "\"\""+      decoded `shouldBe` Right (Listing Nothing Nothing [])++    it "can parse listings from null" $ do+      let decoded :: Either String CommentListing+          decoded = eitherDecode "null"+      decoded `shouldBe` Right (Listing Nothing Nothing [])++  describe "ListingType" $++    it "should have a valid ToQuery instance" $ do+      toQuery "type" Hot `shouldBe` [("type", "hot")]+      toQuery "type" New `shouldBe` [("type", "new")]+      toQuery "type" Rising `shouldBe` [("type", "rising")]+      toQuery "type" Controversial `shouldBe` [("type", "controversial")]+      toQuery "type" Top `shouldBe` [("type", "top")]
+ test/Reddit/Types/PostSpec.hs view
@@ -0,0 +1,22 @@+module Reddit.Types.PostSpec where++import Reddit.Types.Post++import Data.Aeson (eitherDecode)+import Data.ByteString.Lazy (ByteString)+import Test.Hspec++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Types.Post" $ do+  let decode' = eitherDecode :: ByteString -> Either String PostID++  it "can parse a PostID" $ do+    decode' "\"t3_1n1qrg\"" `shouldBe` Right (PostID "1n1qrg")+    decode' "\"1n1qrg\"" `shouldBe` Right (PostID "1n1qrg")+    decode' "\"t5_2s580\"" `shouldSatisfy` isLeft
+ test/Reddit/Types/SubredditSpec.hs view
@@ -0,0 +1,27 @@+module Reddit.Types.SubredditSpec where++import Reddit.Types.Subreddit++import Data.Aeson (eitherDecode)+import Data.ByteString.Lazy (ByteString)+import Test.Hspec++isLeft :: Either a b -> Bool+isLeft = const True `either` const False++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Types.Subreddit" $ do+  let decode' = eitherDecode :: ByteString -> Either String SubredditID++  it "can compare subreddits" $ do+    R "intolerable_test" `shouldBe` R "intolerable_test"+    R "intolerable_test" `shouldBe` R "Intolerable_test"+    R "intolerable_test" `shouldBe` R "INTOLERABLE_TEST"++  it "can parse a SubredditID" $ do+    decode' "\"t5_2s580\"" `shouldBe` Right (SubredditID "2s580")+    decode' "\"2s580\"" `shouldBe` Right (SubredditID "2s580")+    decode' "\"t1_cl1royq\"" `shouldSatisfy` isLeft
+ test/Reddit/Types/UserSpec.hs view
@@ -0,0 +1,66 @@+module Reddit.Types.UserSpec where++import Reddit.Types.User++import Data.Aeson (eitherDecode)+import Test.Hspec+import qualified Data.ByteString.Lazy.Char8 as ByteString++isRight :: Either a b -> Bool+isRight = const False `either` const True++main :: IO ()+main = hspec spec++spec :: Spec+spec =+  describe "Reddit.Types.User" $ do+    aboutMeExample <- runIO $ ByteString.readFile "test/data/aboutMe_example.json"+    getUserInfoExample <- runIO $ ByteString.readFile "test/data/getUserInfo_example.json"++    it "should be able to compare usernames for equality" $ do+      Username "Intolerable" `shouldBe` Username "intolerable"+      Username "Intolerable" `shouldBe` Username "Intolerable"+      Username "INTOLERABLE" `shouldBe` Username "intolerable"++    it "can read the examples" $ do+      aboutMeExample `shouldSatisfy` not . ByteString.null+      getUserInfoExample `shouldSatisfy` not . ByteString.null++    it "can parse a reddit user from json" $ do+      let decoded = eitherDecode aboutMeExample :: Either String User+      decoded `shouldSatisfy` isRight++      case decoded of+        Left _ -> expectationFailure "json parse failed"+        Right user -> do+          userID user `shouldBe` "4gf25"+          userName user `shouldBe` Username "Intolerable"+          linkKarma user `shouldBe` 2399+          commentKarma user `shouldBe` 44631+          hasMail user `shouldBe` Just False+          hasModMail user `shouldBe` Just False+          isFriend user `shouldBe` False+          userIsOver18 user `shouldBe` Just True+          isMod user `shouldBe` True+          hasGold user `shouldBe` False+          hasVerifiedEmail user `shouldBe` Just True++    it "can parse another reddit user from json" $ do+      let decoded = eitherDecode getUserInfoExample :: Either String User+      decoded `shouldSatisfy` isRight++      case decoded of+        Left _ -> expectationFailure "json parse failed"+        Right user -> do+          userID user `shouldBe` "gdifp"+          userName user `shouldBe` Username "intolerable-bot"+          linkKarma user `shouldBe` 1+          commentKarma user `shouldBe` 2668+          hasMail user `shouldBe` Nothing+          hasModMail user `shouldBe` Nothing+          isFriend user `shouldBe` False+          userIsOver18 user `shouldBe` Nothing+          isMod user `shouldBe` False+          hasGold user `shouldBe` False+          hasVerifiedEmail user `shouldBe` Just True
+ test/Reddit/Types/WikiSpec.hs view
@@ -0,0 +1,9 @@+module Reddit.Types.WikiSpec where++import Test.Hspec++main :: IO ()+main = hspec spec++spec :: Spec+spec = describe "Reddit.Types.Wiki" $ return ()