import base64
from lxml import html
from unittest.mock import patch
from odoo import exceptions
from odoo.tools import mute_logger
from odoo.tests.common import users
from odoo.tests import Form, HttpCase, tagged, warmup
from odoo.addons.mail.tests.common import MailCase
from odoo.addons.marketing_card.controllers.marketing_card import SOCIAL_NETWORK_USER_AGENTS
from .common import MarketingCardCommon, mock_image_render, VALID_JPEG
def _extract_values_from_document(rendered_document):
return {
'body': rendered_document.find('.//div[@id="body"]'),
'header': rendered_document.find('.//span[@id="header"]'),
'subheader': rendered_document.find('.//span[@id="subheader"]'),
'section': rendered_document.find('.//span[@id="section"]'),
'sub_section1': rendered_document.find('.//span[@id="sub_section1"]'),
'sub_section2': rendered_document.find('.//span[@id="sub_section2"]'),
'button': rendered_document.find('.//span[@id="button"]'),
'image1': rendered_document.find('.//img[@id="image1"]'),
'image2': rendered_document.find('.//img[@id="image2"]'),
}
class TestMarketingCardMail(MailCase, MarketingCardCommon):
@users('marketing_card_user')
@warmup
@mute_logger('odoo.addons.mail.models.mail_mail')
def test_campaign_send_mailing(self):
campaign = self.campaign.with_user(self.env.user)
self.env.user.sudo().groups_id += self.env.ref('mass_mailing.group_mass_mailing_user')
partners = self.env['res.partner'].sudo().create([{'name': f'Part{n}', 'email': f'partn{n}@test.lan'} for n in range(7)])
mailing_context = campaign.action_share().get('context') | {
'default_email_from': 'test@test.lan',
'default_mailing_domain': [('id', 'in', partners.ids[:5])],
'default_reply_to': 'test@test.lan',
}
mailing = Form(self.env['mailing.mailing'].with_context(mailing_context)).save()
mailing.body_html = mailing.body_arch # normally the js html_field would fill this in
# sending mailing before generating cards sends to no-one
self.assertTrue(mailing.card_requires_sync_count)
with self.assertRaises(exceptions.UserError, msg="There are no recipients selected."):
mailing._action_send_mail()
with self.assertRaises(exceptions.UserError, msg="You should update all the cards before scheduling a mailing."):
mailing.action_launch()
# once cards are updated they can be sent
with self.mock_image_renderer():
mailing.action_update_cards()
self.assertEqual(len(self._wkhtmltoimage_bodies), 5)
self.assertFalse(mailing.card_requires_sync_count)
mailing.action_launch()
mailing.action_cancel()
# modifying the domain such that there are missing cards prevents sending again
mailing.mailing_domain = [('id', 'in', partners.ids[1:6])]
mailing._compute_card_requires_sync_count()
self.assertTrue(mailing.card_requires_sync_count)
with self.assertRaises(exceptions.UserError, msg="You should update all the cards before scheduling a mailing."):
mailing.action_launch()
# updating when the campaign was not modified only updates cards that need to be
with self.mock_image_renderer():
mailing.action_update_cards()
self.assertEqual(len(self._wkhtmltoimage_bodies), 1)
self.assertFalse(mailing.card_requires_sync_count)
# modifying the campaign should lead to all cards relevant being re-rendered
campaign.content_header = "New Header"
mailing._compute_card_requires_sync_count()
self.assertTrue(mailing.card_requires_sync_count)
with self.mock_image_renderer():
mailing.action_update_cards()
self.assertEqual(len(self._wkhtmltoimage_bodies), 5)
with self.mock_mail_gateway(), self.assertQueryCount(243):
mailing._action_send_mail()
cards = self.env['card.card'].search([('campaign_id', '=', campaign.id)])
self.assertEqual(len(cards), 6)
self.assertEqual(len(cards.filtered(lambda card: not card.requires_sync)), 5)
self.assertEqual(len(self._mails), 5)
IrHttp = self.env['ir.http']
for sent_mail in self._mails:
record_id = int(sent_mail['object_id'].split('-')[0])
card = cards.filtered(lambda card: card.res_id == record_id)
self.assertEqual(len(card), 1)
preview_url = f"{campaign.get_base_url()}/cards/{IrHttp._slug(card)}/preview"
image_url = f"{campaign.get_base_url()}/cards/{IrHttp._slug(card)}/card.jpg"
self.assertIn(f'
"""
campaign.body_html = arbitrary_qweb
# Normally, this should raise an AccessError, as `body_html` is a related to `card_template_id.body`
# and the user does not have the write rights on `card.template`.
# However, the ORM doesn't forward the new value to the related,
# and the new value is put in the cache without error.
# In the real world, using the web client or the XMLRPC API,
# a user would have to do this operation in two requests:
# First set the body_html on the campaign,
# Then trigger the render (e.g. with `action_update_cards`),
# and the cache would have changed between the two operations, hence setting an arbitrary value on body_html
# on a campaign wouldn't work.
# Just ensure that the value is well not written in db, nor on the current campaign, nor on the related.
# Force a cache invalidation to force a re-fetch from database
campaign.invalidate_recordset(fnames=['body_html'])
self.assertTrue(arbitrary_qweb not in campaign.body_html)
self.assertTrue(arbitrary_qweb not in campaign.card_template_id.body)
with self.assertRaisesRegex(exceptions.AccessError, 'You are not allowed to modify'):
campaign.card_template_id.body = arbitrary_qweb
def test_mail_render_security_render_field_write_access(self):
"""Check the rendered fields on card.campaign are not both rendered and writeable.
See _check_access_right_dynamic_template override.
"""
CardCampaign = self.env['card.campaign'].with_user(self.marketing_card_manager)
# Asserts all render fields are related to card.template, not stored on the campaign itself, and readonly
# If one of the render fields doesn't fulfil this assumption, the `_unrestricted_rendering = True` must be
# reconsidered for security reasons.
self.assertTrue(
all(
field.related_field.model_name == 'card.template'
and not field.store
and field.readonly
for field in CardCampaign._fields.values() if hasattr(field, 'render_engine')
)
)
# Asserts the manager doesn't have write access to card.template
self.assertFalse(CardCampaign.card_template_id.has_access('write'))