{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} module Main where import Web.Twitter.Types import Test.Framework import Test.Framework.Providers.HUnit import Test.Framework.Providers.QuickCheck2 import Test.HUnit import Data.Aeson hiding (Error) import Data.Aeson.Types (parseEither) import qualified Data.Aeson.Types as Aeson import qualified Data.HashMap.Strict as M import Data.Maybe import Instances() import Fixtures loadFixturesTH 'parseJSONValue main :: IO () main = defaultMain [ testGroup "Unit tests" unittests , testGroup "Property tests" proptests ] where unittests = [ testCase "case_parseStatus" case_parseStatus , testCase "case_parseStatusQuoted" case_parseStatusQuoted , testCase "case_parseStatusWithPhoto" case_parseStatusWithPhoto , testCase "case_parseStatusIncludeEntities" case_parseStatusIncludeEntities , testCase "case_parseSearchStatusMetadata" case_parseSearchStatusMetadata , testCase "case_parseSearchStatusBodyStatus" case_parseSearchStatusBodyStatus , testCase "case_parseSearchStatusBodySearchStatus" case_parseSearchStatusBodySearchStatus , testCase "case_parseDirectMessage" case_parseDirectMessage , testCase "case_parseEventFavorite" case_parseEventFavorite , testCase "case_parseEventUnfavorite" case_parseEventUnfavorite , testCase "case_parseDelete" case_parseDelete , testCase "case_parseErrorMsg" case_parseErrorMsg , testCase "case_parseMediaEntity" case_parseMediaEntity , testCase "case_parseEmptyEntity" case_parseEmptyEntity , testCase "case_parseEntityHashTag" case_parseEntityHashTag , testCase "case_parseExtendedEntities" case_parseExtendedEntities , testCase "case_parseUser" case_parseUser , testCase "case_parseList" case_parseList ] proptests = [ testProperty "prop_fromToStatus" prop_fromToStatus , testProperty "prop_fromToSearchStatus" prop_fromToSearchStatus , testProperty "prop_fromToSearchMetadata" prop_fromToSearchMetadata , testProperty "prop_fromToRetweetedStatus" prop_fromToRetweetedStatus , testProperty "prop_fromToDirectMessage" prop_fromToDirectMessage , testProperty "prop_fromToEventTarget" prop_fromToEventTarget , testProperty "prop_fromToEvent" prop_fromToEvent , testProperty "prop_fromToDelete" prop_fromToDelete , testProperty "prop_fromToUser" prop_fromToUser , testProperty "prop_fromToList" prop_fromToList , testProperty "prop_fromToHashTagEntity" prop_fromToHashTagEntity , testProperty "prop_fromToUserEntity" prop_fromToUserEntity , testProperty "prop_fromToURLEntity" prop_fromToURLEntity , testProperty "prop_fromToMediaEntity" prop_fromToMediaEntity , testProperty "prop_fromToMediaSize" prop_fromToMediaSize , testProperty "prop_fromToCoordinates" prop_fromToCoordinates , testProperty "prop_fromToPlace" prop_fromToPlace , testProperty "prop_fromToBoundingBox" prop_fromToBoundingBox , testProperty "prop_fromToEntities" prop_fromToEntities , testProperty "prop_fromToContributor" prop_fromToContributor , testProperty "prop_fromToImageSizeType" prop_fromToImageSizeType , testProperty "prop_fromToUploadedMedia" prop_fromToUploadedMedia ] withJSON :: FromJSON a => Value -> (a -> Assertion) -> Assertion withJSON js f = either assertFailure id $ do o <- parseEither parseJSON js return $ f o case_parseStatus :: Assertion case_parseStatus = withJSON fixture_status01 $ \obj -> do statusCreatedAt obj @?= "Sat Sep 10 22:23:38 +0000 2011" statusId obj @?= 112652479837110273 statusText obj @?= "@twitter meets @seepicturely at #tcdisrupt cc.@boscomonkey @episod http://t.co/6J2EgYM" statusSource obj @?= "Instagram" statusTruncated obj @?= False statusEntities obj @?= Nothing statusExtendedEntities obj @?= Nothing statusInReplyToStatusId obj @?= Nothing statusInReplyToUserId obj @?= Just 783214 statusFavorited obj @?= Just False statusQuotedStatus obj @?= Nothing statusQuotedStatusId obj @?= Nothing statusRetweetCount obj @?= 0 (userScreenName . statusUser) obj @?= "imeoin" statusRetweetedStatus obj @?= Nothing statusPlace obj @?= Nothing statusFavoriteCount obj @?= 0 statusLang obj @?= Nothing statusPossiblySensitive obj @?= Just False statusCoordinates obj @?= Nothing case_parseStatusQuoted :: Assertion case_parseStatusQuoted = withJSON fixture_status_quoted $ \obj -> do statusId obj @?= 641660763770372100 statusText obj @?= "Wow! Congrats! https://t.co/EPMMldEcci" statusQuotedStatusId obj @?= Just 641653574284537900 let qs = fromJust $ statusQuotedStatus obj statusCreatedAt qs @?= "Wed Sep 09 16:45:08 +0000 2015" statusId qs @?= 641653574284537900 statusText qs @?= "Very happy to say that I'm joining @mesosphere as a Distributed Systems Engineer!" statusSource qs @?= "Twitter Web Client" let ent = fromJust $ statusEntities qs enURLs ent @?= [] enMedia ent @?= [] enHashTags ent @?= [] map (userEntityUserId . entityBody) (enUserMentions ent) @?= [1872399366] map (userEntityUserScreenName . entityBody) (enUserMentions ent) @?= ["mesosphere"] statusExtendedEntities qs @?= Nothing statusInReplyToStatusId qs @?= Nothing statusInReplyToUserId qs @?= Nothing statusFavorited qs @?= Just False statusQuotedStatus qs @?= Nothing statusQuotedStatusId qs @?= Nothing statusRetweetCount qs @?= 7 (userScreenName . statusUser) qs @?= "neil_conway" statusRetweeted qs @?= Just False statusRetweetedStatus qs @?= Nothing statusPlace qs @?= Nothing statusFavoriteCount qs @?= 63 statusLang qs @?= Just "en" statusPossiblySensitive qs @?= Nothing statusCoordinates qs @?= Nothing case_parseStatusWithPhoto :: Assertion case_parseStatusWithPhoto = withJSON fixture_status_thimura_with_photo $ \obj -> do statusId obj @?= 491143410770657280 statusText obj @?= "近所の海です http://t.co/FjSOU8dDoD" statusTruncated obj @?= False let ent = fromJust $ statusEntities obj enHashTags ent @?= [] enUserMentions ent @?= [] enURLs ent @?= [] length (enMedia ent) @?= 1 map (meMediaURLHttps . entityBody) (enMedia ent) @?= ["https://pbs.twimg.com/media/BtDkUVaCQAIpWBU.jpg"] let exent = fromJust $ statusExtendedEntities obj enHashTags exent @?= [] enUserMentions exent @?= [] enURLs exent @?= [] length (enMedia ent) @?= 1 statusInReplyToStatusId obj @?= Nothing statusInReplyToUserId obj @?= Nothing statusFavorited obj @?= Just False statusRetweetCount obj @?= 4 (userScreenName . statusUser) obj @?= "thimura" statusRetweetedStatus obj @?= Nothing statusPlace obj @?= Nothing statusFavoriteCount obj @?= 9 statusLang obj @?= Just "ja" statusPossiblySensitive obj @?= Just False statusCoordinates obj @?= Nothing case_parseStatusIncludeEntities :: Assertion case_parseStatusIncludeEntities = withJSON fixture_status_with_entity $ \obj -> do statusId obj @?= 112652479837110273 statusRetweetCount obj @?= 0 (userScreenName . statusUser) obj @?= "imeoin" let ent = fromMaybe (Entities [] [] [] []) $ statusEntities obj (map entityIndices . enHashTags) ent @?= [[32,42]] (hashTagText . entityBody . head . enHashTags) ent @?= "tcdisrupt" case_parseSearchStatusMetadata :: Assertion case_parseSearchStatusMetadata = withJSON fixture_search_haskell $ \obj -> do let status = (searchResultStatuses obj) :: [Status] length status @?= 1 let metadata = searchResultSearchMetadata obj searchMetadataMaxId metadata @?= 495597397733433345 searchMetadataSinceId metadata @?= 0 searchMetadataRefreshURL metadata @?= "?since_id=495597397733433345&q=haskell&include_entities=1" searchMetadataNextResults metadata @?= Just "?max_id=495594369802440705&q=haskell&include_entities=1" searchMetadataCount metadata @?= 1 searchMetadataCompletedIn metadata @?= Just 0.043 searchMetadataSinceIdStr metadata @?= "0" searchMetadataQuery metadata @?= "haskell" searchMetadataMaxIdStr metadata @?= "495597397733433345" case_parseSearchStatusBodyStatus :: Assertion case_parseSearchStatusBodyStatus = withJSON fixture_search_haskell $ \obj -> do let status = (searchResultStatuses obj) :: [Status] length status @?= 1 statusText (head status) @?= "haskell" case_parseSearchStatusBodySearchStatus :: Assertion case_parseSearchStatusBodySearchStatus = withJSON fixture_search_haskell $ \obj -> do let status = (searchResultStatuses obj) :: [SearchStatus] length status @?= 1 searchStatusText (head status) @?= "haskell" case_parseDirectMessage :: Assertion case_parseDirectMessage = withJSON fixture_direct_message_thimura $ \obj -> do dmCreatedAt obj @?= "Sat Aug 02 16:10:04 +0000 2014" dmSenderScreenName obj @?= "thimura_shinku" (userScreenName . dmSender) obj @?= "thimura_shinku" dmText obj @?= "おまえの明日が、今日よりもずっと、楽しい事で溢れているようにと、祈っているよ" dmRecipientScreeName obj @?= "thimura" dmId obj @?= 495602442466123776 (userScreenName . dmRecipient) obj @?= "thimura" dmRecipientId obj @?= 69179963 dmSenderId obj @?= 2566877347 dmCoordinates obj @?= Nothing case_parseEventFavorite :: Assertion case_parseEventFavorite = withJSON fixture_event_favorite_thimura $ \obj -> do evCreatedAt obj @?= "Sat Aug 02 16:32:01 +0000 2014" evEvent obj @?= "favorite" let Just (ETStatus targetObj) = evTargetObject obj statusId targetObj @?= 495597326736449536 statusText targetObj @?= "haskell" let ETUser targetUser = evTarget obj userScreenName targetUser @?= "thimura" let ETUser sourceUser = evSource obj userScreenName sourceUser @?= "thimura_shinku" case_parseEventUnfavorite :: Assertion case_parseEventUnfavorite = withJSON fixture_event_unfavorite_thimura $ \obj -> do evCreatedAt obj @?= "Sat Aug 02 16:32:10 +0000 2014" evEvent obj @?= "unfavorite" let Just (ETStatus targetObj) = evTargetObject obj statusId targetObj @?= 495597326736449536 statusText targetObj @?= "haskell" let ETUser targetUser = evTarget obj userScreenName targetUser @?= "thimura" let ETUser sourceUser = evSource obj userScreenName sourceUser @?= "thimura_shinku" case_parseDelete :: Assertion case_parseDelete = withJSON fixture_delete $ \obj -> do delId obj @?= 495607981833064448 delUserId obj @?= 2566877347 case_parseErrorMsg :: Assertion case_parseErrorMsg = case parseStatus fixture_error_not_authorized of Aeson.Error str -> "Not authorized" @=? str Aeson.Success _ -> assertFailure "errorMsgJson should be parsed as an error." where parseStatus :: Value -> Aeson.Result Status parseStatus = Aeson.parse parseJSON case_parseMediaEntity :: Assertion case_parseMediaEntity = withJSON fixture_media_entity $ \obj -> do let entities = statusEntities obj assert $ isJust entities let Just ent = entities media = enMedia ent length media @?= 1 let me = entityBody $ head media meType me @?= "photo" meId me @?= 114080493040967680 let sizes = meSizes me assert $ M.member "thumb" sizes assert $ M.member "large" sizes let Just mediaSize = M.lookup "large" sizes msWidth mediaSize @?= 226 msHeight mediaSize @?= 238 msResize mediaSize @?= "fit" ueURL (meURL me) @?= "http://t.co/rJC5Pxsu" meMediaURLHttps me @?= "https://pbs.twimg.com/media/AZVLmp-CIAAbkyy.jpg" case_parseEmptyEntity :: Assertion case_parseEmptyEntity = withJSON (parseJSONValue "{}") $ \entity -> do length (enHashTags entity) @?= 0 length (enUserMentions entity) @?= 0 length (enURLs entity) @?= 0 length (enMedia entity) @?= 0 case_parseEntityHashTag :: Assertion case_parseEntityHashTag = withJSON fixture_entity01 $ \entity -> do length (enHashTags entity) @?= 1 length (enUserMentions entity) @?= 1 length (enURLs entity) @?= 1 length (enMedia entity) @?= 0 let urlEntity = entityBody . head . enURLs $ entity ueURL urlEntity @?= "http://t.co/IOwBrTZR" ueExpanded urlEntity @?= "http://www.youtube.com/watch?v=oHg5SJYRHA0" ueDisplay urlEntity @?= "youtube.com/watch?v=oHg5SJ\x2026" let mentionsUser = entityBody . head . enUserMentions $ entity userEntityUserName mentionsUser @?= "Twitter API" userEntityUserScreenName mentionsUser @?= "twitterapi" userEntityUserId mentionsUser @?= 6253282 let HashTagEntity hashtag = entityBody . head . enHashTags $ entity hashtag @?= "lol" case_parseExtendedEntities :: Assertion case_parseExtendedEntities = withJSON fixture_media_extended_entity $ \obj -> do let entities = statusExtendedEntities obj assert $ isJust entities let Just ent = entities media = enMedia ent length media @?= 4 let me = entityBody $ head media ueURL (meURL me) @?= "http://t.co/qOjPwmgLKO" meMediaURL me @?= "http://pbs.twimg.com/media/BqgdlpaCQAA5OSu.jpg" case_parseUser :: Assertion case_parseUser = withJSON fixture_user_thimura $ \obj -> do userId obj @?= 69179963 userName obj @?= "ちむら" userScreenName obj @?= "thimura" userDescription obj @?= Just "真紅かわいい" userLocation obj @?= Just "State# Irotoridori.No.World" userProfileImageURL obj @?= Just "http://pbs.twimg.com/profile_images/414044387346116609/VNMfLpY7_normal.png" userURL obj @?= Just "http://t.co/TFUAsAffX0" userProtected obj @?= False userFollowersCount obj @?= 754 userFriendsCount obj @?= 780 userStatusesCount obj @?= 24709 userLang obj @?= "en" userCreatedAt obj @?= "Thu Aug 27 02:48:06 +0000 2009" userFavoritesCount obj @?= 17313 case_parseList :: Assertion case_parseList = withJSON fixture_list_thimura_haskell $ \obj -> do listId obj @?= 20849097 listName obj @?= "haskell" listFullName obj @?= "@thimura/haskell" listMemberCount obj @?= 50 listSubscriberCount obj @?= 1 listMode obj @?= "public" (userScreenName . listUser) obj @?= "thimura" fromToJSON :: (Eq a, FromJSON a, ToJSON a) => a -> Bool fromToJSON obj = case fromJSON . toJSON $ obj of Aeson.Error _ -> False Aeson.Success a -> a == obj -- prop_fromToStreamingAPI :: StreamingAPI -> Bool -- prop_fromToStreamingAPI = fromToJSON prop_fromToStatus :: Status -> Bool prop_fromToStatus = fromToJSON prop_fromToSearchStatus :: SearchStatus -> Bool prop_fromToSearchStatus = fromToJSON prop_fromToSearchMetadata :: SearchMetadata -> Bool prop_fromToSearchMetadata = fromToJSON prop_fromToRetweetedStatus :: RetweetedStatus -> Bool prop_fromToRetweetedStatus = fromToJSON prop_fromToDirectMessage :: DirectMessage -> Bool prop_fromToDirectMessage = fromToJSON prop_fromToEventTarget :: EventTarget -> Bool prop_fromToEventTarget = fromToJSON prop_fromToEvent :: Event -> Bool prop_fromToEvent = fromToJSON prop_fromToDelete :: Delete -> Bool prop_fromToDelete = fromToJSON prop_fromToUser :: User -> Bool prop_fromToUser = fromToJSON prop_fromToList :: List -> Bool prop_fromToList = fromToJSON prop_fromToHashTagEntity :: HashTagEntity -> Bool prop_fromToHashTagEntity = fromToJSON prop_fromToUserEntity :: UserEntity -> Bool prop_fromToUserEntity = fromToJSON prop_fromToURLEntity :: URLEntity -> Bool prop_fromToURLEntity = fromToJSON prop_fromToMediaEntity :: MediaEntity -> Bool prop_fromToMediaEntity = fromToJSON prop_fromToMediaSize :: MediaSize -> Bool prop_fromToMediaSize = fromToJSON prop_fromToCoordinates :: Coordinates -> Bool prop_fromToCoordinates = fromToJSON prop_fromToPlace :: Place -> Bool prop_fromToPlace = fromToJSON prop_fromToBoundingBox :: BoundingBox -> Bool prop_fromToBoundingBox = fromToJSON prop_fromToEntities :: Entities -> Bool prop_fromToEntities = fromToJSON prop_fromToContributor :: Contributor -> Bool prop_fromToContributor = fromToJSON prop_fromToImageSizeType :: ImageSizeType -> Bool prop_fromToImageSizeType = fromToJSON prop_fromToUploadedMedia :: UploadedMedia -> Bool prop_fromToUploadedMedia = fromToJSON