closes odoo/odoo#87527 Related: odoo/upgrade#3497 Related: odoo/enterprise#26939 Signed-off-by: Raphael Collet <rco@odoo.com>
690 lines
34 KiB
Python
690 lines
34 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import datetime, timedelta
|
|
from freezegun import freeze_time
|
|
|
|
from odoo import exceptions
|
|
from odoo.addons.knowledge.tests.common import KnowledgeCommonWData
|
|
from odoo.tests.common import tagged, users
|
|
from odoo.tools import mute_logger
|
|
|
|
|
|
@tagged('knowledge_internals', 'knowledge_management')
|
|
class TestKnowledgeArticleBusiness(KnowledgeCommonWData):
|
|
""" Test business API and main tools or helpers methods. """
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule')
|
|
@users('employee')
|
|
def test_article_archive(self):
|
|
""" Testing archive that should also archive children. """
|
|
article_shared = self.article_shared.with_env(self.env)
|
|
article_workspace = self.article_workspace.with_env(self.env)
|
|
wkspace_children = self.workspace_children.with_env(self.env)
|
|
# to test descendants computation, add some sub children
|
|
wkspace_grandchildren = self.env['knowledge.article'].create([
|
|
{'name': 'Grand Children of workspace',
|
|
'parent_id': wkspace_children[0].id,
|
|
},
|
|
{'name': 'Grand Children of workspace',
|
|
'parent_id': wkspace_children[0].id,
|
|
},
|
|
{'name': 'Grand Children of workspace',
|
|
'parent_id': wkspace_children[1].id,
|
|
}
|
|
])
|
|
wkspace_grandgrandchildren = self.env['knowledge.article'].create([
|
|
{'name': 'Grand Grand Children of workspace',
|
|
'parent_id': wkspace_grandchildren[1].id,
|
|
},
|
|
{'name': 'Grand Children of workspace',
|
|
'parent_id': wkspace_grandchildren[2].id,
|
|
},
|
|
])
|
|
|
|
# no read access -> cracboum
|
|
with self.assertRaises(exceptions.AccessError,
|
|
msg='Employee can read thus not archive'):
|
|
article_shared.action_archive()
|
|
|
|
# set the root + children inactive
|
|
article_workspace.action_archive()
|
|
self.assertFalse(article_workspace.active)
|
|
for article in wkspace_children + wkspace_grandchildren + wkspace_grandgrandchildren:
|
|
self.assertFalse(article.active, 'Archive: should propagate to children')
|
|
self.assertEqual(article.root_article_id, article_workspace,
|
|
'Archive: does not change hierarchy when archiving without breaking hierarchy')
|
|
|
|
# reset as active
|
|
(article_workspace + wkspace_children + wkspace_grandchildren + wkspace_grandgrandchildren).toggle_active()
|
|
for article in article_workspace + wkspace_children + wkspace_grandchildren + wkspace_grandgrandchildren:
|
|
self.assertTrue(article.active)
|
|
|
|
# set only part of tree inactive
|
|
wkspace_children.action_archive()
|
|
self.assertTrue(article_workspace.active)
|
|
for article in wkspace_children + wkspace_grandchildren + wkspace_grandgrandchildren:
|
|
self.assertFalse(article.active, 'Archive: should propagate to children')
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule')
|
|
@users('employee')
|
|
def test_article_archive_mixed_rights(self):
|
|
""" Test archive in case of mixed rights """
|
|
# give write access to shared section, but have children in read or none
|
|
self.article_shared.article_member_ids.sudo().filtered(
|
|
lambda article: article.partner_id == self.partner_employee
|
|
).write({'permission': 'write'})
|
|
# one additional read child and one additional none child
|
|
self.shared_children += self.env['knowledge.article'].sudo().create([
|
|
{'article_member_ids': [
|
|
(0, 0, {'partner_id': self.partner_admin.id,
|
|
'permission': 'write',
|
|
}),
|
|
(0, 0, {'partner_id': self.partner_employee.id,
|
|
'permission': 'read',
|
|
}),
|
|
],
|
|
'internal_permission': False,
|
|
'name': 'Shared Child2',
|
|
'parent_id': self.article_shared.id,
|
|
},
|
|
{'article_member_ids': [
|
|
(0, 0, {'partner_id': self.partner_admin.id,
|
|
'permission': 'write',
|
|
}),
|
|
(0, 0, {'partner_id': self.partner_employee.id,
|
|
'permission': 'none',
|
|
}),
|
|
],
|
|
'internal_permission': False,
|
|
'name': 'Shared Child3',
|
|
'parent_id': self.article_shared.id,
|
|
},
|
|
])
|
|
|
|
# prepare comparison data as sudo
|
|
writable_child_su = self.article_shared.child_ids.filtered(lambda article: article.name in ['Shared Child1'])
|
|
readonly_child_su = self.article_shared.child_ids.filtered(lambda article: article.name in ['Shared Child2'])
|
|
hidden_child_su = self.article_shared.child_ids.filtered(lambda article: article.name in ['Shared Child3'])
|
|
|
|
# perform archive as user
|
|
article_shared = self.article_shared.with_env(self.env)
|
|
article_shared.invalidate_model(['child_ids']) # context dependent
|
|
shared_children = article_shared.child_ids
|
|
writable_child, readonly_child = writable_child_su.with_env(self.env), readonly_child_su.with_env(self.env)
|
|
self.assertEqual(len(shared_children), 2)
|
|
self.assertEqual(shared_children, writable_child + readonly_child, 'Should see only two first children')
|
|
|
|
article_shared.action_archive()
|
|
# check writable articles have been archived, readonly or hidden not
|
|
self.assertFalse(article_shared.active)
|
|
self.assertFalse(writable_child.active)
|
|
self.assertTrue(readonly_child.active)
|
|
self.assertTrue(hidden_child_su.active)
|
|
# check hierarchy
|
|
self.assertEqual(writable_child.parent_id, article_shared,
|
|
'Archive: archived articles hierarchy does not change')
|
|
self.assertFalse(readonly_child.parent_id, 'Archive: article should be extracted in archive process as non writable')
|
|
self.assertEqual(readonly_child.root_article_id, readonly_child)
|
|
self.assertFalse(hidden_child_su.parent_id, 'Archive: article should be extracted in archive process as non writable')
|
|
self.assertEqual(hidden_child_su.root_article_id, hidden_child_su)
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule')
|
|
@users('employee')
|
|
def test_article_create(self):
|
|
""" Testing the helper to create articles with right values. """
|
|
Article = self.env['knowledge.article']
|
|
article = self.article_workspace.with_env(self.env)
|
|
readonly_article = self.article_shared.with_env(self.env)
|
|
|
|
_title = 'Fthagn'
|
|
new = Article.article_create(title=_title, parent_id=False, is_private=False)
|
|
self.assertMembers(new, 'write', {})
|
|
self.assertFalse(new.article_member_ids)
|
|
self.assertEqual(new.body, f'<h1>{_title}</h1>')
|
|
self.assertEqual(new.category, 'workspace')
|
|
self.assertEqual(new.name, _title)
|
|
self.assertFalse(new.parent_id)
|
|
self.assertEqual(new.sequence, self._base_sequence + 1)
|
|
|
|
_title = 'Fthagn, but private'
|
|
private = Article.article_create(title=_title, parent_id=False, is_private=True)
|
|
self.assertMembers(private, 'none', {self.env.user.partner_id: 'write'})
|
|
self.assertEqual(private.category, 'private')
|
|
self.assertFalse(private.parent_id)
|
|
self.assertEqual(private.sequence, self._base_sequence + 2)
|
|
|
|
_title = 'Fthagn, but with parent (workspace)'
|
|
child = Article.article_create(title=_title, parent_id=article.id, is_private=False)
|
|
self.assertMembers(child, False, {})
|
|
self.assertEqual(child.category, 'workspace')
|
|
self.assertEqual(child.parent_id, article)
|
|
self.assertEqual(child.sequence, 2, 'Already two children existing')
|
|
|
|
_title = 'Fthagn, but with parent (private): forces private'
|
|
child_private = Article.article_create(title=_title, parent_id=private.id, is_private=False)
|
|
self.assertMembers(child_private, False, {})
|
|
self.assertFalse(child_private.article_member_ids)
|
|
self.assertEqual(child_private.category, 'private')
|
|
self.assertEqual(child_private.parent_id, private)
|
|
self.assertEqual(child_private.sequence, 0)
|
|
|
|
_title = 'Fthagn, but private under non private: cracboum'
|
|
with self.assertRaises(exceptions.ValidationError):
|
|
Article.article_create(title=_title, parent_id=article.id, is_private=True)
|
|
|
|
_title = 'Fthagn, but with parent read only: cracboum'
|
|
with self.assertRaises(exceptions.AccessError):
|
|
Article.article_create(title=_title, parent_id=readonly_article.id, is_private=False)
|
|
|
|
private_nonmember = Article.sudo().create({
|
|
'article_member_ids': [
|
|
(0, 0, {'partner_id': self.partner_employee2.id,
|
|
'permission': 'write',}),
|
|
(0, 0, {'partner_id': self.partner_employee.id,
|
|
'permission': 'none',}),
|
|
],
|
|
'internal_permission': 'none',
|
|
'name': 'AdminPrivate',
|
|
})
|
|
_title = 'Fthagn, but with parent private none: cracboum'
|
|
with self.assertRaises(exceptions.AccessError):
|
|
Article.article_create(title=_title, parent_id=private_nonmember.id, is_private=False)
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests')
|
|
@users('employee')
|
|
def test_article_invite_members(self):
|
|
""" Test inviting members API. Create a hierarchy of 3 shared articles
|
|
and check privilege is not granted below invited articles. """
|
|
direct_child_read, direct_child_write = self.env['knowledge.article'].sudo().create([
|
|
{'article_member_ids': [
|
|
(0, 0, {'partner_id': self.partner_employee_manager.id,
|
|
'permission': 'write',
|
|
}),
|
|
(0, 0, {'partner_id': self.partner_employee.id,
|
|
'permission': 'read',
|
|
}),
|
|
],
|
|
'internal_permission': False,
|
|
'name': 'Shared Readonly Child (should not propagate)',
|
|
'parent_id': self.shared_children.id,
|
|
},
|
|
{'article_member_ids': [
|
|
(0, 0, {'partner_id': self.partner_employee.id,
|
|
'permission': 'write',
|
|
}),
|
|
],
|
|
'internal_permission': False,
|
|
'name': 'Shared Writable Child (propagate is ok)',
|
|
'parent_id': self.shared_children.id,
|
|
}
|
|
]).with_env(self.env)
|
|
grand_child = self.env['knowledge.article'].sudo().create({
|
|
'article_member_ids': [
|
|
(0, 0, {'partner_id': self.partner_employee.id,
|
|
'permission': 'write',
|
|
}),
|
|
],
|
|
'internal_permission': 'read',
|
|
'name': 'Shared GrandChild (blocked by readonly parent, should not propagate)',
|
|
'parent_id': direct_child_read.id,
|
|
}).with_env(self.env)
|
|
|
|
shared_article = self.shared_children.with_env(self.env)
|
|
self.assertMembers(shared_article, False,
|
|
{self.partner_employee: 'write'})
|
|
self.assertMembers(direct_child_read, False,
|
|
{self.partner_employee_manager: 'write',
|
|
self.partner_employee: 'read'})
|
|
self.assertMembers(direct_child_write, False,
|
|
{self.partner_employee: 'write'})
|
|
self.assertMembers(grand_child, 'read',
|
|
{self.partner_employee: 'write'})
|
|
|
|
# invite a mix of shared and internal people
|
|
partners = (self.customer + self.partner_employee_manager + self.partner_employee2).with_env(self.env)
|
|
with self.mock_mail_gateway():
|
|
shared_article.invite_members(partners, 'write')
|
|
self.assertMembers(shared_article, False,
|
|
{self.partner_employee: 'write',
|
|
self.customer: 'read', # shared partners are always read only
|
|
self.partner_employee_manager: 'write',
|
|
self.partner_employee2: 'write'},
|
|
msg='Invite: should add rights for people')
|
|
self.assertMembers(direct_child_read, False,
|
|
{self.partner_employee: 'read',
|
|
self.customer: 'none',
|
|
self.partner_employee_manager: 'write',
|
|
self.partner_employee2: 'none'},
|
|
msg='Invite: rights should be stopped for non writable children')
|
|
self.assertMembers(direct_child_write, False,
|
|
{self.partner_employee: 'write'},
|
|
msg='Invite: writable child should not be impacted')
|
|
self.assertMembers(grand_child, 'read',
|
|
{self.partner_employee: 'write'},
|
|
msg='Invite: descendants should not be impacted')
|
|
|
|
# check access is effectively granted
|
|
shared_article.with_user(self.user_employee2).check_access_rule('write')
|
|
direct_child_write.with_user(self.user_employee2).check_access_rule('write')
|
|
with self.assertRaises(exceptions.AccessError,
|
|
msg='Invite: access should have been blocked'):
|
|
direct_child_read.with_user(self.user_employee2).check_access_rule('read')
|
|
with self.assertRaises(exceptions.AccessError,
|
|
msg='Invite: access should have been blocked'):
|
|
grand_child.with_user(self.user_employee2).check_access_rule('read')
|
|
|
|
# employee2 is downgraded, employee_manager is removed
|
|
with self.mock_mail_gateway():
|
|
shared_article.invite_members(partners[2], 'read')
|
|
with self.mock_mail_gateway():
|
|
shared_article.invite_members(partners[1], 'none')
|
|
|
|
self.assertMembers(shared_article, False,
|
|
{self.partner_employee: 'write',
|
|
self.customer: 'read',
|
|
self.partner_employee_manager: 'none',
|
|
self.partner_employee2: 'read'})
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.mail.models.mail_mail')
|
|
@users('employee')
|
|
def test_article_invite_members_rights(self):
|
|
""" Testing trying to bypass granted privilege: inviting people require
|
|
write access. """
|
|
article_shared = self.article_shared.with_env(self.env)
|
|
|
|
partners = (self.customer + self.partner_employee_manager + self.partner_employee2).with_env(self.env)
|
|
with self.assertRaises(exceptions.AccessError,
|
|
msg='Invite: cannot invite with read permission'):
|
|
article_shared.invite_members(partners, 'write')
|
|
|
|
with self.assertRaises(exceptions.AccessError,
|
|
msg='Invite: cannot try to reject people with read permission'):
|
|
article_shared.invite_members(partners, 'none')
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.mail.models.mail_mail')
|
|
@users('employee')
|
|
def test_article_invite_members_non_accessible_children(self):
|
|
""" Test that user cannot give access to non-accessible children article
|
|
when inviting people. """
|
|
private_parent = self.env['knowledge.article'].create([{
|
|
'article_member_ids': [(0, 0, {
|
|
'partner_id': self.partner_employee.id,
|
|
'permission': 'write',
|
|
})
|
|
],
|
|
'internal_permission': 'none',
|
|
'name': 'Private parent',
|
|
'parent_id': False,
|
|
}])
|
|
child_no_access, child_read_access, child_write_access = self.env['knowledge.article'].sudo().create([
|
|
{'article_member_ids': [(0, 0, {
|
|
'partner_id': self.partner_employee.id,
|
|
'permission': 'none',
|
|
})
|
|
],
|
|
'internal_permission': 'write',
|
|
'name': 'Shared No Access Child (should not propagate)',
|
|
'parent_id': private_parent.id,
|
|
},
|
|
{'article_member_ids': [(0, 0, {
|
|
'partner_id': self.partner_employee.id,
|
|
'permission': 'read',
|
|
})
|
|
],
|
|
'internal_permission': 'write',
|
|
'name': 'Shared Read Child (should not propagate)',
|
|
'parent_id': private_parent.id,
|
|
},
|
|
{'internal_permission': False,
|
|
'name': 'Shared Inherited Write Child (should propagate)',
|
|
'parent_id': private_parent.id,
|
|
}
|
|
]).with_env(self.env)
|
|
grandchild_no_access, grandchild_read_access, grandchild_write_access = self.env['knowledge.article'].sudo().create([
|
|
{'internal_permission': False,
|
|
'name': 'Shared inherit No access GrandChild (should not propagate)',
|
|
'parent_id': child_no_access.id,
|
|
},
|
|
{'internal_permission': False,
|
|
'name': 'Shared inherit read GrandChild (should not propagate)',
|
|
'parent_id': child_read_access.id,
|
|
},
|
|
{'internal_permission': False,
|
|
'name': 'Shared inherit write GrandChild (should propagate)',
|
|
'parent_id': child_write_access.id,
|
|
}
|
|
]).with_env(self.env)
|
|
|
|
partners = self.partner_employee_manager.with_env(self.env)
|
|
with self.mock_mail_gateway():
|
|
private_parent.invite_members(partners, 'read')
|
|
|
|
# Manager got read on article
|
|
self.assertMembers(private_parent, 'none', {
|
|
self.partner_employee: 'write',
|
|
self.partner_employee_manager: 'read'
|
|
})
|
|
|
|
# CHILDREN
|
|
# Manager got none on child_read_access
|
|
self.assertMembers(child_read_access, 'write', {
|
|
self.partner_employee: 'read',
|
|
self.partner_employee_manager: 'none'
|
|
})
|
|
|
|
# Manager got none on child_no_access
|
|
self.assertMembers(child_no_access, 'write', {
|
|
self.partner_employee: 'none',
|
|
self.partner_employee_manager: 'none'
|
|
})
|
|
|
|
# Manager got inherited read on child_write_access
|
|
self.assertMembers(child_write_access, False, {})
|
|
self.assertTrue(child_write_access.user_has_write_access)
|
|
self.assertTrue(child_write_access.with_user(self.user_employee_manager).user_has_access)
|
|
|
|
# GRAND CHILDREN
|
|
# Manager got inherited none on child_read_access and Employee still have inherited member access
|
|
self.assertMembers(grandchild_read_access, False, {})
|
|
self.assertTrue(grandchild_read_access.user_has_access)
|
|
with self.assertRaises(exceptions.AccessError):
|
|
grandchild_read_access.with_user(self.user_employee_manager).body # Acls should trigger AccessError
|
|
|
|
# Manager got inherited none on child_no_access and Employee still have no access
|
|
self.assertMembers(grandchild_no_access, False, {})
|
|
with self.assertRaises(exceptions.AccessError):
|
|
grandchild_no_access.body # Acls should trigger AccessError
|
|
with self.assertRaises(exceptions.AccessError):
|
|
grandchild_no_access.with_user(self.user_employee_manager).body # Acls should trigger AccessError
|
|
|
|
# Manager got inherited read on grandchild_write_access and Employee still have write access
|
|
self.assertMembers(grandchild_write_access, False, {})
|
|
self.assertTrue(grandchild_write_access.user_has_write_access)
|
|
self.assertTrue(grandchild_write_access.with_user(self.user_employee_manager).user_has_access)
|
|
|
|
@users('employee')
|
|
def test_article_toggle_favorite(self):
|
|
""" Testing the API for toggling favorites. """
|
|
playground_articles = (self.article_workspace + self.workspace_children).with_env(self.env)
|
|
self.assertEqual(playground_articles.mapped('is_user_favorite'), [False, False, False])
|
|
|
|
playground_articles[0].action_toggle_favorite()
|
|
playground_articles.invalidate_model(['is_user_favorite'])
|
|
self.assertEqual(playground_articles.mapped('is_user_favorite'), [True, False, False])
|
|
|
|
# correct uid-based computation
|
|
playground_articles_asmanager = playground_articles.with_user(self.user_employee_manager)
|
|
self.assertEqual(playground_articles_asmanager.mapped('is_user_favorite'), [False, False, False])
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule')
|
|
@users('employee')
|
|
def test_article_move_to(self):
|
|
""" Testing the API for moving articles. """
|
|
article_workspace = self.article_workspace.with_env(self.env)
|
|
article_shared = self.article_shared.with_env(self.env)
|
|
workspace_children = self.workspace_children.with_env(self.env)
|
|
|
|
with self.assertRaises(exceptions.AccessError,
|
|
msg='Cannot move under readonly parent'):
|
|
workspace_children[0].move_to(parent_id=article_shared.id)
|
|
with self.assertRaises(exceptions.AccessError,
|
|
msg='Cannot move a readonly article'):
|
|
article_shared[0].move_to(parent_id=article_workspace.id)
|
|
with self.assertRaises(exceptions.AccessError,
|
|
msg='Cannot move a readonly article (even out of any hierarchy)'):
|
|
article_shared[0].move_to(parent_id=False)
|
|
|
|
# valid move: put second child of workspace under the first one
|
|
workspace_children[1].move_to(parent_id=workspace_children[0].id)
|
|
workspace_children.flush_model()
|
|
self.assertEqual(article_workspace.child_ids, workspace_children[0])
|
|
self.assertEqual(article_workspace._get_descendants(), workspace_children)
|
|
self.assertEqual(workspace_children.root_article_id, article_workspace)
|
|
self.assertEqual(workspace_children[1].parent_id, workspace_children[0])
|
|
self.assertEqual(workspace_children[0].parent_id, article_workspace)
|
|
|
|
# Test that desynced articles are resynced when moved to root
|
|
workspace_children[0]._desync_access_from_parents()
|
|
self.assertTrue(workspace_children[0].is_desynchronized)
|
|
|
|
# other valid move: first child is moved to private section
|
|
workspace_children[0].move_to(parent_id=False, is_private=True)
|
|
workspace_children.flush_model()
|
|
self.assertMembers(workspace_children[0], 'none', {self.partner_employee: 'write'})
|
|
self.assertEqual(workspace_children[0].category, 'private')
|
|
self.assertEqual(workspace_children[0].internal_permission, 'none')
|
|
self.assertFalse(workspace_children[0].is_desynchronized)
|
|
self.assertFalse(workspace_children[0].parent_id)
|
|
self.assertEqual(workspace_children.root_article_id, workspace_children[0])
|
|
|
|
@users('employee')
|
|
def test_article_sort_for_user(self):
|
|
""" Testing the sort + custom info returned by get_user_sorted_articles """
|
|
self.workspace_children.write({
|
|
'favorite_ids': [
|
|
(0, 0, {'user_id': user.id})
|
|
for user in self.user_admin + self.user_employee2 + self.user_employee_manager
|
|
],
|
|
})
|
|
article_workspace = self.article_workspace.with_env(self.env)
|
|
workspace_children = self.workspace_children.with_env(self.env)
|
|
(article_workspace + workspace_children[1]).action_toggle_favorite()
|
|
|
|
new_root_child = self.env['knowledge.article'].create({
|
|
'name': 'Child3 without parent name in its name',
|
|
'parent_id': article_workspace.id,
|
|
})
|
|
new_root_child.flush_model()
|
|
|
|
# ensure initial values
|
|
self.assertTrue(article_workspace.is_user_favorite)
|
|
self.assertEqual(article_workspace.favorite_count, 2)
|
|
self.assertEqual(article_workspace.user_favorite_sequence, 1)
|
|
self.assertFalse(workspace_children[0].is_user_favorite)
|
|
self.assertEqual(workspace_children[0].favorite_count, 3)
|
|
self.assertEqual(workspace_children[0].user_favorite_sequence, -1)
|
|
self.assertTrue(workspace_children[1].is_user_favorite)
|
|
self.assertEqual(workspace_children[1].favorite_count, 4)
|
|
self.assertEqual(workspace_children[1].user_favorite_sequence, 2)
|
|
self.assertFalse(new_root_child.is_user_favorite)
|
|
self.assertEqual(new_root_child.favorite_count, 0)
|
|
self.assertEqual(new_root_child.user_favorite_sequence, -1)
|
|
|
|
# search also includes descendants of articles having the term in their name
|
|
result = self.env['knowledge.article'].get_user_sorted_articles('laygroun')
|
|
expected = self.article_workspace + self.workspace_children[1] + self.workspace_children[0] + new_root_child
|
|
found_ids = [a['id'] for a in result]
|
|
self.assertEqual(found_ids, expected.ids)
|
|
# check returned result once (just to be sure)
|
|
workspace_info = next(article_result for article_result in result if article_result['id'] == article_workspace.id)
|
|
self.assertTrue(workspace_info['is_user_favorite'], article_workspace.name)
|
|
self.assertFalse(workspace_info['icon'])
|
|
self.assertEqual(workspace_info['favorite_count'], 2)
|
|
self.assertEqual(workspace_info['name'], article_workspace.name)
|
|
self.assertEqual(workspace_info['root_article_id'], (article_workspace.id, f'📄 {article_workspace.name}'))
|
|
|
|
|
|
@tagged('knowledge_internals')
|
|
class TestKnowledgeArticleFields(KnowledgeCommonWData):
|
|
""" Test fields and their management. """
|
|
|
|
@users('employee')
|
|
def test_favorites(self):
|
|
""" Testing the API for toggling favorites. """
|
|
playground_articles = (self.article_workspace + self.workspace_children).with_env(self.env)
|
|
self.assertEqual(playground_articles.mapped('is_user_favorite'), [False, False, False])
|
|
|
|
playground_articles[0].write({'favorite_ids': [(0, 0, {'user_id': self.env.uid})]})
|
|
self.assertEqual(playground_articles.mapped('is_user_favorite'), [True, False, False])
|
|
self.assertEqual(playground_articles.mapped('user_favorite_sequence'), [1, -1, -1])
|
|
favorites = self.env['knowledge.article.favorite'].sudo().search([('user_id', '=', self.env.uid)])
|
|
self.assertEqual(favorites.article_id, playground_articles[0])
|
|
self.assertEqual(favorites.sequence, 1)
|
|
|
|
playground_articles[1].action_toggle_favorite()
|
|
self.assertEqual(playground_articles.mapped('is_user_favorite'), [True, True, False])
|
|
self.assertEqual(playground_articles.mapped('user_favorite_sequence'), [1, 2, -1])
|
|
favorites = self.env['knowledge.article.favorite'].sudo().search([('user_id', '=', self.env.uid)])
|
|
self.assertEqual(favorites.article_id, playground_articles[0:2])
|
|
self.assertEqual(favorites.mapped('sequence'), [1, 2])
|
|
|
|
playground_articles[2].with_user(self.user_employee2).action_toggle_favorite()
|
|
favorites = self.env['knowledge.article.favorite'].sudo().search([('user_id', '=', self.user_employee2.id)])
|
|
self.assertEqual(favorites.article_id, playground_articles[2])
|
|
self.assertEqual(favorites.sequence, 1, 'Favorite: should not be impacted by other people sequence')
|
|
|
|
@users('employee')
|
|
def test_fields_edition(self):
|
|
_reference_dt = datetime(2022, 5, 31, 10, 0, 0)
|
|
body_values = [False, '', '<p><br /></p>', '<p>MyBody</p>']
|
|
|
|
for index, body in enumerate(body_values):
|
|
self.patch(self.env.cr, 'now', lambda: _reference_dt)
|
|
with freeze_time(_reference_dt):
|
|
article = self.env['knowledge.article'].create({
|
|
'body': body,
|
|
'internal_permission': 'write',
|
|
'name': 'MyArticle,'
|
|
})
|
|
self.assertEqual(article.last_edition_uid, self.env.user)
|
|
self.assertEqual(article.last_edition_date, _reference_dt)
|
|
|
|
self.patch(self.env.cr, 'now', lambda: _reference_dt + timedelta(days=1))
|
|
|
|
# fields that does not change content
|
|
with freeze_time(_reference_dt + timedelta(days=1)):
|
|
article.with_user(self.user_employee2).write({
|
|
'name': 'NoContentEdition'
|
|
})
|
|
self.assertEqual(article.last_edition_uid, self.env.user)
|
|
self.assertEqual(article.last_edition_date, _reference_dt)
|
|
|
|
# fields that change content
|
|
with freeze_time(_reference_dt + timedelta(days=1)):
|
|
article.with_user(self.user_employee2).write({
|
|
'body': body_values[(index + 1) if index < (len(body_values)-1) else 0]
|
|
})
|
|
# the with_user() below is necessary for the test to succeed,
|
|
# and that's kind of a bad smell...
|
|
article.with_user(self.user_employee2).flush_model()
|
|
self.assertEqual(article.last_edition_uid, self.user_employee2)
|
|
self.assertEqual(article.last_edition_date, _reference_dt + timedelta(days=1))
|
|
|
|
|
|
@tagged('knowledge_internals', 'knowledge_management')
|
|
class TestKnowledgeCommonWDataInitialValue(KnowledgeCommonWData):
|
|
""" Test initial values or our test data once so that other tests do not have
|
|
to do it. """
|
|
|
|
def test_initial_values(self):
|
|
""" Ensure all tests have the same basis (global values computed as root) """
|
|
# root
|
|
article_workspace = self.article_workspace
|
|
self.assertTrue(article_workspace.category, 'workspace')
|
|
self.assertEqual(article_workspace.sequence, 999)
|
|
article_shared = self.article_shared
|
|
self.assertTrue(article_shared.category, 'shared')
|
|
self.assertTrue(article_shared.sequence, 998)
|
|
|
|
# workspace children
|
|
workspace_children = article_workspace.child_ids
|
|
self.assertEqual(
|
|
workspace_children.mapped('inherited_permission'),
|
|
['write', 'write']
|
|
)
|
|
self.assertEqual(workspace_children.inherited_permission_parent_id, article_workspace)
|
|
self.assertEqual(
|
|
workspace_children.mapped('internal_permission'),
|
|
[False, False]
|
|
)
|
|
self.assertEqual(workspace_children.root_article_id, article_workspace)
|
|
self.assertEqual(workspace_children.mapped('sequence'), [0, 1])
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule')
|
|
@users('employee')
|
|
def test_initial_values_as_employee(self):
|
|
""" Ensure all tests have the same basis (user specific computed as
|
|
employee for acl-dependent tests) """
|
|
article_workspace = self.article_workspace.with_env(self.env)
|
|
self.assertTrue(article_workspace.user_has_access)
|
|
self.assertTrue(article_workspace.user_has_write_access)
|
|
|
|
article_shared = self.article_shared.with_env(self.env)
|
|
self.assertTrue(article_shared.user_has_access)
|
|
self.assertFalse(article_shared.user_has_write_access)
|
|
|
|
article_private = self.article_private_manager.with_env(self.env)
|
|
with self.assertRaises(exceptions.AccessError):
|
|
self.assertFalse(article_private.body)
|
|
|
|
|
|
@tagged('post_install', '-at_install', 'knowledge_internals', 'knowledge_management')
|
|
class TestKnowledgeShare(KnowledgeCommonWData):
|
|
""" Test share feature. """
|
|
|
|
@mute_logger('odoo.addons.base.models.ir_rule', 'odoo.addons.mail.models.mail_mail', 'odoo.models.unlink', 'odoo.tests')
|
|
@users('employee2')
|
|
def test_knowledge_article_share(self):
|
|
# private article of "employee manager"
|
|
knowledge_article_sudo = self.env['knowledge.article'].sudo().create({
|
|
'name': 'Test Article',
|
|
'body': '<p>Content</p>',
|
|
'internal_permission': 'none',
|
|
'article_member_ids': [(0, 0, {
|
|
'partner_id': self.partner_employee_manager.id,
|
|
'permission': 'write',
|
|
})],
|
|
})
|
|
article = knowledge_article_sudo.with_env(self.env)
|
|
self.assertFalse(article.user_has_access)
|
|
|
|
# employee2 is not supposed to be able to share it
|
|
with self.assertRaises(exceptions.AccessError):
|
|
self._knowledge_article_share(article, self.partner_portal.ids, 'read')
|
|
|
|
# give employee2 read access on the document
|
|
knowledge_article_sudo.write({
|
|
'article_member_ids': [(0, 0, {
|
|
'partner_id': self.partner_employee2.id,
|
|
'permission': 'read',
|
|
})]
|
|
})
|
|
self.assertTrue(article.user_has_access)
|
|
|
|
# still not supposed to be able to share it
|
|
with self.assertRaises(exceptions.AccessError):
|
|
self._knowledge_article_share(article, self.partner_portal.ids, 'read')
|
|
|
|
# modify employee2 access to write
|
|
knowledge_article_sudo.article_member_ids.filtered(
|
|
lambda member: member.partner_id == self.partner_employee2
|
|
).write({'permission': 'write'})
|
|
|
|
# now they should be able to share it
|
|
with self.mock_mail_gateway(), self.mock_mail_app():
|
|
self._knowledge_article_share(article, self.partner_portal.ids, 'read')
|
|
|
|
# check that portal user received an invitation link
|
|
self.assertEqual(len(self._new_msgs), 1)
|
|
self.assertIn(
|
|
knowledge_article_sudo._get_invite_url(self.partner_portal),
|
|
self._new_msgs.body
|
|
)
|
|
|
|
with self.with_user('portal_test'):
|
|
# portal should now have read access to the article
|
|
# (re-browse to have the current user context for user_permission)
|
|
article_asportal = knowledge_article_sudo.with_env(self.env)
|
|
self.assertTrue(article_asportal.user_has_access)
|
|
|
|
def _knowledge_article_share(self, article, partner_ids, permission='write'):
|
|
""" Re-browse the article to make sure we have the current user context on it.
|
|
Necessary for all access fields compute methods in knowledge.article. """
|
|
|
|
return self.env['knowledge.invite'].create({
|
|
'article_id': self.env['knowledge.article'].browse(article.id).id,
|
|
'partner_ids': partner_ids,
|
|
'permission': permission,
|
|
}).action_invite_members()
|