fradrive/test/Handler/Sheet/PersonalisedFilesSpec.hs
2021-02-15 15:31:23 +01:00

124 lines
5.2 KiB
Haskell

module Handler.Sheet.PersonalisedFilesSpec where
import TestImport
import Utils.Files
import Handler.Sheet.PersonalisedFiles
import qualified Yesod.Persist as Yesod
import ModelSpec ()
import Data.Universe.Class
import Data.Conduit
import qualified Data.Conduit.Combinators as C
import Control.Lens.Extras
import Control.Monad.Trans.Maybe
import System.FilePath (dropDrive)
import Data.Time.Clock (diffUTCTime)
import Data.Char (chr)
import Database.Persist.Sql (transactionUndo)
import Data.Bitraversable
import qualified Data.Set as Set
instance Arbitrary (FileReferenceResidual PersonalisedSheetFile) where
arbitrary = PersonalisedSheetFileResidual
<$> arbitrary
<*> arbitrary
<*> elements [ sfType | sfType <- universeF, sfType /= SheetMarking ]
instance Arbitrary PersonalisedSheetFilesDownloadAnonymous where
arbitrary = elements universeF
spec :: Spec
spec = withApp . describe "Personalised sheet file zip encoding" $ do
it "roundtrips" . replicateM_ 10 . runHandler . Yesod.runDB $ do
term <- liftIO $ generate arbitrary
tid <- insert term
school <- liftIO $ generate arbitrary
ssh <- insert school
course <- liftIO $ generate arbitrary <&> \c -> c { courseTerm = tid, courseSchool = ssh }
cid <- insert course
sheet <- liftIO $ generate arbitrary <&> \s -> s { sheetCourse = cid }
shid <- insert sheet
sheetFiles' <- liftIO . generate . listOf $ scale (`div` 2) arbitrary
sheetFiles <- fmap catMaybes . forM sheetFiles' $ \(f', res') -> runMaybeT $ do
let f = f' { fileTitle = filter (/= chr 0) $ fileTitle f' } -- PostgreSQL doesn't like to store NUL-bytes in text
guard . not . null . dropDrive $ fileTitle f
uid <-
let userLoop = do
user <- liftIO $ generate arbitrary
lift (insertUnique user) >>= maybe userLoop return
in userLoop
let res = res' { personalisedSheetFileResidualSheet = shid, personalisedSheetFileResidualUser = uid }
fRef <- lift (sinkFile (transFile generalize f) :: DB FileReference)
now <- liftIO getCurrentTime
void . lift . insert $ CourseParticipant cid (personalisedSheetFileResidualUser res) now Nothing CourseParticipantActive
void . lift . insert $ _FileReference # (fRef, res)
return (f, res)
anonMode <- liftIO $ generate arbitrary
let
fpL :: forall m. Lens' (Either PersonalisedSheetFile (File m)) FilePath
fpL = lens (either personalisedSheetFileTitle fileTitle) $ \f' path -> case f' of
Left pf -> Left pf { personalisedSheetFileTitle = path }
Right f -> Right f { fileTitle = path }
isDirectory = either (is _Nothing . personalisedSheetFileContent) (is _Nothing . fileContent)
loadFile :: Either (PersonalisedSheetFileUnresolved (Either PersonalisedSheetFile DBFile)) (Either PersonalisedSheetFile DBFile, FileReferenceResidual PersonalisedSheetFile)
-> DB (Either (PersonalisedSheetFileUnresolved (Either PersonalisedSheetFile PureFile)) (Either PersonalisedSheetFile PureFile, FileReferenceResidual PersonalisedSheetFile))
loadFile = bitraverse loadUnresolved loadResolved
where
loadUnresolved = traverse $ traverse toPureFile
loadResolved (f, fRes) = (, fRes) <$> traverse toPureFile f
recoveredFiles <- runConduit $
sourcePersonalisedSheetFiles cid (Just shid) Nothing anonMode Set.empty
.| resolvePersonalisedSheetFiles fpL isDirectory cid shid
.| C.mapM loadFile
.| C.foldMap pure
let
checkFile :: Either (PersonalisedSheetFileUnresolved (Either PersonalisedSheetFile PureFile)) (Either PersonalisedSheetFile PureFile, FileReferenceResidual PersonalisedSheetFile)
-> (PureFile, FileReferenceResidual PersonalisedSheetFile)
-> Bool
checkFile (Left _) _
= False
checkFile (Right (recFile, recResidual)) (file, residual)
= recResidual == residual
&& case recFile of
Right f -> f == file
Left pf -> dropDrive (fileTitle file) == dropDrive (personalisedSheetFileTitle pf)
&& abs (fileModified file `diffUTCTime` personalisedSheetFileModified pf) < 1e-6 -- Precision is a PostgreSQL limitation
&& fileReferenceContent (pureFileToFileReference file) == personalisedSheetFileContent pf
errors = go [] sheetFiles recoveredFiles
where go acc xs [] = reverse acc ++ map Left xs
go acc [] ys = reverse acc ++ map Right ys
go acc xs (y:ys)
| (xs', _ : xs'') <- break (checkFile y) xs
= go acc (xs' ++ xs'') ys
| is (_Left . _PSFUnresolved) y
, fromMaybe False $ previews (_Left . _PSFUnresolved . _Right . _fileTitle) ("meta-informationen" `isInfixOf`) y -- DEBUG; remove once _PSFUnresolvedCollatable works
= go acc xs ys
| isn't (_Left . _PSFUnresolved) y
, isn't _Right y
= go acc xs ys
| otherwise = go (Right y : acc) xs ys
unless (null errors) . liftIO $
expectationFailure $ show recoveredFiles ++ " does not match " ++ show sheetFiles ++ ": " ++ show errors
transactionUndo