#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Standalone test runner for view configuration unit tests. This allows running tests without the full Odoo test framework. Tests verify: - price_subtotal field is editable in vendor bill form - price_total field is editable in vendor bill form - Fields are readonly in non-vendor-bill contexts Requirements tested: 1.1, 2.1, 5.2 """ import sys import os from lxml import etree def test_view_xml_structure(): """ Test that the view XML file has the correct structure. Requirement 5.2: Use proper view inheritance """ view_path = os.path.join( os.path.dirname(__file__), '../views/account_move_views.xml' ) if not os.path.exists(view_path): return False, f"View file not found at {view_path}" try: tree = etree.parse(view_path) root = tree.getroot() # Check that it's an Odoo XML file if root.tag != 'odoo': return False, f"Root tag should be 'odoo', got '{root.tag}'" # Find the record element records = root.xpath("//record[@id='view_move_form_editable_totals']") if not records: return False, "Could not find record with id 'view_move_form_editable_totals'" record = records[0] # Check model if record.get('model') != 'ir.ui.view': return False, f"Record model should be 'ir.ui.view', got '{record.get('model')}'" # Check inherit_id field inherit_fields = record.xpath(".//field[@name='inherit_id']") if not inherit_fields: return False, "Missing inherit_id field" inherit_ref = inherit_fields[0].get('ref') if inherit_ref != 'account.view_move_form': return False, f"inherit_id should reference 'account.view_move_form', got '{inherit_ref}'" return True, "View XML structure is correct" except Exception as e: return False, f"Error parsing XML: {e}" def test_price_subtotal_field_editable(): """ Test that price_subtotal field is made editable in the view. Requirement 1.1: price_subtotal field should be editable in vendor bill form """ view_path = os.path.join( os.path.dirname(__file__), '../views/account_move_views.xml' ) try: tree = etree.parse(view_path) root = tree.getroot() # Find the xpath that modifies price_subtotal xpath_elements = root.xpath( "//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_subtotal']\"]" ) if not xpath_elements: return False, "XPath for price_subtotal field not found in view" xpath_element = xpath_elements[0] # Check that readonly attribute is set to 0 readonly_attrs = xpath_element.xpath(".//attribute[@name='readonly']") if not readonly_attrs: return False, "Readonly attribute not set for price_subtotal" if readonly_attrs[0].text != '0': return False, f"price_subtotal readonly should be '0', got '{readonly_attrs[0].text}'" # Check that attrs attribute exists for conditional readonly attrs_elements = xpath_element.xpath(".//attribute[@name='attrs']") if not attrs_elements: return False, "Attrs attribute not set for price_subtotal" attrs_text = attrs_elements[0].text if 'readonly' not in attrs_text: return False, "Attrs should contain readonly condition" if 'draft' not in attrs_text: return False, "Attrs should reference draft state" return True, "price_subtotal field is correctly configured as editable" except Exception as e: return False, f"Error checking price_subtotal field: {e}" def test_price_total_field_editable(): """ Test that price_total field is made editable in the view. Requirement 2.1: price_total field should be editable in vendor bill form """ view_path = os.path.join( os.path.dirname(__file__), '../views/account_move_views.xml' ) try: tree = etree.parse(view_path) root = tree.getroot() # Find the xpath that modifies price_total xpath_elements = root.xpath( "//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_total']\"]" ) if not xpath_elements: return False, "XPath for price_total field not found in view" xpath_element = xpath_elements[0] # Check that readonly attribute is set to 0 readonly_attrs = xpath_element.xpath(".//attribute[@name='readonly']") if not readonly_attrs: return False, "Readonly attribute not set for price_total" if readonly_attrs[0].text != '0': return False, f"price_total readonly should be '0', got '{readonly_attrs[0].text}'" # Check that attrs attribute exists for conditional readonly attrs_elements = xpath_element.xpath(".//attribute[@name='attrs']") if not attrs_elements: return False, "Attrs attribute not set for price_total" attrs_text = attrs_elements[0].text if 'readonly' not in attrs_text: return False, "Attrs should contain readonly condition" if 'draft' not in attrs_text: return False, "Attrs should reference draft state" return True, "price_total field is correctly configured as editable" except Exception as e: return False, f"Error checking price_total field: {e}" def test_fields_readonly_conditions(): """ Test that fields have attrs to make them readonly when state is not draft. Requirement 5.2: Fields should be readonly in non-draft states """ view_path = os.path.join( os.path.dirname(__file__), '../views/account_move_views.xml' ) try: tree = etree.parse(view_path) root = tree.getroot() # Check price_subtotal attrs xpath_subtotal = root.xpath( "//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_subtotal']\"]" ) if not xpath_subtotal: return False, "price_subtotal xpath not found" attrs_subtotal = xpath_subtotal[0].xpath(".//attribute[@name='attrs']") if not attrs_subtotal: return False, "Attrs not set for price_subtotal" attrs_text = attrs_subtotal[0].text if 'readonly' not in attrs_text: return False, "price_subtotal attrs should contain readonly condition" if 'parent.state' not in attrs_text: return False, "price_subtotal attrs should reference parent.state" # Check price_total attrs xpath_total = root.xpath( "//xpath[@expr=\"//field[@name='invoice_line_ids']/tree//field[@name='price_total']\"]" ) if not xpath_total: return False, "price_total xpath not found" attrs_total = xpath_total[0].xpath(".//attribute[@name='attrs']") if not attrs_total: return False, "Attrs not set for price_total" attrs_text = attrs_total[0].text if 'readonly' not in attrs_text: return False, "price_total attrs should contain readonly condition" if 'parent.state' not in attrs_text: return False, "price_total attrs should reference parent.state" return True, "Both fields have correct readonly conditions based on state" except Exception as e: return False, f"Error checking readonly conditions: {e}" def test_view_inheritance_pattern(): """ Test that the view follows Odoo's inheritance best practices. Requirement 5.2: Use proper view inheritance """ view_path = os.path.join( os.path.dirname(__file__), '../views/account_move_views.xml' ) try: tree = etree.parse(view_path) root = tree.getroot() # Check that we're using xpath for modifications xpaths = root.xpath("//xpath") if len(xpaths) < 2: return False, f"Expected at least 2 xpath elements, found {len(xpaths)}" # Check that we're using position="attributes" for field modifications for xpath in xpaths: position = xpath.get('position') if position != 'attributes': return False, f"XPath should use position='attributes', got '{position}'" # Check that we're using attribute elements attributes = root.xpath("//attribute") if len(attributes) < 4: # At least 2 fields × 2 attributes each return False, f"Expected at least 4 attribute elements, found {len(attributes)}" return True, "View follows proper inheritance patterns" except Exception as e: return False, f"Error checking inheritance pattern: {e}" if __name__ == '__main__': print("Running View Configuration Unit Tests for vendor-bill-editable-totals") print("=" * 70) tests = [ ("View XML structure", test_view_xml_structure), ("price_subtotal field editable", test_price_subtotal_field_editable), ("price_total field editable", test_price_total_field_editable), ("Fields readonly conditions", test_fields_readonly_conditions), ("View inheritance pattern", test_view_inheritance_pattern), ] passed = 0 failed = 0 for test_name, test_func in tests: print(f"\nTest: {test_name}") print("-" * 70) try: success, message = test_func() if success: print(f"✓ PASSED: {message}") passed += 1 else: print(f"✗ FAILED: {message}") failed += 1 except Exception as e: print(f"✗ FAILED with exception: {e}") import traceback traceback.print_exc() failed += 1 print("\n" + "=" * 70) print(f"Results: {passed} passed, {failed} failed out of {len(tests)} tests") if failed == 0: print("✓ All view configuration tests passed!") sys.exit(0) else: print("✗ Some view configuration tests failed!") sys.exit(1)