Merge pull request #64 from expsa/khazraji_un

make unit test
This commit is contained in:
mohammed-alkhazrji 2025-12-18 20:08:13 +03:00 committed by GitHub
commit 178be1d641
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 1072 additions and 0 deletions

View File

@ -35,4 +35,5 @@
'installable': True,
'application': True,
'auto_install': False,
'test_tags': ['standard', 'at_install'],
}

View File

@ -0,0 +1,7 @@
from . import test_presence_state_computation
from . import test_working_hours_constraints
from . import test_shift_time_calculation
from . import test_signin_signout_constraints
from . import test_flexible_hours_computation
from . import test_calendar_update_flow
from . import test_attendance_report_calculations

View File

@ -0,0 +1,165 @@
from odoo.tests.common import TransactionCase
from odoo.exceptions import ValidationError
from datetime import date
from unittest.mock import MagicMock
class TestHrAttendanceReport(TransactionCase):
def setUp(self):
super(TestHrAttendanceReport, self).setUp()
self.manager_user = self.env['hr.employee'].create({'name': 'Manager', 'finger_print': False})
self.dept = self.env['hr.department'].create({'name': 'IT', 'manager_id': self.manager_user.id})
self.calendar = self.env['resource.calendar'].create({
'name': 'Standard 8h',
'is_full_day': True,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0,
'full_min_sign_out': 16.0,
'full_max_sign_out': 17.0,
'working_hours': 8.0,
'working_days': 5,
'state': 'confirm',
})
self.employee = self.env['hr.employee'].create({
'name': 'Ahmed Ali',
'department_id': self.dept.id,
'finger_print': True,
'state': 'open',
'resource_calendar_id': self.calendar.id
})
self.contract = self.env['hr.contract'].create({
'name': 'Contract',
'employee_id': self.employee.id,
'total_allowance': 4000.0,
'state': 'program_directory',
'date_start': date(2023, 1, 1),
'resource_calendar_id': self.calendar.id,
})
def test_01_generate_report_deduction_logic(self):
common_vals = {
'employee_id': self.employee.id,
'calendar_id': self.calendar.id,
'attending_type': 'in_cal',
'sequence': 1,
'approve_lateness': True,
'approve_exit_out': True,
}
trans1 = self.env['hr.attendance.transaction'].create({
**common_vals, 'date': date(2023, 10, 1), 'plan_hours': 8.0, 'is_absent': True,
})
trans2 = self.env['hr.attendance.transaction'].create({
**common_vals, 'date': date(2023, 10, 2), 'plan_hours': 8.0, 'official_hours': 6.0, 'lateness': 2.0,
'is_absent': False,
})
type(trans1).public_holiday = property(lambda self: False)
type(trans1).normal_leave = property(lambda self: False)
# ------------------------------------------
# 5. توليد التقرير
report = self.env['hr.attendance.report'].create({
'name': 'Report',
'date_from': date(2023, 10, 1),
'date_to': date(2023, 10, 5),
'selected_employee_ids': [(4, self.employee.id)]
})
report.generate_report()
self.assertTrue(report.line_ids, "No record")
line = report.line_ids[0]
self.assertAlmostEqual(line.total_hours, 10.0, msg="fild hours")
self.assertAlmostEqual(line.total_deduction, 1000.0, msg="mins")
self.assertEqual(report.state, 'generated')
from odoo.tests.common import TransactionCase
from datetime import date
from unittest.mock import MagicMock
class TestFlexibleAttendanceReport(TransactionCase):
def setUp(self):
super(TestFlexibleAttendanceReport, self).setUp()
self.manager = self.env['hr.employee'].create({'name': 'Manager'})
self.dept = self.env['hr.department'].create({'name': 'IT', 'manager_id': self.manager.id})
self.calendar_flex = self.env['resource.calendar'].create({
'name': 'Flexible 40h',
'is_flexible': True,
'is_full_day': True,
'number_of_flexi_days': 5,
'working_hours': 8.0,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0,
'full_min_sign_out': 16.0,
'full_max_sign_out': 17.0,
'state': 'confirm',
})
self.employee = self.env['hr.employee'].create({
'name': 'Flex Employee',
'department_id': self.dept.id,
'resource_calendar_id': self.calendar_flex.id,
'finger_print': True,
'state': 'open'
})
def test_flexible_calculation_logic(self):
def create_mock_transaction(plan, office, is_absent=False, sign_in=True, sign_out=True):
mock = MagicMock()
mock.plan_hours = plan
mock.office_hours = office
mock.is_absent = is_absent
mock.public_holiday = False
mock.normal_leave = False
mock.personal_permission_id = False
mock.official_id = False
mock.approve_personal_permission = False
mock.total_permission_hours = 0.0
mock.total_leave_hours = 0.0
mock.total_mission_hours = 0.0
mock.sign_in = sign_in
mock.sign_out = sign_out
mock.early_exit = 0.0
mock.lateness = 0.0
mock.break_duration = 0.0
mock.approve_exit_out = False
mock.approve_lateness = False
return mock
all_trans_list = [
create_mock_transaction(8.0, 6.0),
create_mock_transaction(8.0, 0.0, is_absent=True),
create_mock_transaction(8.0, 4.0, sign_out=False)
]
def create_mock_recordset(items):
m = MagicMock()
m.__iter__.return_value = iter(items)
m.filtered.side_effect = lambda func: create_mock_recordset([i for i in items if func(i)])
m.mapped.side_effect = lambda name: [getattr(i, name) for i in items]
m.__len__.return_value = len(items)
return m
transactions = create_mock_recordset(all_trans_list)
report = self.env['hr.attendance.report'].new()
result = report.calcualte_flexible_transaction(transactions)
self.assertAlmostEqual(result['missed_hours'], 14.0, msg="Missed hours calculation failed")
self.assertEqual(result['actual_absent_days'], 1, msg="Absent days count failed")
self.assertAlmostEqual(result['missing_punch_hours'], 4.0, msg="Missing punch hours failed")

