129 lines
4.3 KiB
TypeScript
129 lines
4.3 KiB
TypeScript
import { PrismaClient } from "./apps/web/prisma/generated/client/index.js";
|
|
import { TaskStatus, MCPToolName, SettlementPhase, TaskDifficulty } from "./packages/contracts/src/enums/index.js";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
async function main() {
|
|
console.log("=== Idempotency and Payment Flow Test ===");
|
|
|
|
// 1. Create a mock task
|
|
const taskId = uuidv4();
|
|
await prisma.task.create({
|
|
data: {
|
|
id: taskId,
|
|
title: "Test Task",
|
|
difficulty: TaskDifficulty.COMPONENT,
|
|
status: TaskStatus.OPEN,
|
|
requirements: "Test requirements",
|
|
base_reward_cents: 500,
|
|
}
|
|
});
|
|
console.log(`[+] Created Task ${taskId}`);
|
|
|
|
// 2. Simulate Claim Task (Auth Hold)
|
|
console.log(`\n[+] Simulating MCP: claim_task`);
|
|
|
|
// First attempt (Should succeed and create Auth Hold)
|
|
const agentId = "agent-test-1";
|
|
const idempotencyKeyClaim = `${taskId}_${MCPToolName.CLAIM_TASK}`;
|
|
|
|
const claim1 = await prisma.$transaction(async (tx) => {
|
|
// authHold logic
|
|
const existing = await tx.ledgerEntry.findUnique({ where: { idempotency_key: idempotencyKeyClaim }});
|
|
if (existing) {
|
|
console.log("Idempotent hit for claim_task!");
|
|
return existing;
|
|
}
|
|
|
|
await tx.task.update({
|
|
where: { id: taskId },
|
|
data: { status: TaskStatus.EXECUTING, agent_id: agentId }
|
|
});
|
|
|
|
return await tx.ledgerEntry.create({
|
|
data: {
|
|
task_id: taskId,
|
|
phase: SettlementPhase.AUTH_HOLD,
|
|
amount_cents: 500,
|
|
currency: "USD",
|
|
stripe_object_id: "pi_mock_" + uuidv4(),
|
|
idempotency_key: idempotencyKeyClaim
|
|
}
|
|
});
|
|
});
|
|
console.log(`[1] First claim_task attempt created ledger entry: ${claim1.id}`);
|
|
|
|
// Second attempt (Should hit idempotency key and NOT duplicate)
|
|
const claim2 = await prisma.$transaction(async (tx) => {
|
|
const existing = await tx.ledgerEntry.findUnique({ where: { idempotency_key: idempotencyKeyClaim }});
|
|
if (existing) {
|
|
console.log("Idempotent hit for claim_task!");
|
|
return existing;
|
|
}
|
|
// Should not reach here
|
|
throw new Error("Idempotency failed!");
|
|
});
|
|
console.log(`[2] Second claim_task attempt returned same entry: ${claim2.id}`);
|
|
|
|
// 3. Simulate Submit Solution (Capture)
|
|
console.log(`\n[+] Simulating MCP: submit_solution`);
|
|
const idempotencyKeySubmit = `${taskId}_${MCPToolName.SUBMIT_SOLUTION}`;
|
|
|
|
const submit1 = await prisma.$transaction(async (tx) => {
|
|
const existing = await tx.ledgerEntry.findUnique({ where: { idempotency_key: idempotencyKeySubmit }});
|
|
if (existing) {
|
|
console.log("Idempotent hit for submit_solution!");
|
|
return existing;
|
|
}
|
|
|
|
await tx.task.update({
|
|
where: { id: taskId },
|
|
data: { status: TaskStatus.COMPLETED }
|
|
});
|
|
|
|
return await tx.ledgerEntry.create({
|
|
data: {
|
|
task_id: taskId,
|
|
phase: SettlementPhase.CAPTURE,
|
|
amount_cents: 500,
|
|
currency: "USD",
|
|
stripe_object_id: claim1.stripe_object_id,
|
|
idempotency_key: idempotencyKeySubmit
|
|
}
|
|
});
|
|
});
|
|
console.log(`[1] First submit_solution attempt created ledger entry: ${submit1.id}`);
|
|
|
|
const submit2 = await prisma.$transaction(async (tx) => {
|
|
const existing = await tx.ledgerEntry.findUnique({ where: { idempotency_key: idempotencyKeySubmit }});
|
|
if (existing) {
|
|
console.log("Idempotent hit for submit_solution!");
|
|
return existing;
|
|
}
|
|
throw new Error("Idempotency failed!");
|
|
});
|
|
console.log(`[2] Second submit_solution attempt returned same entry: ${submit2.id}`);
|
|
|
|
// Verify DB state
|
|
const finalTask = await prisma.task.findUnique({ where: { id: taskId }, include: { ledger_entries: true } });
|
|
console.log(`\n=== Final Verification ===`);
|
|
console.log(`Task Status: ${finalTask?.status}`);
|
|
console.log(`Ledger Entries: ${finalTask?.ledger_entries.length}`);
|
|
finalTask?.ledger_entries.forEach(entry => {
|
|
console.log(` - Phase: ${entry.phase}, Amount: ${entry.amount_cents}, Key: ${entry.idempotency_key}`);
|
|
});
|
|
|
|
if (finalTask?.status === TaskStatus.COMPLETED && finalTask.ledger_entries.length === 2) {
|
|
console.log("\n✅ End-to-End Simulation Passed! Idempotency and State transitions are correct.");
|
|
} else {
|
|
console.log("\n❌ Simulation Failed.");
|
|
}
|
|
}
|
|
|
|
main()
|
|
.catch(console.error)
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|