packages feed

acolyte-grpc-0.1.0.0: test/Properties.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where

import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Text (Text)
import qualified Data.Text as T

import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range

import Acolyte.Core
import Acolyte.Server (Json)
import Acolyte.Grpc


-- ===================================================================
-- Test type with trivial GrpcCodec (identity on ByteString)
-- ===================================================================

newtype TestMsg = TestMsg ByteString
  deriving (Eq, Show)

instance GrpcCodec TestMsg where
  grpcEncode (TestMsg bs) = bs
  grpcDecode bs = Just (TestMsg bs)


-- ===================================================================
-- Property: GrpcCodec roundtrip
-- ===================================================================

prop_grpcCodecRoundtrip :: Property
prop_grpcCodecRoundtrip = property $ do
  bs <- forAll $ Gen.bytes (Range.linear 0 200)
  let msg = TestMsg bs
  grpcDecode (grpcEncode msg) === Just msg


-- ===================================================================
-- Property: Proto generation produces valid syntax
-- ===================================================================

-- Test types for proto generation
data TestUser = TestUser !Text
  deriving (Eq, Show)

instance GrpcCodec TestUser where
  grpcEncode (TestUser t) = encodeText t
  grpcDecode bs = Just (TestUser (decodeText bs))

instance ProtoMessageName TestUser where
  protoMessageName = "TestUser"

instance GrpcCodec Text where
  grpcEncode = encodeText
  grpcDecode = Just . decodeText

instance ProtoMessageName Text where
  protoMessageName = "StringValue"

-- Simple UTF-8 helpers (matching the existing test pattern)
encodeText :: Text -> ByteString
encodeText t = BS.pack (map (fromIntegral . fromEnum) (T.unpack t))

decodeText :: ByteString -> Text
decodeText = T.pack . map (toEnum . fromIntegral) . BS.unpack

-- API for proto generation test
type TestPath = '[ 'Lit "test" ]
type TestAPI = '[ Get TestPath Text
                , Post TestPath (Json TestUser) (Json TestUser)
                ]

prop_protoGenerationValid :: Property
prop_protoGenerationValid = property $ do
  -- Generate with varying package and service names
  pkg <- forAll $ Gen.text (Range.linear 1 20) Gen.alpha
  svc <- forAll $ Gen.text (Range.linear 1 20) Gen.alpha

  let proto = generateProto @TestAPI pkg svc

  -- Must start with proto3 syntax
  assert $ T.isPrefixOf "syntax = \"proto3\";" proto

  -- Must contain package declaration
  assert $ T.isInfixOf ("package " <> pkg <> ";") proto

  -- Must contain service declaration
  assert $ T.isInfixOf ("service " <> svc <> " {") proto

  -- Must have balanced braces
  let opens  = T.count "{" proto
      closes = T.count "}" proto
  opens === closes


-- ===================================================================
-- Main
-- ===================================================================

tests :: IO Bool
tests = checkParallel $$(discover)

main :: IO ()
main = do
  ok <- tests
  if ok then pure () else error "Property tests failed"