-
Notifications
You must be signed in to change notification settings - Fork 192
fix: disable auto top-up after 7 days #1918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f65e3b3
154efd9
2534d41
cfa8653
fc9a58e
3f18768
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| import { afterEach, beforeEach, describe, expect, test } from "vitest"; | ||
|
|
||
| import { app } from "@/index.js"; | ||
| import { createTestUser, deleteAll } from "@/testing.js"; | ||
|
|
||
| import { db, tables } from "@llmgateway/db"; | ||
|
|
||
| describe("organization route", () => { | ||
| let token: string; | ||
|
|
||
| beforeEach(async () => { | ||
| token = await createTestUser(); | ||
|
|
||
| await db.insert(tables.organization).values({ | ||
| id: "test-org-id", | ||
| name: "Test Organization", | ||
| billingEmail: "test@example.com", | ||
| autoTopUpEnabled: false, | ||
| autoTopUpThreshold: "10", | ||
| autoTopUpAmount: "10", | ||
| }); | ||
|
|
||
| await db.insert(tables.userOrganization).values({ | ||
| userId: "test-user-id", | ||
| organizationId: "test-org-id", | ||
| role: "owner", | ||
| }); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| await deleteAll(); | ||
| }); | ||
|
|
||
| test("PATCH /orgs/{id} logs enabling auto top-up in audit log", async () => { | ||
| const response = await app.request("/orgs/test-org-id", { | ||
| method: "PATCH", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Cookie: token, | ||
| }, | ||
| body: JSON.stringify({ | ||
| autoTopUpEnabled: true, | ||
| }), | ||
| }); | ||
|
|
||
| expect(response.status).toBe(200); | ||
|
|
||
| const auditLogs = await db.query.auditLog.findMany({ | ||
| where: { | ||
| organizationId: { | ||
| eq: "test-org-id", | ||
| }, | ||
| action: { | ||
| eq: "payment.auto_topup.update", | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| expect(auditLogs).toHaveLength(1); | ||
| expect(auditLogs[0]?.userId).toBe("test-user-id"); | ||
| expect(auditLogs[0]?.resourceId).toBe("test-org-id"); | ||
| expect(auditLogs[0]?.metadata).toMatchObject({ | ||
| changes: { | ||
| autoTopUpEnabled: { | ||
| old: false, | ||
| new: true, | ||
| }, | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| test("PATCH /orgs/{id} logs top-up setting changes separately from organization updates", async () => { | ||
| const response = await app.request("/orgs/test-org-id", { | ||
| method: "PATCH", | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Cookie: token, | ||
| }, | ||
| body: JSON.stringify({ | ||
| name: "Renamed Organization", | ||
| autoTopUpThreshold: 25, | ||
| autoTopUpAmount: 50, | ||
| }), | ||
| }); | ||
|
|
||
| expect(response.status).toBe(200); | ||
|
|
||
| const orgAuditLogs = await db.query.auditLog.findMany({ | ||
| where: { | ||
| organizationId: { | ||
| eq: "test-org-id", | ||
| }, | ||
| action: { | ||
| eq: "organization.update", | ||
| }, | ||
| }, | ||
| }); | ||
| expect(orgAuditLogs).toHaveLength(1); | ||
| expect(orgAuditLogs[0]?.metadata).toMatchObject({ | ||
| changes: { | ||
| name: { | ||
| old: "Test Organization", | ||
| new: "Renamed Organization", | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| const autoTopUpAuditLogs = await db.query.auditLog.findMany({ | ||
| where: { | ||
| organizationId: { | ||
| eq: "test-org-id", | ||
| }, | ||
| action: { | ||
| eq: "payment.auto_topup.update", | ||
| }, | ||
| }, | ||
| }); | ||
| expect(autoTopUpAuditLogs).toHaveLength(1); | ||
| expect(autoTopUpAuditLogs[0]?.metadata).toMatchObject({ | ||
| changes: { | ||
| autoTopUpThreshold: { | ||
| old: "10", | ||
| new: "25", | ||
| }, | ||
| autoTopUpAmount: { | ||
| old: "10", | ||
| new: "50", | ||
| }, | ||
| }, | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -568,6 +568,7 @@ async function recordCreditTopUp({ | |
| credits: sql`${tables.organization.credits} + ${finalCreditAmount}`, | ||
| paymentFailureCount: 0, | ||
| lastPaymentFailureAt: null, | ||
| paymentFailureStartedAt: null, | ||
| }) | ||
| .where(eq(tables.organization.id, organizationId)); | ||
|
|
||
|
|
@@ -810,6 +811,7 @@ async function handlePaymentIntentSucceeded( | |
| credits: sql`${tables.organization.credits} + ${finalCreditAmount}`, | ||
| paymentFailureCount: 0, | ||
| lastPaymentFailureAt: null, | ||
| paymentFailureStartedAt: null, | ||
| }) | ||
| .where(eq(tables.organization.id, organizationId)); | ||
|
|
||
|
|
@@ -1027,6 +1029,7 @@ async function handlePaymentIntentFailed( | |
| // Calculate new failure count and check if we should send an email | ||
| const previousFailureCount = organization.paymentFailureCount ?? 0; | ||
| const previousFailureAt = organization.lastPaymentFailureAt; | ||
| const failureStartedAt = organization.paymentFailureStartedAt ?? new Date(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This assignment updates the 7-day disable timer for every Useful? React with 👍 / 👎. |
||
| const newFailureCount = previousFailureCount + 1; | ||
|
|
||
| // Update organization with new failure count and timestamp | ||
|
|
@@ -1035,6 +1038,7 @@ async function handlePaymentIntentFailed( | |
| .set({ | ||
| paymentFailureCount: newFailureCount, | ||
| lastPaymentFailureAt: new Date(), | ||
| paymentFailureStartedAt: failureStartedAt, | ||
| }) | ||
|
Comment on lines
1032
to
1042
|
||
| .where(eq(tables.organization.id, organizationId)); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assert exact
organization.updatechange keys to enforce separation.Line 99 currently uses
toMatchObject, which permits extra fields. A regression where auto-top-up fields leak intoorganization.updatewould still pass.🔧 Tighten the assertion
🤖 Prompt for AI Agents