packages feed

slackblocks-3.1.0: test/Boundaries.hs

module Main (main) where

import Control.Exception (evaluate)
import Control.Monad (forM_, unless)
import Data.Aeson (ToJSON (..), Value (..), object, (.=))
import Data.List.NonEmpty qualified as NE
import Data.Map.Strict qualified as Map
import Data.Text qualified as T
import Slackblocks

assert :: String -> Bool -> IO ()
assert label condition = unless condition (fail label)

accept :: (Show e) => Either e a -> IO a
accept = either (fail . show) pure

reject :: String -> ErrorCategory -> [PathPiece] -> Result a -> IO ()
reject label category path value = case value of
  Right _ -> fail (label <> ": unexpectedly accepted")
  Left errors -> do
    diagnosticLength <- evaluate (length (show errors))
    assert (label <> ": empty diagnostics") (diagnosticLength > 0)
    assert (label <> ": wrong category/path: " <> show errors) $
      any (\e -> errorCategory e == category && errorPath e == path) (NE.toList errors)

main :: IO ()
main = do
  let pending = taskCardBlock "task" "Waiting" TaskStatusPending
      pendingWire = object ["type" .= String "task_card", "task_id" .= String "task", "title" .= String "Waiting", "status" .= String "pending"]
  reject "standalone pending construction" TypeMismatch [Field "status"] (buildTaskCardBlock pending)
  reject "standalone pending ingress" TypeMismatch [Field "status"] (fromValue pendingWire :: Result TaskCardBlock)
  reject "standalone role ingress" TypeMismatch [Field "status"] (fromValue pendingWire :: Result Block)
  task <- accept (buildPlanTask pending)
  plan <- accept (buildPlanBlock (planBlock "Plan" [task]))
  assert "pending task only has plan wire form" $
    toJSON task
      == object
        ["task_id" .= String "task", "title" .= String "Waiting", "status" .= String "pending"]
  accept (fromValue (toJSON plan) :: Result PlanBlock) >>= assert "plan roundtrip" . (== plan)
  reject "duplicate plan task IDs" InvalidUsage [Field "tasks"] (buildPlanBlock (planBlock "Plan" [task, task]))
  reject "reserved extension" InvalidUsage [Field "text"] $
    buildPlainText ((plainText "safe") {plainTextExtensions = Map.singleton "text" (String "unchecked")})
  reject
    "unknown style"
    TypeMismatch
    [Field "mystery"]
    (fromValue (object ["mystery" .= False]) :: Result RichTextStyle)
  reject "style false still unsupported" TypeMismatch [Field "style", Field "highlight"] $
    buildRichTextText ((richTextText "Text") {richTextTextStyle = Just (richTextStyle {styleHighlight = Just False})})
  forM_ ["NaN", "Infinity", " 1", "1 ", "1e", "--1", "١", "1.2.3"] $ \bound ->
    reject ("bad decimal " <> T.unpack bound) TypeMismatch [Field "min_value"] $
      buildNumberInputElement ((numberInputElement True) {numberInputElementMinValue = Just bound})
  forM_
    [ ("0.100000000000000000000000000001", "0.100000000000000000000000000002"),
      ("-1e99999999999999999999999", "-1e99999999999999999999998"),
      ("-0", "0.0"),
      ("+1.0e0", "1"),
      (".5", "1.")
    ]
    $ \(lo, hi) -> do
      value <-
        accept $
          buildNumberInputElement
            ( (numberInputElement True)
                { numberInputElementMinValue = Just lo,
                  numberInputElementMaxValue = Just hi
                }
            )
      accept (fromValue (toJSON value) :: Result NumberInputElement) >>= assert "exact decimal roundtrip" . (== value)
  reject "nearby exact inverted decimals" OutOfRange [] $
    buildNumberInputElement
      ( (numberInputElement True)
          { numberInputElementMinValue = Just "0.100000000000000000000000000002",
            numberInputElementMaxValue = Just "0.100000000000000000000000000001"
          }
      )
  let opaque = object ["type" .= String "data_visualization", "blocks" .= [pendingWire], "integer" .= (9007199254740993 :: Integer)]
  webhook <- accept $ buildWebhookMessage (webhookMessage {webhookMessageMetadata = Just (Map.singleton "event_payload" opaque)})
  accept (fromValue (toJSON webhook) :: Result WebhookMessage) >>= assert "metadata remains opaque and exact" . (== webhook)
  reject
    "nested path"
    LengthExceeded
    [Field "blocks", Index 0, Field "text", Field "text"]
    (fromValue (object ["channel" .= String "C123", "blocks" .= [object ["type" .= String "header", "text" .= object ["type" .= String "plain_text", "text" .= String ""]]]]) :: Result MessagePayload)
  putStrLn "Native/ingress context, exact-decimal, metadata, extension, style and error-path boundaries passed."