283 lines
13 KiB
Python
283 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo.addons.hr.tests.common import TestHrCommon
|
|
from odoo.addons.iap_extract.tests.test_extract_mixin import TestExtractMixin
|
|
from odoo.tests import tagged, Form
|
|
|
|
from ..models.hr_applicant import OCR_VERSION
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestRecruitmentExtractProcess(TestHrCommon, TestExtractMixin):
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.applicant = cls.env['hr.applicant'].create({'partner_name': 'John Doe'})
|
|
cls.attachment = cls.env['ir.attachment'].create({
|
|
'name': "an attachment",
|
|
'raw': b'My attachment',
|
|
'mimetype': 'plain/text'
|
|
})
|
|
|
|
def get_result_success_response(self):
|
|
return {
|
|
'status': 'success',
|
|
'results': [{
|
|
'name': {'selected_value': {'content': 'Johnny Doe'}, 'candidates': []},
|
|
'email': {'selected_value': {'content': 'john@doe.com'}, 'candidates': []},
|
|
'phone': {'selected_value': {'content': '+32488888888'}, 'candidates': []},
|
|
'full_text_annotation': '',
|
|
}],
|
|
}
|
|
|
|
def test_auto_send_for_digitization(self):
|
|
# test the `auto_send` mode for digitization does send the attachment upon upload
|
|
self.env.company.recruitment_extract_show_ocr_option_selection = 'auto_send'
|
|
expected_parse_params = {
|
|
'version': OCR_VERSION,
|
|
'account_token': 'test_token',
|
|
'dbuuid': self.env['ir.config_parameter'].sudo().get_param('database.uuid'),
|
|
'documents': [self.attachment.datas.decode('utf-8')],
|
|
'user_infos': {
|
|
'user_email': self.env.ref('base.user_root').email,
|
|
'user_lang': self.env.ref('base.user_root').lang,
|
|
},
|
|
'webhook_url': f'{self.applicant.get_base_url()}/hr_recruitment_extract/request_done',
|
|
}
|
|
|
|
with self._mock_iap_extract(
|
|
extract_response=self.parse_success_response(),
|
|
assert_params=expected_parse_params,
|
|
):
|
|
self.applicant.message_post(attachment_ids=[self.attachment.id])
|
|
|
|
self.assertEqual(self.applicant.extract_state, 'waiting_extraction')
|
|
self.assertEqual(self.applicant.extract_document_uuid, 'some_token')
|
|
self.assertTrue(self.applicant.extract_state_processed)
|
|
self.assertTrue(self.applicant.partner_name)
|
|
self.assertFalse(self.applicant.email_from)
|
|
self.assertFalse(self.applicant.partner_phone)
|
|
|
|
extract_response = self.get_result_success_response()
|
|
expected_get_results_params = {
|
|
'version': OCR_VERSION,
|
|
'document_token': 'some_token',
|
|
'account_token': self.applicant._get_iap_account().sudo().account_token,
|
|
}
|
|
with self._mock_iap_extract(
|
|
extract_response=extract_response,
|
|
assert_params=expected_get_results_params,
|
|
):
|
|
self.applicant.check_all_status()
|
|
|
|
self.assertEqual(self.applicant.partner_name, extract_response['results'][0]['name']['selected_value']['content'])
|
|
self.assertEqual(self.applicant.email_from, extract_response['results'][0]['email']['selected_value']['content'])
|
|
self.assertEqual(self.applicant.partner_phone, extract_response['results'][0]['phone']['selected_value']['content'])
|
|
|
|
def test_manual_send_for_digitization(self):
|
|
# test the `manual_send` mode for digitization
|
|
self.env.company.recruitment_extract_show_ocr_option_selection = 'manual_send'
|
|
|
|
self.assertEqual(self.applicant.extract_state, 'no_extract_requested')
|
|
self.assertFalse(self.applicant.extract_can_show_send_button)
|
|
|
|
with self._mock_iap_extract(extract_response=self.parse_success_response()):
|
|
self.applicant.message_post(attachment_ids=[self.attachment.id])
|
|
|
|
self.assertEqual(self.applicant.extract_state, 'no_extract_requested')
|
|
self.assertTrue(self.applicant.extract_can_show_send_button)
|
|
|
|
with self._mock_iap_extract(extract_response=self.parse_success_response()):
|
|
self.applicant.action_send_batch_for_digitization()
|
|
|
|
# upon success, no button shall be provided
|
|
self.assertFalse(self.applicant.extract_can_show_send_button)
|
|
|
|
extract_response = self.get_result_success_response()
|
|
with self._mock_iap_extract(extract_response=extract_response):
|
|
self.applicant.check_all_status()
|
|
|
|
self.assertEqual(self.applicant.partner_name, extract_response['results'][0]['name']['selected_value']['content'])
|
|
self.assertEqual(self.applicant.email_from, extract_response['results'][0]['email']['selected_value']['content'])
|
|
self.assertEqual(self.applicant.partner_phone, extract_response['results'][0]['phone']['selected_value']['content'])
|
|
|
|
def test_no_send_for_digitization(self):
|
|
# test that the `no_send` mode for digitization prevents the users from sending
|
|
self.env.company.recruitment_extract_show_ocr_option_selection = 'no_send'
|
|
|
|
with self._mock_iap_extract(extract_response=self.parse_success_response()):
|
|
self.applicant.message_post(attachment_ids=[self.attachment.id])
|
|
|
|
self.assertEqual(self.applicant.extract_state, 'no_extract_requested')
|
|
self.assertFalse(self.applicant.extract_can_show_send_button)
|
|
|
|
def test_show_resend_button_when_not_enough_credits(self):
|
|
# test that upon not enough credit error, the retry button is provided
|
|
self.env.company.recruitment_extract_show_ocr_option_selection = 'auto_send'
|
|
|
|
with self._mock_iap_extract(extract_response=self.parse_credit_error_response()):
|
|
self.applicant.message_post(attachment_ids=[self.attachment.id])
|
|
|
|
self.assertFalse(self.applicant.extract_can_show_send_button)
|
|
|
|
def test_status_not_ready(self):
|
|
# test the 'processing' ocr status effects
|
|
self.env.company.recruitment_extract_show_ocr_option_selection = 'auto_send'
|
|
|
|
with self._mock_iap_extract(extract_response=self.parse_processing_response()):
|
|
self.applicant._check_ocr_status()
|
|
|
|
self.assertEqual(self.applicant.extract_state, 'extract_not_ready')
|
|
self.assertFalse(self.applicant.extract_can_show_send_button)
|
|
|
|
def test_applicant_validation(self):
|
|
# test that when the applicant is hired, the validation is sent to the server
|
|
self.env.company.recruitment_extract_show_ocr_option_selection = 'auto_send'
|
|
|
|
with self._mock_iap_extract(extract_response=self.parse_success_response()):
|
|
self.applicant.message_post(attachment_ids=[self.attachment.id])
|
|
|
|
with self._mock_iap_extract(extract_response=self.get_result_success_response()):
|
|
self.applicant._check_ocr_status()
|
|
|
|
self.assertEqual(self.applicant.extract_state, 'waiting_validation')
|
|
|
|
expected_validation_params = {
|
|
'version': OCR_VERSION,
|
|
'values': {
|
|
'email': {'content': self.applicant.email_from},
|
|
'phone': {'content': self.applicant.partner_phone},
|
|
'name': {'content': self.applicant.partner_name},
|
|
},
|
|
'document_token': 'some_token',
|
|
'account_token': self.applicant._get_iap_account().sudo().account_token,
|
|
}
|
|
|
|
hired_stages = self.env['hr.recruitment.stage'].search([('hired_stage', '=', True)])
|
|
|
|
with self._mock_iap_extract(
|
|
extract_response=self.validate_success_response(),
|
|
assert_params=expected_validation_params,
|
|
):
|
|
self.applicant.write({'stage_id': hired_stages[0].id})
|
|
|
|
self.assertEqual(self.applicant.extract_state, 'done')
|
|
|
|
def test_ocr_skipped_when_resume_email_differs_from_sender(self):
|
|
# when the extracted CV email does not match the sender, OCR-derived contact fields must not be written to the applicant
|
|
self.applicant.email_from = 'forwarder@company.com'
|
|
self.applicant.partner_name = 'Forwarder Name'
|
|
|
|
# The CV contains a different candidate email
|
|
extract_response = self.get_result_success_response()
|
|
|
|
with self._mock_iap_extract(extract_response=extract_response):
|
|
self.applicant._check_ocr_status()
|
|
|
|
# None of the OCR values should have been applied
|
|
self.assertEqual(self.applicant.email_from, 'forwarder@company.com')
|
|
self.assertEqual(self.applicant.partner_name, 'Forwarder Name')
|
|
self.assertFalse(self.applicant.partner_phone)
|
|
|
|
def test_ocr_applied_when_resume_email_matches_sender(self):
|
|
# when the extracted CV email match the sender, OCR-derived contact fields should be written to the applicant
|
|
self.applicant.email_from = 'john@doe.com'
|
|
self.applicant.partner_name = 'John Doe'
|
|
|
|
# The CV email matches email_from
|
|
extract_response = self.get_result_success_response()
|
|
|
|
with self._mock_iap_extract(extract_response=extract_response):
|
|
self.applicant._check_ocr_status()
|
|
|
|
# The OCR values should have been applied
|
|
self.assertEqual(self.applicant.partner_name, 'Johnny Doe')
|
|
self.assertEqual(self.applicant.email_from, 'john@doe.com')
|
|
self.assertEqual(self.applicant.partner_phone, '+32488888888')
|
|
|
|
def test_ocr_skipped_for_contact_linked_to_company(self):
|
|
# test OCR does not update a contact that belongs to a company
|
|
company_partner = self.env['res.partner'].create({'name': 'Acme Corp', 'is_company': True})
|
|
contact = self.env['res.partner'].create({
|
|
'name': 'Employee',
|
|
'email': 'employee@acme.com',
|
|
'phone': '+1111111111',
|
|
'parent_id': company_partner.id,
|
|
})
|
|
self.applicant.write({
|
|
'partner_id': contact.id,
|
|
'email_from': 'employee@acme.com',
|
|
'partner_name': 'Employee',
|
|
})
|
|
|
|
# email matches sender
|
|
extract_response = self.get_result_success_response()
|
|
extract_response['results'][0]['email']['selected_value']['content'] = 'employee@acme.com'
|
|
|
|
with self._mock_iap_extract(extract_response=extract_response):
|
|
self.applicant._check_ocr_status()
|
|
|
|
# The contact record itself must be untouched
|
|
self.assertEqual(contact.name, 'Employee')
|
|
self.assertEqual(contact.phone, '+1111111111')
|
|
|
|
def test_ocr_skipped_for_contact_linked_to_user(self):
|
|
# test OCR does not update a contact tied to a user account
|
|
user = self.env['res.users'].create({
|
|
'name': 'Test User',
|
|
'login': 'test_user_ocr@example.com',
|
|
'email': 'test_user_ocr@example.com',
|
|
})
|
|
user.partner_id.phone = '+1234567890'
|
|
user_partner = user.partner_id
|
|
|
|
self.applicant.write({
|
|
'partner_id': user_partner.id,
|
|
'email_from': user_partner.email,
|
|
'partner_name': user_partner.name,
|
|
})
|
|
|
|
# email matches sender
|
|
extract_response = self.get_result_success_response()
|
|
extract_response['results'][0]['email']['selected_value']['content'] = user_partner.email
|
|
|
|
with self._mock_iap_extract(extract_response=extract_response):
|
|
self.applicant._check_ocr_status()
|
|
|
|
# The user's partner record must remain unchanged
|
|
self.assertEqual(user_partner.name, 'Test User')
|
|
self.assertEqual(user_partner.phone, '+1234567890')
|
|
|
|
def test_skill_search_on_ocr_results(self):
|
|
if not self.env['ir.module.module']._get('hr_recruitment_skills').state == 'installed':
|
|
self.skipTest("If the 'hr_recruitment_skills' module isn't installed we don't extract skills!")
|
|
|
|
extract_response = self.get_result_success_response()
|
|
extract_response['results'][0]['full_text_annotation'] = 'UIUX designer and graphist'
|
|
|
|
with Form(self.env['hr.skill.type']) as skill_type:
|
|
skill_type.name = 'Technical'
|
|
|
|
with skill_type.skill_ids.new() as skill:
|
|
skill.name = f'UIUX'
|
|
with skill_type.skill_ids.new() as skill:
|
|
skill.name = f'graphist'
|
|
|
|
for x in range(10):
|
|
with skill_type.skill_level_ids.new() as level:
|
|
level.name = f"level {x}"
|
|
level.level_progress = x * 10
|
|
level.default_level = x % 2
|
|
|
|
skill_type = skill_type.save()
|
|
with self._mock_iap_extract(extract_response=extract_response):
|
|
self.applicant._check_ocr_status()
|
|
|
|
created_applicant_skills = self.env['hr.applicant.skill'].search(
|
|
[('applicant_id', '=', self.applicant.id)],
|
|
).mapped('skill_id.id')
|
|
|
|
self.assertCountEqual(created_applicant_skills, skill_type.skill_ids.ids)
|