99module SMPWebTests (smpWebTests ) where
1010
1111import qualified Data.ByteString as B
12- import Data.Word (Word16 )
1312import Simplex.Messaging.Encoding
1413import Test.Hspec hiding (it )
1514import Util
@@ -21,17 +20,73 @@ smpWebDir = "smp-web"
2120callNode :: String -> IO B. ByteString
2221callNode = callNode_ smpWebDir
2322
24- impEnc :: String
25- impEnc = " import { encodeBytes, encodeWord16 } from '@simplex-chat/xftp-web/dist/protocol/encoding.js';"
23+ impProto :: String
24+ impProto = " import { encodeTransmission, decodeTransmission, encodeLGET, decodeLNK, decodeResponse } from './dist/protocol.js';"
25+ <> " import { Decoder } from '@simplex-chat/xftp-web/dist/protocol/encoding.js';"
2626
2727smpWebTests :: SpecWith ()
2828smpWebTests = describe " SMP Web Client" $ do
29- describe " xftp-web imports" $ do
30- it " encodeBytes via xftp-web" $ do
31- let val = " hello" :: B. ByteString
32- actual <- callNode $ impEnc <> jsOut (" encodeBytes(" <> jsUint8 val <> " )" )
33- actual `shouldBe` smpEncode val
34- it " encodeWord16 via xftp-web" $ do
35- let val = 12345 :: Word16
36- actual <- callNode $ impEnc <> jsOut (" encodeWord16(" <> show val <> " )" )
37- actual `shouldBe` smpEncode val
29+ describe " protocol" $ do
30+ describe " transmission" $ do
31+ it " encodeTransmission matches Haskell" $ do
32+ let corrId = " 1"
33+ entityId = B. pack [1 .. 24 ]
34+ command = " LGET"
35+ hsEncoded = smpEncode (corrId :: B. ByteString , entityId :: B. ByteString ) <> command
36+ tsEncoded <- callNode $ impProto
37+ <> jsOut (" encodeTransmission("
38+ <> jsUint8 corrId <> " ,"
39+ <> jsUint8 entityId <> " ,"
40+ <> " new Uint8Array([0x4C,0x47,0x45,0x54])" -- "LGET"
41+ <> " )" )
42+ -- TS encodes with empty auth prefix, HS encodeTransmission_ doesn't include auth
43+ -- So TS output = [0x00] ++ hsEncoded
44+ tsEncoded `shouldBe` (B. singleton 0 <> hsEncoded)
45+
46+ it " decodeTransmission parses Haskell-encoded" $ do
47+ let corrId = " abc"
48+ entityId = B. pack [10 .. 33 ]
49+ command = " TEST"
50+ encoded = smpEncode (B. empty :: B. ByteString ) -- empty auth
51+ <> smpEncode corrId
52+ <> smpEncode entityId
53+ <> command
54+ -- TS decodes and returns corrId ++ entityId ++ command concatenated with length prefixes
55+ tsResult <- callNode $ impProto
56+ <> " const t = decodeTransmission(new Decoder(" <> jsUint8 encoded <> " ));"
57+ <> jsOut (" new Uint8Array([...t.corrId, ...t.entityId, ...t.command])" )
58+ tsResult `shouldBe` (corrId <> entityId <> command)
59+
60+ describe " LGET" $ do
61+ it " encodeLGET produces correct bytes" $ do
62+ tsResult <- callNode $ impProto <> jsOut " encodeLGET()"
63+ tsResult `shouldBe` " LGET"
64+
65+ describe " LNK" $ do
66+ it " decodeLNK parses correctly" $ do
67+ let senderId = B. pack [1 .. 24 ]
68+ fixedData = B. pack [100 .. 110 ]
69+ userData = B. pack [200 .. 220 ]
70+ encoded = smpEncode senderId <> smpEncode (Large fixedData) <> smpEncode (Large userData)
71+ tsResult <- callNode $ impProto
72+ <> " const r = decodeLNK(new Decoder(" <> jsUint8 encoded <> " ));"
73+ <> jsOut (" new Uint8Array([...r.senderId, ...r.encFixedData, ...r.encUserData])" )
74+ tsResult `shouldBe` (senderId <> fixedData <> userData)
75+
76+ describe " decodeResponse" $ do
77+ it " decodes LNK response" $ do
78+ let senderId = B. pack [1 .. 24 ]
79+ fixedData = B. pack [100 .. 110 ]
80+ userData = B. pack [200 .. 220 ]
81+ commandBytes = " LNK " <> smpEncode senderId <> smpEncode (Large fixedData) <> smpEncode (Large userData)
82+ tsResult <- callNode $ impProto
83+ <> " const r = decodeResponse(new Decoder(" <> jsUint8 commandBytes <> " ));"
84+ <> " if (r.type !== 'LNK') throw new Error('expected LNK, got ' + r.type);"
85+ <> jsOut (" new Uint8Array([...r.response.senderId])" )
86+ tsResult `shouldBe` senderId
87+
88+ it " decodes OK response" $ do
89+ tsResult <- callNode $ impProto
90+ <> " const r = decodeResponse(new Decoder(new Uint8Array([0x4F, 0x4B])));" -- "OK"
91+ <> jsOut (" new Uint8Array([r.type === 'OK' ? 1 : 0])" )
92+ tsResult `shouldBe` B. singleton 1
0 commit comments