View File

@ -0,0 +1,190 @@
# -*- coding: utf-8 -*-
from odoo.tests import TransactionCase
class TestCalendarUpdateFlow(TransactionCase):
"""Test cases for calendar update and inheritance flow in resource.calendar"""
def setUp(self):
"""Setup test data - parent and child calendars"""
super(TestCalendarUpdateFlow, self).setUp()
# Create parent calendar
self.parent_calendar = self.env['resource.calendar'].create({
'name': 'Parent Calendar',
'is_full_day': True,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0,
'full_min_sign_out': 17.0,
'full_max_sign_out': 18.0,
'working_hours': 9.0,
'working_days': 5,
'state': 'confirm',
})
# Create test employee with state='open'
self.test_employee = self.env['hr.employee'].create({
'name': 'Test Employee',
'resource_calendar_id': self.parent_calendar.id,
'state': 'open', # ADD THIS LINE
})
def test_act_update_creates_child_calendar(self):
"""
Arrange: Confirmed parent calendar
Act: Call act_update
Assert: New draft calendar created with parent_calendar_id set
"""
# Arrange
initial_count = self.env['resource.calendar'].search_count([])
# Act
result = self.parent_calendar.act_update()
# Assert
self.assertEqual(result['res_model'], 'resource.calendar',
"Should return calendar action")
new_calendar = self.env['resource.calendar'].browse(result['res_id'])
self.assertEqual(new_calendar.parent_calendar_id, self.parent_calendar,
"New calendar should have parent_calendar_id set")
self.assertEqual(new_calendar.state, 'draft',
"New calendar should be in draft state")
self.assertEqual(self.parent_calendar.state, 'update',
"Parent calendar state should change to 'update'")
def test_act_confirm_without_parent_sets_confirmed_state(self):
# Arrange
new_calendar = self.env['resource.calendar'].create({
'name': 'Standalone Calendar',
'is_full_day': True,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0, # Add
'full_min_sign_out': 16.0,
'full_max_sign_out': 17.0, # Add
'working_hours': 8.0,
'state': 'draft',
})
# Act
new_calendar.act_confirm()
# Assert
self.assertEqual(new_calendar.state, 'confirm')
def test_act_confirm_with_changes_creates_new_version(self):
"""
Arrange: Child calendar with major changes and employee using parent
Act: Call act_confirm
Assert: Employee updated to new calendar, parent archived
"""
# Arrange
child_calendar = self.env['resource.calendar'].create({
'name': 'Updated Calendar',
'parent_calendar_id': self.parent_calendar.id,
'is_full_day': True,
'full_min_sign_in': 7.0, # Changed
'full_max_sign_in': 8.0,
'full_min_sign_out': 16.0, # Changed
'full_max_sign_out': 17.0,
'working_hours': 9.0,
'working_days': 5,
'state': 'draft',
})
# Verify employee is linked to parent before confirmation
self.assertEqual(self.test_employee.resource_calendar_id, self.parent_calendar)
self.assertIn(self.test_employee, self.parent_calendar.employee_ids,
"Employee should be in parent's employee_ids")
# Act
child_calendar.act_confirm()
# Refresh to get updated values from database
self.test_employee.invalidate_recordset(['resource_calendar_id'])
child_calendar.invalidate_recordset(['parent_calendar_id'])
self.parent_calendar.invalidate_recordset(['active', 'employee_ids'])
# Assert
self.assertEqual(child_calendar.state, 'confirm',
"Child calendar should be confirmed")
self.assertFalse(self.parent_calendar.active,
"Parent calendar should be archived")
self.assertEqual(self.test_employee.resource_calendar_id.id, child_calendar.id,
"Employee should be assigned to new calendar")
self.assertIn(self.test_employee, child_calendar.employee_ids,
"Employee should be in child's employee_ids")
def test_act_confirm_without_changes_updates_parent(self):
"""
Arrange: Child calendar with no significant changes
Act: Call act_confirm
Assert: Parent should be updated, child deleted
"""
# Arrange
child_calendar = self.env['resource.calendar'].create({
'name': 'Minor Update',
'parent_calendar_id': self.parent_calendar.id,
'is_full_day': self.parent_calendar.is_full_day,
'full_min_sign_in': self.parent_calendar.full_min_sign_in,
'full_max_sign_in': self.parent_calendar.full_max_sign_in,
'full_min_sign_out': self.parent_calendar.full_min_sign_out,
'full_max_sign_out': self.parent_calendar.full_max_sign_out,
'working_hours': self.parent_calendar.working_hours,
'working_days': self.parent_calendar.working_days,
'deduction_rule': False, # Minor change
'state': 'draft',
})
child_id = child_calendar.id
# Act
result = child_calendar.act_confirm()
# Assert
self.assertEqual(result['res_model'], 'resource.calendar',
"Should return to parent calendar")
self.assertEqual(result['res_id'], self.parent_calendar.id,
"Should redirect to parent calendar")
self.assertFalse(self.env['resource.calendar'].search([('id', '=', child_id)]),
"Child calendar should be deleted")
def test_action_back_to_confirm_resets_state(self):
"""
Arrange: Calendar in 'update' state
Act: Call action_back_to_confirm
Assert: State should return to 'confirm'
"""
# Arrange
self.parent_calendar.state = 'update'
# Act
self.parent_calendar.action_back_to_confirm()
# Assert
self.assertEqual(self.parent_calendar.state, 'confirm',
"Calendar state should return to 'confirm'")
def test_name_search_filters_by_confirmed_state(self):
"""
Arrange: Mix of draft and confirmed calendars
Act: Call name_search
Assert: Only confirmed calendars should be returned
"""
# Arrange
draft_calendar = self.env['resource.calendar'].create({
'name': 'Draft Calendar',
'state': 'draft',
})
confirmed_calendar = self.env['resource.calendar'].create({
'name': 'Confirmed Calendar',
'state': 'confirm',
})
# Act
results = self.env['resource.calendar'].name_search(name='Calendar')
result_ids = [r[0] for r in results]
# Assert
self.assertIn(confirmed_calendar.id, result_ids,
"Confirmed calendar should appear in search")
self.assertNotIn(draft_calendar.id, result_ids,
"Draft calendar should not appear in search")

