Commit 9ce197dd authored by Mahmoud Aglan's avatar Mahmoud Aglan

test: mark every test account, and prove the pools never mix

Every account helper in tests/play sets profiles.is_test right after the
account is made (guest or GoTrue admin), before it can queue, and fails
loudly when the marker did not stick.

tests/play/match-pool.mjs: two test accounts and two unmarked stand-ins for
real players, made with the admin API (no guest signups). In every public
queue, in a bucket the app never offers a real player where the game has
one, a test account is not seated with a real player waiting longer, by the
queue call or the status poll; two test accounts are seated together on both
paths; a real player searching passes a waiting test account; two real
players still pair; domino's public tables keep apart the same way; and a
player's own token cannot set is_test either way. Against the code before
the fix it fails 42 checks; after, 63 of 63 pass. Teardown is verified.
Co-Authored-By: 's avatarClaude Opus 5.5 <noreply@anthropic.com>
parent f5207cc4
......@@ -87,8 +87,9 @@ async function createAccount(name) {
if (!r.access_token || !r.profile?.id) throw new Error('account creation failed: ' + JSON.stringify(r).slice(0, 160));
const acc = { id: r.profile.id, token: r.access_token, refresh: r.refresh_token };
accounts.push(acc);
await sb(`profiles?id=eq.${acc.id}`, { method: 'PATCH', body: { display_name: name, level: 4, coins: 500 } });
await sb(`profiles?id=eq.${acc.id}`, { method: 'PATCH', body: { is_test: true, display_name: name, level: 4, coins: 500 } });
acc.profile = (await sb(`profiles?id=eq.${acc.id}&select=*`))?.[0] || r.profile;
if (acc.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + acc.id);
return acc;
}
async function removeAccounts() {
......
......@@ -95,9 +95,10 @@ async function createAccount(i) {
const u = { id: r.profile.id, token: r.access_token, refresh: r.refresh_token };
await sb(`profiles?id=eq.${u.id}`, {
method: 'PATCH', prefer: 'return=minimal',
body: { display_name: i === 0 ? 'سلمى التحدي' : 'كريم الخصم', elo_rapid: 1486 - i * 40, elo_blitz: 1412, elo_bullet: 1390 - i * 30, elo_domino: 1240 + i * 20, level: 7 + i * 5, xp: 3260, total_wins: 18 + i * 9 },
body: { is_test: true, display_name: i === 0 ? 'سلمى التحدي' : 'كريم الخصم', elo_rapid: 1486 - i * 40, elo_blitz: 1412, elo_bullet: 1390 - i * 30, elo_domino: 1240 + i * 20, level: 7 + i * 5, xp: 3260, total_wins: 18 + i * 9 },
});
u.profile = (await sb(`profiles?id=eq.${u.id}&select=*`))?.[0] || r.profile;
if (u.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + u.id);
u.display = u.profile.display_name;
users.push(u);
return u;
......
......@@ -111,9 +111,10 @@ async function createAccount(i) {
const names = ['سارة القطب', 'كريم ناصر', 'ليلى السيد'];
await sb(`profiles?id=eq.${acc.id}`, {
method: 'PATCH',
body: { display_name: `${names[i % names.length]}`, username: `${TAG.toLowerCase()}-${acc.id.slice(0, 8)}`, elo_rapid: 1450 + i * 40, elo_classical: 1500 + i * 40, elo_blitz: 1400, coins: 1200, level: 4 },
body: { is_test: true, display_name: `${names[i % names.length]}`, username: `${TAG.toLowerCase()}-${acc.id.slice(0, 8)}`, elo_rapid: 1450 + i * 40, elo_classical: 1500 + i * 40, elo_blitz: 1400, coins: 1200, level: 4 },
});
acc.profile = (await sb(`profiles?id=eq.${acc.id}&select=*`))?.[0] || r.profile;
if (acc.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + acc.id);
return acc;
}
......
......@@ -92,8 +92,9 @@ async function createAccount(n) {
if (!r.access_token || !r.profile?.id) throw new Error('guest account failed: ' + JSON.stringify(r).slice(0, 160));
const acc = { id: r.profile.id, token: r.access_token, refresh: r.refresh_token };
created.users.push(acc);
await sb(`profiles?id=eq.${acc.id}`, { method: 'PATCH', body: { display_name: `${TAG} ${n}` } });
await sb(`profiles?id=eq.${acc.id}`, { method: 'PATCH', body: { is_test: true, display_name: `${TAG} ${n}` } });
acc.profile = (await sb(`profiles?id=eq.${acc.id}&select=*`))?.[0] || r.profile;
if (acc.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + acc.id);
return acc;
}
......
......@@ -103,9 +103,10 @@ async function createAccount(tag, extra = {}) {
const u = { id: r.profile.id, token: r.access_token, refresh: r.refresh_token, tag };
await sb(`profiles?id=eq.${u.id}`, {
method: 'PATCH', prefer: 'return=minimal',
body: { display_name: `ZZZ-GHOST ${tag}`, elo_rapid: 1320, elo_blitz: 1290, elo_bullet: 1250, elo_classical: 1340, elo_domino: 1260, level: 11, xp: 5200, total_wins: 30, ...extra },
body: { is_test: true, display_name: `ZZZ-GHOST ${tag}`, elo_rapid: 1320, elo_blitz: 1290, elo_bullet: 1250, elo_classical: 1340, elo_domino: 1260, level: 11, xp: 5200, total_wins: 30, ...extra },
});
u.profile = (await sb(`profiles?id=eq.${u.id}&select=*`))?.[0] || r.profile;
if (u.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + u.id);
users.push(u);
return u;
}
......
......@@ -91,8 +91,9 @@ async function createAccount(name) {
if (!r.access_token || !r.profile?.id) throw new Error('account creation failed: ' + JSON.stringify(r).slice(0, 200));
const a = { id: r.profile.id, token: r.access_token, refresh: r.refresh_token };
accounts.push(a);
await sb(`profiles?id=eq.${a.id}`, { method: 'PATCH', body: { display_name: name, level: 3 } });
await sb(`profiles?id=eq.${a.id}`, { method: 'PATCH', body: { is_test: true, display_name: name, level: 3 } });
a.profile = (await sb(`profiles?id=eq.${a.id}&select=*`))?.[0] || r.profile;
if (a.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + a.id);
return a;
}
......
This diff is collapsed.
......@@ -141,9 +141,10 @@ async function createUser(label, name, extra = {}) {
}
await sb(`profiles?id=eq.${u.id}`, {
method: 'PATCH', prefer: 'return=minimal',
body: { display_name: name, username: `zzorg_${STAMP.toLowerCase()}_${label.toLowerCase()}`, level: 6, xp: 2400, ...extra },
body: { is_test: true, display_name: name, username: `zzorg_${STAMP.toLowerCase()}_${label.toLowerCase()}`, level: 6, xp: 2400, ...extra },
});
u.profile = (await sb(`profiles?id=eq.${u.id}&select=*`))?.[0];
if (u.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + u.id);
return u;
}
......
......@@ -81,6 +81,13 @@ function mint(sub, email, anonymous = false) {
return `${head}.${body}.${sig}`;
}
/** Mark an account as made by the tests (profiles.is_test): public queues never seat it with a real player. */
async function markTest(id) {
await sb(`profiles?id=eq.${id}`, { method: 'PATCH', prefer: 'return=minimal', body: { is_test: true } });
const p = await sb(`profiles?id=eq.${id}&select=is_test`);
if (p?.[0]?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + id);
}
async function makeUser(label, i) {
const email = `zzredeem-${STAMP}-${label.toLowerCase()}@el3ab.test`;
const password = `Zz-${STAMP}-${label}-pw`;
......@@ -91,7 +98,8 @@ async function makeUser(label, i) {
if (!r.id) throw new Error('admin create user failed: ' + show(r));
const u = { label, id: r.id, email, password, token: mint(r.id, email), ip: `10.88.${i}.${(parseInt(STAMP, 36) % 200) + 1}` };
made.users.push(u);
await sb(`profiles?id=eq.${u.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { display_name: `ZZ ${label} ${STAMP}`, level: 3 } });
await sb(`profiles?id=eq.${u.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { is_test: true, display_name: `ZZ ${label} ${STAMP}`, level: 3 } });
await markTest(u.id);
return u;
}
......@@ -528,7 +536,8 @@ async function guestFlow(users, org, basic, approvalCode, group) {
const G = { label: 'G', id: g.user.id, token: g.access_token, ip: '10.89.3.2' };
made.users.push(G);
made.ips.add('10.89.3.1');
await sb(`profiles?id=eq.${G.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { coins: 123 } });
await sb(`profiles?id=eq.${G.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { is_test: true, coins: 123 } });
await markTest(G.id);
const gp = await api(G, 'redeem.php', { query: { action: 'preview', code: basic.code, source: 'link' } });
check('guest preview: account guest, intent_saved', gp.status === 200 && gp.data.account === 'guest' && gp.data.intent_saved === true, show(gp.data));
const ga = await api(G, 'redeem.php', { body: { action: 'apply', code: basic.code, source: 'link' } });
......@@ -577,13 +586,13 @@ async function signupFlow(users, org, basic) {
section('signup with a code');
const good = await fetch(`${BASE}/api/auth.php`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Real-IP': '10.89.4.1' },
body: JSON.stringify({ action: 'signup', email: `zzredeem-${STAMP}-su1@el3ab.test`, password: 'Zz-signup-1', username: `zzs1_${STAMP}`, redeem_code: basic.display, redeem_source: 'link' }) }).then(x => x.json());
if (good.user?.id) made.users.push({ label: 'SU1', id: good.user.id });
if (good.user?.id) { made.users.push({ label: 'SU1', id: good.user.id }); await markTest(good.user.id); }
check('signup with a good code: tokens and redeem applied', !!good.access_token && good.redeem?.status === 'applied', show(good.redeem || good));
const r1 = good.user?.id ? await sb(`redeem_redemptions?player_id=eq.${good.user.id}&select=source,was_new_signup`) : [];
check('signup redemption keeps the link source and is a new signup', r1?.[0]?.source === 'link' && r1[0].was_new_signup === true, show(r1));
const bad = await fetch(`${BASE}/api/auth.php`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Real-IP': '10.89.4.1' },
body: JSON.stringify({ action: 'signup', email: `zzredeem-${STAMP}-su2@el3ab.test`, password: 'Zz-signup-2', username: `zzs2_${STAMP}`, redeem_code: 'ZZZZ-ZZZZ' }) }).then(x => x.json());
if (bad.user?.id) made.users.push({ label: 'SU2', id: bad.user.id, email: `zzredeem-${STAMP}-su2@el3ab.test`, password: 'Zz-signup-2', token: bad.access_token });
if (bad.user?.id) { made.users.push({ label: 'SU2', id: bad.user.id, email: `zzredeem-${STAMP}-su2@el3ab.test`, password: 'Zz-signup-2', token: bad.access_token }); await markTest(bad.user.id); }
check('signup with a bad code: the account is made anyway, redeem error block', !!bad.access_token && bad.redeem?.ok === false && bad.redeem.error === 'not_found' && bad.redeem.keep_code === false, show(bad.redeem || bad));
const dup = await fetch(`${BASE}/api/auth.php`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Real-IP': '10.89.4.1' },
body: JSON.stringify({ action: 'signup', email: users.O.email, password: 'Zz-signup-3', username: `zzs3_${STAMP}` }) });
......
......@@ -96,8 +96,9 @@ async function adminUser(label) {
if (!login.access_token) throw new Error('login failed: ' + JSON.stringify(login).slice(0, 200));
p.token = login.access_token;
p.refresh = login.refresh_token;
await sb(`profiles?id=eq.${p.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { display_name: `لاعب ${label} ${STAMP}`, username: `zzrd_${label}_${STAMP}` } });
await sb(`profiles?id=eq.${p.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { is_test: true, display_name: `لاعب ${label} ${STAMP}`, username: `zzrd_${label}_${STAMP}` } });
p.profile = (await sb(`profiles?id=eq.${p.id}&select=*`))?.[0] || null;
if (p.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + p.id);
return p;
}
......@@ -251,7 +252,11 @@ async function linkSignup() {
await tap(page, '#reg-btn');
const landed = await seen(page, '.rd-veil .rd-stamp', 25000);
const st = await stored(page);
if (st.auth?.userId) people.push({ id: st.auth.userId, email, label: 'new' });
if (st.auth?.userId) {
people.push({ id: st.auth.userId, email, label: 'new' });
// A test account (profiles.is_test): public queues never seat it with a real player.
await sb(`profiles?id=eq.${st.auth.userId}`, { method: 'PATCH', prefer: 'return=minimal', body: { is_test: true } });
}
check('the reveal stamps the ticket after signup', landed);
check('the welcome card names the org', await waitFor(page, (n) => (document.querySelector('.rd-welcome-title')?.textContent || '').includes(n), ORG_NAME_AR, 8000));
await shot(page, 'signup-reveal');
......@@ -366,6 +371,7 @@ async function guestUpgrade() {
if (!g.access_token || !g.profile?.id) { check('a guest account', false, JSON.stringify(g).slice(0, 160)); return; }
const guest = { id: g.profile.id, token: g.access_token, refresh: g.refresh_token, profile: g.profile, label: 'guest' };
people.push(guest);
await sb(`profiles?id=eq.${guest.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { is_test: true } });
const tab = await newPage('guest');
const { page } = tab;
await openAs(tab, guest, `r/${code.display}`, { guest: true });
......
......@@ -49,8 +49,9 @@ export async function createAccount(name) {
if (!r.access_token || !r.profile?.id) throw new Error('account creation failed: ' + JSON.stringify(r).slice(0, 160));
const a = { id: r.profile.id, token: r.access_token, refresh: r.refresh_token, name };
accounts.push(a);
await sb(`profiles?id=eq.${a.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { display_name: name, level: 5 } });
await sb(`profiles?id=eq.${a.id}`, { method: 'PATCH', prefer: 'return=minimal', body: { is_test: true, display_name: name, level: 5 } });
a.profile = (await sb(`profiles?id=eq.${a.id}&select=*`))?.[0] || r.profile;
if (a.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + a.id);
return a;
}
......
......@@ -88,9 +88,10 @@ async function createAccount(i) {
const u = { id: r.profile.id, token: r.access_token, refresh: r.refresh_token, name: `${TAG} ${i === 0 ? 'A' : 'B'}` };
await sb(`profiles?id=eq.${u.id}`, {
method: 'PATCH', prefer: 'return=minimal',
body: { display_name: i === 0 ? 'ليلى السيد' : 'يوسف كمال', elo_rapid: 1486 - i * 40, elo_blitz: 1412, elo_bullet: 1390 - i * 30, elo_domino: 1240, level: 7 + i * 5, xp: 3260, total_wins: 18 + i * 9 },
body: { is_test: true, display_name: i === 0 ? 'ليلى السيد' : 'يوسف كمال', elo_rapid: 1486 - i * 40, elo_blitz: 1412, elo_bullet: 1390 - i * 30, elo_domino: 1240, level: 7 + i * 5, xp: 3260, total_wins: 18 + i * 9 },
});
u.profile = (await sb(`profiles?id=eq.${u.id}&select=*`))?.[0] || r.profile;
if (u.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + u.id);
u.display = u.profile.display_name;
users.push(u);
return u;
......
......@@ -84,9 +84,10 @@ async function createAccount(i) {
users.push(u);
await sb(`profiles?id=eq.${u.id}`, {
method: 'PATCH', prefer: 'return=minimal',
body: { display_name: u.name, username: `zzsoc_${STAMP}_${u.label.toLowerCase()}`, level: 4 + i * 3, xp: 1800 },
body: { is_test: true, display_name: u.name, username: `zzsoc_${STAMP}_${u.label.toLowerCase()}`, level: 4 + i * 3, xp: 1800 },
});
u.profile = (await sb(`profiles?id=eq.${u.id}&select=*`))?.[0] || r.profile;
if (u.profile?.is_test !== true) throw new Error('the test marker (profiles.is_test) did not stick on ' + u.id);
u.username = u.profile.username;
return u;
}
......
......@@ -101,7 +101,7 @@ async function createGuest() {
made.user = { id: r.profile.id, token: r.access_token, refresh: r.refresh_token };
await sb(`profiles?id=eq.${made.user.id}`, {
method: 'PATCH', prefer: 'return=minimal',
body: { display_name: TAG, username: `zzrules_${STAMP}`, coins: 50, elo_rapid: 1200, level: 6 },
body: { is_test: true, display_name: TAG, username: `zzrules_${STAMP}`, coins: 50, elo_rapid: 1200, level: 6 },
});
made.user.profile = await one(`profiles?id=eq.${made.user.id}&select=*`);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment