test(bt): tablet PIN self-service entech smoke (Task 7)
10-step smoke via odoo-shell: 1. Pick real no-PIN shop user 2. _generate_for_user -> assert 4-digit code + active row 3. Wrong code -> assert rejected + attempt_count incremented 4. Correct code -> assert ok + used_at set 5. _sign_reset_token + _verify_reset_token roundtrip 6. set_tablet_pin (mirrors set_pin endpoint reset_token branch) 7. verify_tablet_pin -> assert new PIN works 8. mail.template ref resolves 9. fp.notification.template ref resolves 10. Cleanup cron ref resolves Cleans up: reverts PIN + deletes reset rows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
129
fusion_plating/fusion_plating_shopfloor/scripts/bt_pin_reset.py
Normal file
129
fusion_plating/fusion_plating_shopfloor/scripts/bt_pin_reset.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Tablet PIN self-service — entech smoke.
|
||||||
|
|
||||||
|
Spec: docs/superpowers/specs/2026-05-25-tablet-pin-self-service-design.md
|
||||||
|
Plan: docs/superpowers/plans/2026-05-25-tablet-pin-self-service-plan.md
|
||||||
|
|
||||||
|
Run on entech via odoo-shell. Picks a real shop-floor user with no
|
||||||
|
current PIN, runs the full create flow end-to-end via the model
|
||||||
|
helpers (controller endpoints exercise the same paths under the
|
||||||
|
hood; this script lets us verify pre-HTTP).
|
||||||
|
|
||||||
|
Cleans up at the end: reverts PIN + deletes reset rows for the user.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _ok(cond, label):
|
||||||
|
if cond:
|
||||||
|
print('OK -', label)
|
||||||
|
else:
|
||||||
|
print('FAIL -', label)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
|
||||||
|
# Pick a real user — first active shop-branch user with no PIN.
|
||||||
|
gids = []
|
||||||
|
for xmlid in (
|
||||||
|
'fusion_plating.group_fp_technician',
|
||||||
|
'fusion_plating.group_fp_manager',
|
||||||
|
'fusion_plating.group_fp_quality_manager',
|
||||||
|
'fusion_plating.group_fp_owner',
|
||||||
|
):
|
||||||
|
g = env.ref(xmlid, raise_if_not_found=False)
|
||||||
|
if g:
|
||||||
|
gids.append(g.id)
|
||||||
|
user = env['res.users'].sudo().search([
|
||||||
|
('all_group_ids', 'in', gids),
|
||||||
|
('share', '=', False),
|
||||||
|
('active', '=', True),
|
||||||
|
('x_fc_tablet_pin_hash', '=', False),
|
||||||
|
], limit=1)
|
||||||
|
_ok(bool(user), f'found a no-PIN shop user: {user.name if user else None}')
|
||||||
|
|
||||||
|
original_hash = user.sudo().x_fc_tablet_pin_hash # for cleanup
|
||||||
|
|
||||||
|
Reset = env['fp.tablet.pin.reset']
|
||||||
|
|
||||||
|
# 1. Generate a code
|
||||||
|
rec, code = Reset._generate_for_user(user)
|
||||||
|
_ok(rec.exists(), 'reset row created')
|
||||||
|
_ok(len(code) == 4 and code.isdigit(), f'code is 4 digits: {code}')
|
||||||
|
_ok(not rec.used_at, 'row is active (used_at is null)')
|
||||||
|
|
||||||
|
# 2. Verify wrong code
|
||||||
|
wrong = '9999' if code != '9999' else '0000'
|
||||||
|
ok, err = rec._verify_and_consume(wrong)
|
||||||
|
_ok(not ok and err == 'wrong_code', f'wrong code rejected: err={err}')
|
||||||
|
|
||||||
|
rec.invalidate_recordset(['attempt_count'])
|
||||||
|
_ok(rec.attempt_count == 1, f'attempt_count is 1: {rec.attempt_count}')
|
||||||
|
|
||||||
|
# 3. Verify correct code
|
||||||
|
ok, err = rec._verify_and_consume(code)
|
||||||
|
_ok(ok and err is None, f'correct code accepted: err={err}')
|
||||||
|
|
||||||
|
rec.invalidate_recordset(['used_at'])
|
||||||
|
_ok(bool(rec.used_at), 'row marked used after success')
|
||||||
|
|
||||||
|
# 4. Sign reset token + verify
|
||||||
|
token = Reset._sign_reset_token(user.id)
|
||||||
|
_ok('.' in token, 'token has body.sig shape')
|
||||||
|
|
||||||
|
uid = Reset._verify_reset_token(token)
|
||||||
|
_ok(uid == user.id, f'token verifies to correct uid: {uid}')
|
||||||
|
|
||||||
|
# 5. Set PIN (simulates the set_pin endpoint reset_token branch)
|
||||||
|
user.set_tablet_pin('4321')
|
||||||
|
user.invalidate_recordset(['x_fc_tablet_pin_hash'])
|
||||||
|
_ok(bool(user.sudo().x_fc_tablet_pin_hash), 'PIN hash set on user')
|
||||||
|
|
||||||
|
# 6. Verify the new PIN works
|
||||||
|
_ok(user.verify_tablet_pin('4321'), 'new PIN verifies')
|
||||||
|
|
||||||
|
# 7. Audit events written
|
||||||
|
audit_count = env['fp.tablet.session.event'].sudo().search_count([
|
||||||
|
('attempted_user_id', '=', user.id),
|
||||||
|
('event_type', 'in', (
|
||||||
|
'pin_reset_requested',
|
||||||
|
'pin_reset_code_verified',
|
||||||
|
'failed_unlock',
|
||||||
|
)),
|
||||||
|
])
|
||||||
|
# Direct model calls don't write audit (controller does); so we don't
|
||||||
|
# assert > 0 here. Just print the count for visibility.
|
||||||
|
print(f' audit events for user (informational): {audit_count}')
|
||||||
|
|
||||||
|
# 8. Mail template exists
|
||||||
|
tpl = env.ref(
|
||||||
|
'fusion_plating_shopfloor.fp_mail_template_tablet_pin_reset',
|
||||||
|
raise_if_not_found=False,
|
||||||
|
)
|
||||||
|
_ok(bool(tpl), 'mail.template fp_mail_template_tablet_pin_reset exists')
|
||||||
|
|
||||||
|
# 9. Notification template wrapper exists
|
||||||
|
notif = env.ref(
|
||||||
|
'fusion_plating_shopfloor.fp_notif_tablet_pin_reset',
|
||||||
|
raise_if_not_found=False,
|
||||||
|
)
|
||||||
|
_ok(bool(notif), 'fp.notification.template fp_notif_tablet_pin_reset exists')
|
||||||
|
|
||||||
|
# 10. Cleanup cron exists
|
||||||
|
cron = env.ref(
|
||||||
|
'fusion_plating_shopfloor.cron_purge_expired_pin_resets',
|
||||||
|
raise_if_not_found=False,
|
||||||
|
)
|
||||||
|
_ok(bool(cron), 'cleanup cron cron_purge_expired_pin_resets exists')
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
user.sudo().write({
|
||||||
|
'x_fc_tablet_pin_hash': original_hash or False,
|
||||||
|
})
|
||||||
|
env['fp.tablet.pin.reset'].sudo().search([
|
||||||
|
('user_id', '=', user.id),
|
||||||
|
]).unlink()
|
||||||
|
env.cr.commit()
|
||||||
|
print('cleanup: PIN reverted, reset rows deleted')
|
||||||
|
|
||||||
|
print()
|
||||||
|
print('--- bt_pin_reset: ALL PASS ---')
|
||||||
|
print(f' Tested user: {user.name} (uid={user.id})')
|
||||||
Reference in New Issue
Block a user