# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import werkzeug.datastructures from odoo.fields import Date from odoo.addons.website_membership_signup_required.controllers.main import ( AuthSignupHomeMembership as AuthSignupHome, ) from odoo.addons.website_membership_signup_required.controllers.website_sale import ( WebsiteSaleMembershipSignup, ) from odoo.addons.website.tools import MockRequest from odoo.tests import TransactionCase, tagged @tagged("post_install", "-at_install") class TestMembershipSignup(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() cls.website = cls.env.ref("website.default_website") cls.website.auth_signup_uninvited = "b2b" today = Date.to_date(Date.today()) cls.membership_product = cls.env["product.product"].create({ "name": "Membership Card", "membership": True, "list_price": 50.0, "membership_date_from": today.replace(month=1, day=1), "membership_date_to": today.replace(month=12, day=31), }) cls.regular_product = cls.env["product.product"].create({ "name": "Regular Mug", "membership": False, "list_price": 10.0, }) cls.no_user_partner = cls.env["res.partner"].create({ "firstname": "Backend", "lastname": "Partner", }) # -- RF-07 / RF-default ------------------------------------------------ def test_01_website_default_flag(self): new_website = self.env["website"].create({"name": "Aux site"}) self.assertTrue(new_website.membership_signup_required) # -- RF-01 helper ------------------------------------------------------- def test_02_is_required_membership_product_anonymous(self): self.assertTrue( self.website._membership_signup_required_for( self.membership_product.id, is_public=True ) ) # -- RF-05 ------------------------------------------------------------- def test_03_is_required_non_membership_product(self): self.assertFalse( self.website._membership_signup_required_for( self.regular_product.id, is_public=True ) ) # -- RF-06 ------------------------------------------------------------- def test_04_is_required_authenticated_user(self): self.assertFalse( self.website._membership_signup_required_for( self.membership_product.id, is_public=False ) ) # -- RF-07 ------------------------------------------------------------- def test_05_is_required_disabled_on_website(self): self.website.membership_signup_required = False self.assertFalse( self.website._membership_signup_required_for( self.membership_product.id, is_public=True ) ) # -- RF-03 ------------------------------------------------------------- def test_06_signup_scope_override_with_marker(self): Users = self.env["res.users"] # No marker -> delegates to super (b2b because of setUpClass). with MockRequest(self.env, website=self.website) as mock_request: mock_request.httprequest.args = werkzeug.datastructures.MultiDict() self.assertEqual(Users._get_signup_invitation_scope(), "b2b") # Query param marker -> b2c. with MockRequest(self.env, website=self.website) as mock_request: mock_request.httprequest.args = werkzeug.datastructures.MultiDict( {"membership_signup": "1"} ) self.assertEqual(Users._get_signup_invitation_scope(), "b2c") # Session flag marker -> b2c. with MockRequest(self.env, website=self.website) as mock_request: mock_request.httprequest.args = werkzeug.datastructures.MultiDict() mock_request.session["membership_signup_active"] = True self.assertEqual(Users._get_signup_invitation_scope(), "b2c") # Marker removed again -> back to b2b. with MockRequest(self.env, website=self.website) as mock_request: mock_request.httprequest.args = werkzeug.datastructures.MultiDict() self.assertEqual(Users._get_signup_invitation_scope(), "b2b") # -- RF-09 ------------------------------------------------------------- def test_07_prepare_signup_values_firstname_lastname(self): controller = AuthSignupHome() qcontext = { "login": "joohn.doe@example.com", "password": "verystrongpwd", "confirm_password": "verystrongpwd", "firstname": "John", "lastname": "Doe", } with MockRequest(self.env, website=self.website): values = controller._prepare_signup_values(qcontext) self.assertEqual(values.get("firstname"), "John") self.assertEqual(values.get("lastname"), "Doe") # partner_firstname: `name` is composed (lastname + firstname, or # firstname + lastname depending on _get_names_order) and added by # our override so that the core signup check (no name or partner) # passes. partner_firstname's `res.partner.create` will later drop # this composed `name` and recompute it from the split fields. self.assertIn("name", values) self.assertIn("John", values["name"]) self.assertIn("Doe", values["name"]) # -- RF-08 ------------------------------------------------------------- def test_08_backend_sale_order_with_membership_product_without_user(self): order = self.env["sale.order"].create({ "partner_id": self.no_user_partner.id, "order_line": [(0, 0, { "product_id": self.membership_product.id, "product_uom_qty": 1.0, })], }) self.assertTrue(order) self.assertEqual(order.order_line.product_id, self.membership_product) # Confirming the SO drives membership creation (core `membership`). order.action_confirm() self.assertEqual(order.state, "sale") # -- Phone not mandatory at checkout (EMES) ---------------------------- def test_09_phone_not_mandatory_address_fields(self): """`phone` must not be part of the mandatory checkout fields, for any address (billing or delivery) and any flow on the EMES website. The core default (`website_sale/controllers/main.py: _get_mandatory_address_fields`) includes 'phone'; our override drops it. The frontend `address.js` then no longer marks the input `:required` nor shows the asterisk, and the server-side `_check_billing_address` / `_check_delivery_address` no longer reject empty phones. """ country = self.env.ref("base.es") ctrl = WebsiteSaleMembershipSignup() fields = ctrl._get_mandatory_address_fields(country) self.assertNotIn("phone", fields) # Default mandatory fields still present. self.assertIn("name", fields) self.assertIn("street", fields) self.assertIn("city", fields) self.assertIn("country_id", fields) def test_10_phone_not_in_portal_mandatory_fields(self): """`phone` must also be dropped from the portal-side mandatory field list (`portal/controllers/portal.py:_get_mandatory_fields` returns `["name", "phone", "email", "street", "city", "country_id"]`). `website_sale`'s `_get_mandatory_billing_address_fields` UNIONS that set with the address set from `_get_mandatory_address_fields`, so even with the address override alone, 'phone' would come back via the portal path for billing. Hence this second override. """ ctrl = WebsiteSaleMembershipSignup() fields = ctrl._get_mandatory_fields() self.assertNotIn("phone", fields) # Other portal mandatory fields still present. self.assertIn("name", fields) self.assertIn("email", fields) self.assertIn("country_id", fields) def test_11_phone_not_in_mandatory_billing_address_fields(self): """End-to-end check of the billing set: the union of `_get_mandatory_address_fields` + `_get_mandatory_fields` (which is what `_get_mandatory_billing_address_fields` returns) must NOT contain 'phone'. """ country = self.env.ref("base.es") ctrl = WebsiteSaleMembershipSignup() billing_fields = ctrl._get_mandatory_billing_address_fields(country) self.assertNotIn("phone", billing_fields) self.assertIn("email", billing_fields)