319 lines
11 KiB
Python
319 lines
11 KiB
Python
from odoo.tests.common import TransactionCase
|
|
from odoo.tools.misc import find_in_path
|
|
from datetime import datetime
|
|
import base64
|
|
|
|
|
|
class TestHtmlToPdf(TransactionCase):
|
|
"""
|
|
Test the functionality of converting an email to a PDF when it's received for a supplier invoice.
|
|
|
|
This test simulates the full flow of an email being received through
|
|
the mail alias and verifies that an account move is created with a PDF
|
|
attachment generated from the email content.
|
|
|
|
It's important to also run the tests in account, which can be done using the test tag:
|
|
|
|
`/account:TestAccountIncomingSupplierInvoice.test_extend_with_attachments_document_formats`
|
|
|
|
This specific test ensures we are not creating more attachments than necessary.
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
# Set up sender and alias for testing
|
|
cls.sender_email = "sender@example.com"
|
|
cls.alias_email = "test-invoices@example.com"
|
|
|
|
# Check if wkhtmltopdf is available
|
|
cls.wkhtmltopdf_available = bool(find_in_path("wkhtmltopdf"))
|
|
|
|
# Get the model class to access the classmethod
|
|
cls.account_move = cls.env["account.move"]
|
|
|
|
# Simple test HTML content
|
|
cls.test_html = """
|
|
<html>
|
|
<head>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 20px; }
|
|
h1 { color: #333; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Test HTML Document</h1>
|
|
<p>This is a test paragraph with <b>bold text</b> and <i>italic text</i>.</p>
|
|
<ul>
|
|
<li>List item 1</li>
|
|
<li>List item 2</li>
|
|
<li>List item 3</li>
|
|
</ul>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
# Set up a supplier for testing
|
|
cls.supplier = cls.env["res.partner"].create(
|
|
{
|
|
"name": "Test Supplier",
|
|
"email": cls.sender_email,
|
|
}
|
|
)
|
|
|
|
# Set up a journal for incoming invoices
|
|
cls.journal = cls.env["account.journal"].search(
|
|
[("type", "=", "purchase")], limit=1
|
|
)
|
|
|
|
# Set up the mail alias domain
|
|
|
|
cls.alias_domain = cls.env["mail.alias.domain"].create({"name": "example.com"})
|
|
# Set up a mail alias for the journal
|
|
cls.alias = cls.env["mail.alias"].create(
|
|
{
|
|
"alias_name": cls.alias_email,
|
|
"alias_model_id": cls.env["ir.model"]
|
|
.search([("model", "=", "account.move")], limit=1)
|
|
.id,
|
|
"alias_domain_id": cls.alias_domain.id,
|
|
"alias_defaults": f'{{"move_type": "in_invoice", "journal_id": {cls.journal.id}}}',
|
|
}
|
|
)
|
|
cls.journal.alias_id = cls.alias
|
|
|
|
def test_html_to_pdf_conversion(self):
|
|
"""Test the direct HTML to PDF conversion."""
|
|
if not self.wkhtmltopdf_available:
|
|
self.skipTest("wkhtmltopdf not available")
|
|
|
|
# Call the method to convert HTML to PDF
|
|
pdf_content = self.account_move._html_to_pdf(self.test_html)
|
|
|
|
# Verify the PDF was created
|
|
self.assertTrue(pdf_content, "PDF content should be generated")
|
|
|
|
# Verify it's a valid PDF
|
|
self.assertTrue(
|
|
pdf_content.startswith(b"%PDF-"), "Content should be a valid PDF"
|
|
)
|
|
self.assertTrue(len(pdf_content) > 100, "PDF should have reasonable size")
|
|
|
|
def test_plain_text_to_pdf_conversion(self):
|
|
"""Test the conversion of plain text email to PDF."""
|
|
if not self.wkhtmltopdf_available:
|
|
self.skipTest("wkhtmltopdf not available")
|
|
|
|
# Simple plain text content
|
|
plain_text = "This is a plain text email.\nIt has no HTML formatting.\nJust plain text content.\n\nRegards,\nTest Sender"
|
|
|
|
|
|
|
|
# Call the method to convert plain text to PDF
|
|
pdf_content = self.account_move._html_to_pdf(plain_text)
|
|
|
|
|
|
|
|
# Verify the PDF was created
|
|
self.assertTrue(
|
|
pdf_content, "PDF content should be generated even for plain text"
|
|
)
|
|
|
|
# Verify it's a valid PDF
|
|
is_pdf = pdf_content and pdf_content.startswith(b"%PDF-")
|
|
|
|
self.assertTrue(is_pdf, "Content should be a valid PDF")
|
|
self.assertTrue(len(pdf_content) > 100, "PDF should have reasonable size")
|
|
|
|
# Integration test
|
|
def test_account_email_to_pdf_full_flow(self):
|
|
"""Test that an email without attachments is correctly converted to PDF
|
|
and an account move is created.
|
|
|
|
This test simulates the full flow of an email being received through
|
|
the mail alias and verifies that an account move is created with a PDF
|
|
attachment generated from the email content.
|
|
"""
|
|
|
|
# Get the current timestamp
|
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
message_id = f"<test123_{timestamp}@example.com>"
|
|
subject = f"Invoice from Test Supplier {timestamp}"
|
|
date = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
|
|
|
# HTML body of the email
|
|
html_body = "<html><body><h1>Invoice Test</h1><p>This is a test invoice from Test Supplier.</p><p>Amount: $100.00</p><p>Date: 2025-03-27</p></body></html>"
|
|
|
|
# Construct the raw email with proper headers
|
|
# Make sure the From header is correctly formatted for Odoo to parse
|
|
raw_email = f"""Return-Path: <{self.sender_email}>
|
|
X-Original-To: {self.alias_email}
|
|
Delivered-To: {self.alias_email}
|
|
Received: from mail.example.com (mail.example.com [192.168.1.1])
|
|
From: {self.sender_email}
|
|
To: {self.alias_email}
|
|
Subject: {subject}
|
|
Date: {date}
|
|
Message-ID: {message_id}
|
|
MIME-Version: 1.0
|
|
Content-Type: text/html; charset=UTF-8
|
|
Content-Transfer-Encoding: 7bit
|
|
|
|
{html_body}"""
|
|
|
|
# Process the email through the mail gateway
|
|
# This simulates what happens when fetchmail receives an email
|
|
invoice_count_before = self.env["account.move"].search_count(
|
|
[("move_type", "=", "in_invoice")]
|
|
)
|
|
|
|
# Process the email through the mail gateway
|
|
# We need to specify the model explicitly since we're in a test environment
|
|
# In production, the alias would determine this automatically
|
|
self.env["mail.thread"].with_context(fetchmail_server_id=1).message_process(
|
|
model=None,
|
|
message=raw_email,
|
|
save_original=True,
|
|
strip_attachments=False,
|
|
)
|
|
|
|
# Count account moves after the test
|
|
# Note: move_type is the correct field name in Odoo, even if the linter doesn't recognize it
|
|
move_count_after = self.env["account.move"].search_count(
|
|
[("move_type", "=", "in_invoice")]
|
|
)
|
|
self.assertEqual(
|
|
move_count_after,
|
|
invoice_count_before + 1,
|
|
"A new account move should be created",
|
|
)
|
|
|
|
# Find the newly created invoice
|
|
invoice = self.env["account.move"].search(
|
|
[("move_type", "=", "in_invoice")], order="id desc", limit=1
|
|
)
|
|
self.assertTrue(invoice, "An account move should be created")
|
|
|
|
messages = invoice.message_ids
|
|
self.assertEqual(len(messages), 1, "The account move should have one message")
|
|
|
|
attachment = messages.attachment_ids
|
|
|
|
self.assertTrue(attachment, "The message should have an attachment")
|
|
|
|
attachment = attachment.filtered(
|
|
lambda a: a.mimetype == "application/pdf" or ".pdf" in a.name
|
|
)
|
|
self.assertTrue(attachment, "The message should have a PDF attachment")
|
|
|
|
# Verify the invoice is linked to the correct supplier
|
|
self.assertEqual(
|
|
invoice.partner_id,
|
|
self.supplier,
|
|
"The invoice should be linked to the correct supplier",
|
|
)
|
|
|
|
# Verify the invoice type
|
|
self.assertEqual(
|
|
invoice.move_type, "in_invoice", "The invoice should be an incoming invoice"
|
|
)
|
|
|
|
def test_plain_text_email_to_pdf_full_flow(self):
|
|
"""Test that a plain text email without attachments is correctly converted to PDF
|
|
and an account move is created.
|
|
|
|
This test simulates the full flow of a plain text email being received through
|
|
the mail alias and verifies that an account move is created with a PDF
|
|
attachment generated from the email content.
|
|
"""
|
|
|
|
|
|
# Get the current timestamp
|
|
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
|
|
message_id = f"<test123_{timestamp}@example.com>"
|
|
subject = f"Plain Text Invoice from Test Supplier {timestamp}"
|
|
date = datetime.now().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
|
|
|
# Plain text body of the email
|
|
plain_text_body = """Invoice Test
|
|
|
|
This is a test invoice from Test Supplier.
|
|
Amount: $100.00
|
|
Date: 2025-03-27
|
|
|
|
Thank you for your business.
|
|
"""
|
|
|
|
# Construct the raw email with proper headers
|
|
# Make sure the From header is correctly formatted for Odoo to parse
|
|
raw_email = f"""Return-Path: <{self.sender_email}>
|
|
X-Original-To: {self.alias_email}
|
|
Delivered-To: {self.alias_email}
|
|
Received: from mail.example.com (mail.example.com [192.168.1.1])
|
|
From: {self.sender_email}
|
|
To: {self.alias_email}
|
|
Subject: {subject}
|
|
Date: {date}
|
|
Message-ID: {message_id}
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 7bit
|
|
|
|
{plain_text_body}"""
|
|
|
|
# Process the email through the mail gateway
|
|
# This simulates what happens when fetchmail receives an email
|
|
invoice_count_before = self.env["account.move"].search_count(
|
|
[("move_type", "=", "in_invoice")]
|
|
)
|
|
|
|
# Process the email through the mail gateway
|
|
|
|
self.env["mail.thread"].with_context(fetchmail_server_id=1).message_process(
|
|
model=None,
|
|
message=raw_email,
|
|
save_original=True,
|
|
strip_attachments=False,
|
|
)
|
|
|
|
# Count account moves after the test
|
|
move_count_after = self.env["account.move"].search_count(
|
|
[("move_type", "=", "in_invoice")]
|
|
)
|
|
self.assertEqual(
|
|
move_count_after,
|
|
invoice_count_before + 1,
|
|
"A new account move should be created from plain text email",
|
|
)
|
|
|
|
# Find the newly created invoice
|
|
invoice = self.env["account.move"].search(
|
|
[("move_type", "=", "in_invoice")], order="id desc", limit=1
|
|
)
|
|
self.assertTrue(
|
|
invoice, "An account move should be created from plain text email"
|
|
)
|
|
|
|
messages = invoice.message_ids
|
|
self.assertEqual(len(messages), 1, "The account move should have one message")
|
|
|
|
attachment = messages.attachment_ids
|
|
self.assertTrue(attachment, "The message should have an attachment")
|
|
|
|
attachment = attachment.filtered(
|
|
lambda a: a.mimetype == "application/pdf" or ".pdf" in a.name
|
|
)
|
|
self.assertTrue(attachment, "The message should have a PDF attachment")
|
|
|
|
# Verify PDF content if possible
|
|
if attachment:
|
|
pdf_data = base64.b64decode(attachment.datas) if attachment.datas else b""
|
|
|
|
self.assertTrue(
|
|
pdf_data.startswith(b"%PDF-"),
|
|
f"Attachment should be a valid PDF even when source is plain text, starts with {pdf_data[:20]}",
|
|
)
|
|
self.assertTrue(
|
|
len(pdf_data) > 100, "PDF from plain text should have reasonable size"
|
|
)
|
|
|