mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-27 17:58:47 +00:00
443 lines
19 KiB
Haskell
443 lines
19 KiB
Haskell
{-# LANGUAGE DataKinds #-}
|
|
{-# LANGUAGE DisambiguateRecordFields #-}
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
|
{-# LANGUAGE GADTs #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
|
|
module BadgeTests (badgeTests) where
|
|
|
|
import Control.Concurrent.STM (atomically)
|
|
import Data.Aeson (FromJSON, ToJSON)
|
|
import qualified Data.Aeson as J
|
|
import qualified Data.Aeson.KeyMap as JM
|
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
|
import Data.Map.Strict (Map)
|
|
import qualified Data.Map.Strict as M
|
|
import Data.Text (Text)
|
|
import Data.Time.Clock (UTCTime, addUTCTime, getCurrentTime, nominalDay)
|
|
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
|
|
import qualified Simplex.Messaging.Crypto as C
|
|
import Simplex.Chat.Badges
|
|
import qualified Simplex.Chat.Badges as CB
|
|
import Simplex.Chat.Badges.Service
|
|
import Simplex.Chat.Badges.Types
|
|
-- PaymentStatus is hidden: its PSFailed collides with BadgePurchaseStatus's, and the column
|
|
-- spellings of both are pinned below. The payment statuses are reached through PT instead.
|
|
import Simplex.Chat.PaymentService hiding (PaymentStatus (..))
|
|
import qualified Simplex.Chat.PaymentService.Types as PT
|
|
import Simplex.Messaging.Crypto.BBS
|
|
import Simplex.Messaging.Encoding.String (TextEncoding (..))
|
|
import Test.Hspec
|
|
|
|
badgeTests :: Spec
|
|
badgeTests = do
|
|
it "full workflow: request, issue, verify credential, generate and verify proof" testFullWorkflow
|
|
it "should reject badge with tampered type" testTamperedType
|
|
it "should reject badge with tampered expiry" testTamperedExpiry
|
|
it "should reject badge with wrong server key" testWrongKey
|
|
it "should report a key index missing from configured keys" testUnknownKeyIdx
|
|
it "should compute badge status correctly" testExpiryCheck
|
|
it "should treat lifetime badges as always active" testLifetimeBadge
|
|
it "should accept unknown badge types" testUnknownBadgeType
|
|
it "credential serializes to a paste-able token and back" testCredentialSerialization
|
|
it "round-trips BadgeItemStatus JSON with the documented spellings" testBadgeItemStatusJSON
|
|
it "round-trips CardProvider and CryptoCurrency JSON" testCardCryptoJSON
|
|
it "round-trips BadgePurchaseStatus, InvoiceStatus and PaymentStatus DB column spellings" testBadgeStatusColumnSpelling
|
|
it "round-trips OfferDiscount and service payment method/destination JSON" testOfferDiscountAndPaymentMethodJSON
|
|
it "round-trips every ServicePayment constructor" testServicePaymentJSON
|
|
it "round-trips every known StatementCreditType constructor" testStatementCreditTypeJSON
|
|
it "decodes an unrecognised ledgerCredit tag into SCUnknown and re-encodes it byte-identically" testStatementCreditUnknownTag
|
|
it "round-trips every known StatementDebitType constructor" testStatementDebitTypeJSON
|
|
it "decodes an unrecognised ledgerDebit tag into SDUnknown and re-encodes it byte-identically" testStatementDebitUnknownTag
|
|
it "round-trips StatementEntry, BadgeBalance and BadgeStatement JSON" testStatementEntryJSON
|
|
it "round-trips BadgePrice, BadgeOffer and BadgeCatalog JSON" testCatalogJSON
|
|
it "round-trips BadgeUpgrade and ServiceInvoice JSON" testBadgeUpgradeAndInvoiceJSON
|
|
it "round-trips every BadgeServiceCommand constructor" testBadgeServiceCommandJSON
|
|
it "round-trips every BadgeServiceResponse constructor" testBadgeServiceResponseJSON
|
|
it "round-trips BadgeServiceRequest JSON" testBadgeServiceRequestJSON
|
|
|
|
proofOf :: BadgeProof -> BBSProof
|
|
proofOf (BadgeProof _ _ p _) = p
|
|
|
|
proofInfo :: BadgeProof -> BadgeInfo
|
|
proofInfo (BadgeProof _ _ _ i) = i
|
|
|
|
testKeyIdx :: Int
|
|
testKeyIdx = 1
|
|
|
|
keysFor :: BBSPublicKey -> Map Int BBSPublicKey
|
|
keysFor = M.singleton testKeyIdx
|
|
|
|
testFullWorkflow :: IO ()
|
|
testFullWorkflow = do
|
|
Right (pk, sk) <- bbsKeyGen
|
|
drg <- C.newRandom
|
|
mk <- generateMasterKey drg
|
|
let req = BadgeRequest {masterKey = mk, badgeInfo = BadgeInfo {badgeType = BTSupporter, badgeExpiry = Just futureTime, badgeExtra = ""}}
|
|
Just vreq <- verifyPayment (BPRedeemCode "TEST") req
|
|
Right cred <- issueBadge testKeyIdx sk vreq
|
|
let BadgeCredential idx mk' _ _ = cred
|
|
idx `shouldBe` testKeyIdx
|
|
mk' `shouldBe` mk
|
|
verifyCredential pk cred >>= (`shouldBe` True)
|
|
Right badge <- generateBadgeProof pk cred (BBSPresHeader "nonce-1")
|
|
-- the proof inherits the credential's key index, so receivers find the right key
|
|
let BadgeProof {badgeKeyIdx} = badge
|
|
badgeKeyIdx `shouldBe` testKeyIdx
|
|
verifyBadge (keysFor pk) badge >>= (`shouldBe` Just True)
|
|
Right badge2 <- generateBadgeProof pk cred (BBSPresHeader "nonce-2")
|
|
verifyBadge (keysFor pk) badge2 >>= (`shouldBe` Just True)
|
|
proofOf badge `shouldNotBe` proofOf badge2
|
|
|
|
testTamperedType :: IO ()
|
|
testTamperedType = do
|
|
(pk, BadgeProof idx ph p info) <- issueBadgeProof BTSupporter (Just futureTime)
|
|
verifyBadge (keysFor pk) (BadgeProof idx ph p info {CB.badgeType = BTLegend}) >>= (`shouldBe` Just False)
|
|
|
|
testTamperedExpiry :: IO ()
|
|
testTamperedExpiry = do
|
|
(pk, BadgeProof idx ph p info) <- issueBadgeProof BTSupporter (Just futureTime)
|
|
verifyBadge (keysFor pk) (BadgeProof idx ph p info {CB.badgeExpiry = Just pastTime}) >>= (`shouldBe` Just False)
|
|
|
|
testWrongKey :: IO ()
|
|
testWrongKey = do
|
|
(_, badge) <- issueBadgeProof BTSupporter (Just futureTime)
|
|
Right (pk2, _) <- bbsKeyGen
|
|
verifyBadge (keysFor pk2) badge >>= (`shouldBe` Just False)
|
|
|
|
testUnknownKeyIdx :: IO ()
|
|
testUnknownKeyIdx = do
|
|
(pk, badge) <- issueBadgeProof BTSupporter (Just futureTime)
|
|
-- a key index not in the configured keys cannot be verified at all (Nothing)
|
|
verifyBadge (M.singleton (testKeyIdx + 1) pk) badge >>= (`shouldBe` Nothing)
|
|
|
|
testExpiryCheck :: IO ()
|
|
testExpiryCheck = do
|
|
now <- getCurrentTime
|
|
let info expiry = BadgeInfo {badgeType = BTSupporter, badgeExpiry = expiry, badgeExtra = ""}
|
|
futureInfo = info (Just futureTime)
|
|
mkBadgeStatus now (Just True) futureInfo `shouldBe` BSActive
|
|
mkBadgeStatus now (Just True) (info (Just (addUTCTime (-nominalDay) now))) `shouldBe` BSExpired
|
|
mkBadgeStatus now (Just True) (info (Just pastTime)) `shouldBe` BSExpiredOld
|
|
mkBadgeStatus now (Just False) futureInfo `shouldBe` BSFailed
|
|
mkBadgeStatus now Nothing futureInfo `shouldBe` BSUnknownKey
|
|
|
|
testLifetimeBadge :: IO ()
|
|
testLifetimeBadge = do
|
|
now <- getCurrentTime
|
|
(pk, badge) <- issueBadgeProof BTInvestor Nothing
|
|
verifyBadge (keysFor pk) badge >>= (`shouldBe` Just True)
|
|
mkBadgeStatus now (Just True) (proofInfo badge) `shouldBe` BSActive
|
|
|
|
testUnknownBadgeType :: IO ()
|
|
testUnknownBadgeType = do
|
|
(pk, badge) <- issueBadgeProof (BTUnknown "future_type") (Just futureTime)
|
|
verifyBadge (keysFor pk) badge >>= (`shouldBe` Just True)
|
|
|
|
testCredentialSerialization :: IO ()
|
|
testCredentialSerialization = do
|
|
Right (pk, sk) <- bbsKeyGen
|
|
drg <- C.newRandom
|
|
mk <- generateMasterKey drg
|
|
let mkCred expiry = do
|
|
Right cred <- issueBadge testKeyIdx sk (VerifiedBadgeRequest BadgeRequest {masterKey = mk, badgeInfo = BadgeInfo {badgeType = BTSupporter, badgeExpiry = expiry, badgeExtra = ""}})
|
|
pure cred
|
|
dated <- mkCred (Just futureTime)
|
|
lifetime <- mkCred Nothing
|
|
J.eitherDecode (J.encode dated) `shouldBe` Right dated
|
|
J.eitherDecode (J.encode lifetime) `shouldBe` Right lifetime
|
|
-- a decoded credential still verifies against the issuing key
|
|
case J.eitherDecode (J.encode dated) of
|
|
Right cred -> verifyCredential pk cred >>= (`shouldBe` True)
|
|
Left e -> expectationFailure e
|
|
|
|
-- Badge protocol JSON (A2)
|
|
|
|
-- encode, decode, re-encode: checks both that FromJSON accepts what ToJSON produces and that the
|
|
-- re-encoding is byte-identical to the original (the specific property SCUnknown/SDUnknown need)
|
|
roundtripsBytes :: forall a. (ToJSON a, FromJSON a) => a -> Expectation
|
|
roundtripsBytes v = case J.eitherDecode bytes :: Either String a of
|
|
Left e -> expectationFailure e
|
|
Right v' -> J.encode v' `shouldBe` bytes
|
|
where
|
|
bytes = J.encode v
|
|
|
|
mkKeyPair :: IO (C.PublicKeyEd25519, C.PrivateKeyEd25519)
|
|
mkKeyPair = do
|
|
drg <- C.newRandom
|
|
(pub, priv :: C.PrivateKeyEd25519) <- atomically $ C.generateKeyPair drg
|
|
pure (pub, priv)
|
|
|
|
jsonBadgeInfo :: BadgeInfo
|
|
jsonBadgeInfo = BadgeInfo {badgeType = BTSupporter, badgeExpiry = Just futureTime, badgeExtra = ""}
|
|
|
|
jsonCredential :: IO BadgeCredential
|
|
jsonCredential = do
|
|
Right (_, sk) <- bbsKeyGen
|
|
drg <- C.newRandom
|
|
mk <- generateMasterKey drg
|
|
Right cred <- issueBadge testKeyIdx sk (VerifiedBadgeRequest BadgeRequest {masterKey = mk, badgeInfo = jsonBadgeInfo})
|
|
pure cred
|
|
|
|
sampleEntry :: IO StatementEntry
|
|
sampleEntry = do
|
|
now <- getCurrentTime
|
|
pure
|
|
StatementEntry
|
|
{ entryId = "entry-1",
|
|
changeMonths = 1,
|
|
balanceMonths = 12,
|
|
balanceStartTs = now,
|
|
balanceBadgeType = BTSupporter,
|
|
wasPausedSince = Nothing,
|
|
createdAt = now,
|
|
entryType = SECredit {credit = SCOpening}
|
|
}
|
|
|
|
sampleBalance :: IO BadgeBalance
|
|
sampleBalance = BadgeBalance <$> sampleEntry
|
|
|
|
samplePaymentDestination :: ServicePaymentDestination
|
|
samplePaymentDestination = SPDCard {provider = CPStripe, url = "https://pay.example/session"}
|
|
|
|
testBadgeItemStatusJSON :: IO ()
|
|
testBadgeItemStatusJSON = do
|
|
mapM_ roundtripsBytes [BISActive, BISDeprecated, BISDisabled]
|
|
J.eitherDecode (J.encode BISActive) `shouldBe` Right ("active" :: Text)
|
|
J.eitherDecode (J.encode BISDeprecated) `shouldBe` Right ("deprecated" :: Text)
|
|
J.eitherDecode (J.encode BISDisabled) `shouldBe` Right ("disabled" :: Text)
|
|
|
|
testCardCryptoJSON :: IO ()
|
|
testCardCryptoJSON = do
|
|
roundtripsBytes CPStripe
|
|
mapM_ roundtripsBytes [CCBtc, CCXmr]
|
|
J.eitherDecode (J.encode CPStripe) `shouldBe` Right ("stripe" :: Text)
|
|
J.eitherDecode (J.encode CCBtc) `shouldBe` Right ("btc" :: Text)
|
|
J.eitherDecode (J.encode CCXmr) `shouldBe` Right ("xmr" :: Text)
|
|
|
|
testBadgeStatusColumnSpelling :: IO ()
|
|
testBadgeStatusColumnSpelling = do
|
|
mapM_ roundtripsBytes [PSAcquiring, PSIssued, PSSuperseded, PSFailed]
|
|
J.eitherDecode (J.encode PSAcquiring) `shouldBe` Right ("acquiring" :: Text)
|
|
J.eitherDecode (J.encode PSIssued) `shouldBe` Right ("issued" :: Text)
|
|
J.eitherDecode (J.encode PSSuperseded) `shouldBe` Right ("superseded" :: Text)
|
|
J.eitherDecode (J.encode PSFailed) `shouldBe` Right ("failed" :: Text)
|
|
-- invoices.status and payments.status have no JSON at all: they never cross the wire, so
|
|
-- TextEncoding is their whole contract and it is asserted directly.
|
|
map textEncode [ISOpen, ISPaid, ISExpired] `shouldBe` ["open", "paid", "expired"]
|
|
map textDecode ["open", "paid", "expired", "settled"]
|
|
`shouldBe` [Just ISOpen, Just ISPaid, Just ISExpired, Nothing]
|
|
map textEncode [PT.PSPending, PT.PSSettled, PT.PSFailed "card declined"]
|
|
`shouldBe` ["pending", "settled", "failed"]
|
|
-- PaymentStatus has no Eq, and textDecode cannot recover the failure text -- it is
|
|
-- payments.exception, a column of its own -- so the spelling is what is compared back.
|
|
map (fmap textEncode . paymentStatus) ["pending", "settled", "failed", "new"]
|
|
`shouldBe` [Just "pending", Just "settled", Just "failed", Nothing]
|
|
where
|
|
paymentStatus :: Text -> Maybe PT.PaymentStatus
|
|
paymentStatus = textDecode
|
|
|
|
testOfferDiscountAndPaymentMethodJSON :: IO ()
|
|
testOfferDiscountAndPaymentMethodJSON = do
|
|
roundtripsBytes (ODFreeMonths {freeMonths = 3})
|
|
roundtripsBytes (ODDiscount {discount = 20})
|
|
roundtripsBytes (SPMCard {provider = CPStripe})
|
|
roundtripsBytes (SPMCrypto {currency = CCBtc})
|
|
roundtripsBytes samplePaymentDestination
|
|
roundtripsBytes (SPDCrypto {currency = CCXmr, address = "4Axxxxxxxxxxxxxxxxxxxxxxxxxxxxx", cryptoAmount = "0.5"})
|
|
|
|
testServicePaymentJSON :: IO ()
|
|
testServicePaymentJSON = do
|
|
roundtripsBytes (SPApple {jws = "jws-token"})
|
|
roundtripsBytes (SPGoogle {token = "google-token"})
|
|
roundtripsBytes (SPInvoice {invoiceId = InvoiceId "inv-1"})
|
|
roundtripsBytes (SPCode {code = "CODE123"})
|
|
roundtripsBytes (SPReceipt {receipt = "receipt-blob"})
|
|
|
|
testStatementCreditTypeJSON :: IO ()
|
|
testStatementCreditTypeJSON = do
|
|
roundtripsBytes (SCPayment {invoiceId = Just (InvoiceId "inv-1")})
|
|
roundtripsBytes (SCPayment {invoiceId = Nothing})
|
|
roundtripsBytes (SCCharge {chargeId = "charge-1"})
|
|
roundtripsBytes SCSupport
|
|
(pub, _) <- mkKeyPair
|
|
roundtripsBytes (SCTransferIn {fromPurchaseKey = pub})
|
|
roundtripsBytes SCOpening
|
|
-- ruling 1: chargeId must serialize as a JSON string (schema: "type": "string"), not a number
|
|
case J.toJSON (SCCharge {chargeId = "c1"}) of
|
|
J.Object o -> JM.lookup "chargeId" o `shouldBe` Just (J.String "c1")
|
|
v -> expectationFailure ("expected a JSON object, got " <> show v)
|
|
|
|
testStatementCreditUnknownTag :: IO ()
|
|
testStatementCreditUnknownTag = do
|
|
let bytes = LB.pack "{\"amount\":42,\"type\":\"futureThing\"}"
|
|
case J.eitherDecode bytes :: Either String StatementCreditType of
|
|
Left e -> expectationFailure e
|
|
Right v@(SCUnknown {tag}) -> do
|
|
tag `shouldBe` "futureThing"
|
|
J.encode v `shouldBe` bytes
|
|
Right other -> expectationFailure ("expected SCUnknown, got " <> show other)
|
|
|
|
testStatementDebitTypeJSON :: IO ()
|
|
testStatementDebitTypeJSON = do
|
|
roundtripsBytes SDRefund
|
|
(pub, _) <- mkKeyPair
|
|
roundtripsBytes (SDUpgrade {toPurchaseKey = pub})
|
|
roundtripsBytes (SDTransferOut {toPurchaseKey = pub})
|
|
roundtripsBytes SDSupport
|
|
roundtripsBytes SDBadge
|
|
roundtripsBytes SDLapse
|
|
|
|
testStatementDebitUnknownTag :: IO ()
|
|
testStatementDebitUnknownTag = do
|
|
let bytes = LB.pack "{\"amount\":7,\"type\":\"somethingNew\"}"
|
|
case J.eitherDecode bytes :: Either String StatementDebitType of
|
|
Left e -> expectationFailure e
|
|
Right v@(SDUnknown {tag}) -> do
|
|
tag `shouldBe` "somethingNew"
|
|
J.encode v `shouldBe` bytes
|
|
Right other -> expectationFailure ("expected SDUnknown, got " <> show other)
|
|
|
|
testStatementEntryJSON :: IO ()
|
|
testStatementEntryJSON = do
|
|
roundtripsBytes (SECredit {credit = SCOpening})
|
|
roundtripsBytes (SEDebit {debit = SDRefund})
|
|
entry <- sampleEntry
|
|
roundtripsBytes entry
|
|
roundtripsBytes (BadgeBalance {lastEntry = entry})
|
|
roundtripsBytes (BadgeStatement {entries = [entry], previousEntryId = Just "entry-0"})
|
|
roundtripsBytes (BadgeStatement {entries = [entry], previousEntryId = Nothing})
|
|
|
|
testCatalogJSON :: IO ()
|
|
testCatalogJSON = do
|
|
now <- getCurrentTime
|
|
let price =
|
|
BadgePrice
|
|
{ priceId = BadgePriceId "price-1",
|
|
badgeType = BTSupporter,
|
|
monthPrice = CurrencyAmount 500,
|
|
currency = "usd",
|
|
status = BISActive,
|
|
createdAt = now
|
|
}
|
|
offer =
|
|
BadgeOffer
|
|
{ offerId = BadgeOfferId "offer-1",
|
|
priceId = Just (BadgePriceId "price-1"),
|
|
months = 12,
|
|
discount = ODDiscount {discount = 10},
|
|
status = BISActive,
|
|
createdAt = now,
|
|
total = Just (CurrencyAmount 5400)
|
|
}
|
|
roundtripsBytes price
|
|
roundtripsBytes offer
|
|
roundtripsBytes (offer {total = Nothing}) -- absent until catalogTotals fills it in (A4)
|
|
roundtripsBytes (BadgeCatalog {prices = [price], offers = [offer]})
|
|
|
|
testBadgeUpgradeAndInvoiceJSON :: IO ()
|
|
testBadgeUpgradeAndInvoiceJSON = do
|
|
now <- getCurrentTime
|
|
balance <- sampleBalance
|
|
(pub, sk) <- mkKeyPair
|
|
let upgrade =
|
|
BadgeUpgrade
|
|
{ fromPurchaseKey = pub,
|
|
receipt = "receipt-text",
|
|
receiptSignature = C.sign' sk "receipt-text",
|
|
balance
|
|
}
|
|
roundtripsBytes upgrade
|
|
let invoice =
|
|
ServiceInvoice
|
|
{ invoiceId = InvoiceId "inv-1",
|
|
price = CurrencyAmount 1000,
|
|
discount = Just (CurrencyAmount 100),
|
|
credit = Nothing,
|
|
amount = CurrencyAmount 900,
|
|
currency = "usd",
|
|
expiresAt = now,
|
|
paymentTo = samplePaymentDestination
|
|
}
|
|
roundtripsBytes invoice
|
|
|
|
testBadgeServiceCommandJSON :: IO ()
|
|
testBadgeServiceCommandJSON = do
|
|
balance <- sampleBalance
|
|
drg <- C.newRandom
|
|
mk <- generateMasterKey drg
|
|
let badgeRequest = BadgeRequest {masterKey = mk, badgeInfo = jsonBadgeInfo}
|
|
(pub, sk) <- mkKeyPair
|
|
let upgrade =
|
|
BadgeUpgrade
|
|
{ fromPurchaseKey = pub,
|
|
receipt = "r",
|
|
receiptSignature = C.sign' sk "r",
|
|
balance
|
|
}
|
|
roundtripsBytes BSCGetBadgeCatalog
|
|
roundtripsBytes
|
|
BSCGetBadgeInvoice
|
|
{ priceId = BadgePriceId "price-1",
|
|
offerId = Just (BadgeOfferId "offer-1"),
|
|
badgeInfo = jsonBadgeInfo,
|
|
paymentVia = SPMCard {provider = CPStripe},
|
|
upgrade = Just upgrade
|
|
}
|
|
roundtripsBytes BSCPurchaseBadge {badgeRequest, payment = SPCode {code = "CODE"}, upgrade = Nothing}
|
|
roundtripsBytes BSCUpgradeBadgeSubscription {badgeRequest, payment = SPInvoice {invoiceId = InvoiceId "inv-1"}, balance}
|
|
roundtripsBytes BSCIssueBadge {badgeRequest, balance}
|
|
roundtripsBytes BSCPauseBadge
|
|
|
|
testBadgeServiceResponseJSON :: IO ()
|
|
testBadgeServiceResponseJSON = do
|
|
now <- getCurrentTime
|
|
entry <- sampleEntry
|
|
cred <- jsonCredential
|
|
let statement = BadgeStatement {entries = [entry], previousEntryId = Nothing}
|
|
catalog = BadgeCatalog {prices = [], offers = []}
|
|
invoice =
|
|
ServiceInvoice
|
|
{ invoiceId = InvoiceId "inv-1",
|
|
price = CurrencyAmount 1000,
|
|
discount = Nothing,
|
|
credit = Nothing,
|
|
amount = CurrencyAmount 1000,
|
|
currency = "usd",
|
|
expiresAt = now,
|
|
paymentTo = samplePaymentDestination
|
|
}
|
|
roundtripsBytes (BSPBadgeCatalog {catalog, badgeStatement = Just statement})
|
|
roundtripsBytes (BSPBadgeCatalog {catalog, badgeStatement = Nothing})
|
|
roundtripsBytes (BSPBadgeInvoice {invoice, badgeType = BTSupporter, months = 12})
|
|
roundtripsBytes (BSPBadgeCredential {credential = Just cred, receipt = Just "r", statement})
|
|
roundtripsBytes (BSPBadgeCredential {credential = Nothing, receipt = Nothing, statement})
|
|
roundtripsBytes (BSPError {code = BSEBadRequest, message = Just "bad request", retryAfter = Just 30})
|
|
roundtripsBytes (BSPError {code = BSEUnknown "future_error", message = Nothing, retryAfter = Nothing})
|
|
|
|
testBadgeServiceRequestJSON :: IO ()
|
|
testBadgeServiceRequestJSON = do
|
|
(pub, _) <- mkKeyPair
|
|
roundtripsBytes (BadgeServiceRequest {version = VersionBadgeService 1, purchaseKey = Just pub, request = BSCPauseBadge})
|
|
roundtripsBytes (BadgeServiceRequest {version = VersionBadgeService 1, purchaseKey = Nothing, request = BSCGetBadgeCatalog})
|
|
|
|
-- Helpers
|
|
|
|
futureTime :: UTCTime
|
|
futureTime = posixSecondsToUTCTime 4102444800 -- 2099-12-31
|
|
|
|
pastTime :: UTCTime
|
|
pastTime = posixSecondsToUTCTime 1577836800 -- 2020-01-01
|
|
|
|
issueBadgeProof :: BadgeType -> Maybe UTCTime -> IO (BBSPublicKey, BadgeProof)
|
|
issueBadgeProof bt expiry = do
|
|
Right (pk, sk) <- bbsKeyGen
|
|
drg <- C.newRandom
|
|
mk <- generateMasterKey drg
|
|
let vreq = VerifiedBadgeRequest BadgeRequest {masterKey = mk, badgeInfo = BadgeInfo {badgeType = bt, badgeExpiry = expiry, badgeExtra = ""}}
|
|
Right cred <- issueBadge testKeyIdx sk vreq
|
|
Right badge <- generateBadgeProof pk cred (BBSPresHeader "test-nonce")
|
|
pure (pk, badge)
|