packages feed

moonlight-category-0.1.0.0: test/finite/FinThinFunctorSpec.hs

module FinThinFunctorSpec
  ( tests,
  )
where

import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Moonlight.Category.Effect.Fixture.FinCat (sampleFinCat)
import Moonlight.Category.Pure.FinCat
  ( FinGeneratorId (..),
    FinMorphismId (..),
    FinObjectId (..),
    mkFinCat,
  )
import Moonlight.Category.Pure.FinCat.Functor
  ( FinThinFunctorApplicationError (..),
    FinThinFunctorValidationError (..),
    applyFinThinFunctor,
    mkFinThinFunctor,
  )
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (Assertion, assertFailure, testCase, (@?=))

tests :: TestTree
tests =
  testGroup
    "finite thin functor"
    [ testCase "retains a validated total object action" testIdentityObjectAction,
      testCase "reports domain and codomain obstructions" testObjectMapObstructions,
      testCase "reports the first order obstruction" testOrderObstruction,
      testCase "rejects a non-thin source" testNonThinSource
    ]

identityObjectMap :: Map.Map FinObjectId FinObjectId
identityObjectMap =
  Map.fromList
    [ (FinObjectId 0, FinObjectId 0),
      (FinObjectId 1, FinObjectId 1),
      (FinObjectId 2, FinObjectId 2)
    ]

testIdentityObjectAction :: Assertion
testIdentityObjectAction =
  case mkFinThinFunctor sampleFinCat sampleFinCat identityObjectMap of
    Left validationError ->
      assertFailure ("expected identity object action to validate: " <> show validationError)
    Right functorValue -> do
      applyFinThinFunctor functorValue (FinObjectId 1) @?= Right (FinObjectId 1)
      applyFinThinFunctor functorValue (FinObjectId 99)
        @?= Left (FinThinFunctorUnknownSourceObject (FinObjectId 99))

testObjectMapObstructions :: Assertion
testObjectMapObstructions = do
  assertValidationError
    (FinThinFunctorMissingSourceObject (FinObjectId 2))
    (mkFinThinFunctor sampleFinCat sampleFinCat (Map.delete (FinObjectId 2) identityObjectMap))
  assertValidationError
    (FinThinFunctorTargetObjectAbsent (FinObjectId 0) (FinObjectId 99))
    ( mkFinThinFunctor
        sampleFinCat
        sampleFinCat
        (Map.insert (FinObjectId 0) (FinObjectId 99) identityObjectMap)
    )

testOrderObstruction :: Assertion
testOrderObstruction =
  assertValidationError
    ( FinThinFunctorOrderNotPreserved
        (FinObjectId 0)
        (FinObjectId 1)
        (FinObjectId 2)
        (FinObjectId 1)
    )
    ( mkFinThinFunctor
        sampleFinCat
        sampleFinCat
        ( Map.fromList
            [ (FinObjectId 0, FinObjectId 2),
              (FinObjectId 1, FinObjectId 1),
              (FinObjectId 2, FinObjectId 0)
            ]
        )
    )

testNonThinSource :: Assertion
testNonThinSource =
  case
    mkFinCat
      (Set.fromList [FinObjectId 0, FinObjectId 1])
      ( Map.singleton
          (FinObjectId 0, FinObjectId 1)
          [ FinGeneratorMorphismId (FinGeneratorId 20),
            FinGeneratorMorphismId (FinGeneratorId 21)
          ]
      )
      Map.empty
    of
      Left validationErrors ->
        assertFailure ("expected the parallel-pair fixture to be a category: " <> show validationErrors)
      Right nonThinCategory ->
        assertValidationError
          FinThinFunctorSourceNotThin
          ( mkFinThinFunctor
              nonThinCategory
              sampleFinCat
              (Map.fromList [(FinObjectId 0, FinObjectId 0), (FinObjectId 1, FinObjectId 1)])
          )

assertValidationError ::
  FinThinFunctorValidationError ->
  Either FinThinFunctorValidationError functorValue ->
  Assertion
assertValidationError expected result =
  case result of
    Left actual -> actual @?= expected
    Right _ -> assertFailure ("expected finite-thin-functor obstruction: " <> show expected)