# -*- coding: utf-8 -*- """Quality Dashboard snapshot endpoint tests. Spec: docs/superpowers/specs/2026-05-25-quality-dashboard-redesign-design.md Plan: docs/superpowers/plans/2026-05-25-quality-dashboard-redesign-plan.md """ from odoo.tests.common import TransactionCase class TestDashboardSnapshotShape(TransactionCase): """Response shape + section ordering tests.""" def _build(self): from odoo.addons.fusion_plating_quality.controllers.fp_quality_dashboard \ import FpQualityDashboardSnapshot return FpQualityDashboardSnapshot(self.env).build() def test_snapshot_has_expected_top_level_keys(self): snap = self._build() self.assertIn('banner', snap) self.assertIn('sections', snap) self.assertIn('computed_at', snap) def test_section_order_is_canonical(self): snap = self._build() types_present = [s['type'] for s in snap['sections']] # Canonical order — cert, hold, ncr, rma, capa, check. # Some types may be absent if their model isn't installed; the # PRESENT ones must appear in this relative order. canonical = ['cert', 'hold', 'ncr', 'rma', 'capa', 'check'] order_in_canonical = [canonical.index(t) for t in types_present] self.assertEqual( order_in_canonical, sorted(order_in_canonical), f"sections out of order: got {types_present}", ) def test_empty_db_returns_all_clear(self): snap = self._build() self.assertTrue(snap['banner']['all_clear']) self.assertEqual(snap['banner']['items'], []) self.assertEqual(snap['banner']['total_matching'], 0) def test_each_section_has_required_keys(self): snap = self._build() for section in snap['sections']: for key in ('type', 'label', 'icon', 'open', 'overdue', 'items', 'open_kanban_xmlid'): self.assertIn( key, section, f"section {section.get('type')} missing key {key!r}", ) self.assertIsInstance(section['items'], list) self.assertIsInstance(section['open'], int) self.assertIsInstance(section['overdue'], int) class TestDashboardSnapshotItems(TransactionCase): """Per-section items list — ranking + cap + shape.""" def _build(self): from odoo.addons.fusion_plating_quality.controllers.fp_quality_dashboard \ import FpQualityDashboardSnapshot return FpQualityDashboardSnapshot(self.env).build() def _get_section(self, snap, type_code): for s in snap['sections']: if s['type'] == type_code: return s return None def test_section_items_empty_when_no_records(self): snap = self._build() cert_sec = self._get_section(snap, 'cert') if cert_sec is None: self.skipTest('fp.certificate not installed') self.assertEqual(cert_sec['items'], []) def test_section_items_capped_at_5(self): if 'fusion.plating.quality.hold' not in self.env: self.skipTest('fusion.plating.quality.hold not installed') partner = self.env['res.partner'].create({'name': 'Cust'}) # Create 8 holds in the same open state Hold = self.env['fusion.plating.quality.hold'] for i in range(8): Hold.create({ 'partner_id': partner.id, 'state': 'on_hold', 'reason': f'test hold {i}', }) snap = self._build() sec = self._get_section(snap, 'hold') self.assertEqual(len(sec['items']), 5) self.assertEqual(sec['open'], 8) def test_item_has_required_keys(self): if 'fusion.plating.quality.hold' not in self.env: self.skipTest('fusion.plating.quality.hold not installed') partner = self.env['res.partner'].create({'name': 'Cust'}) hold = self.env['fusion.plating.quality.hold'].create({ 'partner_id': partner.id, 'state': 'on_hold', 'reason': 'x', }) snap = self._build() sec = self._get_section(snap, 'hold') self.assertEqual(len(sec['items']), 1) item = sec['items'][0] for k in ('id', 'name', 'customer', 'subtitle', 'urgency', 'open_action'): self.assertIn(k, item, f'item missing key {k!r}') self.assertEqual(item['open_action']['res_model'], 'fusion.plating.quality.hold') self.assertEqual(item['open_action']['res_id'], hold.id) class TestDashboardSnapshotBanner(TransactionCase): """Banner population + ranking.""" def _build(self): from odoo.addons.fusion_plating_quality.controllers.fp_quality_dashboard \ import FpQualityDashboardSnapshot return FpQualityDashboardSnapshot(self.env).build() def test_banner_picks_up_overdue_hold(self): from datetime import datetime, timedelta if 'fusion.plating.quality.hold' not in self.env: self.skipTest('fusion.plating.quality.hold not installed') partner = self.env['res.partner'].create({'name': 'Cust'}) hold = self.env['fusion.plating.quality.hold'].create({ 'partner_id': partner.id, 'state': 'on_hold', 'reason': 'old', }) # Backdate create_date past the 3-day overdue threshold old = datetime.now() - timedelta(days=5) self.env.cr.execute( "UPDATE fusion_plating_quality_hold SET create_date = %s WHERE id = %s", (old, hold.id), ) hold.invalidate_recordset(['create_date']) snap = self._build() self.assertFalse(snap['banner']['all_clear']) self.assertEqual(snap['banner']['total_matching'], 1) self.assertEqual(len(snap['banner']['items']), 1) item = snap['banner']['items'][0] self.assertEqual(item['type'], 'hold') self.assertEqual(item['urgency'], 'overdue') def test_banner_caps_at_6_with_overflow_count(self): from datetime import datetime, timedelta if 'fusion.plating.quality.hold' not in self.env: self.skipTest('fusion.plating.quality.hold not installed') partner = self.env['res.partner'].create({'name': 'Cust'}) Hold = self.env['fusion.plating.quality.hold'] # 8 overdue holds old = datetime.now() - timedelta(days=5) for i in range(8): h = Hold.create({ 'partner_id': partner.id, 'state': 'on_hold', 'reason': f'overdue {i}', }) self.env.cr.execute( "UPDATE fusion_plating_quality_hold SET create_date = %s WHERE id = %s", (old, h.id), ) snap = self._build() self.assertEqual(len(snap['banner']['items']), 6) self.assertEqual(snap['banner']['total_matching'], 8) self.assertFalse(snap['banner']['all_clear']) def test_banner_all_clear_when_zero(self): snap = self._build() # Empty DB — no overdue, no critical self.assertTrue(snap['banner']['all_clear']) self.assertEqual(snap['banner']['items'], []) class TestDashboardSnapshotDefensive(TransactionCase): """Module-not-installed + missing-field guards.""" def _build(self): from odoo.addons.fusion_plating_quality.controllers.fp_quality_dashboard \ import FpQualityDashboardSnapshot return FpQualityDashboardSnapshot(self.env).build() def test_missing_partner_field_falls_through(self): # Empty DB + helper must not raise even when x_fc_rush absent. # (In this codebase x_fc_rush isn't a registered field — only # read defensively via the `in partner._fields` check. The # snapshot must build cleanly.) snap = self._build() self.assertIn('banner', snap) self.assertTrue(snap['banner']['all_clear']) def test_computed_at_is_iso_string(self): from datetime import datetime snap = self._build() self.assertIsInstance(snap['computed_at'], str) # Must be parseable as ISO timestamp datetime.fromisoformat(snap['computed_at']) def test_all_sections_have_open_kanban_xmlid(self): snap = self._build() for section in snap['sections']: self.assertTrue( section['open_kanban_xmlid'], f"section {section['type']} missing open_kanban_xmlid", ) # Should contain a dot (module.xmlid format) self.assertIn('.', section['open_kanban_xmlid'])