View File

@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
from odoo.tests import TransactionCase
class TestFlexibleHoursComputation(TransactionCase):
"""Test cases for flexible hours calculation in resource.calendar"""
def setUp(self):
"""Setup test data - calendar for flexible hours"""
super(TestFlexibleHoursComputation, self).setUp()
self.calendar = self.env['resource.calendar'].create({
'name': 'Flexible Hours Test Calendar',
})
def test_flexible_hours_full_day_calculation(self):
"""
Arrange: Full day calendar with flexible days enabled
Act: Trigger compute
Assert: total_flexible_hours = working_hours * number_of_flexi_days
"""
# Arrange
self.calendar.write({
'is_flexible': True,
'is_full_day': True,
'working_hours': 8.0,
'number_of_flexi_days': 5,
'full_min_sign_in': 8.0, # Add
'full_max_sign_in': 9.0, # Add
'full_min_sign_out': 16.0, # Add
'full_max_sign_out': 17.0, # Add
})
# Act
self.calendar.compute_flexible_hours()
# Assert
self.assertEqual(self.calendar.total_flexible_hours, 40.0,
"Flexible hours for full day should be 8 * 5 = 40 hours")
def test_flexible_hours_disabled_returns_zero(self):
"""Calendar with is_flexible=False should return 0"""
# Arrange - Add ALL fields to pass constraints
self.calendar.write({
'is_flexible': False,
'is_full_day': True,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0, # Add
'full_min_sign_out': 16.0,
'full_max_sign_out': 17.0, # Add
'working_hours': 8.0, # Must match time difference
'number_of_flexi_days': 5,
})
# Act
self.calendar.compute_flexible_hours()
# Assert
self.assertEqual(self.calendar.total_flexible_hours, 0.0)
def test_flexible_hours_shift_mode_calculation(self):
"""Shift calendar flexible hours"""
# Arrange - Add ALL shift fields
self.calendar.write({
'is_flexible': True,
'is_full_day': False,
'shift_one_min_sign_in': 6.0,
'shift_one_max_sign_in': 7.0, # Add
'shift_one_min_sign_out': 14.0,
'shift_one_max_sign_out': 15.0, # Add
'shift_one_working_hours': 8.0,
'shift_two_min_sign_in': 14.0,
'shift_two_max_sign_in': 15.0, # Add
'shift_two_min_sign_out': 22.0,
'shift_two_max_sign_out': 23.0, # Add
'shift_two_working_hours': 8.0,
'number_of_flexi_days': 3,
})
# Act
self.calendar.compute_flexible_hours()
# Assert
expected = (8.0 + 8.0) * 3
self.assertEqual(self.calendar.total_flexible_hours, expected)
def test_flexible_hours_with_zero_days(self):
"""Zero flexible days should return 0"""
# Arrange
self.calendar.write({
'is_flexible': True,
'is_full_day': True,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0, # Add
'full_min_sign_out': 16.0,
'full_max_sign_out': 17.0, # Add
'working_hours': 8.0,
'number_of_flexi_days': 0,
})
# Act
self.calendar.compute_flexible_hours()
# Assert
self.assertEqual(self.calendar.total_flexible_hours, 0.0)
def test_flexible_hours_recompute_on_working_hours_change(self):
"""Flexible hours should update when working hours change"""
# Arrange - Add ALL fields
self.calendar.write({
'is_flexible': True,
'is_full_day': True,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0,
'full_min_sign_out': 16.0,
'full_max_sign_out': 17.0,
'working_hours': 8.0,
'number_of_flexi_days': 5,
})
self.calendar.compute_flexible_hours()
initial_hours = self.calendar.total_flexible_hours
# Act - Change times to match 10 hours
self.calendar.write({
'full_min_sign_out': 18.0, # Change to 10 hours
'full_max_sign_out': 19.0,
'working_hours': 10.0,
})
self.calendar.compute_flexible_hours()
# Assert
self.assertEqual(initial_hours, 40.0)
self.assertEqual(self.calendar.total_flexible_hours, 50.0)

