forked from wangziqi/gongxue-base
113 lines
3.4 KiB
JavaScript
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;
|
|
});
|