# -*- coding: utf-8 -*- import json from odoo import fields from odoo.tests import tagged, HttpCase @tagged('-at_install', 'post_install', 'fusion_clock') class TestFusionClockDashboard(HttpCase): @classmethod def setUpClass(cls): super().setUpClass() Users = cls.env['res.users'] Emp = cls.env['hr.employee'] g_user = cls.env.ref('fusion_clock.group_fusion_clock_user') g_lead = cls.env.ref('fusion_clock.group_fusion_clock_team_lead') g_mgr = cls.env.ref('fusion_clock.group_fusion_clock_manager') g_internal = cls.env.ref('base.group_user') def mk_user(login, group): return Users.create({ 'name': login, 'login': login, 'password': login, 'group_ids': [(6, 0, (g_internal + group).ids)], }) cls.mgr_user = mk_user('fc_mgr', g_mgr) cls.mgr_emp = Emp.create({ 'name': 'Manager Mae', 'user_id': cls.mgr_user.id, 'x_fclk_enable_clock': True, }) cls.lead_user = mk_user('fc_lead', g_lead) cls.lead_emp = Emp.create({ 'name': 'Lead Leo', 'user_id': cls.lead_user.id, 'x_fclk_enable_clock': True, }) cls.rep1_user = mk_user('fc_rep1', g_user) cls.rep1_emp = Emp.create({ 'name': 'Report Rita', 'user_id': cls.rep1_user.id, 'parent_id': cls.lead_emp.id, 'x_fclk_enable_clock': True, }) cls.rep2_user = mk_user('fc_rep2', g_user) cls.rep2_emp = Emp.create({ 'name': 'Report Raj', 'user_id': cls.rep2_user.id, 'parent_id': cls.lead_emp.id, 'x_fclk_enable_clock': True, }) cls.other_user = mk_user('fc_other', g_user) cls.other_emp = Emp.create({ 'name': 'Outsider Olga', 'user_id': cls.other_user.id, 'parent_id': cls.mgr_emp.id, 'x_fclk_enable_clock': True, }) # Clock in both of the lead's reports so present_count / roster are # exercised (and so the leak test proves a clocked-in *sibling* never # appears in another employee's payload). Att = cls.env['hr.attendance'] now = fields.Datetime.now() Att.create({'employee_id': cls.rep1_emp.id, 'check_in': now}) Att.create({'employee_id': cls.rep2_emp.id, 'check_in': now}) def _call(self): resp = self.url_open( '/fusion_clock/dashboard_data', data=json.dumps({'jsonrpc': '2.0', 'method': 'call', 'params': {}}), headers={'Content-Type': 'application/json'}, ) self.assertEqual(resp.status_code, 200) result = resp.json()['result'] self.assertFalse(isinstance(result, dict) and result.get('error'), msg=result) return result # ---- Task 1: personal block ---- def test_employee_sees_only_personal(self): self.authenticate('fc_rep1', 'fc_rep1') data = self._call() self.assertEqual(data['role'], 'employee') self.assertIsNone(data['team']) self.assertEqual(data['personal']['employee_name'], 'Report Rita') for key in ('today_hours', 'week_hours', 'overtime_week', 'ontime_streak', 'shift'): self.assertIn(key, data['personal']) # ---- Task 2: team/org scoping ---- def test_team_lead_scoped_to_direct_reports(self): self.authenticate('fc_lead', 'fc_lead') data = self._call() self.assertEqual(data['role'], 'team_lead') self.assertIsNotNone(data['team']) self.assertEqual(data['team']['scope'], 'team') self.assertEqual(data['team']['total_employees'], 2) for key in ('present_count', 'absent_count', 'on_leave_count', 'late_count', 'pending_reasons', 'pending_approvals', 'clocked_in'): self.assertIn(key, data['team']) # both reports are clocked in → present and on the roster self.assertEqual(data['team']['present_count'], 2) self.assertEqual(data['team']['absent_count'], 0) roster_names = {row['employee'] for row in data['team']['clocked_in']} self.assertEqual(roster_names, {'Report Rita', 'Report Raj'}) def test_manager_sees_org_wide(self): self.authenticate('fc_mgr', 'fc_mgr') data = self._call() self.assertEqual(data['role'], 'manager') self.assertIsNotNone(data['team']) self.assertEqual(data['team']['scope'], 'org') self.assertGreaterEqual(data['team']['total_employees'], 5) # the two clocked-in reports are counted org-wide too self.assertGreaterEqual(data['team']['present_count'], 2) # ---- Task 3: no cross-employee leak + no-employee path ---- def test_no_cross_employee_leak_for_employee(self): self.authenticate('fc_rep1', 'fc_rep1') data = self._call() blob = json.dumps(data) self.assertIn('Report Rita', blob) # own name present self.assertNotIn('Report Raj', blob) # sibling absent self.assertNotIn('Outsider Olga', blob) # unrelated absent self.assertNotIn('Lead Leo', blob) # lead absent def test_team_lead_roster_excludes_outsiders(self): self.authenticate('fc_lead', 'fc_lead') data = self._call() blob = json.dumps(data['team']) self.assertNotIn('Outsider Olga', blob) self.assertNotIn('Manager Mae', blob) def test_user_without_employee_gets_error(self): g_user = self.env.ref('fusion_clock.group_fusion_clock_user') g_internal = self.env.ref('base.group_user') self.env['res.users'].create({ 'name': 'No Emp', 'login': 'fc_noemp', 'password': 'fc_noemp', 'group_ids': [(6, 0, (g_internal + g_user).ids)], }) self.authenticate('fc_noemp', 'fc_noemp') resp = self.url_open( '/fusion_clock/dashboard_data', data=json.dumps({'jsonrpc': '2.0', 'method': 'call', 'params': {}}), headers={'Content-Type': 'application/json'}, ) self.assertEqual(resp.status_code, 200) self.assertIn('error', resp.json()['result'])