View File

@ -0,0 +1,168 @@
# -*- coding: utf-8 -*-
from odoo.tests.common import TransactionCase
from datetime import date
from ast import literal_eval
class TestPresenceStateComputation(TransactionCase):
"""Test cases for hr_presence_state computation in hr.employee.base"""
def setUp(self):
"""Setup test data - موظف، مستخدم، وإعدادات النظام"""
super(TestPresenceStateComputation, self).setUp()
# إنشاء مستخدم للموظف
self.test_user = self.env['res.users'].create({
'name': 'Test Employee User',
'login': 'test_employee@test.com',
'email': 'test_employee@test.com',
})
# إنشاء موظف مرتبط بالمستخدم
self.test_employee = self.env['hr.employee'].create({
'name': 'Test Employee',
'user_id': self.test_user.id,
})
# إنشاء موظف بدون مستخدم (للاختبارات)
self.employee_without_user = self.env['hr.employee'].create({
'name': 'Employee Without User',
'user_id': False,
})
# إعداد تاريخ اليوم
self.today = date.today()
# الحصول على config parameter
self.config_param = self.env['ir.config_parameter'].sudo()
def test_presence_state_to_define_when_login_check_disabled(self):
# Arrange
self.config_param.set_param('hr.hr_presence_control_login', 'False')
# Act
self.test_employee._compute_presence_state()
# Assert
self.assertEqual(
self.test_employee.hr_presence_state,
'to_define',
"Presence state should be 'present' when attendance record exists for today"
)
def test_presence_state_present_when_attendance_exists(self):
# Arrange
self.config_param.set_param('hr.hr_presence_control_login', 'True')
self.env['attendance.attendance'].sudo().create({
'employee_id': self.test_employee.id,
'action_date': self.today,
})
# Act
self.test_employee._compute_presence_state()
# Assert
self.assertEqual(
self.test_employee.hr_presence_state,
'present',
"Presence state should be 'to_define' when no attendance record exists"
)
def test_presence_state_to_define_when_no_attendance_exists(self):
# Arrange
self.config_param.set_param('hr.hr_presence_control_login', 'True')
existing_attendance = self.env['attendance.attendance'].sudo().search([
('employee_id', '=', self.test_employee.id),
('action_date', '=', self.today)
])
existing_attendance.unlink()
# Act
self.test_employee._compute_presence_state()
# Assert
self.assertEqual(
self.test_employee.hr_presence_state,
'to_define',
"حالة الحضور يجب أن تكون 'to_define' عند عدم وجود سجل حضور"
)
def test_presence_state_multiple_employees(self):
# Arrange
self.config_param.set_param('hr.hr_presence_control_login', 'True')
user2 = self.env['res.users'].create({
'name': 'Test Employee 2',
'login': 'test_employee2@test.com',
})
employee2 = self.env['hr.employee'].create({
'name': 'Test Employee 2',
'user_id': user2.id,
})
self.env['attendance.attendance'].sudo().create({
'employee_id': employee2.id,
'action_date': self.today,
})
user3 = self.env['res.users'].create({
'name': 'Test Employee 3',
'login': 'test_employee3@test.com',
})
employee3 = self.env['hr.employee'].create({
'name': 'Test Employee 3',
'user_id': user3.id,
})
employees = self.test_employee | employee2 | employee3
# Act
employees._compute_presence_state()
# Assert
self.assertEqual(employee2.hr_presence_state, 'present',
"Second employee should have state 'present'")
self.assertEqual(employee3.hr_presence_state, 'to_define',
"Third employee should have state 'to_define'")
def test_presence_state_employee_without_user(self):
# Arrange
self.config_param.set_param('hr.hr_presence_control_login', 'True')
# Act
self.employee_without_user._compute_presence_state()
# Assert
self.assertEqual(
self.employee_without_user.hr_presence_state,
'to_define',
"the employee with not user must be'to_define'"
)
def test_presence_state_updates_correctly_on_multiple_calls(self):
# Arrange
self.config_param.set_param('hr.hr_presence_control_login', 'True')
self.test_employee._compute_presence_state()
state_before = self.test_employee.hr_presence_state
self.env['attendance.attendance'].sudo().create({
'employee_id': self.test_employee.id,
'action_date': self.today,
})
self.test_employee._compute_presence_state()
state_after = self.test_employee.hr_presence_state
# Assert
self.assertEqual(state_before, 'to_define',
"The one case must be 'to_define'")
self.assertEqual(state_after, 'present',
"the one case after present must be 'present'")

