packages feed

todoist-sdk-0.1.2.1: integration-test/SectionIntegrationSpec.hs

{-# LANGUAGE DisambiguateRecordFields #-}

module SectionIntegrationSpec (spec) where

import Control.Applicative (Applicative (pure), (<$>))
import Control.Exception (bracket)
import Control.Monad (void)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Except (ExceptT)
import Data.Bool (Bool (..))
import Data.Function (($))
import Data.Functor (Functor (..))
import Data.List (elem)
import Data.Maybe (Maybe (..))
import Data.Monoid (Monoid (..), (<>))
import Data.Text (Text, pack)
import Helpers
    ( assertSucceeds
    , generateUniqueName
    , getTestConfig
    , liftTodoist
    )
import System.IO (IO, putStrLn)
import Test.Hspec (Spec, describe, it, pendingWith, runIO, shouldBe)
import Text.Show (show)
import qualified Web.Todoist.Domain.Project as P
import Web.Todoist.Domain.Section
    ( Section (..)
    , SectionId (..)
    , SectionParam (..)
    , addSection
    , deleteSection
    , getSection
    , getSections
    , newSectionBuilder
    , updateSection
    , updateSectionBuilder
    )
import Web.Todoist.Domain.Types (Name (..), ProjectId (..))
import Web.Todoist.Internal.Error (TodoistError)
import Web.Todoist.Runner (todoist)
import Web.Todoist.Runner.IO (TodoistConfig)
import Web.Todoist.Util.Builder (runBuilder, withName)

spec :: Spec
spec = do
    maybeConfig <- runIO getTestConfig
    case maybeConfig of
        Nothing ->
            it "requires TODOIST_TEST_API_TOKEN" $
                pendingWith "TODOIST_TEST_API_TOKEN not set"
        Just config -> do
            sectionLifecycleSpec config
            getSectionsSpec config
            updateSectionSpec config

sectionLifecycleSpec :: TodoistConfig -> Spec
sectionLifecycleSpec config =
    describe "Section CRUD lifecycle" $ do
        it "creates, retrieves, and deletes a section" $ do
            projectName <- pack <$> generateUniqueName "IntegTest-Section-Project"
            sectionName <- pack <$> generateUniqueName "IntegTest-Section"

            withTestProjectAndSection config projectName sectionName $ \projectId sectionId -> do
                -- Get section and verify fields
                section <- liftTodoist config (getSection sectionId)
                let Section {_name = secName, _project_id = secProjectId} = section
                liftIO $ secName `shouldBe` Name sectionName
                liftIO $ secProjectId `shouldBe` projectId

                -- Delete section
                liftTodoist config (deleteSection sectionId)

                -- Verify deletion by checking section is not in active sections list
                let params = SectionParam {project_id = Just projectId, cursor = Nothing, limit = Nothing}
                sections <- liftTodoist config (getSections params)
                let sectionIds = fmap (\(Section {_id = sid}) -> sid) sections
                liftIO $ sectionId `elem` sectionIds `shouldBe` False

getSectionsSpec :: TodoistConfig -> Spec
getSectionsSpec config =
    describe "Get multiple sections" $ do
        it "creates multiple sections and retrieves them with getSections" $ do
            projectName <- pack <$> generateUniqueName "IntegTest-GetSections-Project"

            withTestProject config projectName $ \projectId -> do
                let ProjectId {getProjectId = projId} = projectId

                -- Create 3 sections
                section1Name <- pack <$> liftIO (generateUniqueName "Section1")
                section2Name <- pack <$> liftIO (generateUniqueName "Section2")
                section3Name <- pack <$> liftIO (generateUniqueName "Section3")

                sectionId1 <- liftTodoist config (addSection $ runBuilder (newSectionBuilder section1Name projId) mempty)
                sectionId2 <- liftTodoist config (addSection $ runBuilder (newSectionBuilder section2Name projId) mempty)
                sectionId3 <- liftTodoist config (addSection $ runBuilder (newSectionBuilder section3Name projId) mempty)

                -- Get sections for project
                let params = SectionParam {project_id = Just projectId, cursor = Nothing, limit = Nothing}
                sections <- liftTodoist config (getSections params)

                -- Verify count and names
                let sectionNames = fmap (\(Section {_name = n}) -> n) sections
                liftIO $ Name section1Name `elem` sectionNames `shouldBe` True
                liftIO $ Name section2Name `elem` sectionNames `shouldBe` True
                liftIO $ Name section3Name `elem` sectionNames `shouldBe` True

                -- Cleanup
                liftTodoist config (deleteSection sectionId1)
                liftTodoist config (deleteSection sectionId2)
                liftTodoist config (deleteSection sectionId3)

updateSectionSpec :: TodoistConfig -> Spec
updateSectionSpec config =
    describe "Update section" $ do
        it "updates a section name" $ do
            projectName <- pack <$> generateUniqueName "IntegTest-Update-Project"
            sectionName <- pack <$> generateUniqueName "IntegTest-Update-Section"

            withTestProjectAndSection config projectName sectionName $ \_ sectionId -> do
                -- Update section name
                let newName = sectionName <> "-Updated"
                    update = runBuilder updateSectionBuilder (withName newName)
                updatedSection <- liftTodoist config (updateSection update sectionId)

                -- Verify update
                let Section {_name = updatedName, _id = updatedId} = updatedSection
                liftIO $ updatedName `shouldBe` Name newName
                liftIO $ updatedId `shouldBe` sectionId

-- | Bracket helper for creating test project
withTestProject :: TodoistConfig -> Text -> (ProjectId -> ExceptT TodoistError IO a) -> IO ()
withTestProject config projectName action = do
    let createProject = do
            liftIO $ putStrLn $ "Creating test project: " <> show projectName
            liftTodoist config (P.addProject $ runBuilder (P.createProjectBuilder projectName) mempty)

    let deleteProject' projectId = do
            liftIO $ putStrLn $ "Cleaning up test project: " <> show projectName
            void $ todoist config (P.deleteProject projectId)

    let runAction projectId = void $ assertSucceeds $ action projectId

    bracket (assertSucceeds createProject) deleteProject' runAction

-- | Bracket helper for creating test project and section
withTestProjectAndSection ::
    TodoistConfig ->
    Text ->
    Text ->
    (ProjectId -> SectionId -> ExceptT TodoistError IO a) ->
    IO ()
withTestProjectAndSection config projectName sectionName action = do
    let createResources = do
            liftIO $
                putStrLn $
                    "Creating test project and section: " <> show projectName <> " / " <> show sectionName
            projectId <- liftTodoist config (P.addProject $ runBuilder (P.createProjectBuilder projectName) mempty)
            let ProjectId {getProjectId = projId} = projectId
                sectionCreate = runBuilder (newSectionBuilder sectionName projId) mempty
            sectionId <- liftTodoist config (addSection sectionCreate)
            pure (projectId, sectionId)

    let deleteResources (projectId, sectionId) = do
            liftIO $
                putStrLn $
                    "Cleaning up test section and project: " <> show sectionName <> " / " <> show projectName
            void $ todoist config (deleteSection sectionId)
            void $ todoist config (P.deleteProject projectId)

    let runAction (projectId, sectionId) = void $ assertSucceeds $ action projectId sectionId

    bracket (assertSucceeds createResources) deleteResources runAction