|
1 | 1 | import { beforeEach, describe, expect, it, vi } from "vitest"; |
2 | | -import { syncBountyPaymentStatus } from "@/lib/coinpay-payment-sync"; |
| 2 | +import { syncBountyPaymentStatus, syncGigInvoicePaymentStatus } from "@/lib/coinpay-payment-sync"; |
3 | 3 | import { getPaymentStatus } from "@/lib/coinpayportal"; |
4 | 4 | import { onPaymentReceived, onPaymentSent } from "@/lib/reputation-hooks"; |
5 | 5 |
|
@@ -43,6 +43,10 @@ function makeSupabase(initialTables: Record<string, Row[]>) { |
43 | 43 | if (op === "is" && value === null) filters.push((row) => row[field] !== null); |
44 | 44 | return chain; |
45 | 45 | }), |
| 46 | + in: vi.fn((field: string, values: unknown[]) => { |
| 47 | + filters.push((row) => values.includes(row[field])); |
| 48 | + return chain; |
| 49 | + }), |
46 | 50 | order: vi.fn(() => chain), |
47 | 51 | limit: vi.fn((count: number) => { |
48 | 52 | limitCount = count; |
@@ -195,4 +199,52 @@ describe("coinpay payment sync", () => { |
195 | 199 | }); |
196 | 200 | }); |
197 | 201 |
|
| 202 | + it("does not mark a rejected gig invoice paid when a stale CoinPay payment confirms", async () => { |
| 203 | + vi.mocked(getPaymentStatus).mockResolvedValue({ |
| 204 | + success: true, |
| 205 | + payment: { |
| 206 | + id: "cp-invoice-stale", |
| 207 | + status: "confirmed", |
| 208 | + tx_hash: "tx-stale", |
| 209 | + crypto_amount: "0.5", |
| 210 | + blockchain: "SOL", |
| 211 | + }, |
| 212 | + }); |
| 213 | + |
| 214 | + const { supabase, tables } = makeSupabase({ |
| 215 | + gig_invoices: [ |
| 216 | + { |
| 217 | + id: "invoice-1", |
| 218 | + application_id: "app-1", |
| 219 | + gig_id: "gig-1", |
| 220 | + worker_id: "worker-1", |
| 221 | + poster_id: "poster-1", |
| 222 | + status: "rejected", |
| 223 | + amount_usd: 10, |
| 224 | + coinpay_invoice_id: "cp-invoice-stale", |
| 225 | + metadata: { rejection_reason: "duplicate" }, |
| 226 | + }, |
| 227 | + ], |
| 228 | + applications: [{ id: "app-1", status: "accepted" }], |
| 229 | + gigs: [{ id: "gig-1", title: "Fix it" }], |
| 230 | + notifications: [], |
| 231 | + }); |
| 232 | + |
| 233 | + const result = await syncGigInvoicePaymentStatus(supabase, "cp-invoice-stale"); |
| 234 | + |
| 235 | + expect(result).toMatchObject({ |
| 236 | + changed: false, |
| 237 | + local_status: "rejected", |
| 238 | + upstream_status: "confirmed", |
| 239 | + }); |
| 240 | + expect(tables.gig_invoices[0]).toMatchObject({ |
| 241 | + status: "rejected", |
| 242 | + metadata: { rejection_reason: "duplicate" }, |
| 243 | + }); |
| 244 | + expect(tables.applications[0]).toMatchObject({ status: "accepted" }); |
| 245 | + expect(tables.notifications).toHaveLength(0); |
| 246 | + expect(onPaymentSent).not.toHaveBeenCalled(); |
| 247 | + expect(onPaymentReceived).not.toHaveBeenCalled(); |
| 248 | + }); |
| 249 | + |
198 | 250 | }); |
0 commit comments