View File

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
from odoo.tests import TransactionCase
class TestShiftTimeCalculation(TransactionCase):
"""Test cases for shift working hours calculation in resource.calendar"""
def setUp(self):
"""Setup test data - calendar for shift calculations"""
super(TestShiftTimeCalculation, self).setUp()
self.calendar = self.env['resource.calendar'].create({
'name': 'Shift Test Calendar',
'is_full_day': False,
'noke': False,
})
def test_get_shift_working_hour_standard_8_hours(self):
"""
Arrange: Standard 8-hour shift (08:00 to 16:00)
Act: Calculate working hours
Assert: Should return 8.0 hours
"""
# Arrange & Act
result = self.calendar.get_shift_working_hour(8.0, 16.0)
# Assert
self.assertEqual(result, 8.0,
"Standard 8-hour shift should return 8.0 hours")
def test_get_shift_working_hour_with_decimal_minutes(self):
"""
Arrange: Shift with decimal time (08:30 to 17:15)
Act: Calculate working hours
Assert: Should return correct hours with decimal precision
"""
# Arrange & Act
result = self.calendar.get_shift_working_hour(8.5, 17.25)
# Assert
self.assertAlmostEqual(result, 8.75, places=2,
msg="Shift from 08:30 to 17:15 should return 8.75 hours")
def test_shift_one_working_hours_auto_compute_on_change(self):
"""
Arrange: Set shift one min sign in and out
Act: Trigger onchange method
Assert: shift_one_working_hours should be automatically calculated
"""
# Arrange
self.calendar.write({
'shift_one_min_sign_in': 7.0,
'shift_one_max_sign_in': 8.0, # Add
'shift_one_min_sign_out': 15.0,
'shift_one_max_sign_out': 16.0, # Add
})
# Act
self.calendar.work_hours()
# Assert
self.assertEqual(self.calendar.shift_one_working_hours, 8.0,
"Shift one working hours should auto-calculate to 8.0")
def test_shift_two_working_hours_auto_compute_on_change(self):
"""
Arrange: Set shift two min sign in and out
Act: Trigger onchange method
Assert: shift_two_working_hours should be automatically calculated
"""
# Arrange - Add ALL required fields
self.calendar.write({
'shift_two_min_sign_in': 15.0,
'shift_two_max_sign_in': 16.0, # Add this
'shift_two_min_sign_out': 23.0,
'shift_two_max_sign_out': 24.0, # Add this
})
# Act
self.calendar.work_hours()
# Assert
self.assertEqual(self.calendar.shift_two_working_hours, 8.0,
"Shift two working hours should auto-calculate to 8.0")
def test_both_shifts_working_hours_computed_together(self):
"""
Arrange: Set both shifts' times
Act: Trigger onchange
Assert: Both shift working hours should be calculated
"""
# Arrange - Add max fields
self.calendar.write({
'shift_one_min_sign_in': 6.0,
'shift_one_max_sign_in': 7.0, # Add
'shift_one_min_sign_out': 14.0,
'shift_one_max_sign_out': 15.0, # Add
'shift_two_min_sign_in': 14.0,
'shift_two_max_sign_in': 15.0, # Add
'shift_two_min_sign_out': 22.0,
'shift_two_max_sign_out': 23.0, # Add
})
# Act
self.calendar.work_hours()
# Assert
self.assertEqual(self.calendar.shift_one_working_hours, 8.0,
"Shift one should be 8 hours")
self.assertEqual(self.calendar.shift_two_working_hours, 8.0,
"Shift two should be 8 hours")
def test_overnight_shift_calculation(self):
"""
Arrange: Overnight shift (22:00 to 06:00)
Act: Calculate working hours
Assert: Should correctly handle overnight shifts
"""
# Arrange & Act
result = self.calendar.get_shift_working_hour(22.0, 6.0)
# Assert
# Note: This depends on implementation - may need adjustment
expected_hours = 8.0 if result > 0 else -16.0
self.assertGreater(result, 0,
"Overnight shift should return positive hours")

