2025-03-27 10:49:45 -04:00
from odoo . tests . common import TransactionCase
from odoo . tools . misc import find_in_path
2025-03-27 16:34:09 -04:00
from datetime import datetime
2025-03-28 08:00:20 -04:00
import base64
2025-03-27 10:49:45 -04:00
class TestHtmlToPdf ( TransactionCase ) :
2025-03-27 16:34:09 -04:00
"""
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 .
"""
2025-03-27 10:49:45 -04:00
@classmethod
def setUpClass ( cls ) :
super ( ) . setUpClass ( )
2025-03-27 16:34:09 -04:00
# Set up sender and alias for testing
cls . sender_email = " sender@example.com "
cls . alias_email = " test-invoices@example.com "
2025-03-27 10:49:45 -04:00
# 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 : 20 px ; }
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 >
"""
2025-03-27 16:34:09 -04:00
# 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
2025-03-27 10:49:45 -04:00
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 " )
2025-03-28 08:00:20 -04:00
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. \n It has no HTML formatting. \n Just plain text content. \n \n Regards, \n Test Sender "
2025-03-28 08:46:27 -04:00
2025-03-28 08:00:20 -04:00
# Call the method to convert plain text to PDF
pdf_content = self . account_move . _html_to_pdf ( plain_text )
2025-03-28 08:46:27 -04:00
2025-03-28 08:00:20 -04:00
# 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 " )
2025-03-27 16:34:09 -04:00
# 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 .
2025-03-27 10:49:45 -04:00
2025-03-27 16:34:09 -04:00
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 .
2025-03-27 10:49:45 -04:00
"""
2025-03-27 16:34:09 -04:00
# 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 " )
2025-03-27 10:49:45 -04:00
2025-03-27 16:34:09 -04:00
# 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 : 7 bit
{ 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 " ) ]
2025-03-27 10:49:45 -04:00
)
2025-03-27 16:34:09 -04:00
# 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 "
2025-03-27 10:49:45 -04:00
)
2025-03-28 08:00:20 -04:00
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 .
"""
2025-03-28 08:46:27 -04:00
2025-03-28 08:00:20 -04:00
# 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 : 7 bit
{ 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
2025-03-28 08:46:27 -04:00
self . env [ " mail.thread " ] . with_context ( fetchmail_server_id = 1 ) . message_process (
model = None ,
message = raw_email ,
save_original = True ,
strip_attachments = False ,
)
2025-03-28 08:00:20 -04:00
# 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 "
)
2025-03-28 08:46:27 -04:00