packages feed

moonlight-category-0.1.0.0: test/site/PathQuotientSpec.hs

module PathQuotientSpec
  ( tests,
  )
where

import Data.Bifunctor (first)
import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import Moonlight.Category
  ( SiteManifest (..),
    SitePathCategory,
    SitePathMorphism,
    SitePathObject,
    SitePathQuotient,
    SitePathQuotientError (..),
    mkSitePathMorphism,
    mkSitePathObject,
    quotientMapMorphism,
    quotientMapObject,
    siteObjects,
    sitePathCategory,
    sitePathQuotient,
    thinSiteKernel,
  )
import qualified Moonlight.Category.Effect.PathQuotientHarness as PathQuotientHarness
import Moonlight.Category.Effect.SiteGen (diamondManifest)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (Assertion, assertBool, assertFailure, testCase, (@?=))

diamondPathCategory :: Either () (SitePathCategory Int)
diamondPathCategory =
  case thinSiteKernel diamondManifest of
    Left _ -> Left ()
    Right kernel -> Right (sitePathCategory kernel)

tests :: TestTree
tests =
  case diamondPathCategory of
    Left _ ->
      testGroup
        "path-quotient"
        [ testCase "diamond manifest yields a path category" (assertFailure "diamond manifest should admit a site path category")
        ]
    Right category ->
      let objects = Set.toList (siteObjects diamondManifest)
          objectPairs = liftA2 (,) objects objects
       in testGroup
            "path-quotient"
            [ testCase
                "quotient uniqueness per endpoint"
                ( assertBool
                    "quotient should identify all endpoint-equal paths"
                    (all (\(sourceValue, targetValue) -> PathQuotientHarness.quotientUniquenessPerEndpoint @Int category sourceValue targetValue) objectPairs)
                ),
              testCase
                "path-thin codomain faithful"
                (assertBool "path-thin codomain map should be faithful" (PathQuotientHarness.pathThinCodomainFaithful @Int category)),
              testCase
                "interpreter coherence"
                (assertBool "quotient interpreter should agree with the codomain map" (PathQuotientHarness.quotientInterpreterCoherence @Int category)),
              testCase
                "quotientMapObject rejects objects from a different path domain"
                testQuotientMapObjectRejectsWrongDomain,
              testCase
                "quotientMapMorphism rejects morphisms from a different path domain"
                testQuotientMapMorphismRejectsWrongDomain
            ]

testQuotientMapObjectRejectsWrongDomain :: Assertion
testQuotientMapObjectRejectsWrongDomain =
  case wrongDomainFixture of
    Left message ->
      assertFailure message
    Right (leftQuotient, rightObject, _) ->
      quotientMapObject leftQuotient rightObject @?= Left QuotientObjectWrongDomain

testQuotientMapMorphismRejectsWrongDomain :: Assertion
testQuotientMapMorphismRejectsWrongDomain =
  case wrongDomainFixture of
    Left message ->
      assertFailure message
    Right (leftQuotient, _, rightMorphism) ->
      quotientMapMorphism leftQuotient rightMorphism @?= Left QuotientMorphismWrongDomain

wrongDomainFixture :: Either String (SitePathQuotient Int, SitePathObject Int, SitePathMorphism Int)
wrongDomainFixture = do
  leftKernel <- first (("left site kernel rejected: " <>) . show) (thinSiteKernel leftManifest)
  rightKernel <- first (("right site kernel rejected: " <>) . show) (thinSiteKernel rightManifest)
  let leftCategory = sitePathCategory leftKernel
      rightCategory = sitePathCategory rightKernel
      leftQuotient = sitePathQuotient leftCategory
  rightObject <-
    maybe
      (Left "right path object was not constructible")
      Right
      (mkSitePathObject rightCategory 0)
  rightMorphism <-
    maybe
      (Left "right identity path morphism was not constructible")
      Right
      (mkSitePathMorphism rightCategory (0 :| [2]))
  pure (leftQuotient, rightObject, rightMorphism)


leftManifest :: SiteManifest Int
leftManifest =
  SiteManifest
    { siteObjects = Set.fromList [0, 1, 2],
      siteImports =
        Map.fromList
          [ (0, Set.singleton 1),
            (1, Set.singleton 2),
            (2, Set.empty)
          ],
      siteCovers =
        Map.fromList
          [ (0, Set.fromList [1, 2]),
            (1, Set.singleton 2),
            (2, Set.empty)
          ]
    }

rightManifest :: SiteManifest Int
rightManifest =
  SiteManifest
    { siteObjects = Set.fromList [0, 1, 2],
      siteImports =
        Map.fromList
          [ (0, Set.fromList [1, 2]),
            (1, Set.singleton 2),
            (2, Set.empty)
          ],
      siteCovers =
        Map.fromList
          [ (0, Set.fromList [1, 2]),
            (1, Set.singleton 2),
            (2, Set.empty)
          ]
    }