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:
gsinghpal
2026-05-25 16:54:46 -04:00
parent 8c9b645196
commit 9223f8da7c

View 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})')