packages feed

hs-hath-1.1.1: test/R2Spec.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}

module R2Spec ( r2Specs ) where

import           Colog                ( LogAction(..), richMessageAction, Message )
import           Colog.Polysemy       ( Log, runLogAction )

import           Storage.Database     ( FileRecord(..) )

import           Polysemy
import           Polysemy.Error       ( Error, runError )
import           Polysemy.KVStore     ( KVStore, lookupKV, updateKV )

import           Storage.R2           ( R2Connection(..), mkR2Connection, runCacheR2 )

import           Relude               hiding ( Reader )

import           System.Environment   ( setEnv )

import           Test.Hspec

import           Types                ( FileURI, R2Config(..), RPCError, parseFileURI )

-- | R2 test configuration
testR2Config :: R2Config
testR2Config = R2Config
  { r2Endpoint = "https://111e2d89a4500e36dcda4853060fe725.r2.cloudflarestorage.com"
  , r2Bucket   = "hath"
  }

-- | Sample file record for testing
sampleRecord :: FileRecord
sampleRecord = FileRecord
  { fileRecordLRUCounter = 0
  , fileRecordS4         = "test"
  , fileRecordFileId     = "testhash1234-1024-800-600-jpg"
  , fileRecordFileName   = Just "test.jpg"
  , fileRecordBytes      = "test content for R2 storage"
  }

-- | Parse a file URI from the sample record
sampleURI :: FileURI
sampleURI = parseFileURI "testhash1234-1024-800-600-jpg"

-- | Silence logs for cleaner test output
silentLogAction :: LogAction IO Message
silentLogAction = LogAction $ \_ -> pure ()

-- | Run a program with R2 cache effect (silent logging)
runWithR2Silent :: R2Connection 
                -> Sem '[KVStore FileURI FileRecord, Log Message, Error RPCError, Embed IO] a 
                -> IO (Either RPCError a)
runWithR2Silent conn program = 
  runM
  . runError @RPCError
  . runLogAction @IO @Message silentLogAction
  . runCacheR2 conn
  $ program

-- | Lookup with explicit type
lookupFile :: Member (KVStore FileURI FileRecord) r => FileURI -> Sem r (Maybe FileRecord)
lookupFile = lookupKV

-- | Update with explicit type
updateFile :: Member (KVStore FileURI FileRecord) r => FileURI -> Maybe FileRecord -> Sem r ()
updateFile = updateKV

r2Specs :: Spec
r2Specs = describe "R2 Cache Effect" $ do
  
  -- Setup: ensure env vars are set
  beforeAll setupEnv $ do
    
    describe "mkR2Connection" $ do
      it "should create connection with valid env vars" $ \_ -> do
        result <- mkR2Connection testR2Config
        case result of
          Left err -> expectationFailure $ "Connection failed: " <> toString err
          Right _  -> pure ()
      
      it "should fail without R2_ACCESS_KEY" $ \_ -> do
        -- Temporarily unset
        setEnv "R2_ACCESS_KEY" ""
        result <- mkR2Connection testR2Config
        -- Restore
        setEnv "R2_ACCESS_KEY" "cbb541929f87b599e8831ee1917a57fd"
        case result of
          Left _  -> pure ()  -- Expected
          Right _ -> expectationFailure "Should have failed without R2_ACCESS_KEY"

    describe "runCacheR2" $ do
      xit "should store a file record (PUT)" $ \conn -> do
        result <- runWithR2Silent conn $ updateFile sampleURI (Just sampleRecord)
        result `shouldBe` Right ()

      xit "should retrieve a stored file record (GET)" $ \conn -> do
        -- First store
        _ <- runWithR2Silent conn $ updateFile sampleURI (Just sampleRecord)
        -- Then retrieve
        result <- runWithR2Silent conn $ lookupFile sampleURI
        case result of
          Left err -> expectationFailure $ "R2 error: " <> show err
          Right Nothing -> expectationFailure "Expected file record but got Nothing"
          Right (Just record) -> do
            fileRecordBytes record `shouldBe` fileRecordBytes sampleRecord

      it "should return Nothing for non-existent file" $ \conn -> do
        let nonExistentURI = parseFileURI "nonexistent999-1024-800-600-jpg"
        result <- runWithR2Silent conn $ lookupFile nonExistentURI
        case result of
          Left _ -> pure ()  -- Error is acceptable (404 from R2)
          Right Nothing -> pure ()  -- Expected
          Right (Just _) -> expectationFailure "Expected Nothing but got a record"

      xit "should delete a file record (DELETE)" $ \conn -> do
        -- Store first
        _ <- runWithR2Silent conn $ updateFile sampleURI (Just sampleRecord)
        -- Delete
        deleteResult <- runWithR2Silent conn $ updateFile sampleURI Nothing
        deleteResult `shouldBe` Right ()
        -- Verify deleted
        lookupResult <- runWithR2Silent conn $ lookupFile sampleURI
        case lookupResult of
          Left _ -> pure ()  -- Error acceptable
          Right Nothing -> pure ()  -- Expected
          Right (Just _) -> expectationFailure "File should have been deleted"

-- | Setup environment variables and create connection
setupEnv :: IO R2Connection
setupEnv = do
  -- Set env vars (in real tests, these would come from .env)
  setEnv "R2_ACCESS_KEY" "12345678901234567890123456789012" -- 32 chars
  setEnv "R2_SECRET_KEY" "1234567890123456789012345678901234567890" -- 40 chars
  
  result <- mkR2Connection testR2Config
  case result of
    Left err -> error $ "Failed to create R2 connection: " <> err
    Right conn -> pure conn