packages feed

plexus-synapse-3.10.1: test/StreamTrackerSpec.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

module Main where

import Test.Hspec
import Data.Aeson (Value(..), decode)
import qualified Data.ByteString.Lazy.Char8 as BL
import Data.Maybe (fromJust)

import Synapse.Self.Protocol.StreamTracker
import Synapse.Self.Protocol.Validator

main :: IO ()
main = hspec spec

spec :: Spec
spec = do
  describe "StreamTracker" $ do
    describe "trackMessage" $ do
      it "tracks subscription messages" $ do
        tracker <- newTracker
        let msg = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"data\", \"content\": \"hello\" } } }"
        violations <- trackMessage tracker msg
        violations `shouldBe` []
        count <- getStreamCount tracker
        count `shouldBe` 1

      it "detects missing StreamDone violation" $ do
        tracker <- newTracker
        let dataMsg = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"data\", \"content\": \"hello\" } } }"
        _ <- trackMessage tracker dataMsg
        violations <- checkCompletion tracker
        length violations `shouldBe` 1
        pvSeverity (head violations) `shouldBe` Error

      it "detects duplicate StreamDone violation" $ do
        tracker <- newTracker
        let doneMsg = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"done\" } } }"
        _ <- trackMessage tracker doneMsg
        violations <- trackMessage tracker doneMsg
        length violations `shouldBe` 1
        pvSeverity (head violations) `shouldBe` Error

      it "properly completes stream with StreamDone" $ do
        tracker <- newTracker
        let dataMsg = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"data\", \"content\": \"hello\" } } }"
        let doneMsg = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"done\" } } }"
        _ <- trackMessage tracker dataMsg
        _ <- trackMessage tracker doneMsg
        violations <- checkCompletion tracker
        violations `shouldBe` []

      it "tracks multiple subscriptions independently" $ do
        tracker <- newTracker
        let msg1 = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"data\", \"content\": \"stream1\" } } }"
        let msg2 = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 2, \"result\": { \"type\": \"data\", \"content\": \"stream2\" } } }"
        let done1 = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"done\" } } }"

        _ <- trackMessage tracker msg1
        _ <- trackMessage tracker msg2
        _ <- trackMessage tracker done1

        count <- getStreamCount tracker
        count `shouldBe` 2

        -- Only subscription 2 should be incomplete
        violations <- checkCompletion tracker
        length violations `shouldBe` 1

      it "detects messages after StreamDone" $ do
        tracker <- newTracker
        let doneMsg = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"done\" } } }"
        let dataMsg = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"data\", \"content\": \"after done\" } } }"

        _ <- trackMessage tracker doneMsg
        violations <- trackMessage tracker dataMsg

        length violations `shouldBe` 1
        pvSeverity (head violations) `shouldBe` Error

    describe "getAllViolations" $ do
      it "aggregates all violations from all streams" $ do
        tracker <- newTracker
        let msg1 = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"data\", \"content\": \"stream1\" } } }"
        let msg2 = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 2, \"result\": { \"type\": \"data\", \"content\": \"stream2\" } } }"
        let doneMsg1 = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"done\" } } }"
        let doneMsg1Dup = parseJson "{ \"jsonrpc\": \"2.0\", \"method\": \"subscription\", \"params\": { \"subscription\": 1, \"result\": { \"type\": \"done\" } } }"

        _ <- trackMessage tracker msg1
        _ <- trackMessage tracker msg2
        _ <- trackMessage tracker doneMsg1
        _ <- trackMessage tracker doneMsg1Dup  -- Duplicate done

        allViolations <- getAllViolations tracker
        -- Should have: 1 duplicate StreamDone + 1 missing StreamDone for stream 2
        length allViolations `shouldBe` 2

-- Helper to parse JSON strings
parseJson :: String -> Value
parseJson s = fromJust $ decode (BL.pack s)