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:
push:
branches:
- "**"
pull_request:
branches:
- "**"
workflow_dispatch:
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:
runs-on: ubuntu-latest
needs: test
if: github.event_name != 'pull_request'
steps:
- 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.
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.
@@ -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.
=============================== =============================================== ==============================================
Área Cobertura Prueba
=============================== =============================================== ==============================================
Impuesto contra cuenta 55 plantillas de ``tax_do.xml`` contra cuenta ``account_do.tests.test_module.TAX_TEMPLATE_AUDIT``
Clasificación fiscal ``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``
=============================== =============================================== ==============================================
.. list-table::
:header-rows: 1
* - Área
- Cobertura
- Prueba
* - Impuesto contra cuenta
- 55 plantillas de ``tax_do.xml`` contra cuenta.
- ``account_do.tests.test_module.TAX_TEMPLATE_AUDIT``
* - Clasificación fiscal
- ``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
==========
Every pushed commit builds, validates, and publishes the package to the Gitea
PyPI-compatible package registry. The workflow intentionally does not use
``actions/upload-artifact`` because current Gitea Actions runners do not support
the GitHub artifact API required by ``upload-artifact@v4``.
Every pushed commit installs the package, compiles the Python sources, runs the
module tests, builds the distribution, and validates it with ``twine check``.
Only branches named with the Tryton series, such as ``8.0``, continue to the
publishing step after those checks pass.
The publishing workflow creates a CI-only package version by appending the run
number to the module version from ``tryton.cfg``. For example, a module version
of ``8.0.0`` is published as ``8.0.0.post123`` on run number ``123``.
The publishing workflow creates a CI-only package version by reading the
published versions in Gitea's PyPI-compatible package registry and assigning the
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
repository for Twine and does not support ``twine upload --skip-existing``.
Required repository secrets:
* ``GITEA_PACKAGE_USER``
* ``GITEA_PACKAGE_TOKEN``
* ``REGISTRY_USER``
* ``REGISTRY_PASSWORD``
Publish a release branch:
.. code-block:: console
git switch -c 8.0.0
git push origin 8.0.0
git switch 8.0
git push origin 8.0
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"
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"
msgid "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"
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"
msgid "Activos intangibles"
msgstr "Activos intangibles"
+239
View File
@@ -1,6 +1,7 @@
import unittest
import datetime
from collections import Counter
from configparser import ConfigParser
from decimal import Decimal
from pathlib import Path
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
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):
"Test account_do module"
module = 'account_do'
@@ -448,6 +464,229 @@ class AccountDoTestCase(ModuleTestCase):
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):
records = list(_iter_xml_records(
'account_chart_do.xml',