feat(fusion_helpdesk): identity keystone helpers, seen model, admin group

Phase 1-2: build_ticket_vals/scope/unread helpers (utils.py), fusion.helpdesk.ticket.seen read-tracking model, group_reporter_admin. 10 unit tests pass on local Community.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-27 03:23:31 -04:00
parent 960fe67966
commit 33fc046fa2
9 changed files with 292 additions and 1 deletions

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import test_utils
from . import test_seen

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1
"""Tests for fusion.helpdesk.ticket.seen read-tracking."""
from odoo.tests import TransactionCase, tagged
@tagged('post_install', '-at_install', 'fusion_helpdesk')
class TestSeen(TransactionCase):
def test_mark_seen_upserts_and_is_monotonic(self):
Seen = self.env['fusion.helpdesk.ticket.seen']
Seen._mark_seen(central_ticket_id=42, last_message_id=100)
Seen._mark_seen(central_ticket_id=42, last_message_id=120)
Seen._mark_seen(central_ticket_id=42, last_message_id=90) # stale, ignored
rec = Seen.search([
('user_id', '=', self.env.uid),
('central_ticket_id', '=', 42),
])
self.assertEqual(len(rec), 1, "should upsert, not duplicate")
self.assertEqual(rec.last_seen_message_id, 120, "monotonic — never moves back")
def test_seen_map(self):
Seen = self.env['fusion.helpdesk.ticket.seen']
Seen._mark_seen(1, 10)
Seen._mark_seen(2, 20)
self.assertEqual(Seen._seen_map([1, 2, 3]), {1: 10, 2: 20})

View File

@@ -0,0 +1,91 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1
"""Unit tests for the pure helpers in fusion_helpdesk.utils.
These need no live central Odoo — they pin the identity keystone, the
scoping security boundary, the public-message filter and the unread
maths as plain data transformations.
"""
from odoo.tests import TransactionCase, tagged
from odoo.addons.fusion_helpdesk.utils import (
build_ticket_vals,
build_scope_domain,
is_public_message,
compute_unread_count,
)
@tagged('post_install', '-at_install', 'fusion_helpdesk')
class TestBuildTicketVals(TransactionCase):
def test_identity_fields_present(self):
vals = build_ticket_vals(
kind='bug', subject='X', body_html='<p>b</p>',
team_id=1, client_label='ENTECH',
reporter_name='John Doe', reporter_email='john@entech.com',
company_name='ENTECH Inc',
)
self.assertEqual(vals['partner_email'], 'john@entech.com')
self.assertEqual(vals['partner_name'], 'John Doe')
self.assertEqual(vals['x_fc_client_label'], 'ENTECH')
self.assertEqual(vals['partner_company_name'], 'ENTECH Inc')
self.assertEqual(vals['team_id'], 1)
self.assertIn('X', vals['name'])
self.assertIn('[ENTECH]', vals['name'])
def test_no_email_omits_partner_email(self):
vals = build_ticket_vals(
kind='feature', subject='Y', body_html='<p>b</p>',
team_id=False, client_label='', reporter_name='Jane',
reporter_email='', company_name='',
)
self.assertNotIn('partner_email', vals) # never send an empty email
self.assertNotIn('team_id', vals) # omit falsy team
self.assertNotIn('x_fc_client_label', vals) # omit empty label
self.assertEqual(vals['partner_name'], 'Jane')
self.assertIn('Feature Request', vals['name'])
@tagged('post_install', '-at_install', 'fusion_helpdesk')
class TestScopeDomain(TransactionCase):
def test_regular_scope_binds_email_and_label(self):
dom = build_scope_domain(label='ENTECH', email='john@entech.com', is_admin=False)
self.assertIn(('x_fc_client_label', '=', 'ENTECH'), dom)
self.assertIn(('partner_email', '=ilike', 'john@entech.com'), dom)
def test_admin_scope_binds_label_only(self):
dom = build_scope_domain(label='ENTECH', email='a@entech.com', is_admin=True)
self.assertIn(('x_fc_client_label', '=', 'ENTECH'), dom)
self.assertFalse(any(t[0] == 'partner_email' for t in dom))
def test_empty_label_never_matches_everything(self):
dom = build_scope_domain(label='', email='', is_admin=True)
# label term must be present and must NOT be an empty string
label_terms = [t for t in dom if t[0] == 'x_fc_client_label']
self.assertEqual(len(label_terms), 1)
self.assertNotEqual(label_terms[0][2], '')
@tagged('post_install', '-at_install', 'fusion_helpdesk')
class TestMessageFilterAndUnread(TransactionCase):
def test_internal_note_is_not_public(self):
self.assertFalse(is_public_message({'subtype_is_internal': True}))
self.assertTrue(is_public_message({'subtype_is_internal': False}))
self.assertTrue(is_public_message({})) # default visible
def test_unread_count(self):
tickets = [
{'id': 1, 'last_support_msg_id': 10}, # seen 10 -> read
{'id': 2, 'last_support_msg_id': 5}, # seen 3 -> unread
{'id': 3, 'last_support_msg_id': 0}, # no support msg
]
seen = {1: 10, 2: 3}
self.assertEqual(compute_unread_count(tickets, seen), 1)
def test_unread_count_unseen_ticket_counts(self):
tickets = [{'id': 9, 'last_support_msg_id': 4}]
self.assertEqual(compute_unread_count(tickets, {}), 1)