2026-05-26 06:05:33 +00:00
|
|
|
// smoke: member endpoints (Bearer)
|
|
|
|
|
//
|
2026-05-26 17:10:32 +00:00
|
|
|
// Covers (14 endpoints + change password negatives):
|
2026-05-26 06:05:33 +00:00
|
|
|
// GET /api/v1/members/me
|
|
|
|
|
// PATCH /api/v1/members/me
|
|
|
|
|
// POST /api/v1/members/me/verifications/email/start
|
|
|
|
|
// POST /api/v1/members/me/verifications/email/confirm (negative: invalid code)
|
|
|
|
|
// POST /api/v1/members/me/verifications/phone/start
|
|
|
|
|
// POST /api/v1/members/me/verifications/phone/confirm (negative: invalid code)
|
|
|
|
|
// GET /api/v1/members/me/totp (status before enroll)
|
|
|
|
|
// POST /api/v1/members/me/totp/enroll-start
|
|
|
|
|
// POST /api/v1/members/me/totp/enroll-confirm (negative: invalid code)
|
|
|
|
|
// POST /api/v1/members/me/totp/verify (negative: not enrolled)
|
|
|
|
|
// POST /api/v1/members/me/totp/backup-codes (negative: not enrolled)
|
2026-05-26 17:10:32 +00:00
|
|
|
// POST /api/v1/members/me/password (negative: wrong current / no bearer / weak)
|
2026-05-26 06:05:33 +00:00
|
|
|
//
|
|
|
|
|
// Happy paths for TOTP and verification end-to-end live in journeys/.
|
|
|
|
|
import { get, post, patch, del, checkEnvelope, checkError } from '../lib/http.js';
|
|
|
|
|
import { registerAndConfirm } from '../lib/auth.js';
|
|
|
|
|
import { unique } from '../lib/config.js';
|
|
|
|
|
|
|
|
|
|
export const options = {
|
|
|
|
|
vus: 1,
|
|
|
|
|
iterations: 1,
|
|
|
|
|
thresholds: { checks: ['rate==1.0'] },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default function () {
|
2026-05-26 17:10:32 +00:00
|
|
|
const { identity, tokens } = registerAndConfirm();
|
2026-05-26 06:05:33 +00:00
|
|
|
const bearer = { Authorization: `Bearer ${tokens.access_token}` };
|
|
|
|
|
|
|
|
|
|
// 1. GET /me
|
|
|
|
|
const me = checkEnvelope(get('/api/v1/members/me', bearer), 'GET /members/me').data;
|
|
|
|
|
if (me.uid !== tokens.uid) throw new Error('me.uid mismatch');
|
|
|
|
|
|
|
|
|
|
// 2. PATCH /me
|
|
|
|
|
const newName = `smoke-${unique('m')}`;
|
|
|
|
|
const patched = checkEnvelope(patch('/api/v1/members/me', { display_name: newName }, bearer), 'PATCH /members/me').data;
|
|
|
|
|
if (patched.display_name !== newName) throw new Error('display_name not updated');
|
|
|
|
|
|
|
|
|
|
// 3. POST /me/verifications/email/start
|
|
|
|
|
const eStart = checkEnvelope(
|
|
|
|
|
post('/api/v1/members/me/verifications/email/start', { target: `verify-${unique('e')}@k6.local` }, bearer),
|
|
|
|
|
'POST /me/verifications/email/start',
|
|
|
|
|
).data;
|
|
|
|
|
if (!eStart.challenge_id) throw new Error('email start: missing challenge_id');
|
|
|
|
|
|
|
|
|
|
// 4. POST /me/verifications/email/confirm (negative — wrong code 000000)
|
|
|
|
|
checkError(
|
|
|
|
|
post('/api/v1/members/me/verifications/email/confirm', { challenge_id: eStart.challenge_id, code: '000000' }, bearer),
|
|
|
|
|
'POST /me/verifications/email/confirm (bad code)',
|
|
|
|
|
403,
|
|
|
|
|
29505000,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 5. POST /me/verifications/phone/start
|
|
|
|
|
const pStart = checkEnvelope(
|
|
|
|
|
post('/api/v1/members/me/verifications/phone/start', { target: '+886912000001' }, bearer),
|
|
|
|
|
'POST /me/verifications/phone/start',
|
|
|
|
|
).data;
|
|
|
|
|
if (!pStart.challenge_id) throw new Error('phone start: missing challenge_id');
|
|
|
|
|
|
|
|
|
|
// 6. POST /me/verifications/phone/confirm (negative)
|
|
|
|
|
checkError(
|
|
|
|
|
post('/api/v1/members/me/verifications/phone/confirm', { challenge_id: pStart.challenge_id, code: '000000' }, bearer),
|
|
|
|
|
'POST /me/verifications/phone/confirm (bad code)',
|
|
|
|
|
403,
|
|
|
|
|
29505000,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 7. GET /me/totp (status — not enrolled)
|
|
|
|
|
const totpStatus = checkEnvelope(get('/api/v1/members/me/totp', bearer), 'GET /me/totp').data;
|
|
|
|
|
if (totpStatus.enrolled !== false) throw new Error('totp should not be enrolled');
|
|
|
|
|
|
|
|
|
|
// 8. POST /me/totp/enroll-start (happy)
|
|
|
|
|
const enroll = checkEnvelope(post('/api/v1/members/me/totp/enroll-start', null, bearer), 'POST /me/totp/enroll-start').data;
|
|
|
|
|
if (!enroll.otpauth_url) throw new Error('enroll-start: missing otpauth_url');
|
|
|
|
|
|
|
|
|
|
// 9. POST /me/totp/enroll-confirm (negative — bad code)
|
|
|
|
|
checkError(
|
|
|
|
|
post('/api/v1/members/me/totp/enroll-confirm', { code: '000000' }, bearer),
|
|
|
|
|
'POST /me/totp/enroll-confirm (bad code)',
|
|
|
|
|
403,
|
|
|
|
|
29505000,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 10. POST /me/totp/verify (negative — not enrolled)
|
|
|
|
|
checkError(
|
|
|
|
|
post('/api/v1/members/me/totp/verify', { code: '000000' }, bearer),
|
|
|
|
|
'POST /me/totp/verify (not enrolled)',
|
|
|
|
|
409,
|
|
|
|
|
29309000,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 11. POST /me/totp/backup-codes (negative — not enrolled)
|
|
|
|
|
checkError(
|
|
|
|
|
post('/api/v1/members/me/totp/backup-codes', null, bearer),
|
|
|
|
|
'POST /me/totp/backup-codes (not enrolled)',
|
|
|
|
|
409,
|
|
|
|
|
29309000,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 12. DELETE /me/totp — when not enrolled the contract returns 200 (idempotent disable).
|
|
|
|
|
// Accept either 200 success envelope or 404 (member-not-found edge case).
|
|
|
|
|
const delRes = del('/api/v1/members/me/totp', null, bearer);
|
|
|
|
|
if (delRes.status !== 200 && delRes.status !== 404) {
|
|
|
|
|
throw new Error(`DELETE /me/totp unexpected status ${delRes.status}: ${delRes.body}`);
|
|
|
|
|
}
|
2026-05-26 17:10:32 +00:00
|
|
|
|
|
|
|
|
// 13. POST /me/password (negative — wrong current password)
|
|
|
|
|
checkError(
|
|
|
|
|
post(
|
|
|
|
|
'/api/v1/members/me/password',
|
|
|
|
|
{ current_password: 'WrongPass-1!', new_password: 'K6-NewPass-2!' },
|
|
|
|
|
bearer,
|
|
|
|
|
),
|
|
|
|
|
'POST /me/password (wrong current)',
|
|
|
|
|
401,
|
|
|
|
|
29501000,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 14. POST /me/password (negative — no bearer)
|
|
|
|
|
checkError(
|
|
|
|
|
post('/api/v1/members/me/password', {
|
|
|
|
|
current_password: identity.password,
|
|
|
|
|
new_password: 'K6-NewPass-2!',
|
|
|
|
|
}),
|
|
|
|
|
'POST /me/password (no bearer)',
|
|
|
|
|
401,
|
|
|
|
|
29501000,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 15. POST /me/password (negative — weak new password)
|
|
|
|
|
const weakPwd = post(
|
|
|
|
|
'/api/v1/members/me/password',
|
|
|
|
|
{ current_password: identity.password, new_password: 'short' },
|
|
|
|
|
bearer,
|
|
|
|
|
);
|
|
|
|
|
if (weakPwd.status !== 400) {
|
|
|
|
|
throw new Error(`POST /me/password weak password: expected 400 got ${weakPwd.status}`);
|
|
|
|
|
}
|
2026-05-26 06:05:33 +00:00
|
|
|
}
|