Files
gongxue-base/scripts/auto-badge-concurrency-test.js

113 lines
3.4 KiB
JavaScript

import assert from 'node:assert/strict';
import pg from 'pg';
import { autoGrantBadges } from '../apps/api/src/features/profile/badges.ts';
const DATABASE_URL = process.env.DATABASE_URL || 'postgresql://postgres:postgres@127.0.0.1:54322/postgres';
const TENANT_ID = process.env.TENANT_ID || '00000000-0000-0000-0000-000000000001';
const USER_ID = process.env.USER_ID || '00000000-0000-0000-0000-000000000101';
const BADGE_ID = '00000000-0000-0000-0000-00000000b881';
const pool = new pg.Pool({ connectionString: DATABASE_URL, max: 8 });
async function resetFixture() {
await pool.query(
`
delete from public.user_notifications
where tenant_id = $1
and dedupe_key = $2
`,
[TENANT_ID, `badge:${BADGE_ID}:user:${USER_ID}`],
);
await pool.query(
`
delete from public.user_badges
where tenant_id = $1
and (badge_id = $2::uuid or legacy_id = $3)
`,
[TENANT_ID, BADGE_ID, `auto:${BADGE_ID}:user:${USER_ID}`],
);
await pool.query(
`
delete from public.badges
where tenant_id = $1 and id = $2::uuid
`,
[TENANT_ID, BADGE_ID],
);
await pool.query(
`
insert into public.badges (
id, tenant_id, legacy_id, name, description, category, level,
unlock_type, condition_field, condition_operator, condition_value,
condition_extra, metadata, sort_order, is_active
)
values (
$1::uuid, $2, 'integration-badge-auto-concurrency',
'并发自动勋章烟测', '并发触发时只能发放一次', 'practice', 1,
'practice_count', 'concurrencyGrantCount', 'gte', 1,
'{}'::jsonb, '{"source":"auto-badge-concurrency-test"}'::jsonb, 99, true
)
`,
[BADGE_ID, TENANT_ID],
);
}
async function grantOnce(index) {
const client = await pool.connect();
try {
await client.query('begin');
const grants = await autoGrantBadges(client, {
tenantId: TENANT_ID,
userId: USER_ID,
trigger: 'practice_count',
evidence: {
concurrencyGrantCount: 1,
workerIndex: index,
},
});
await client.query('commit');
return grants;
} catch (error) {
await client.query('rollback').catch(() => undefined);
throw error;
} finally {
client.release();
}
}
async function main() {
try {
await resetFixture();
const results = await Promise.all(Array.from({ length: 20 }, (_, index) => grantOnce(index)));
const grantedRows = results.flat().filter(item => item.badgeId === BADGE_ID);
const count = await pool.query(
`
select count(*)::int as count
from public.user_badges
where tenant_id = $1 and user_id = $2 and badge_id = $3::uuid
`,
[TENANT_ID, USER_ID, BADGE_ID],
);
const legacyCount = await pool.query(
`
select count(*)::int as count
from public.user_badges
where tenant_id = $1 and legacy_id = $2
`,
[TENANT_ID, `auto:${BADGE_ID}:user:${USER_ID}`],
);
assert.equal(Number(count.rows[0]?.count || 0), 1, 'concurrent auto badge grants should create one user_badges row');
assert.equal(Number(legacyCount.rows[0]?.count || 0), 0, 'auto badge grants should not write legacy_id idempotency keys');
assert.equal(grantedRows.length, 1, 'only one concurrent caller should receive a new grant payload');
console.log('Auto badge concurrency test complete.');
} finally {
await pool.end();
}
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});