feat(fusion_plating_shopfloor): /fp/tablet/set_pin + /fp/tablet/reset_pin_for endpoints (P6.1.2)
set_pin is self-service: requires old PIN if a hash exists, validates 4-digit format. reset_pin_for is manager-only (enforced server-side via has_group); clears the hash + posts to chatter. Both endpoints log INFO on success and WARNING on access-control denials. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,16 @@ import json
|
||||
from odoo.tests.common import HttpCase, TransactionCase, tagged
|
||||
|
||||
|
||||
def _rpc(case, url, **params):
|
||||
"""Helper for HTTP/JSON-RPC tests — wraps Odoo's url_open."""
|
||||
res = case.url_open(
|
||||
url,
|
||||
data=json.dumps({'jsonrpc': '2.0', 'params': params}),
|
||||
headers={'Content-Type': 'application/json'},
|
||||
)
|
||||
return res.json()['result']
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'fp_shopfloor', 'fp_tablet_pin')
|
||||
class TestTabletPinHash(TransactionCase):
|
||||
"""P6.1.1 — model fields + hash helpers + set/verify/clear methods."""
|
||||
@@ -52,3 +62,65 @@ class TestTabletPinHash(TransactionCase):
|
||||
# Check chatter
|
||||
last_msg = self.user.message_ids[:1]
|
||||
self.assertIn('reset by', (last_msg.body or '').lower())
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'fp_shopfloor', 'fp_tablet_pin')
|
||||
class TestTabletSetPin(HttpCase):
|
||||
"""P6.1.2 — /fp/tablet/set_pin endpoint."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.authenticate("admin", "admin")
|
||||
|
||||
def test_set_pin_first_time(self):
|
||||
res = _rpc(self, '/fp/tablet/set_pin', new_pin='1234')
|
||||
self.assertTrue(res['ok'])
|
||||
|
||||
def test_set_pin_change_requires_old(self):
|
||||
admin = self.env.ref('base.user_admin')
|
||||
admin.set_tablet_pin('1234')
|
||||
# Change without old — rejected
|
||||
res = _rpc(self, '/fp/tablet/set_pin', new_pin='5678')
|
||||
self.assertFalse(res['ok'])
|
||||
# Change with wrong old — rejected
|
||||
res = _rpc(self, '/fp/tablet/set_pin', old_pin='9999', new_pin='5678')
|
||||
self.assertFalse(res['ok'])
|
||||
# Change with correct old — accepted
|
||||
res = _rpc(self, '/fp/tablet/set_pin', old_pin='1234', new_pin='5678')
|
||||
self.assertTrue(res['ok'])
|
||||
|
||||
def test_set_pin_rejects_non_4_digit(self):
|
||||
for bad in ('123', '12345', 'abcd', ''):
|
||||
res = _rpc(self, '/fp/tablet/set_pin', new_pin=bad)
|
||||
self.assertFalse(res['ok'], f'PIN {bad!r} should be rejected')
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'fp_shopfloor', 'fp_tablet_pin')
|
||||
class TestTabletResetPinFor(HttpCase):
|
||||
"""P6.1.2 — /fp/tablet/reset_pin_for endpoint."""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.target = self.env['res.users'].create({
|
||||
'name': 'Reset Target', 'login': 'reset@example.com',
|
||||
})
|
||||
self.target.sudo().set_tablet_pin('1234')
|
||||
|
||||
def test_reset_requires_manager_group(self):
|
||||
# Plain operator can't reset
|
||||
op_group = self.env.ref('fusion_plating.group_fusion_plating_operator')
|
||||
self.env['res.users'].create({
|
||||
'name': 'Op', 'login': 'op@example.com',
|
||||
'password': 'op@example.com',
|
||||
'group_ids': [(6, 0, [op_group.id])],
|
||||
})
|
||||
self.authenticate('op@example.com', 'op@example.com')
|
||||
res = _rpc(self, '/fp/tablet/reset_pin_for', user_id=self.target.id)
|
||||
self.assertFalse(res['ok'])
|
||||
|
||||
def test_reset_as_admin_clears_hash(self):
|
||||
self.authenticate('admin', 'admin')
|
||||
res = _rpc(self, '/fp/tablet/reset_pin_for', user_id=self.target.id)
|
||||
self.assertTrue(res['ok'])
|
||||
self.target.invalidate_recordset(['x_fc_tablet_pin_hash'])
|
||||
self.assertFalse(self.target.sudo().x_fc_tablet_pin_hash)
|
||||
|
||||
Reference in New Issue
Block a user