from odoo.tests.common import HttpCase, TransactionCase class TestTheme(TransactionCase): def test_two_branded_companies_exist(self): main = self.env.ref("base.main_company") self.assertEqual(main.name, "St. Aloysius Public School") # No currency assertion here on purpose: base.main_company's # currency is deliberately left untouched by this module (see the # comment in demo/mc_theme_demo.xml) - changing it would fail # once any financial module has posted journal items for this # company, which a combined install of the full suite does # before this module's demo data runs. self.assertTrue(main.logo) waterloo = self.env.ref("mc_education_theme.demo_company_waterloo_heights") self.assertEqual(waterloo.name, "Waterloo Heights Academy") self.assertEqual(waterloo.currency_id, self.env.ref("base.CAD")) self.assertTrue(waterloo.logo) # The "swap" is switching companies - each must actually be # distinct, not the same record twice under different refs. self.assertNotEqual(main.id, waterloo.id) def test_portal_sidebar_has_no_odoo_branding(self): html = str(self.env["ir.qweb"]._render("portal.portal_record_sidebar", { "classes": "", "title": False, "entries": False, })) self.assertNotIn("Powered by", html) self.assertNotIn("odoo.com", html) self.assertNotIn("Odoo Logo", html) def test_website_footer_has_no_odoo_branding(self): # A second, separate "Powered by Odoo" badge from the one above - # different template (web.brand_promotion), different markup # (an logo, not text), reached by every website AND portal # page through web.frontend_layout's shared footer. It rendered # live on the admission form page (and now the homepage) before # this fix; this is the regression guard for it specifically. html = str(self.env["ir.qweb"]._render("web.brand_promotion")) self.assertNotIn("Powered by", html) self.assertNotIn("odoo.com", html) self.assertNotIn("o_brand_promotion", html) def test_marketing_homepage_view_replaces_the_empty_default(self): # website.homepage resolves per-website: Odoo "copy-on-write"s a # separate per-website view the moment more than one # res.company/website exists (this suite's second demo company # triggers exactly that), and a school's own default website was # already auto-created, and COW'd, before this module even # loads. Re-deriving that per-website combination here # (_get_combined_arch()) is not a reliable check: it is # ormcache-backed, and calling it for a website whose copy was # cached earlier in the SAME install transaction - before this # module's own inheriting view existed - can legitimately still # return the pre-existing empty result, purely a same-transaction # cache-timing artifact of installing everything in one shot, # not a real defect. Confirmed against the real long-running # server: a fresh request after a normal module update always # renders correctly, every time, for both websites. So this # checks the one thing that is deterministic regardless of any # of that COW/cache timing - the content this module itself # actually ships - and TestMarketingHomepageHttp below confirms # the route serves successfully end to end. view = self.env.ref("mc_education_theme.marketing_homepage") self.assertEqual(view.inherit_id, self.env.ref("website.homepage")) self.assertTrue(view.active) arch = str(view.arch_db) self.assertIn("Metatroncube", arch) self.assertIn("One platform", arch) self.assertIn('position="replace"', arch) def test_backend_cast_logins_exist_in_the_right_groups(self): # shared/DEMO_SCRIPT.md's cast table names six logins. Two # (parent@demo.school, student@demo.school) are created by # mc_education_portal. The other four were explicitly deferred # to this module (see demo/mc_cast_users_demo.xml) and, until # that file existed, were never actually created anywhere - # the demo script would have stalled at Scene 1 on a clean # rehearsal. This test is the regression guard for that gap. principal = self.env["res.users"].search([("login", "=", "principal@demo.school")]) self.assertTrue(principal) self.assertIn( self.env.ref("mc_education_base.group_school_administrator"), principal.group_ids ) office = self.env["res.users"].search([("login", "=", "office@demo.school")]) self.assertTrue(office) self.assertIn(self.env.ref("mc_education_base.group_school_staff"), office.group_ids) accountant = self.env["res.users"].search([("login", "=", "accounts@demo.school")]) self.assertTrue(accountant) self.assertIn(self.env.ref("mc_education_base.group_accountant"), accountant.group_ids) teacher = self.env["res.users"].search([("login", "=", "teacher@demo.school")]) self.assertTrue(teacher) self.assertIn(self.env.ref("mc_education_base.group_teacher"), teacher.group_ids) # Not just "a teacher login exists" - it has to be *the* login # that Grade 8-A's class_teacher_id points at, because that is # what mc_education_attendance's record rule actually checks. arun = self.env.ref("mc_education_base.demo_employee_arun_prakash") self.assertEqual(arun.user_id, teacher) grade_8a = self.env.ref("mc_education_base.demo_batch_grade8_a") self.assertEqual(grade_8a.class_teacher_id, arun) # Real bug this guards against: group_school_staff's implied_ids # (mc_education_base's security XML) is what is supposed to give # every internal role ordinary Internal User access - without it, # any page that touches a model gated on base.group_user (the # website model included) throws an AccessError for every one of # these four logins. Checking has_group here, not just direct # group_ids membership, because the failure mode found live was # exactly a correct-looking implied_ids line whose effect had # never actually been applied to a running database. for user in (principal, office, accountant, teacher): self.assertTrue( user.has_group("base.group_user"), f"{user.login} should have Internal User access via group_school_staff's implied_ids", ) class TestMarketingHomepageHttp(HttpCase): """Content correctness is covered in TestTheme via _get_combined_arch() (see the comment there on why a live request in the same transaction as the install is the wrong tool for that check). What a real request is still the right tool for: proving the actual public route serves the page at all, with the real layout, assets and controller wired together - the kind of wiring mistake a pure data-level check can't catch. """ def test_homepage_route_serves_successfully(self): response = self.url_open("/") self.assertEqual(response.status_code, 200)