mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-02-10 06:45:28 -05:00
## Summary Fixes the flaky `test_block_credit_reset` test that was failing on multiple PRs with `assert 0 == 1000`. ## Root Cause The test calls `disable_test_user_transactions()` which sets `updatedAt` to 35 days ago from the **actual current time**. It then mocks `time_now` to January 1st. **The bug**: If the test runs in early February, 35 days ago is January — the **same month** as the mocked `time_now`. The credit refill logic only triggers when the balance snapshot is from a *different* month, so no refill happens and the balance stays at 0. ## Fix After calling `disable_test_user_transactions()`, explicitly set `updatedAt` to December of the previous year. This ensures it's always in a different month than the mocked `month1` (January), regardless of when the test runs. ## Testing CI will verify the fix.
227 lines
8.2 KiB
Python
227 lines
8.2 KiB
Python
from datetime import datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
from prisma.enums import CreditTransactionType
|
|
from prisma.models import CreditTransaction, UserBalance
|
|
|
|
from backend.blocks.llm import AITextGeneratorBlock
|
|
from backend.data.block import get_block
|
|
from backend.data.credit import BetaUserCredit, UsageTransactionMetadata
|
|
from backend.data.execution import ExecutionContext, NodeExecutionEntry
|
|
from backend.data.user import DEFAULT_USER_ID
|
|
from backend.executor.utils import block_usage_cost
|
|
from backend.integrations.credentials_store import openai_credentials
|
|
from backend.util.test import SpinTestServer
|
|
|
|
REFILL_VALUE = 1000
|
|
user_credit = BetaUserCredit(REFILL_VALUE)
|
|
|
|
|
|
async def disable_test_user_transactions():
|
|
await CreditTransaction.prisma().delete_many(where={"userId": DEFAULT_USER_ID})
|
|
# Also reset the balance to 0 and set updatedAt to old date to trigger monthly refill
|
|
old_date = datetime.now(timezone.utc) - timedelta(days=35) # More than a month ago
|
|
await UserBalance.prisma().upsert(
|
|
where={"userId": DEFAULT_USER_ID},
|
|
data={
|
|
"create": {"userId": DEFAULT_USER_ID, "balance": 0},
|
|
"update": {"balance": 0, "updatedAt": old_date},
|
|
},
|
|
)
|
|
|
|
|
|
async def top_up(amount: int):
|
|
balance, _ = await user_credit._add_transaction(
|
|
DEFAULT_USER_ID,
|
|
amount,
|
|
CreditTransactionType.TOP_UP,
|
|
)
|
|
return balance
|
|
|
|
|
|
async def spend_credits(entry: NodeExecutionEntry) -> int:
|
|
block = get_block(entry.block_id)
|
|
if not block:
|
|
raise RuntimeError(f"Block {entry.block_id} not found")
|
|
|
|
cost, matching_filter = block_usage_cost(block=block, input_data=entry.inputs)
|
|
await user_credit.spend_credits(
|
|
entry.user_id,
|
|
cost,
|
|
UsageTransactionMetadata(
|
|
graph_exec_id=entry.graph_exec_id,
|
|
graph_id=entry.graph_id,
|
|
node_id=entry.node_id,
|
|
node_exec_id=entry.node_exec_id,
|
|
block_id=entry.block_id,
|
|
block=entry.block_id,
|
|
input=matching_filter,
|
|
reason=f"Ran block {entry.block_id} {block.name}",
|
|
),
|
|
)
|
|
|
|
return cost
|
|
|
|
|
|
@pytest.mark.asyncio(loop_scope="session")
|
|
async def test_block_credit_usage(server: SpinTestServer):
|
|
await disable_test_user_transactions()
|
|
await top_up(100)
|
|
current_credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
|
|
|
spending_amount_1 = await spend_credits(
|
|
NodeExecutionEntry(
|
|
user_id=DEFAULT_USER_ID,
|
|
graph_id="test_graph",
|
|
graph_version=1,
|
|
node_id="test_node",
|
|
graph_exec_id="test_graph_exec",
|
|
node_exec_id="test_node_exec",
|
|
block_id=AITextGeneratorBlock().id,
|
|
inputs={
|
|
"model": "gpt-4-turbo",
|
|
"credentials": {
|
|
"id": openai_credentials.id,
|
|
"provider": openai_credentials.provider,
|
|
"type": openai_credentials.type,
|
|
},
|
|
},
|
|
execution_context=ExecutionContext(user_timezone="UTC"),
|
|
),
|
|
)
|
|
assert spending_amount_1 > 0
|
|
|
|
spending_amount_2 = await spend_credits(
|
|
NodeExecutionEntry(
|
|
user_id=DEFAULT_USER_ID,
|
|
graph_id="test_graph",
|
|
graph_version=1,
|
|
node_id="test_node",
|
|
graph_exec_id="test_graph_exec",
|
|
node_exec_id="test_node_exec",
|
|
block_id=AITextGeneratorBlock().id,
|
|
inputs={"model": "gpt-4-turbo", "api_key": "owned_api_key"},
|
|
execution_context=ExecutionContext(user_timezone="UTC"),
|
|
),
|
|
)
|
|
assert spending_amount_2 == 0
|
|
|
|
new_credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
|
assert new_credit == current_credit - spending_amount_1 - spending_amount_2
|
|
|
|
|
|
@pytest.mark.asyncio(loop_scope="session")
|
|
async def test_block_credit_top_up(server: SpinTestServer):
|
|
await disable_test_user_transactions()
|
|
current_credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
|
|
|
await top_up(100)
|
|
|
|
new_credit = await user_credit.get_credits(DEFAULT_USER_ID)
|
|
assert new_credit == current_credit + 100
|
|
|
|
|
|
@pytest.mark.asyncio(loop_scope="session")
|
|
async def test_block_credit_reset(server: SpinTestServer):
|
|
"""Test that BetaUserCredit provides monthly refills correctly."""
|
|
await disable_test_user_transactions()
|
|
|
|
# Save original time_now function for restoration
|
|
original_time_now = user_credit.time_now
|
|
|
|
try:
|
|
# Test month 1 behavior
|
|
month1 = datetime.now(timezone.utc).replace(month=1, day=1)
|
|
user_credit.time_now = lambda: month1
|
|
|
|
# IMPORTANT: Set updatedAt to December of previous year to ensure it's
|
|
# in a different month than month1 (January). This fixes a timing bug
|
|
# where if the test runs in early February, 35 days ago would be January,
|
|
# matching the mocked month1 and preventing the refill from triggering.
|
|
dec_previous_year = month1.replace(year=month1.year - 1, month=12, day=15)
|
|
await UserBalance.prisma().update(
|
|
where={"userId": DEFAULT_USER_ID},
|
|
data={"updatedAt": dec_previous_year},
|
|
)
|
|
|
|
# First call in month 1 should trigger refill
|
|
balance = await user_credit.get_credits(DEFAULT_USER_ID)
|
|
assert balance == REFILL_VALUE # Should get 1000 credits
|
|
|
|
# Manually create a transaction with month 1 timestamp to establish history
|
|
await CreditTransaction.prisma().create(
|
|
data={
|
|
"userId": DEFAULT_USER_ID,
|
|
"amount": 100,
|
|
"type": CreditTransactionType.TOP_UP,
|
|
"runningBalance": 1100,
|
|
"isActive": True,
|
|
"createdAt": month1, # Set specific timestamp
|
|
}
|
|
)
|
|
|
|
# Update user balance to match
|
|
await UserBalance.prisma().upsert(
|
|
where={"userId": DEFAULT_USER_ID},
|
|
data={
|
|
"create": {"userId": DEFAULT_USER_ID, "balance": 1100},
|
|
"update": {"balance": 1100},
|
|
},
|
|
)
|
|
|
|
# Now test month 2 behavior
|
|
month2 = datetime.now(timezone.utc).replace(month=2, day=1)
|
|
user_credit.time_now = lambda: month2
|
|
|
|
# In month 2, since balance (1100) > refill (1000), no refill should happen
|
|
month2_balance = await user_credit.get_credits(DEFAULT_USER_ID)
|
|
assert month2_balance == 1100 # Balance persists, no reset
|
|
|
|
# Now test the refill behavior when balance is low
|
|
# Set balance below refill threshold
|
|
await UserBalance.prisma().update(
|
|
where={"userId": DEFAULT_USER_ID}, data={"balance": 400}
|
|
)
|
|
|
|
# Create a month 2 transaction to update the last transaction time
|
|
await CreditTransaction.prisma().create(
|
|
data={
|
|
"userId": DEFAULT_USER_ID,
|
|
"amount": -700, # Spent 700 to get to 400
|
|
"type": CreditTransactionType.USAGE,
|
|
"runningBalance": 400,
|
|
"isActive": True,
|
|
"createdAt": month2,
|
|
}
|
|
)
|
|
|
|
# Move to month 3
|
|
month3 = datetime.now(timezone.utc).replace(month=3, day=1)
|
|
user_credit.time_now = lambda: month3
|
|
|
|
# Should get refilled since balance (400) < refill value (1000)
|
|
month3_balance = await user_credit.get_credits(DEFAULT_USER_ID)
|
|
assert month3_balance == REFILL_VALUE # Should be refilled to 1000
|
|
|
|
# Verify the refill transaction was created
|
|
refill_tx = await CreditTransaction.prisma().find_first(
|
|
where={
|
|
"userId": DEFAULT_USER_ID,
|
|
"type": CreditTransactionType.GRANT,
|
|
"transactionKey": {"contains": "MONTHLY-CREDIT-TOP-UP"},
|
|
},
|
|
order={"createdAt": "desc"},
|
|
)
|
|
assert refill_tx is not None, "Monthly refill transaction should be created"
|
|
assert refill_tx.amount == 600, "Refill should be 600 (1000 - 400)"
|
|
finally:
|
|
# Restore original time_now function
|
|
user_credit.time_now = original_time_now
|
|
|
|
|
|
@pytest.mark.asyncio(loop_scope="session")
|
|
async def test_credit_refill(server: SpinTestServer):
|
|
await disable_test_user_transactions()
|
|
balance = await user_credit.get_credits(DEFAULT_USER_ID)
|
|
assert balance == REFILL_VALUE
|