graphula: A simple interface for generating persistent data and linking its dependencies

[ library, mit, network ] [ Propose Tags ]

Please see README.md


[Skip to Readme]
Versions [RSS] [faq] 2.0.0.1, 2.0.0.2, 2.0.0.3, 2.0.0.4, 2.0.0.5, 2.0.1.0
Change log CHANGELOG.md
Dependencies base (>=4.14.1.0 && <5), containers (>=0.6.2.1), directory (>=1.3.6.0), generics-eot (>=0.4.0.1), HUnit (>=1.6.1.0), mtl (>=2.2.2), persistent (>=2.11.0.4), QuickCheck (>=2.14.2), random (>=1.1), semigroups (>=0.19.1), temporary (>=1.3), text (>=1.2.4.1), transformers (>=0.5.6.2), unliftio (>=0.2.14), unliftio-core (>=0.2.0.1) [details]
License MIT
Author
Maintainer Freckle Education
Category Network
Home page https://github.com/freckle/graphula#readme
Bug tracker https://github.com/freckle/graphula/issues
Source repo head: git clone https://github.com/freckle/graphula
Uploaded by PatrickBrisbin at 2021-11-02T16:01:12Z
Distributions LTSHaskell:2.0.1.0, NixOS:2.0.0.5, Stackage:2.0.1.0
Downloads 666 total (62 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs available [build log]
Last success reported on 2021-11-02 [all 1 reports]

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

For package maintainers and hackage trustees

Candidates


Readme for graphula-2.0.1.0

[back to package description]

Graphula

Graphula is a simple interface for generating persistent data and linking its dependencies. We use this interface to generate fixtures for automated testing.

Arbitrary Data

Graphula utilizes QuickCheck to generate random data. We need to declare Arbitrary instances for our models.

share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
School
  name String
  deriving Show Eq Generic

Teacher
  schoolId SchoolId
  name String
  deriving Show Eq Generic

Course
  schoolId SchoolId
  teacherId TeacherId
  name String
  deriving Show Eq Generic

Student
  name String
  deriving Show Eq Generic

Question
  content String
  deriving Show Eq Generic

Answer
  questionId QuestionId
  studentId StudentId
  yes Bool
  UniqueAnswer questionId studentId
  deriving Show Eq Generic
|]

instance Arbitrary School where
  arbitrary = genericArbitrary

instance Arbitrary Teacher where
  arbitrary = genericArbitrary

instance Arbitrary Course where
  arbitrary = genericArbitrary

instance Arbitrary Student where
  arbitrary = genericArbitrary

instance Arbitrary Question where
  arbitrary = genericArbitrary

instance Arbitrary Answer where
  arbitrary = genericArbitrary

Dependencies

We declare dependencies via the HasDependencies typeclass and its associated type Dependencies. If a model does not have any dependencies, we only need to declare an empty instance.

instance HasDependencies School

instance HasDependencies Student

instance HasDependencies Question

For single-dependency models, we use the Only type.

instance HasDependencies Teacher where
  type Dependencies Teacher = Only SchoolId

Multi-dependency models use tuples. Declare these dependencies in the order they appear in the model's type definition. HasDependencies leverages generic programming to inject dependencies for you.

instance HasDependencies Course where
  type Dependencies Course = (SchoolId, TeacherId)

instance HasDependencies Answer where
  type Dependencies Answer = (QuestionId, StudentId)

Logging failures

runGraphulaLogged will dump generated data to a temporary file. Or runGraphulaLoggedWithFileT can be used to pass an explicit path.

loggingSpec :: IO ()
loggingSpec = do
  let
    logFile :: FilePath
    logFile = "test.graphula"

    failingGraph :: IO ()
    failingGraph = runGraphulaT Nothing runDB . runGraphulaLoggedWithFileT logFile $ do
      student <- node @Student () mempty
      question <- node @Question () mempty
      answer <- node @Answer
        (entityKey question, entityKey student)
        $ edit $ \a -> a { answerYes = True }

      -- Test failures will cause the graph to be logged (not any exception)
      liftIO $ answerYes (entityVal answer) `shouldBe` False

  failingGraph `shouldThrow` anyException

  n <- lines <$> readFile logFile
  n `shouldSatisfy` (not . null)

Running It

simpleSpec :: IO ()
simpleSpec =
  runGraphulaT Nothing runDB $ do
    school <- node @School () mempty
    teacher <- node @Teacher (Only $ entityKey school) mempty
    course <- node @Course (entityKey school, entityKey teacher) mempty
    student <- node @Student () $ edit $ \s -> s { studentName = "Pat" }
    question <- node @Question () mempty
    answer <- node @Answer
      (entityKey question, entityKey student)
      $ edit $ \a -> a { answerYes = True }

    liftIO $ do
      -- Typically, you would run some other function like "fetch correct
      -- answers at school" and assert you found the correct answers you
      -- generated. In this example we just assert some things about the data
      -- directly:
      teacherSchoolId (entityVal teacher) `shouldBe` entityKey school
      courseTeacherId (entityVal course) `shouldBe` entityKey teacher
      answerYes (entityVal answer) `shouldBe` True