View File

@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
from odoo.tests import TransactionCase
from odoo.exceptions import ValidationError
class TestSignInSignOutConstraints(TransactionCase):
"""Test cases for sign in/out time constraints in resource.calendar"""
def setUp(self):
"""Setup test data - calendar for time validation"""
super(TestSignInSignOutConstraints, self).setUp()
self.calendar = self.env['resource.calendar'].create({
'name': 'Time Constraints Test Calendar',
'noke': False,
})
def test_full_day_max_sign_in_less_than_min_raises_error(self):
# Arrange & Act & Assert
with self.assertRaises(ValidationError) as context:
self.calendar.write({
'is_full_day': True,
'full_min_sign_in': 9.0,
'full_min_sign_out': 8.0,
'full_max_sign_in': 8.0, # Before min
'full_max_sign_out': 18.0,
})
self.assertIn('Max sign in should be greater than or equal min sign in',
str(context.exception))
def test_full_day_min_sign_out_before_min_sign_in_raises_error(self):
"""
Arrange: Set min sign out before min sign in for full day
Act: Try to write invalid data
Assert: ValidationError raised automatically by @api.constrains
"""
# Act & Assert - wrap the write() call
with self.assertRaises(ValidationError) as context:
self.calendar.write({
'is_full_day': True,
'full_min_sign_in': 10.0,
'full_max_sign_in': 11.0,
'full_min_sign_out': 9.0, # Invalid: before min sign in
'full_max_sign_out': 18.0,
'working_hours': 8.0, # Add this to avoid other constraints
})
# Optional: verify error message
self.assertIn('min sign out should be greater than or equal min sign in',
str(context.exception))
def test_shift_one_max_sign_in_less_than_min_raises_error(self):
"""
Arrange: Set invalid shift one times (max < min)
Act: Try to write
Assert: ValidationError raised
"""
with self.assertRaises(ValidationError) as context:
self.calendar.write({
'is_full_day': False,
'shift_one_min_sign_in': 10.0, # min = 10
'shift_one_max_sign_in': 9.0, # max = 9 (INVALID!)
'shift_one_min_sign_out': 16.0,
'shift_one_max_sign_out': 17.0,
'shift_one_working_hours': 6.0, # Add working hours
})
self.assertIn('Max sign in should be greater than or equal min sign in',
str(context.exception))
def test_shift_two_min_sign_out_before_min_sign_in_raises_error(self):
"""
Arrange: Set invalid shift two times
Act: Try to write
Assert: ValidationError raised
"""
with self.assertRaises(ValidationError) as context:
self.calendar.write({
'is_full_day': False,
'shift_two_min_sign_in': 16.0,
'shift_two_max_sign_in': 17.0,
'shift_two_min_sign_out': 15.0, # Invalid: before min sign in
'shift_two_max_sign_out': 24.0,
'shift_two_working_hours': 8.0, # Add working hours
})
self.assertIn('shift two', str(context.exception).lower())
def test_full_day_max_sign_out_less_than_min_sign_out_raises_error(self):
# This test is CORRECT - error is expected and happening!
with self.assertRaises(ValidationError) as context:
self.calendar.write({
'is_full_day': True,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0,
'full_min_sign_out': 17.0,
'full_max_sign_out': 16.0, # Before min - ERROR EXPECTED
})
self.assertIn('Max sign out should be greater', str(context.exception))
def test_full_day_valid_times_do_not_raise_error(self):
"""
Arrange: Set valid full day times
Act: Save calendar
Assert: No validation error should be raised
"""
# Arrange
self.calendar.write({
'is_full_day': True,
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0,
'full_min_sign_out': 17.0,
'full_max_sign_out': 18.0,
})
# Act & Assert
try:
self.calendar.full_time_constrains()
except ValidationError:
self.fail("Valid full day times should not raise ValidationError")
def test_noke_mode_bypasses_time_constraints(self):
"""
Arrange: Enable noke mode with invalid times
Act: Save calendar
Assert: No validation error (noke mode disables checks)
"""
# Arrange
self.calendar.write({
'is_full_day': True,
'noke': True,
'full_min_sign_in': 10.0,
'full_max_sign_in': 8.0, # Invalid, but noke=True
'full_min_sign_out': 17.0,
'full_max_sign_out': 16.0,
})
# Act & Assert
try:
self.calendar.full_time_constrains()
except ValidationError:
self.fail("Noke mode should bypass time constraints validation")

