packages feed

valiant-0.1.0.0: test/Valiant/Binary/EncodeSpec.hs

module Valiant.Binary.EncodeSpec (spec) where

import Data.ByteString qualified as BS
import Data.Int (Int16, Int32, Int64)
import Data.Text (Text)
import Data.Time
  ( LocalTime (..)
  , TimeOfDay (..)
  , UTCTime (..)
  , fromGregorian
  , secondsToDiffTime
  )
import Valiant.Binary.Encode ()
import PgWire.Binary.Types (PgEncode (..))
import Test.Hspec

spec :: Spec
spec = do
  describe "Bool" $ do
    it "encodes True as 0x01" $ do
      pgEncode True `shouldBe` BS.singleton 1
    it "encodes False as 0x00" $ do
      pgEncode False `shouldBe` BS.singleton 0

  describe "Int16" $ do
    it "encodes 0" $ do
      pgEncode (0 :: Int16) `shouldBe` BS.pack [0, 0]
    it "encodes 256 as big-endian" $ do
      pgEncode (256 :: Int16) `shouldBe` BS.pack [1, 0]
    it "encodes -1" $ do
      pgEncode (-1 :: Int16) `shouldBe` BS.pack [0xFF, 0xFF]

  describe "Int32" $ do
    it "encodes 1 as big-endian" $ do
      pgEncode (1 :: Int32) `shouldBe` BS.pack [0, 0, 0, 1]
    it "encodes 16777216 (0x01000000)" $ do
      pgEncode (16777216 :: Int32) `shouldBe` BS.pack [1, 0, 0, 0]

  describe "Int64" $ do
    it "encodes 1 as 8 bytes big-endian" $ do
      pgEncode (1 :: Int64) `shouldBe` BS.pack [0, 0, 0, 0, 0, 0, 0, 1]

  describe "Text" $ do
    it "encodes as UTF-8" $ do
      pgEncode ("hello" :: Text) `shouldBe` "hello"

  describe "ByteString" $ do
    it "encodes as identity" $ do
      pgEncode ("raw bytes" :: BS.ByteString) `shouldBe` "raw bytes"

  describe "UTCTime" $ do
    it "encodes PG epoch (2000-01-01 00:00:00 UTC) as zero" $ do
      let pgEpoch = UTCTime (fromGregorian 2000 1 1) 0
          bs = pgEncode pgEpoch
      -- PG epoch should encode to 0 microseconds
      BS.length bs `shouldBe` 8
      bs `shouldBe` BS.pack [0, 0, 0, 0, 0, 0, 0, 0]

    it "encodes a time after PG epoch as positive" $ do
      -- 2000-01-01 00:00:01 UTC = 1 second = 1000000 microseconds after PG epoch
      let t = UTCTime (fromGregorian 2000 1 1) 1
          bs = pgEncode t
      BS.length bs `shouldBe` 8
      -- 1000000 = 0x000000000F4240
      bs `shouldBe` BS.pack [0, 0, 0, 0, 0, 0x0F, 0x42, 0x40]

    it "encodes a known date correctly (2024-01-15 12:30:00 UTC)" $ do
      let t = UTCTime (fromGregorian 2024 1 15) (secondsToDiffTime (12 * 3600 + 30 * 60))
          bs = pgEncode t
      BS.length bs `shouldBe` 8

  describe "Day" $ do
    it "encodes PG epoch day (2000-01-01) as zero" $ do
      let d = fromGregorian 2000 1 1
      pgEncode d `shouldBe` BS.pack [0, 0, 0, 0]

    it "encodes 2000-01-02 as 1" $ do
      let d = fromGregorian 2000 1 2
      pgEncode d `shouldBe` BS.pack [0, 0, 0, 1]

    it "encodes a date before PG epoch as negative" $ do
      -- 1999-12-31 is -1 days from PG epoch
      let d = fromGregorian 1999 12 31
      pgEncode d `shouldBe` BS.pack [0xFF, 0xFF, 0xFF, 0xFF]

    it "produces 4 bytes" $ do
      let d = fromGregorian 2024 6 15
      BS.length (pgEncode d) `shouldBe` 4

  describe "TimeOfDay" $ do
    it "encodes midnight as zero" $ do
      let tod = TimeOfDay 0 0 0
      pgEncode tod `shouldBe` BS.pack [0, 0, 0, 0, 0, 0, 0, 0]

    it "encodes 00:00:01 as 1000000 microseconds" $ do
      let tod = TimeOfDay 0 0 1
      pgEncode tod `shouldBe` BS.pack [0, 0, 0, 0, 0, 0x0F, 0x42, 0x40]

    it "encodes 01:00:00 as 3600000000 microseconds" $ do
      let tod = TimeOfDay 1 0 0
          bs = pgEncode tod
      BS.length bs `shouldBe` 8

    it "produces 8 bytes" $ do
      let tod = TimeOfDay 12 30 45
      BS.length (pgEncode tod) `shouldBe` 8

  describe "LocalTime" $ do
    it "encodes PG epoch midnight as zero" $ do
      let lt = LocalTime (fromGregorian 2000 1 1) (TimeOfDay 0 0 0)
      pgEncode lt `shouldBe` BS.pack [0, 0, 0, 0, 0, 0, 0, 0]

    it "encodes PG epoch + 1 second" $ do
      -- 1 second = 1000000 microseconds
      let lt = LocalTime (fromGregorian 2000 1 1) (TimeOfDay 0 0 1)
      pgEncode lt `shouldBe` BS.pack [0, 0, 0, 0, 0, 0x0F, 0x42, 0x40]

    it "produces 8 bytes" $ do
      let lt = LocalTime (fromGregorian 2024 3 15) (TimeOfDay 14 30 0)
      BS.length (pgEncode lt) `shouldBe` 8