basic love
This commit is contained in:
@@ -13,8 +13,12 @@
|
|||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
name = "hs";
|
name = "hs";
|
||||||
|
|
||||||
|
nativeBuildInputs = with pkgs; [ pkg-config ];
|
||||||
|
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
zlib
|
zlib
|
||||||
|
libpq
|
||||||
|
haskellPackages.hspec-discover
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,9 +16,17 @@ extra-source-files:
|
|||||||
|
|
||||||
library
|
library
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
Data.Track
|
Data.Persist.Tap
|
||||||
|
Data.Persist.TapTrans
|
||||||
|
Data.State.Track
|
||||||
|
Data.Types
|
||||||
Env.Cache
|
Env.Cache
|
||||||
|
Env.Persist.Tap
|
||||||
|
Infrastructure.Mongo
|
||||||
|
Infrastructure.Persist.Tap
|
||||||
Lib
|
Lib
|
||||||
|
Serialize.Bson
|
||||||
|
Serialize.Str
|
||||||
other-modules:
|
other-modules:
|
||||||
Paths_hq_sample
|
Paths_hq_sample
|
||||||
hs-source-dirs:
|
hs-source-dirs:
|
||||||
@@ -33,6 +41,10 @@ library
|
|||||||
FlexibleContexts
|
FlexibleContexts
|
||||||
DerivingStrategies
|
DerivingStrategies
|
||||||
GeneralizedNewtypeDeriving
|
GeneralizedNewtypeDeriving
|
||||||
|
NamedFieldPuns
|
||||||
|
FlexibleInstances
|
||||||
|
UndecidableInstances
|
||||||
|
ScopedTypeVariables
|
||||||
build-depends:
|
build-depends:
|
||||||
aeson
|
aeson
|
||||||
, async
|
, async
|
||||||
@@ -40,26 +52,23 @@ library
|
|||||||
, bytestring
|
, bytestring
|
||||||
, conferer
|
, conferer
|
||||||
, containers
|
, containers
|
||||||
|
, data-default <0.8.0
|
||||||
|
, data-default-class <0.2
|
||||||
, directory
|
, directory
|
||||||
, generic-optics
|
, generic-optics
|
||||||
, hedis
|
, hedis
|
||||||
, katip
|
, katip
|
||||||
|
, mongoDB
|
||||||
, mtl
|
, mtl
|
||||||
, optics
|
, optics
|
||||||
, persistent
|
, resource-pool
|
||||||
, servant
|
, spock
|
||||||
, servant-client
|
|
||||||
, servant-quickcheck
|
|
||||||
, servant-server
|
|
||||||
, servant-swagger
|
|
||||||
, stm
|
, stm
|
||||||
, text
|
, text
|
||||||
, time
|
, time
|
||||||
, transformers
|
, transformers
|
||||||
, unordered-containers
|
, unordered-containers
|
||||||
, wai
|
|
||||||
, wai-websockets
|
, wai-websockets
|
||||||
, warp
|
|
||||||
, websockets
|
, websockets
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
@@ -79,6 +88,10 @@ executable hq-sample
|
|||||||
FlexibleContexts
|
FlexibleContexts
|
||||||
DerivingStrategies
|
DerivingStrategies
|
||||||
GeneralizedNewtypeDeriving
|
GeneralizedNewtypeDeriving
|
||||||
|
NamedFieldPuns
|
||||||
|
FlexibleInstances
|
||||||
|
UndecidableInstances
|
||||||
|
ScopedTypeVariables
|
||||||
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
ghc-options: -threaded -rtsopts -with-rtsopts=-N
|
||||||
build-depends:
|
build-depends:
|
||||||
aeson
|
aeson
|
||||||
@@ -87,34 +100,32 @@ executable hq-sample
|
|||||||
, bytestring
|
, bytestring
|
||||||
, conferer
|
, conferer
|
||||||
, containers
|
, containers
|
||||||
|
, data-default <0.8.0
|
||||||
|
, data-default-class <0.2
|
||||||
, directory
|
, directory
|
||||||
, generic-optics
|
, generic-optics
|
||||||
, hedis
|
, hedis
|
||||||
, hq-sample
|
, hq-sample
|
||||||
, katip
|
, katip
|
||||||
|
, mongoDB
|
||||||
, mtl
|
, mtl
|
||||||
, optics
|
, optics
|
||||||
, persistent
|
, resource-pool
|
||||||
, servant
|
, spock
|
||||||
, servant-client
|
|
||||||
, servant-quickcheck
|
|
||||||
, servant-server
|
|
||||||
, servant-swagger
|
|
||||||
, stm
|
, stm
|
||||||
, text
|
, text
|
||||||
, time
|
, time
|
||||||
, transformers
|
, transformers
|
||||||
, unordered-containers
|
, unordered-containers
|
||||||
, wai
|
|
||||||
, wai-websockets
|
, wai-websockets
|
||||||
, warp
|
|
||||||
, websockets
|
, websockets
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
||||||
test-suite hq-sample-test
|
test-suite hq-sample-test
|
||||||
type: exitcode-stdio-1.0
|
type: exitcode-stdio-1.0
|
||||||
main-is: Main.hs
|
main-is: Spec.hs
|
||||||
other-modules:
|
other-modules:
|
||||||
|
Data.TypesSpec
|
||||||
Paths_hq_sample
|
Paths_hq_sample
|
||||||
hs-source-dirs:
|
hs-source-dirs:
|
||||||
test
|
test
|
||||||
@@ -128,37 +139,39 @@ test-suite hq-sample-test
|
|||||||
FlexibleContexts
|
FlexibleContexts
|
||||||
DerivingStrategies
|
DerivingStrategies
|
||||||
GeneralizedNewtypeDeriving
|
GeneralizedNewtypeDeriving
|
||||||
|
NamedFieldPuns
|
||||||
|
FlexibleInstances
|
||||||
|
UndecidableInstances
|
||||||
|
ScopedTypeVariables
|
||||||
|
build-tool-depends:
|
||||||
|
hspec-discover:hspec-discover
|
||||||
build-depends:
|
build-depends:
|
||||||
QuickCheck
|
aeson
|
||||||
, aeson
|
|
||||||
, async
|
, async
|
||||||
, base
|
, base
|
||||||
, bytestring
|
, bytestring
|
||||||
, conferer
|
, conferer
|
||||||
, containers
|
, containers
|
||||||
|
, data-default <0.8.0
|
||||||
|
, data-default-class <0.2
|
||||||
, directory
|
, directory
|
||||||
, generic-optics
|
, generic-optics
|
||||||
, hedis
|
, hedis
|
||||||
, hq-sample
|
, hq-sample
|
||||||
|
, hspec
|
||||||
|
, hspec-discover
|
||||||
|
, hspec-wai
|
||||||
, katip
|
, katip
|
||||||
|
, mongoDB
|
||||||
, mtl
|
, mtl
|
||||||
, optics
|
, optics
|
||||||
, persistent
|
, resource-pool
|
||||||
, servant
|
, spock
|
||||||
, servant-client
|
|
||||||
, servant-quickcheck
|
|
||||||
, servant-server
|
|
||||||
, servant-swagger
|
|
||||||
, stm
|
, stm
|
||||||
, tasty
|
|
||||||
, tasty-hunit
|
|
||||||
, tasty-quickcheck
|
|
||||||
, text
|
, text
|
||||||
, time
|
, time
|
||||||
, transformers
|
, transformers
|
||||||
, unordered-containers
|
, unordered-containers
|
||||||
, wai
|
|
||||||
, wai-websockets
|
, wai-websockets
|
||||||
, warp
|
|
||||||
, websockets
|
, websockets
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|||||||
31
package.yaml
31
package.yaml
@@ -16,6 +16,10 @@ default-extensions:
|
|||||||
- FlexibleContexts
|
- FlexibleContexts
|
||||||
- DerivingStrategies
|
- DerivingStrategies
|
||||||
- GeneralizedNewtypeDeriving
|
- GeneralizedNewtypeDeriving
|
||||||
|
- NamedFieldPuns
|
||||||
|
- FlexibleInstances
|
||||||
|
- UndecidableInstances
|
||||||
|
- ScopedTypeVariables
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- base
|
- base
|
||||||
@@ -31,24 +35,22 @@ dependencies:
|
|||||||
- directory
|
- directory
|
||||||
- time
|
- time
|
||||||
|
|
||||||
- servant-swagger
|
- mongoDB
|
||||||
- servant-server
|
|
||||||
- servant-client
|
|
||||||
- servant-quickcheck
|
|
||||||
|
|
||||||
- persistent
|
|
||||||
- hedis
|
- hedis
|
||||||
- katip
|
- katip
|
||||||
- conferer
|
- conferer
|
||||||
- warp
|
|
||||||
- wai
|
|
||||||
- aeson
|
- aeson
|
||||||
- servant
|
|
||||||
|
- spock
|
||||||
- wai-websockets
|
- wai-websockets
|
||||||
- websockets
|
- websockets
|
||||||
|
|
||||||
- stm
|
- stm
|
||||||
- async
|
- async
|
||||||
- generic-optics
|
- generic-optics
|
||||||
|
- resource-pool
|
||||||
|
- data-default-class < 0.2
|
||||||
|
- data-default < 0.8.0
|
||||||
|
|
||||||
library:
|
library:
|
||||||
source-dirs: src
|
source-dirs: src
|
||||||
@@ -67,14 +69,15 @@ executables:
|
|||||||
|
|
||||||
tests:
|
tests:
|
||||||
hq-sample-test:
|
hq-sample-test:
|
||||||
main: Main.hs
|
main: Spec.hs
|
||||||
source-dirs: test
|
source-dirs: test
|
||||||
|
build-tools:
|
||||||
|
- hspec-discover:hspec-discover
|
||||||
dependencies:
|
dependencies:
|
||||||
- base
|
- base
|
||||||
|
|
||||||
- hq-sample
|
- hq-sample
|
||||||
|
|
||||||
- tasty
|
- hspec
|
||||||
- tasty-hunit
|
- hspec-discover
|
||||||
- tasty-quickcheck
|
- hspec-wai
|
||||||
- QuickCheck
|
|
||||||
|
|||||||
32
src/Data/Persist/Tap.hs
Normal file
32
src/Data/Persist/Tap.hs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
module Data.Persist.Tap where
|
||||||
|
|
||||||
|
import Data.Coerce (coerce)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Types (UserId)
|
||||||
|
import Database.MongoDB
|
||||||
|
import GHC.Generics (Generic)
|
||||||
|
import Serialize.Bson (BsonTrans (..))
|
||||||
|
import Serialize.Str
|
||||||
|
|
||||||
|
newtype TapId = TapId Text
|
||||||
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
data TapOccupation = Official | Verified | Base
|
||||||
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
data TapRole = Music | TTS
|
||||||
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
data TapPermissions = Public | OwnerOnly | Whitelist [UserId] | Blacklist [UserId]
|
||||||
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
data Tap = Tap
|
||||||
|
{ tapId :: TapId
|
||||||
|
, tapOwnerId :: UserId
|
||||||
|
, tapOccupation :: TapOccupation
|
||||||
|
, tapRoles :: [TapRole]
|
||||||
|
, tapPermissions :: TapPermissions
|
||||||
|
}
|
||||||
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
newtype CreateTap = CreateTap Tap
|
||||||
34
src/Data/Persist/TapTrans.hs
Normal file
34
src/Data/Persist/TapTrans.hs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
module Data.Persist.TapTrans where
|
||||||
|
|
||||||
|
import Data.Coerce (coerce)
|
||||||
|
import Data.Persist.Tap
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Serialize.Bson
|
||||||
|
import Serialize.Str
|
||||||
|
|
||||||
|
instance StrTrans TapOccupation where
|
||||||
|
toStr occu = case occu of
|
||||||
|
Official -> "official"
|
||||||
|
Verified -> "verified"
|
||||||
|
Base -> "base"
|
||||||
|
|
||||||
|
fromStr s = case s of
|
||||||
|
"official" -> Just Official
|
||||||
|
"verified" -> Just Verified
|
||||||
|
"base" -> Just Base
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
instance StrTrans TapRole where
|
||||||
|
toStr role = case role of
|
||||||
|
Music -> "music"
|
||||||
|
TTS -> "tts"
|
||||||
|
|
||||||
|
fromStr s = case s of
|
||||||
|
"music" -> Just Music
|
||||||
|
"tts" -> Just TTS
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
instance StrTrans TapId where
|
||||||
|
toStr = coerce
|
||||||
|
fromStr = Just . TapId
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
module Data.Track where
|
module Data.State.Track where
|
||||||
|
|
||||||
import Data.Aeson (FromJSON, ToJSON)
|
import Data.Aeson (FromJSON, ToJSON)
|
||||||
import Data.Word (Word64)
|
import Data.Word (Word64)
|
||||||
@@ -9,3 +9,4 @@ newtype TrackId = TrackId Word64
|
|||||||
deriving newtype (ToJSON, FromJSON)
|
deriving newtype (ToJSON, FromJSON)
|
||||||
|
|
||||||
data Track = Track {trackId :: TrackId}
|
data Track = Track {trackId :: TrackId}
|
||||||
|
deriving (Show, Eq, Ord, Generic)
|
||||||
23
src/Data/Types.hs
Normal file
23
src/Data/Types.hs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
module Data.Types (UserId (..)) where
|
||||||
|
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import GHC.Generics (Generic)
|
||||||
|
import Serialize.Str
|
||||||
|
|
||||||
|
newtype UserId = Discord Text
|
||||||
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
instance StrTrans UserId where
|
||||||
|
toStr userId = case userId of
|
||||||
|
Discord discordId -> "d:" <> discordId
|
||||||
|
|
||||||
|
fromStr s = do
|
||||||
|
let parts = T.splitOn ":" s
|
||||||
|
(kind, content) <- case parts of
|
||||||
|
(kind : content : _) -> Just (kind, content)
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
case kind of
|
||||||
|
"d" -> pure $ Discord content
|
||||||
|
_ -> Nothing
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
module Env.Cache where
|
module Env.Cache where
|
||||||
|
|
||||||
|
import Data.Text (Text)
|
||||||
|
|
||||||
class (Monad m) => MonadCache m where
|
class (Monad m) => MonadCache m where
|
||||||
writeCache :: String -> String -> m ()
|
writeCache :: Text -> Text -> m ()
|
||||||
|
readCache :: Text -> m (Maybe String)
|
||||||
|
|||||||
8
src/Env/Persist/Tap.hs
Normal file
8
src/Env/Persist/Tap.hs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module Env.Persist.Tap (MonadTaps (..)) where
|
||||||
|
|
||||||
|
import Data.Persist.Tap (CreateTap, Tap, TapId)
|
||||||
|
|
||||||
|
class (Monad m) => MonadTaps m where
|
||||||
|
createTap :: CreateTap -> m Tap
|
||||||
|
getTap :: TapId -> m (Maybe Tap)
|
||||||
|
deleteTap :: TapId -> m ()
|
||||||
41
src/Infrastructure/Mongo.hs
Normal file
41
src/Infrastructure/Mongo.hs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
module Infrastructure.Mongo where
|
||||||
|
|
||||||
|
import Control.Monad.IO.Class (MonadIO (liftIO))
|
||||||
|
import Control.Monad.Reader (MonadReader (ask))
|
||||||
|
import Control.Monad.Trans.Reader (ReaderT)
|
||||||
|
import Data.Pool (Pool, PoolConfig, defaultPoolConfig, newPool, withResource)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Database.MongoDB (Host (Host), Pipe, PortID (PortNumber), access, close, connect)
|
||||||
|
import Database.MongoDB.Query (Action, master)
|
||||||
|
|
||||||
|
data MongoEnv = MongoEnv
|
||||||
|
{ mongoPool :: Pool Pipe
|
||||||
|
, mongoDBName :: Text
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype MongoM a = MongoM {unMongoM :: ReaderT MongoEnv IO a}
|
||||||
|
deriving (Functor, Applicative, Monad, MonadIO, MonadReader MongoEnv)
|
||||||
|
|
||||||
|
mkMongoEnv :: Text -> Int -> Text -> IO MongoEnv
|
||||||
|
mkMongoEnv host port dbName = do
|
||||||
|
let mongoHost = Host (T.unpack host) (PortNumber (fromIntegral port))
|
||||||
|
|
||||||
|
pool <-
|
||||||
|
newPool $
|
||||||
|
defaultPoolConfig
|
||||||
|
(connect mongoHost)
|
||||||
|
close
|
||||||
|
60 -- ttl
|
||||||
|
10 -- max resources
|
||||||
|
return
|
||||||
|
MongoEnv
|
||||||
|
{ mongoPool = pool
|
||||||
|
, mongoDBName = dbName
|
||||||
|
}
|
||||||
|
|
||||||
|
runMongoAction :: Action IO r -> MongoM r
|
||||||
|
runMongoAction action = do
|
||||||
|
MongoEnv{mongoPool, mongoDBName} <- ask
|
||||||
|
liftIO $ withResource mongoPool $ \pipe ->
|
||||||
|
access pipe master mongoDBName action
|
||||||
59
src/Infrastructure/Persist/Tap.hs
Normal file
59
src/Infrastructure/Persist/Tap.hs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
module Infrastructure.Persist.Tap where
|
||||||
|
|
||||||
|
import Data.Persist.Tap
|
||||||
|
import Data.Persist.TapTrans
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Database.MongoDB
|
||||||
|
import qualified Database.MongoDB as M
|
||||||
|
import Env.Persist.Tap (MonadTaps (..))
|
||||||
|
import Infrastructure.Mongo (MongoM, runMongoAction)
|
||||||
|
import Serialize.Bson (BsonTrans (fromBson, toBson))
|
||||||
|
import Serialize.Str (StrTrans (..))
|
||||||
|
|
||||||
|
instance Val TapPermissions where
|
||||||
|
val Public = Doc ["type" =: ("public" :: Text)]
|
||||||
|
val OwnerOnly = Doc ["type" =: ("owner" :: Text)]
|
||||||
|
val (Whitelist uids) = Doc ["type" =: ("whitelist" :: Text), "users" =: uids]
|
||||||
|
val (Blacklist uids) = Doc ["type" =: ("blacklist" :: Text), "users" =: uids]
|
||||||
|
|
||||||
|
cast' (Doc d) = do
|
||||||
|
t <- cast' =<< M.lookup "type" d
|
||||||
|
case t :: Text of
|
||||||
|
"public" -> Just Public
|
||||||
|
"owner" -> Just OwnerOnly
|
||||||
|
"whitelist" -> Whitelist <$> (M.lookup "users" d >>= cast')
|
||||||
|
"blacklist" -> Blacklist <$> (M.lookup "users" d >>= cast')
|
||||||
|
_ -> Nothing
|
||||||
|
cast' _ = Nothing
|
||||||
|
|
||||||
|
instance BsonTrans Tap where
|
||||||
|
toBson tap =
|
||||||
|
let roles = toStr <$> tapRoles tap
|
||||||
|
in ["id" =: tapId tap, "ownerId" =: tapOwnerId tap, "occupation" =: tapOccupation tap, "roles" =: roles, "permissions" =: tapPermissions tap]
|
||||||
|
|
||||||
|
fromBson bson = do
|
||||||
|
id <- g "id"
|
||||||
|
ownerId <- g "ownerId"
|
||||||
|
occupation <- g "occupation"
|
||||||
|
roles <- g "roles" :: Maybe [Text]
|
||||||
|
roles <- mapM fromStr roles
|
||||||
|
permissions <- g "permissions"
|
||||||
|
|
||||||
|
pure $ Tap{tapId = id, tapOwnerId = ownerId, tapOccupation = occupation, tapRoles = roles, tapPermissions = permissions}
|
||||||
|
where
|
||||||
|
g key = M.lookup key bson >>= cast'
|
||||||
|
|
||||||
|
tapCollection :: Collection
|
||||||
|
tapCollection = "taps"
|
||||||
|
|
||||||
|
instance MonadTaps MongoM where
|
||||||
|
createTap (CreateTap tap) = runMongoAction $ do
|
||||||
|
insert tapCollection $ toBson tap
|
||||||
|
return tap
|
||||||
|
|
||||||
|
getTap id = runMongoAction $ do
|
||||||
|
doc <- findOne (select ["id" =: id] tapCollection)
|
||||||
|
return $ fromBson =<< doc
|
||||||
|
|
||||||
|
deleteTap id = runMongoAction $ do
|
||||||
|
delete (select ["id" =: id] tapCollection)
|
||||||
7
src/Serialize/Bson.hs
Normal file
7
src/Serialize/Bson.hs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module Serialize.Bson (BsonTrans (..)) where
|
||||||
|
|
||||||
|
import Database.MongoDB
|
||||||
|
|
||||||
|
class BsonTrans a where
|
||||||
|
toBson :: a -> Document
|
||||||
|
fromBson :: Document -> Maybe a
|
||||||
16
src/Serialize/Str.hs
Normal file
16
src/Serialize/Str.hs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module Serialize.Str (StrTrans (..)) where
|
||||||
|
|
||||||
|
import Data.Data (Typeable)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import Database.MongoDB
|
||||||
|
|
||||||
|
class StrTrans a where
|
||||||
|
toStr :: a -> Text
|
||||||
|
fromStr :: Text -> Maybe a
|
||||||
|
|
||||||
|
instance {-# OVERLAPPABLE #-} (StrTrans a, Eq a, Show a, Typeable a) => Val a where
|
||||||
|
val = String . toStr
|
||||||
|
|
||||||
|
cast' (String d) = fromStr d
|
||||||
|
cast' _ = Nothing
|
||||||
16
test/Data/TypesSpec.hs
Normal file
16
test/Data/TypesSpec.hs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module Data.TypesSpec (spec) where
|
||||||
|
|
||||||
|
import Data.Types (UserId (..))
|
||||||
|
import Serialize.Str
|
||||||
|
import Test.Hspec
|
||||||
|
|
||||||
|
spec :: Spec
|
||||||
|
spec = describe "userId StringTrans" $ do
|
||||||
|
it "fromStr should convert String to UserId::Discord" $ do
|
||||||
|
fromStr "d:muffin" `shouldBe` Just (Discord "muffin")
|
||||||
|
|
||||||
|
it "fromStr should fail conversion" $ do
|
||||||
|
(fromStr "muffin" :: Maybe UserId) `shouldBe` Nothing
|
||||||
|
|
||||||
|
it "toStr should convert UserId to String" $ do
|
||||||
|
toStr (Discord "muffin") `shouldBe` "d:muffin"
|
||||||
26
test/Main.hs
26
test/Main.hs
@@ -1,26 +0,0 @@
|
|||||||
{-# LANGUAGE FlexibleInstances #-}
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
|
||||||
|
|
||||||
module Main (main) where
|
|
||||||
|
|
||||||
import Data.List (isInfixOf)
|
|
||||||
import Test.Tasty
|
|
||||||
import Test.Tasty.HUnit
|
|
||||||
import Test.Tasty.QuickCheck as QC
|
|
||||||
|
|
||||||
main :: IO ()
|
|
||||||
main = spec >>= defaultMain
|
|
||||||
|
|
||||||
spec :: IO TestTree
|
|
||||||
spec = return $ testGroup "example tests" [baseTests]
|
|
||||||
|
|
||||||
baseTests :: TestTree
|
|
||||||
baseTests =
|
|
||||||
testGroup
|
|
||||||
"Base"
|
|
||||||
[ QC.testProperty "example function works" $
|
|
||||||
\(num :: Integer) ->
|
|
||||||
num * 0 == 0
|
|
||||||
]
|
|
||||||
1
test/Spec.hs
Normal file
1
test/Spec.hs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
|
||||||
Reference in New Issue
Block a user