template-monorepo/test/k6/smoke/member.js

134 lines
5.0 KiB
JavaScript
Raw Permalink Normal View History

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
2026-05-27 09:28:13 +00:00
// 13. POST /me/password (negative — totp not enrolled)
2026-05-26 17:10:32 +00:00
checkError(
post(
'/api/v1/members/me/password',
2026-05-27 09:28:13 +00:00
{ current_password: identity.password, new_password: 'K6-NewPass-2!' },
2026-05-26 17:10:32 +00:00
bearer,
),
2026-05-27 09:28:13 +00:00
'POST /me/password (totp not enrolled)',
403,
29505000,
2026-05-26 17:10:32 +00:00
);
// 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,
);
2026-05-26 06:05:33 +00:00
}