Strengthen account_do tests and publishing checks

Add static coverage for module configuration, XML inventory, tax views, translations, migration metadata, and the Gitea publishing workflow.

Make Gitea Actions run install, compileall, unittest, build, and twine check before publishing packages from series branches.

Fix packaging documentation and RST rendering issues so package validation passes before upload.
This commit is contained in:
José Arturo García
2026-08-09 13:24:59 -04:00
parent 1e05afd8bb
commit fc5cef658f
6 changed files with 330 additions and 25 deletions
+40 -1
View File
@@ -1,14 +1,53 @@
name: Publish Package name: Test and Publish Package
on: on:
push: push:
branches: branches:
- "**" - "**"
pull_request:
branches:
- "**"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install package and test dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e '.[test]'
- name: Compile Python sources
run: |
python -m compileall .
- name: Run module tests
run: |
python -m unittest discover -s tests -p 'test*.py'
- name: Build package
run: |
python -m pip install build twine
python -m build
- name: Check package
run: |
python -m twine check dist/*
publish: publish:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: test
if: github.event_name != 'pull_request'
steps: steps:
- name: Checkout - name: Checkout
+31 -13
View File
@@ -23,7 +23,7 @@ Pruebas
Las pruebas validan la instalación del módulo, la creación del plan para una compañía, la integridad de referencias XML, la clasificación contable principal, los códigos fiscales y las categorías fiscales dominicanas. Las pruebas validan la instalación del módulo, la creación del plan para una compañía, la integridad de referencias XML, la clasificación contable principal, los códigos fiscales y las categorías fiscales dominicanas.
Cumplimiento base NIIF Cumplimiento base NIIF
--------------------- ----------------------
``account_do`` cubre el 100% de la base estructural que corresponde a una localización contable: catálogo NIIF/RD, tipos de presentación, cuentas fiscales dominicanas, impuestos, reglas, metadata legal y pruebas de instalación. La medición NIIF, estimaciones, cierres, estados financieros, notas y revelaciones dependen de políticas contables y módulos operativos complementarios. ``account_do`` cubre el 100% de la base estructural que corresponde a una localización contable: catálogo NIIF/RD, tipos de presentación, cuentas fiscales dominicanas, impuestos, reglas, metadata legal y pruebas de instalación. La medición NIIF, estimaciones, cierres, estados financieros, notas y revelaciones dependen de políticas contables y módulos operativos complementarios.
@@ -49,15 +49,33 @@ Matriz de auditoría
La cobertura de auditoría fiscal queda formalizada en pruebas para evitar cambios silenciosos en cuentas, clasificación o vigencias. La cobertura de auditoría fiscal queda formalizada en pruebas para evitar cambios silenciosos en cuentas, clasificación o vigencias.
=============================== =============================================== ============================================== .. list-table::
Área Cobertura Prueba :header-rows: 1
=============================== =============================================== ==============================================
Impuesto contra cuenta 55 plantillas de ``tax_do.xml`` contra cuenta ``account_do.tests.test_module.TAX_TEMPLATE_AUDIT`` * - Área
Clasificación fiscal ``tax_kind``, ``tax_fiscal_type`` y aplicación ``test_tax_templates_are_classified`` - Cobertura
Metadata legal y vigencia Referencia, artículo, formulario, estado y fecha ``test_tax_templates_are_classified`` - Prueba
Signos de tax codes Plantillas y códigos reales creados por compañía ``test_tax_code_templates_use_expected_signs`` y chart real * - Impuesto contra cuenta
Reglas con sustitución Impuesto cheques 0.15% hasta 2026-07-02 y 0.20% desde 2026-07-03 ``test_tax_rule_templates_include_date_sensitive_bank_tax`` - 55 plantillas de ``tax_do.xml`` contra cuenta.
Documentación contra XML La matriz apunta a XML real y falla ante ids/campos faltantes ``test_xml_references_are_resolved_inside_module`` - ``account_do.tests.test_module.TAX_TEMPLATE_AUDIT``
Plan NIIF contra XML Códigos NIIF críticos, tipos y políticas ``test_ifrs_chart_is_complete_and_typed`` y ``test_ifrs_policy_matrix_is_represented_in_chart`` * - Clasificación fiscal
Facturas reales Factura venta/compra posteada con ITBIS 18% ``account_invoice_do.tests.test_module`` - ``tax_kind``, ``tax_fiscal_type`` y aplicación.
=============================== =============================================== ============================================== - ``test_tax_templates_are_classified``
* - Metadata legal y vigencia
- Referencia, artículo, formulario, estado y fecha.
- ``test_tax_templates_are_classified``
* - Signos de tax codes
- Plantillas y códigos reales creados por compañía.
- ``test_tax_code_templates_use_expected_signs`` y chart real.
* - Reglas con sustitución
- Impuesto cheques 0.15% hasta 2026-07-02 y 0.20% desde 2026-07-03.
- ``test_tax_rule_templates_include_date_sensitive_bank_tax``
* - Documentación contra XML
- La matriz apunta a XML real y falla ante ids/campos faltantes.
- ``test_xml_references_are_resolved_inside_module``
* - Plan NIIF contra XML
- Códigos NIIF críticos, tipos y políticas.
- ``test_ifrs_chart_is_complete_and_typed`` y ``test_ifrs_policy_matrix_is_represented_in_chart``
* - Facturas reales
- Factura venta/compra posteada con ITBIS 18%.
- ``account_invoice_do.tests.test_module``
+12 -11
View File
@@ -1,29 +1,30 @@
Publishing Publishing
========== ==========
Every pushed commit builds, validates, and publishes the package to the Gitea Every pushed commit installs the package, compiles the Python sources, runs the
PyPI-compatible package registry. The workflow intentionally does not use module tests, builds the distribution, and validates it with ``twine check``.
``actions/upload-artifact`` because current Gitea Actions runners do not support Only branches named with the Tryton series, such as ``8.0``, continue to the
the GitHub artifact API required by ``upload-artifact@v4``. publishing step after those checks pass.
The publishing workflow creates a CI-only package version by appending the run The publishing workflow creates a CI-only package version by reading the
number to the module version from ``tryton.cfg``. For example, a module version published versions in Gitea's PyPI-compatible package registry and assigning the
of ``8.0.0`` is published as ``8.0.0.post123`` on run number ``123``. next patch version for the branch series. For example, branch ``8.0`` publishes
``8.0.0`` when no package exists yet, then ``8.0.1``, ``8.0.2``, and so on.
This is necessary because Gitea's PyPI-compatible registry is a custom package This is necessary because Gitea's PyPI-compatible registry is a custom package
repository for Twine and does not support ``twine upload --skip-existing``. repository for Twine and does not support ``twine upload --skip-existing``.
Required repository secrets: Required repository secrets:
* ``GITEA_PACKAGE_USER`` * ``REGISTRY_USER``
* ``GITEA_PACKAGE_TOKEN`` * ``REGISTRY_PASSWORD``
Publish a release branch: Publish a release branch:
.. code-block:: console .. code-block:: console
git switch -c 8.0.0 git switch 8.0
git push origin 8.0.0 git push origin 8.0
Install from Gitea: Install from Gitea:
+4
View File
@@ -1074,6 +1074,10 @@ msgctxt "model:account.account.type.template,name:do_type_income_tax"
msgid "Impuesto a las ganancias" msgid "Impuesto a las ganancias"
msgstr "Impuesto a las ganancias" msgstr "Impuesto a las ganancias"
msgctxt "model:account.account.type.template,name:do_type_control"
msgid "Cuentas de cierre y control"
msgstr "Cuentas de cierre y control"
msgctxt "model:account.account.type.template,name:do_type_intangible" msgctxt "model:account.account.type.template,name:do_type_intangible"
msgid "Activos intangibles" msgid "Activos intangibles"
msgstr "Activos intangibles" msgstr "Activos intangibles"
+4
View File
@@ -1066,6 +1066,10 @@ msgctxt "model:account.account.type.template,name:do_type_income_tax"
msgid "Impuesto a las ganancias" msgid "Impuesto a las ganancias"
msgstr "Impuesto a las ganancias" msgstr "Impuesto a las ganancias"
msgctxt "model:account.account.type.template,name:do_type_control"
msgid "Cuentas de cierre y control"
msgstr "Cuentas de cierre y control"
msgctxt "model:account.account.type.template,name:do_type_intangible" msgctxt "model:account.account.type.template,name:do_type_intangible"
msgid "Activos intangibles" msgid "Activos intangibles"
msgstr "Activos intangibles" msgstr "Activos intangibles"
+239
View File
@@ -1,6 +1,7 @@
import unittest import unittest
import datetime import datetime
from collections import Counter from collections import Counter
from configparser import ConfigParser
from decimal import Decimal from decimal import Decimal
from pathlib import Path from pathlib import Path
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
@@ -237,6 +238,21 @@ def _iter_xml_records(*filenames):
yield filename, record.get('id'), record.get('model'), values yield filename, record.get('id'), record.get('model'), values
def _po_entries(filename):
entries = []
context = msgid = msgstr = None
for line in (MODULE_DIR / filename).read_text(encoding='utf-8').splitlines():
if line.startswith('msgctxt '):
context = line.split(' ', 1)[1].strip().strip('"')
elif line.startswith('msgid '):
msgid = line.split(' ', 1)[1].strip().strip('"')
elif line.startswith('msgstr '):
msgstr = line.split(' ', 1)[1].strip().strip('"')
entries.append((context, msgid, msgstr))
context = msgid = msgstr = None
return entries
class AccountDoTestCase(ModuleTestCase): class AccountDoTestCase(ModuleTestCase):
"Test account_do module" "Test account_do module"
module = 'account_do' module = 'account_do'
@@ -448,6 +464,229 @@ class AccountDoTestCase(ModuleTestCase):
class AccountDoUnitTestCase(unittest.TestCase): class AccountDoUnitTestCase(unittest.TestCase):
def test_static_project_configuration_is_complete(self):
config = ConfigParser()
config.read(MODULE_DIR / 'tryton.cfg', encoding='utf-8')
def lines(section, option):
return [
line.strip()
for line in config.get(section, option).splitlines()
if line.strip()
]
self.assertEqual(config.get('tryton', 'version'), '8.0.0')
self.assertEqual(
set(lines('tryton', 'depends')),
{'account', 'company', 'currency'})
self.assertEqual(
lines('tryton', 'xml'),
[
'account_chart_do.xml',
'tax_do.xml',
'tax_view.xml',
'tax_code_do.xml',
'tax_rule_do.xml',
])
self.assertEqual(
lines('register', 'model'),
[
'tax.TaxTemplate',
'tax.Tax',
'tax.TaxCode',
'tax.TaxCodeLine',
])
for filename in lines('tryton', 'xml'):
self.assertTrue((MODULE_DIR / filename).is_file())
pyproject = (MODULE_DIR / 'pyproject.toml').read_text(encoding='utf-8')
self.assertIn("name = 'trytond_account_do'", pyproject)
self.assertIn(
"account_do = 'trytond.modules.account_do'", pyproject)
for package_file in [
'README.rst',
'IMPUESTOS_RD.md',
'doc/**/*.rst',
'locale/**/*.po',
'tests/**/*.rst',
]:
self.assertIn(package_file, pyproject)
self.assertEqual(
(MODULE_DIR / '__init__.py').read_text(encoding='utf-8'), '')
def test_xml_record_inventory_is_explicit(self):
expected = {
'account_chart_do.xml': {
'account.account.type.template': 36,
'account.account.template': 281,
},
'tax_do.xml': {
'account.tax.group': 7,
'account.tax.template': 55,
},
'tax_code_do.xml': {
'account.tax.code.template': 49,
'account.tax.code.line.template': 103,
},
'tax_rule_do.xml': {
'account.tax.rule.template': 28,
'account.tax.rule.line.template': 35,
},
'tax_view.xml': {
'ir.ui.view': 4,
},
}
for filename, expected_counts in expected.items():
records = list(_iter_xml_records(filename))
with self.subTest(filename=filename):
self.assertEqual(
dict(Counter(model for _, _, model, _ in records)),
expected_counts)
self.assertFalse([
(record_id, model)
for _, record_id, model, _ in records
if not record_id or not model])
duplicate_ids = [
record_id
for record_id, count in Counter(
record_id for _, record_id, _, _ in records).items()
if count > 1]
self.assertEqual(duplicate_ids, [])
def test_gitea_workflow_tests_before_publish(self):
workflow = (
MODULE_DIR / '.gitea' / 'workflows' / 'publish.yml'
).read_text(encoding='utf-8')
self.assertIn('test:', workflow)
self.assertIn('publish:', workflow)
self.assertIn('needs: test', workflow)
self.assertIn("python -m pip install -e '.[test]'", workflow)
self.assertIn('python -m compileall .', workflow)
self.assertIn(
"python -m unittest discover -s tests -p 'test*.py'", workflow)
self.assertIn('python -m build', workflow)
self.assertLess(
workflow.index('test:'),
workflow.index('publish:'))
self.assertLess(
workflow.index("python -m unittest discover -s tests -p 'test*.py'"),
workflow.index('Set CI package version'))
def test_tax_views_expose_all_localization_fields(self):
localization_fields = [
'tax_kind',
'tax_fiscal_type',
'tax_application',
'dgii_fiscal_status',
'dgii_form_hint',
'dgii_legal_reference',
'dgii_legal_article',
]
form = ET.parse(MODULE_DIR / 'view/tax_form.xml').getroot()
list_ = ET.parse(MODULE_DIR / 'view/tax_list.xml').getroot()
form_fields = [
element.get('name')
for element in form.findall('.//field')
if element.get('name') in localization_fields
]
list_fields = [
element.get('name')
for element in list_.findall('.//field')
if element.get('name') in localization_fields
]
self.assertEqual(form_fields, localization_fields)
self.assertEqual(list_fields, localization_fields[:3])
view_records = {
(values['model'], values['name'], values['inherit'])
for _, _, model, values in _iter_xml_records('tax_view.xml')
if model == 'ir.ui.view'
}
self.assertEqual(view_records, {
('account.tax.template', 'tax_form',
'account.tax_template_view_form'),
('account.tax', 'tax_form', 'account.tax_view_form'),
('account.tax.template', 'tax_list',
'account.tax_template_view_list'),
('account.tax', 'tax_list', 'account.tax_view_list'),
})
def test_translation_catalogs_cover_module_terms(self):
type_templates = {
record_id: values['name']
for _, record_id, model, values in _iter_xml_records(
'account_chart_do.xml')
if model == 'account.account.type.template'
}
account_templates = {
record_id: values['name']
for _, record_id, model, values in _iter_xml_records(
'account_chart_do.xml')
if model == 'account.account.template'
}
required_field_contexts = {
'field:account.tax,tax_kind:',
'field:account.tax.template,tax_kind:',
'field:account.tax,tax_fiscal_type:',
'field:account.tax.template,tax_fiscal_type:',
'field:account.tax,tax_application:',
'field:account.tax.template,tax_application:',
'field:account.tax,dgii_fiscal_status:',
'field:account.tax.template,dgii_fiscal_status:',
'field:account.tax,dgii_form_hint:',
'field:account.tax.template,dgii_form_hint:',
'field:account.tax,dgii_legal_article:',
'field:account.tax.template,dgii_legal_article:',
'field:account.tax,dgii_legal_reference:',
'field:account.tax.template,dgii_legal_reference:',
}
for filename in ['locale/es.po', 'locale/es_419.po']:
entries = _po_entries(filename)
contexts = {context for context, _msgid, _msgstr in entries}
entry_keys = [(context, msgid) for context, msgid, _ in entries]
with self.subTest(filename=filename):
self.assertEqual(
[key for key, count in Counter(entry_keys).items()
if count > 1],
[])
self.assertLessEqual(required_field_contexts, contexts)
for record_id in type_templates:
self.assertIn(
'model:account.account.type.template,name:%s'
% record_id,
contexts)
for record_id in [
'do_account_110201',
'do_account_210101',
'do_account_21021901',
'do_account_21021902',
]:
self.assertIn(
'model:account.account.template,name:%s' % record_id,
contexts)
def test_migration_metadata_is_explicit(self):
from trytond.modules.account_do.tax import (
MODEL_DATA_RENAMES, ROOT_TAX_CODE_CHILDREN)
self.assertEqual(MODEL_DATA_RENAMES, {
'do_tax_group_otros': 'do_tax_group_others',
'do_tc_otros': 'do_tc_others',
'do_tc_otros_propina': 'do_tc_others_tip',
'do_tc_otros_cheques': 'do_tc_others_checks',
'do_tc_otros_activos': 'do_tc_others_assets',
'do_tc_otros_iti': 'do_tc_others_iti',
})
self.assertEqual(ROOT_TAX_CODE_CHILDREN, [
'ITBIS — Balance Neto (Débito Crédito)',
'ISR - Retenciones',
'ISR - Retenciones en la Fuente',
'ISC - Impuesto Selectivo al Consumo',
'CDT INDOTEL 2% (Ley 153-98)',
'Otros Impuestos y Contribuciones',
])
def test_xml_references_are_resolved_inside_module(self): def test_xml_references_are_resolved_inside_module(self):
records = list(_iter_xml_records( records = list(_iter_xml_records(
'account_chart_do.xml', 'account_chart_do.xml',