From 4a9f8512376874064a93743fb110a5087e26c21e Mon Sep 17 00:00:00 2001 From: spacedandy-agents-simplex <342056592+spacedandy-agents-simplex@users.noreply.github.com> Date: Wed, 21 Oct 2026 19:34:39 +0000 Subject: [PATCH] core: test that the newest issued badge credential is taken by its ledger row After a tier change the newer issuance can end before the older one, so ordering issuances by period end would answer the superseded credential to the worker and to the resume and tier-change paths. Pin the row order: the test fails if the query goes back to period_end order. Run: 015-badge-fixes, slice 3 --- tests/Bots/BadgeService/BotTests.hs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/Bots/BadgeService/BotTests.hs b/tests/Bots/BadgeService/BotTests.hs index d4b82fdff5..e98f4ce04b 100644 --- a/tests/Bots/BadgeService/BotTests.hs +++ b/tests/Bots/BadgeService/BotTests.hs @@ -72,7 +72,7 @@ import Simplex.Chat.Options (ChatOpts (..), CoreChatOpts (..)) import Simplex.Chat.Options.DB import Simplex.Chat.PaymentService (ServicePayment (..), googlePurchaseRef) import Simplex.Chat.PaymentService.Types (InvoiceId (..)) -import Simplex.Chat.Store.Badges (getNextHeldStoreReceipt) +import Simplex.Chat.Store.Badges (getLatestIssuedCredential, getNextHeldStoreReceipt) import Simplex.Messaging.Agent (disposeAgentClient) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..)) import Simplex.Messaging.Agent.RetryInterval (RetryInterval (..)) @@ -124,6 +124,7 @@ badgeServiceTests = do it "should sign a renewal with the master key stored on the purchase" testRenewalSignsWithStoredMasterKey it "should leave the client holding the same ledger rows as the service" testClientReplicatesLedger it "should renew a badge whose credential is lapsing, with no command" testWorkerRenews + it "should take the newest issued credential by its ledger row, not by the period it ends" testLatestIssuedByRow it "should keep answering and renewing a holder after its partly used multi-use code is revoked" testRevokedMultiUseHolderRenews it "should return a multi-use holder's renewed credential on a repeat, apart from a later holder" testMultiUseHoldersRenewApart it "should request from the wake it set a day before the credential lapses" testRequestWakeFires @@ -1032,6 +1033,27 @@ testWorkerRenews ps = alice <## "ok" waitShownIssued (chatController alice) +testLatestIssuedByRow :: HasCallStack => TestParams -> IO () +testLatestIssuedByRow ps = + withBadgeServiceEnv ps $ \BadgeServiceEnv {bsClock, bsClientCfg, bsController = cc} -> + withNewTestChatCfg ps bsClientCfg "alice" aliceProfile $ \alice -> do + code <- issueCode cc BTSupporter 3 + redeemFirstBadge alice code + redeemed <- ledgerRows (chatController alice) "badge_ledger" + setClockAt bsClock $ fst $ renewalMoments redeemed + alice ##> "/_app activate" + alice <## "ok" + _ <- waitLedgerRows (chatController alice) 3 + alice <##. "1: supporter" + withTransaction (chatStore $ chatController alice) $ \db -> do + -- as a tier change leaves them: the newer issuance ends before the older one + [(purchaseId, olderId, olderEnd), (_, newerId, newerEnd)] :: [(Int64, Int64, UTCTime)] <- + DB.query_ db "SELECT badge_purchase_id, entry_id, period_end FROM badge_issuances ORDER BY entry_id" + DB.execute db "UPDATE badge_issuances SET period_end = ? WHERE entry_id = ?" (newerEnd, olderId) + DB.execute db "UPDATE badge_issuances SET period_end = ? WHERE entry_id = ?" (olderEnd, newerId) + [Only (Binary newest)] <- DB.query db "SELECT credential FROM badge_issuances WHERE entry_id = ?" (Only newerId) + getLatestIssuedCredential db purchaseId `shouldReturn` J.decodeStrict' newest + testRevokedMultiUseHolderRenews :: HasCallStack => TestParams -> IO () testRevokedMultiUseHolderRenews ps = withBadgeServiceEnv ps $ \env@BadgeServiceEnv {bsClock, bsClientCfg, bsController = cc} ->