From 3de056de86418cecd3a6758712be6a1449d47095 Mon Sep 17 00:00:00 2001 From: Ayrat Hudaygulov Date: Sun, 18 Oct 2026 12:08:32 +0100 Subject: [PATCH 1/2] Add report action to individual coupon photos --- src/CouponHubBot/Services/BotHelpers.fs | 1 + tests/CouponHubBot.Tests/ReportFlowTests.fs | 41 +++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/CouponHubBot/Services/BotHelpers.fs b/src/CouponHubBot/Services/BotHelpers.fs index eaedc61..7de5a01 100644 --- a/src/CouponHubBot/Services/BotHelpers.fs +++ b/src/CouponHubBot/Services/BotHelpers.fs @@ -663,6 +663,7 @@ let singleTakenKeyboard (c: Coupon) = inlineKb [| [| btn "Вернуть" $"return:{c.id}:del" btn "Использован" $"used:{c.id}:del" |] + [| btn "⚠️ Не сработал" $"report:{c.id}" |] |] /// "Какой купон уже использован?" step: one button per currently-held coupon plus «Отмена». diff --git a/tests/CouponHubBot.Tests/ReportFlowTests.fs b/tests/CouponHubBot.Tests/ReportFlowTests.fs index 9f8a2b9..460b6d9 100644 --- a/tests/CouponHubBot.Tests/ReportFlowTests.fs +++ b/tests/CouponHubBot.Tests/ReportFlowTests.fs @@ -163,6 +163,47 @@ type ReportFlowTests(fixture: DefaultCouponHubTestContainers) = Assert.Equal("reported", status) } + [] + let ``Holder reports directly from the coupon photo after confirmation`` () = + task { + do! fixture.ClearFakeCalls() + do! fixture.TruncateCoupons() + let owner = Tg.user(id = 9791L, username = "report_photo_owner") + let taker = Tg.user(id = 9792L, username = "report_photo_taker") + do! fixture.SetChatMemberStatus(owner.Id, "member") + do! fixture.SetChatMemberStatus(taker.Id, "member") + + let! _ = fixture.SendUpdate(Tg.dmPhotoWithCaption("/add 10 50 2026-01-25", owner)) + let! couponId = getLatestCouponId () + do! fixture.ClearFakeCalls() + let! _ = fixture.SendUpdate(Tg.dmMessage($"/take {couponId}", taker)) + let! photos = fixture.GetFakeCalls("sendPhoto") + let photo = Assert.Single(photos) + use doc = JsonDocument.Parse(photo.Body) + let rows = doc.RootElement.GetProperty("reply_markup").GetProperty("inline_keyboard") + Assert.Equal(2, rows.GetArrayLength()) + Assert.Equal(2, rows[0].GetArrayLength()) + Assert.Equal($"return:{couponId}:del", rows[0][0].GetProperty("callback_data").GetString()) + Assert.Equal($"used:{couponId}:del", rows[0][1].GetProperty("callback_data").GetString()) + Assert.Equal(1, rows[1].GetArrayLength()) + Assert.Equal("⚠️ Не сработал", rows[1][0].GetProperty("text").GetString()) + let callback = rows[1][0].GetProperty("callback_data").GetString() + + do! fixture.ClearFakeCalls() + let! response = fixture.SendUpdate(Tg.dmCallback(callback, taker)) + Assert.Equal(HttpStatusCode.OK, response.StatusCode) + let! calls = fixture.GetFakeCalls("sendMessage") + Assert.True(findCallWithText calls taker.Id "Подтвердить?") + let! pendingStatus = getCouponStatus couponId + Assert.Equal("taken", pendingStatus) + + let! _ = fixture.SendUpdate(Tg.dmCallback($"report:{couponId}:confirm", taker)) + let! status = getCouponStatus couponId + Assert.Equal("reported", status) + let! usedCount = getEventCount couponId "used" + Assert.Equal(0L, usedCount) + } + // ── 3. Non-holder cannot report ───────────────────────────────────────── [] From 2968e36598e72504e729e2b0a86c721c9b9a6bc2 Mon Sep 17 00:00:00 2001 From: Ayrat Hudaygulov Date: Sun, 18 Oct 2026 12:11:13 +0100 Subject: [PATCH 2/2] Fix nested JSON indexing in photo report test --- tests/CouponHubBot.Tests/ReportFlowTests.fs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/CouponHubBot.Tests/ReportFlowTests.fs b/tests/CouponHubBot.Tests/ReportFlowTests.fs index 460b6d9..edf0a82 100644 --- a/tests/CouponHubBot.Tests/ReportFlowTests.fs +++ b/tests/CouponHubBot.Tests/ReportFlowTests.fs @@ -183,11 +183,11 @@ type ReportFlowTests(fixture: DefaultCouponHubTestContainers) = let rows = doc.RootElement.GetProperty("reply_markup").GetProperty("inline_keyboard") Assert.Equal(2, rows.GetArrayLength()) Assert.Equal(2, rows[0].GetArrayLength()) - Assert.Equal($"return:{couponId}:del", rows[0][0].GetProperty("callback_data").GetString()) - Assert.Equal($"used:{couponId}:del", rows[0][1].GetProperty("callback_data").GetString()) + Assert.Equal($"return:{couponId}:del", rows.[0].[0].GetProperty("callback_data").GetString()) + Assert.Equal($"used:{couponId}:del", rows.[0].[1].GetProperty("callback_data").GetString()) Assert.Equal(1, rows[1].GetArrayLength()) - Assert.Equal("⚠️ Не сработал", rows[1][0].GetProperty("text").GetString()) - let callback = rows[1][0].GetProperty("callback_data").GetString() + Assert.Equal("⚠️ Не сработал", rows.[1].[0].GetProperty("text").GetString()) + let callback = rows.[1].[0].GetProperty("callback_data").GetString() do! fixture.ClearFakeCalls() let! response = fixture.SendUpdate(Tg.dmCallback(callback, taker))