View File

@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
from odoo.tests import TransactionCase
from odoo.exceptions import ValidationError
class TestWorkingHoursConstraints(TransactionCase):
"""Test cases for working hours validation constraints in resource.calendar"""
def setUp(self):
super(TestWorkingHoursConstraints, self).setUp()
# Create base calendar
self.calendar = self.env['resource.calendar'].create({
'name': 'Test Calendar',
'is_full_day': True,
'noke': False,
})
def test_full_day_working_hours_mismatch_raises_error(self):
"""
Arrange: Set full day sign in/out times with mismatched working hours
Act: Try to save the calendar
Assert: ValidationError should be raised
"""
# Arrange & Act & Assert - Error happens during write()
with self.assertRaises(ValidationError) as context:
self.calendar.write({
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0,
'full_min_sign_out': 16.0,
'full_max_sign_out': 17.0,
'working_hours': 7.0, # Wrong value - should be 8.0
})
self.assertIn('8.0', str(context.exception),
"Error message should mention the correct working hours (8.0)")
def test_shift_one_working_hours_match_time_difference(self):
"""
Arrange: Set shift one times with matching working hours
Act: Save the calendar
Assert: No validation error should be raised
"""
# Arrange
self.calendar.write({
'is_full_day': False,
'shift_one_min_sign_in': 7.0,
'shift_one_max_sign_in': 8.0, # Add max
'shift_one_min_sign_out': 15.0,
'shift_one_max_sign_out': 16.0, # Add max
'shift_one_working_hours': 8.0,
'shift_two_min_sign_in': 15.0, # Add shift two to avoid errors
'shift_two_max_sign_in': 16.0,
'shift_two_min_sign_out': 23.0,
'shift_two_max_sign_out': 24.0,
'shift_two_working_hours': 8.0,
})
# Act & Assert
try:
self.calendar.result_greed_constrains()
except ValidationError:
self.fail("ValidationError should not be raised for valid shift one working hours")
def test_shift_one_working_hours_mismatch_raises_error(self):
"""
Arrange: Set shift one times with mismatched working hours
Act: Try to save the calendar
Assert: ValidationError should be raised
"""
# Arrange & Act & Assert
with self.assertRaises(ValidationError) as context:
self.calendar.write({
'is_full_day': False,
'shift_one_min_sign_in': 7.0,
'shift_one_max_sign_in': 8.0,
'shift_one_min_sign_out': 15.0,
'shift_one_max_sign_out': 16.0,
'shift_one_working_hours': 9.0, # Wrong - should be 8.0
'shift_two_min_sign_in': 15.0,
'shift_two_max_sign_in': 16.0,
'shift_two_min_sign_out': 23.0,
'shift_two_max_sign_out': 24.0,
'shift_two_working_hours': 8.0,
})
self.assertIn('8.0', str(context.exception),
"Error message should mention the correct shift one working hours")
def test_shift_two_working_hours_exceeds_maximum_raises_error(self):
"""
Arrange: Set shift two working hours greater than time difference
Act: Try to save the calendar
Assert: ValidationError should be raised
"""
# Arrange & Act & Assert
with self.assertRaises(ValidationError) as context:
self.calendar.write({
'is_full_day': False,
'shift_one_min_sign_in': 7.0,
'shift_one_max_sign_in': 8.0,
'shift_one_min_sign_out': 15.0,
'shift_one_max_sign_out': 16.0,
'shift_one_working_hours': 8.0,
'shift_two_min_sign_in': 15.0,
'shift_two_max_sign_in': 16.0,
'shift_two_min_sign_out': 23.0,
'shift_two_max_sign_out': 24.0,
'shift_two_working_hours': 10.0, # Greater than 8 hours
})
self.assertIn('8.0', str(context.exception),
"Error message should mention the maximum allowed shift two working hours")
def test_full_day_working_hours_match_time_difference(self):
"""
Arrange: Set full day sign in/out times with matching working hours
Act: Save the calendar
Assert: No validation error should be raised
"""
# Arrange - Add ALL required fields to pass other constraints
self.calendar.write({
'full_min_sign_in': 8.0,
'full_max_sign_in': 9.0, # Add this
'full_min_sign_out': 16.0,
'full_max_sign_out': 17.0, # Add this
'working_hours': 8.0,
})
# Act & Assert
try:
self.calendar.result_greed_constrains()
except ValidationError:
self.fail("ValidationError should not be raised when working hours match time difference")