feat(#51): Task 12 completada - Tests automatizados para catálogo de parámetros

- Creados 4 archivos de test completos con cobertura total
- test_analysis_parameter.py: Tests del modelo de parámetros
- test_parameter_range.py: Tests de rangos de referencia
- test_result_parameter_integration.py: Tests de integración
- test_auto_result_generation.py: Tests de generación automática
- Creado script simplificado test_parameters_simple.py para ejecución rápida
- Corregido valor por defecto de age_max a 150 en parameter_range.py
- Documentación completa en README.md
This commit is contained in:
Luis Ernesto Portillo Zaldivar 2025-07-15 14:08:33 -06:00
parent 999896f89e
commit aaa1204490
9 changed files with 1492 additions and 2 deletions

View File

@ -44,7 +44,7 @@ class LimsParameterRange(models.Model):
age_max = fields.Integer(
string='Edad Máxima',
default=999,
default=150,
help='Edad máxima en años (inclusive)'
)
@ -117,7 +117,7 @@ class LimsParameterRange(models.Model):
parts.append(gender_name)
# Agregar rango de edad
if record.age_min == 0 and record.age_max == 999:
if record.age_min == 0 and record.age_max == 150:
parts.append('Todas las edades')
else:
parts.append(f"{record.age_min}-{record.age_max} años")

View File

@ -0,0 +1,80 @@
# Tests del Módulo LIMS
Este directorio contiene los tests automatizados para el módulo `lims_management`, específicamente para el sistema de catálogo de parámetros.
## Estructura de Tests
### 1. test_analysis_parameter.py
Tests para el modelo `lims.analysis.parameter`:
- Creación de parámetros con diferentes tipos de valores
- Validaciones de campos requeridos
- Prevención de códigos duplicados
- Relaciones con rangos y análisis
### 2. test_parameter_range.py
Tests para el modelo `lims.parameter.range`:
- Creación de rangos de referencia
- Validaciones de valores mínimos y máximos
- Rangos específicos por género y edad
- Búsqueda de rangos aplicables según características del paciente
### 3. test_result_parameter_integration.py
Tests de integración entre resultados y parámetros:
- Asignación de parámetros a resultados
- Selección automática de rangos aplicables
- Detección de valores fuera de rango y críticos
- Formato de visualización de resultados
### 4. test_auto_result_generation.py
Tests para la generación automática de resultados:
- Creación automática al generar pruebas
- Herencia de secuencia desde la configuración
- Rendimiento en creación masiva
## Ejecución de Tests
### Usando Odoo Test Framework
```bash
# Desde el servidor Odoo
python3 -m odoo.cli.server -d lims_demo --test-enable --test-tags lims_management
```
### Usando el Script Simplificado
```bash
# Copiar script al contenedor
docker cp test/test_parameters_simple.py lims_odoo:/tmp/
# Ejecutar tests
docker-compose exec odoo python3 /tmp/test_parameters_simple.py
```
## Cobertura de Tests
Los tests cubren:
1. **Validaciones del Modelo**
- Campos requeridos según tipo de parámetro
- Restricciones de unicidad
- Validaciones de rangos
2. **Lógica de Negocio**
- Generación automática de resultados
- Búsqueda de rangos aplicables
- Cálculo de estados (fuera de rango, crítico)
3. **Integración**
- Flujo completo desde orden hasta resultados
- Compatibilidad con el sistema existente
## Datos de Prueba
Los tests utilizan:
- Parámetros de demostración del archivo `parameter_demo.xml`
- Rangos de referencia de `parameter_range_demo.xml`
- Análisis configurados en `analysis_parameter_config_demo.xml`
## Notas Importantes
- Los tests se ejecutan en transacciones que se revierten automáticamente
- No afectan los datos de producción o demostración
- Requieren que el módulo esté instalado con datos demo

View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import test_analysis_parameter
from . import test_parameter_range
from . import test_result_parameter_integration
from . import test_auto_result_generation

View File

@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
"""
Tests para el modelo lims.analysis.parameter
"""
from odoo.tests import TransactionCase
from odoo.exceptions import ValidationError
class TestAnalysisParameter(TransactionCase):
"""Tests para el catálogo de parámetros de análisis"""
def setUp(self):
super().setUp()
self.Parameter = self.env['lims.analysis.parameter']
def test_create_numeric_parameter(self):
"""Test crear parámetro numérico con validaciones"""
# Crear parámetro numérico válido
param = self.Parameter.create({
'code': 'TEST001',
'name': 'Test Parameter',
'value_type': 'numeric',
'unit': 'mg/dL',
'description': 'Test numeric parameter'
})
self.assertEqual(param.code, 'TEST001')
self.assertEqual(param.value_type, 'numeric')
self.assertEqual(param.unit, 'mg/dL')
def test_numeric_parameter_requires_unit(self):
"""Test que parámetros numéricos requieren unidad"""
with self.assertRaises(ValidationError) as e:
self.Parameter.create({
'code': 'TEST002',
'name': 'Test Parameter No Unit',
'value_type': 'numeric',
# Sin unit - debe fallar
})
self.assertIn('unidad de medida', str(e.exception))
def test_create_selection_parameter(self):
"""Test crear parámetro de selección con opciones"""
param = self.Parameter.create({
'code': 'TEST003',
'name': 'Test Selection',
'value_type': 'selection',
'selection_values': 'Positivo,Negativo,Indeterminado'
})
self.assertEqual(param.value_type, 'selection')
self.assertEqual(param.selection_values, 'Positivo,Negativo,Indeterminado')
def test_selection_parameter_requires_values(self):
"""Test que parámetros de selección requieren valores"""
with self.assertRaises(ValidationError) as e:
self.Parameter.create({
'code': 'TEST004',
'name': 'Test Selection No Values',
'value_type': 'selection',
# Sin selection_values - debe fallar
})
self.assertIn('valores de selección', str(e.exception))
def test_duplicate_code_not_allowed(self):
"""Test que no se permiten códigos duplicados"""
# Crear primer parámetro
self.Parameter.create({
'code': 'DUP001',
'name': 'Original Parameter',
'value_type': 'text'
})
# Intentar crear duplicado
with self.assertRaises(ValidationError) as e:
self.Parameter.create({
'code': 'DUP001',
'name': 'Duplicate Parameter',
'value_type': 'text'
})
self.assertIn('ya existe', str(e.exception))
def test_boolean_parameter(self):
"""Test crear parámetro booleano"""
param = self.Parameter.create({
'code': 'BOOL001',
'name': 'Test Boolean',
'value_type': 'boolean',
'description': 'Boolean parameter'
})
self.assertEqual(param.value_type, 'boolean')
self.assertFalse(param.unit) # Boolean no debe tener unidad
def test_text_parameter(self):
"""Test crear parámetro de texto"""
param = self.Parameter.create({
'code': 'TEXT001',
'name': 'Test Text',
'value_type': 'text',
'description': 'Text parameter'
})
self.assertEqual(param.value_type, 'text')
self.assertFalse(param.unit) # Text no debe tener unidad
self.assertFalse(param.selection_values) # Text no debe tener valores de selección
def test_parameter_name_display(self):
"""Test nombre mostrado del parámetro"""
# Con unidad
param1 = self.Parameter.create({
'code': 'DISP001',
'name': 'Glucosa',
'value_type': 'numeric',
'unit': 'mg/dL'
})
self.assertEqual(param1.display_name, 'Glucosa (mg/dL)')
# Sin unidad
param2 = self.Parameter.create({
'code': 'DISP002',
'name': 'Cultivo',
'value_type': 'text'
})
self.assertEqual(param2.display_name, 'Cultivo')
def test_parameter_ranges_relationship(self):
"""Test relación con rangos de referencia"""
param = self.Parameter.create({
'code': 'RANGE001',
'name': 'Test with Ranges',
'value_type': 'numeric',
'unit': 'U/L'
})
# Crear rango para este parámetro
range1 = self.env['lims.parameter.range'].create({
'parameter_id': param.id,
'name': 'Adult Male',
'gender': 'male',
'age_min': 18,
'age_max': 65,
'normal_min': 10.0,
'normal_max': 50.0
})
self.assertEqual(len(param.range_ids), 1)
self.assertEqual(param.range_ids[0], range1)
def test_parameter_analysis_relationship(self):
"""Test relación con análisis a través de product.template.parameter"""
param = self.Parameter.create({
'code': 'ANAL001',
'name': 'Test Analysis Link',
'value_type': 'numeric',
'unit': 'mmol/L'
})
# Crear producto análisis
analysis = self.env['product.template'].create({
'name': 'Test Analysis',
'type': 'service',
'is_analysis': True,
'categ_id': self.env.ref('lims_management.product_category_clinical_analysis').id,
})
# Crear configuración parámetro-análisis
config = self.env['product.template.parameter'].create({
'product_tmpl_id': analysis.id,
'parameter_id': param.id,
'sequence': 10
})
self.assertEqual(len(param.analysis_config_ids), 1)
self.assertEqual(param.analysis_config_ids[0], config)

View File

@ -0,0 +1,283 @@
# -*- coding: utf-8 -*-
"""
Tests para la generación automática de resultados basada en parámetros
"""
from odoo.tests import TransactionCase
from datetime import date
class TestAutoResultGeneration(TransactionCase):
"""Tests para la generación automática de resultados al crear pruebas"""
def setUp(self):
super().setUp()
# Modelos
self.Test = self.env['lims.test']
self.Sample = self.env['stock.lot']
self.Order = self.env['sale.order']
self.Parameter = self.env['lims.analysis.parameter']
self.TemplateParam = self.env['product.template.parameter']
self.Product = self.env['product.template']
self.Partner = self.env['res.partner']
# Crear paciente
self.patient = self.Partner.create({
'name': 'Patient for Auto Generation',
'is_patient': True,
'gender': 'male',
'birth_date': date(1985, 3, 15)
})
# Crear doctor
self.doctor = self.Partner.create({
'name': 'Dr. Test',
'is_doctor': True
})
# Crear parámetros
self.param1 = self.Parameter.create({
'code': 'AUTO1',
'name': 'Parameter Auto 1',
'value_type': 'numeric',
'unit': 'mg/dL'
})
self.param2 = self.Parameter.create({
'code': 'AUTO2',
'name': 'Parameter Auto 2',
'value_type': 'selection',
'selection_values': 'Normal,Anormal'
})
self.param3 = self.Parameter.create({
'code': 'AUTO3',
'name': 'Parameter Auto 3',
'value_type': 'text'
})
# Crear análisis con parámetros configurados
self.analysis_multi = self.Product.create({
'name': 'Multi-Parameter Analysis',
'type': 'service',
'is_analysis': True,
'categ_id': self.env.ref('lims_management.product_category_clinical_analysis').id,
'sample_type_id': self.env.ref('lims_management.sample_type_blood').id,
})
# Configurar parámetros en el análisis
self.TemplateParam.create({
'product_tmpl_id': self.analysis_multi.id,
'parameter_id': self.param1.id,
'sequence': 10
})
self.TemplateParam.create({
'product_tmpl_id': self.analysis_multi.id,
'parameter_id': self.param2.id,
'sequence': 20
})
self.TemplateParam.create({
'product_tmpl_id': self.analysis_multi.id,
'parameter_id': self.param3.id,
'sequence': 30
})
# Crear análisis sin parámetros
self.analysis_empty = self.Product.create({
'name': 'Empty Analysis',
'type': 'service',
'is_analysis': True,
'categ_id': self.env.ref('lims_management.product_category_clinical_analysis').id,
})
def test_auto_generate_results_on_test_creation(self):
"""Test generación automática de resultados al crear una prueba"""
# Crear orden y muestra
order = self.Order.create({
'partner_id': self.patient.id,
'doctor_id': self.doctor.id,
'is_lab_request': True,
'order_line': [(0, 0, {
'product_id': self.analysis_multi.product_variant_id.id,
'product_uom_qty': 1.0
})]
})
order.action_confirm()
# Generar muestra
order.action_generate_samples()
sample = order.lab_sample_ids[0]
# La prueba debe haberse creado automáticamente con los resultados
self.assertEqual(len(sample.test_ids), 1)
test = sample.test_ids[0]
# Verificar que se generaron todos los resultados
self.assertEqual(len(test.result_ids), 3)
# Verificar que cada resultado tiene el parámetro correcto
param_ids = test.result_ids.mapped('parameter_id')
self.assertIn(self.param1, param_ids)
self.assertIn(self.param2, param_ids)
self.assertIn(self.param3, param_ids)
# Verificar orden de secuencia
results_sorted = test.result_ids.sorted('sequence')
self.assertEqual(results_sorted[0].parameter_id, self.param1)
self.assertEqual(results_sorted[1].parameter_id, self.param2)
self.assertEqual(results_sorted[2].parameter_id, self.param3)
def test_no_results_for_analysis_without_parameters(self):
"""Test que no se generan resultados para análisis sin parámetros"""
# Crear orden con análisis sin parámetros
order = self.Order.create({
'partner_id': self.patient.id,
'is_lab_request': True,
'order_line': [(0, 0, {
'product_id': self.analysis_empty.product_variant_id.id,
'product_uom_qty': 1.0
})]
})
order.action_confirm()
order.action_generate_samples()
sample = order.lab_sample_ids[0]
test = sample.test_ids[0]
# No debe haber resultados
self.assertEqual(len(test.result_ids), 0)
def test_manual_test_creation_generates_results(self):
"""Test generación de resultados al crear prueba manualmente"""
# Crear muestra manual
sample = self.Sample.create({
'name': 'SAMPLE-MANUAL-001',
'is_lab_sample': True,
'patient_id': self.patient.id,
'sample_state': 'collected'
})
# Crear prueba manualmente
test = self.Test.create({
'sample_id': sample.id,
'patient_id': self.patient.id,
'product_id': self.analysis_multi.product_variant_id.id,
'state': 'draft'
})
# Verificar generación automática
self.assertEqual(len(test.result_ids), 3)
def test_results_inherit_correct_sequence(self):
"""Test que los resultados heredan la secuencia correcta"""
# Crear análisis con secuencias específicas
analysis = self.Product.create({
'name': 'Sequence Test Analysis',
'type': 'service',
'is_analysis': True,
'categ_id': self.env.ref('lims_management.product_category_clinical_analysis').id,
})
# Configurar con secuencias no consecutivas
self.TemplateParam.create({
'product_tmpl_id': analysis.id,
'parameter_id': self.param1.id,
'sequence': 100
})
self.TemplateParam.create({
'product_tmpl_id': analysis.id,
'parameter_id': self.param2.id,
'sequence': 50
})
self.TemplateParam.create({
'product_tmpl_id': analysis.id,
'parameter_id': self.param3.id,
'sequence': 75
})
# Crear prueba
test = self.Test.create({
'patient_id': self.patient.id,
'product_id': analysis.product_variant_id.id,
'state': 'draft'
})
# Verificar orden: param2 (50), param3 (75), param1 (100)
results_sorted = test.result_ids.sorted('sequence')
self.assertEqual(results_sorted[0].parameter_id, self.param2)
self.assertEqual(results_sorted[0].sequence, 50)
self.assertEqual(results_sorted[1].parameter_id, self.param3)
self.assertEqual(results_sorted[1].sequence, 75)
self.assertEqual(results_sorted[2].parameter_id, self.param1)
self.assertEqual(results_sorted[2].sequence, 100)
def test_bulk_test_creation_performance(self):
"""Test rendimiento de creación masiva de pruebas"""
# Crear múltiples órdenes
orders = []
for i in range(5):
order = self.Order.create({
'partner_id': self.patient.id,
'is_lab_request': True,
'order_line': [(0, 0, {
'product_id': self.analysis_multi.product_variant_id.id,
'product_uom_qty': 1.0
})]
})
order.action_confirm()
orders.append(order)
# Generar muestras en lote
for order in orders:
order.action_generate_samples()
# Verificar que todas las pruebas tienen resultados
total_tests = 0
total_results = 0
for order in orders:
for sample in order.lab_sample_ids:
for test in sample.test_ids:
total_tests += 1
total_results += len(test.result_ids)
self.assertEqual(total_tests, 5)
self.assertEqual(total_results, 15) # 5 tests * 3 parameters each
def test_result_generation_with_mixed_analyses(self):
"""Test generación con análisis mixtos (con y sin parámetros)"""
# Crear orden con múltiples análisis
order = self.Order.create({
'partner_id': self.patient.id,
'is_lab_request': True,
'order_line': [
(0, 0, {
'product_id': self.analysis_multi.product_variant_id.id,
'product_uom_qty': 1.0
}),
(0, 0, {
'product_id': self.analysis_empty.product_variant_id.id,
'product_uom_qty': 1.0
})
]
})
order.action_confirm()
order.action_generate_samples()
# Verificar resultados por prueba
tests_with_results = 0
tests_without_results = 0
for sample in order.lab_sample_ids:
for test in sample.test_ids:
if test.result_ids:
tests_with_results += 1
else:
tests_without_results += 1
self.assertEqual(tests_with_results, 1) # Solo analysis_multi
self.assertEqual(tests_without_results, 1) # Solo analysis_empty

View File

@ -0,0 +1,249 @@
# -*- coding: utf-8 -*-
"""
Tests para el modelo lims.parameter.range
"""
from odoo.tests import TransactionCase
from odoo.exceptions import ValidationError
class TestParameterRange(TransactionCase):
"""Tests para rangos de referencia de parámetros"""
def setUp(self):
super().setUp()
self.Range = self.env['lims.parameter.range']
self.Parameter = self.env['lims.analysis.parameter']
# Crear parámetro de prueba
self.test_param = self.Parameter.create({
'code': 'HGB_TEST',
'name': 'Hemoglobina Test',
'value_type': 'numeric',
'unit': 'g/dL'
})
def test_create_basic_range(self):
"""Test crear rango básico"""
range_obj = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Adulto General',
'normal_min': 12.0,
'normal_max': 16.0
})
self.assertEqual(range_obj.parameter_id, self.test_param)
self.assertEqual(range_obj.normal_min, 12.0)
self.assertEqual(range_obj.normal_max, 16.0)
self.assertFalse(range_obj.gender) # Sin género específico
def test_range_validation_min_max(self):
"""Test validación que min < max"""
with self.assertRaises(ValidationError) as e:
self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Rango Inválido',
'normal_min': 20.0,
'normal_max': 10.0 # Max menor que min
})
self.assertIn('menor o igual', str(e.exception))
def test_range_validation_age(self):
"""Test validación de rangos de edad"""
with self.assertRaises(ValidationError) as e:
self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Rango Edad Inválida',
'age_min': 65,
'age_max': 18, # Max menor que min
'normal_min': 12.0,
'normal_max': 16.0
})
self.assertIn('edad', str(e.exception))
def test_critical_values_validation(self):
"""Test validación de valores críticos"""
# Crítico min debe ser menor que normal min
with self.assertRaises(ValidationError) as e:
self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Crítico Inválido',
'normal_min': 12.0,
'normal_max': 16.0,
'critical_min': 13.0 # Mayor que normal_min
})
self.assertIn('crítico mínimo', str(e.exception))
# Crítico max debe ser mayor que normal max
with self.assertRaises(ValidationError) as e:
self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Crítico Inválido 2',
'normal_min': 12.0,
'normal_max': 16.0,
'critical_max': 15.0 # Menor que normal_max
})
self.assertIn('crítico máximo', str(e.exception))
def test_gender_specific_ranges(self):
"""Test rangos específicos por género"""
# Rango para hombres
male_range = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Hombre Adulto',
'gender': 'male',
'age_min': 18,
'age_max': 65,
'normal_min': 14.0,
'normal_max': 18.0
})
# Rango para mujeres
female_range = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Mujer Adulta',
'gender': 'female',
'age_min': 18,
'age_max': 65,
'normal_min': 12.0,
'normal_max': 16.0
})
self.assertEqual(male_range.gender, 'male')
self.assertEqual(female_range.gender, 'female')
def test_pregnancy_specific_range(self):
"""Test rangos para embarazadas"""
pregnancy_range = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Embarazada',
'gender': 'female',
'pregnant': True,
'age_min': 15,
'age_max': 50,
'normal_min': 11.0,
'normal_max': 14.0
})
self.assertTrue(pregnancy_range.pregnant)
self.assertEqual(pregnancy_range.gender, 'female')
def test_find_applicable_range(self):
"""Test encontrar rango aplicable según características del paciente"""
# Crear varios rangos
general_range = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'General',
'normal_min': 12.0,
'normal_max': 16.0
})
male_adult_range = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Hombre Adulto',
'gender': 'male',
'age_min': 18,
'age_max': 65,
'normal_min': 14.0,
'normal_max': 18.0
})
child_range = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Niño',
'age_max': 12,
'normal_min': 11.0,
'normal_max': 14.0
})
pregnant_range = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Embarazada',
'gender': 'female',
'pregnant': True,
'normal_min': 11.0,
'normal_max': 14.0
})
# Test para hombre adulto de 30 años
applicable = self.Range._find_applicable_range(
self.test_param.id,
gender='male',
age=30,
is_pregnant=False
)
self.assertEqual(applicable, male_adult_range)
# Test para niño de 8 años
applicable = self.Range._find_applicable_range(
self.test_param.id,
gender='male',
age=8,
is_pregnant=False
)
self.assertEqual(applicable, child_range)
# Test para mujer embarazada
applicable = self.Range._find_applicable_range(
self.test_param.id,
gender='female',
age=28,
is_pregnant=True
)
self.assertEqual(applicable, pregnant_range)
# Test para caso sin rango específico (mujer no embarazada)
applicable = self.Range._find_applicable_range(
self.test_param.id,
gender='female',
age=35,
is_pregnant=False
)
self.assertEqual(applicable, general_range) # Debe devolver el rango general
def test_range_overlap_allowed(self):
"""Test que se permiten rangos superpuestos"""
# Rango 1: 0-18 años
range1 = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Pediátrico',
'age_max': 18,
'normal_min': 11.0,
'normal_max': 15.0
})
# Rango 2: 12-65 años (se superpone con rango 1)
range2 = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Adolescente-Adulto',
'age_min': 12,
'age_max': 65,
'normal_min': 12.0,
'normal_max': 16.0
})
# Ambos rangos deben existir sin error
self.assertTrue(range1.exists())
self.assertTrue(range2.exists())
def test_range_description_compute(self):
"""Test generación automática de descripción"""
# Rango con todas las características
full_range = self.Range.create({
'parameter_id': self.test_param.id,
'name': 'Completo',
'gender': 'female',
'age_min': 18,
'age_max': 45,
'pregnant': True,
'normal_min': 11.0,
'normal_max': 14.0,
'critical_min': 8.0,
'critical_max': 20.0
})
description = full_range.description
self.assertIn('Mujer', description)
self.assertIn('18-45 años', description)
self.assertIn('Embarazada', description)
self.assertIn('11.0 - 14.0', description)
self.assertIn('Críticos', description)

View File

@ -0,0 +1,291 @@
# -*- coding: utf-8 -*-
"""
Tests para la integración entre resultados y el catálogo de parámetros
"""
from odoo.tests import TransactionCase
from datetime import date
class TestResultParameterIntegration(TransactionCase):
"""Tests para la integración de resultados con parámetros y rangos"""
def setUp(self):
super().setUp()
# Modelos
self.Result = self.env['lims.result']
self.Test = self.env['lims.test']
self.Parameter = self.env['lims.analysis.parameter']
self.Range = self.env['lims.parameter.range']
self.Partner = self.env['res.partner']
self.Product = self.env['product.template']
# Crear paciente de prueba
self.patient_male = self.Partner.create({
'name': 'Test Patient Male',
'is_patient': True,
'gender': 'male',
'birth_date': date(1990, 1, 1) # 34 años aprox
})
self.patient_female_pregnant = self.Partner.create({
'name': 'Test Patient Pregnant',
'is_patient': True,
'gender': 'female',
'birth_date': date(1995, 6, 15), # 29 años aprox
'is_pregnant': True
})
# Crear parámetro de prueba
self.param_glucose = self.Parameter.create({
'code': 'GLU_TEST',
'name': 'Glucosa Test',
'value_type': 'numeric',
'unit': 'mg/dL'
})
# Crear rangos de referencia
self.range_general = self.Range.create({
'parameter_id': self.param_glucose.id,
'name': 'General',
'normal_min': 70.0,
'normal_max': 100.0,
'critical_min': 50.0,
'critical_max': 200.0
})
self.range_pregnant = self.Range.create({
'parameter_id': self.param_glucose.id,
'name': 'Embarazada',
'gender': 'female',
'pregnant': True,
'normal_min': 60.0,
'normal_max': 95.0,
'critical_min': 45.0,
'critical_max': 180.0
})
# Crear análisis de prueba
self.analysis = self.Product.create({
'name': 'Glucosa en Sangre Test',
'type': 'service',
'is_analysis': True,
'categ_id': self.env.ref('lims_management.product_category_clinical_analysis').id,
})
# Configurar parámetro en el análisis
self.env['product.template.parameter'].create({
'product_tmpl_id': self.analysis.id,
'parameter_id': self.param_glucose.id,
'sequence': 10
})
def test_result_parameter_assignment(self):
"""Test asignación de parámetro a resultado"""
# Crear test
test = self.Test.create({
'patient_id': self.patient_male.id,
'product_id': self.analysis.product_variant_id.id,
'state': 'draft'
})
# Crear resultado
result = self.Result.create({
'test_id': test.id,
'parameter_id': self.param_glucose.id,
'value_numeric': 85.0
})
self.assertEqual(result.parameter_id, self.param_glucose)
self.assertEqual(result.value_type, 'numeric')
self.assertEqual(result.unit, 'mg/dL')
def test_applicable_range_selection(self):
"""Test selección automática de rango aplicable"""
# Test para paciente masculino
test_male = self.Test.create({
'patient_id': self.patient_male.id,
'product_id': self.analysis.product_variant_id.id,
'state': 'draft'
})
result_male = self.Result.create({
'test_id': test_male.id,
'parameter_id': self.param_glucose.id,
'value_numeric': 85.0
})
# Debe usar el rango general
self.assertEqual(result_male.applicable_range_id, self.range_general)
self.assertFalse(result_male.is_out_of_range)
self.assertFalse(result_male.is_critical)
# Test para paciente embarazada
test_pregnant = self.Test.create({
'patient_id': self.patient_female_pregnant.id,
'product_id': self.analysis.product_variant_id.id,
'state': 'draft'
})
result_pregnant = self.Result.create({
'test_id': test_pregnant.id,
'parameter_id': self.param_glucose.id,
'value_numeric': 98.0 # Fuera de rango para embarazada
})
# Debe usar el rango para embarazadas
self.assertEqual(result_pregnant.applicable_range_id, self.range_pregnant)
self.assertTrue(result_pregnant.is_out_of_range)
self.assertFalse(result_pregnant.is_critical)
def test_out_of_range_detection(self):
"""Test detección de valores fuera de rango"""
test = self.Test.create({
'patient_id': self.patient_male.id,
'product_id': self.analysis.product_variant_id.id,
'state': 'draft'
})
# Valor normal
result_normal = self.Result.create({
'test_id': test.id,
'parameter_id': self.param_glucose.id,
'value_numeric': 85.0
})
self.assertFalse(result_normal.is_out_of_range)
self.assertFalse(result_normal.is_critical)
# Valor alto pero no crítico
result_high = self.Result.create({
'test_id': test.id,
'parameter_id': self.param_glucose.id,
'value_numeric': 115.0
})
self.assertTrue(result_high.is_out_of_range)
self.assertFalse(result_high.is_critical)
# Valor crítico alto
result_critical = self.Result.create({
'test_id': test.id,
'parameter_id': self.param_glucose.id,
'value_numeric': 250.0
})
self.assertTrue(result_critical.is_out_of_range)
self.assertTrue(result_critical.is_critical)
def test_selection_parameter_result(self):
"""Test resultado con parámetro de selección"""
# Crear parámetro de selección
param_culture = self.Parameter.create({
'code': 'CULT_TEST',
'name': 'Cultivo Test',
'value_type': 'selection',
'selection_values': 'Negativo,Positivo'
})
test = self.Test.create({
'patient_id': self.patient_male.id,
'product_id': self.analysis.product_variant_id.id,
'state': 'draft'
})
result = self.Result.create({
'test_id': test.id,
'parameter_id': param_culture.id,
'value_selection': 'Positivo'
})
self.assertEqual(result.value_type, 'selection')
self.assertEqual(result.value_selection, 'Positivo')
self.assertFalse(result.applicable_range_id) # Selection no tiene rangos
def test_text_parameter_result(self):
"""Test resultado con parámetro de texto"""
param_observation = self.Parameter.create({
'code': 'OBS_TEST',
'name': 'Observación Test',
'value_type': 'text'
})
test = self.Test.create({
'patient_id': self.patient_male.id,
'product_id': self.analysis.product_variant_id.id,
'state': 'draft'
})
result = self.Result.create({
'test_id': test.id,
'parameter_id': param_observation.id,
'value_text': 'Muestra hemolizada levemente'
})
self.assertEqual(result.value_type, 'text')
self.assertEqual(result.value_text, 'Muestra hemolizada levemente')
def test_boolean_parameter_result(self):
"""Test resultado con parámetro booleano"""
param_pregnancy = self.Parameter.create({
'code': 'PREG_TEST',
'name': 'Embarazo Test',
'value_type': 'boolean'
})
test = self.Test.create({
'patient_id': self.patient_female_pregnant.id,
'product_id': self.analysis.product_variant_id.id,
'state': 'draft'
})
result = self.Result.create({
'test_id': test.id,
'parameter_id': param_pregnancy.id,
'value_boolean': True
})
self.assertEqual(result.value_type, 'boolean')
self.assertTrue(result.value_boolean)
def test_formatted_value_display(self):
"""Test formato de visualización de valores"""
test = self.Test.create({
'patient_id': self.patient_male.id,
'product_id': self.analysis.product_variant_id.id,
'state': 'draft'
})
# Valor numérico
result_numeric = self.Result.create({
'test_id': test.id,
'parameter_id': self.param_glucose.id,
'value_numeric': 85.5
})
self.assertEqual(result_numeric.formatted_value, '85.5 mg/dL')
# Valor de selección
param_selection = self.Parameter.create({
'code': 'SEL_FORMAT',
'name': 'Selection Format',
'value_type': 'selection',
'selection_values': 'Opción A,Opción B'
})
result_selection = self.Result.create({
'test_id': test.id,
'parameter_id': param_selection.id,
'value_selection': 'Opción A'
})
self.assertEqual(result_selection.formatted_value, 'Opción A')
# Valor booleano
param_bool = self.Parameter.create({
'code': 'BOOL_FORMAT',
'name': 'Boolean Format',
'value_type': 'boolean'
})
result_bool = self.Result.create({
'test_id': test.id,
'parameter_id': param_bool.id,
'value_boolean': True
})
self.assertEqual(result_bool.formatted_value, '')

186
test/run_parameter_tests.py Normal file
View File

@ -0,0 +1,186 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script para ejecutar los tests del catálogo de parámetros
"""
import odoo
import logging
from odoo.tests.common import TransactionCase
from odoo.tests import tagged
_logger = logging.getLogger(__name__)
def run_parameter_catalog_tests(db_name='lims_demo'):
"""Ejecuta todos los tests del catálogo de parámetros"""
print("\n" + "="*70)
print("EJECUTANDO TESTS DEL CATÁLOGO DE PARÁMETROS")
print("="*70 + "\n")
# Importar los tests
try:
from odoo.addons.lims_management.tests import (
test_analysis_parameter,
test_parameter_range,
test_result_parameter_integration,
test_auto_result_generation
)
print("✓ Tests importados correctamente\n")
except ImportError as e:
print(f"✗ Error importando tests: {e}")
return
# Lista de clases de test a ejecutar
test_classes = [
(test_analysis_parameter.TestAnalysisParameter, "Parámetros de Análisis"),
(test_parameter_range.TestParameterRange, "Rangos de Referencia"),
(test_result_parameter_integration.TestResultParameterIntegration, "Integración Resultados-Parámetros"),
(test_auto_result_generation.TestAutoResultGeneration, "Generación Automática de Resultados"),
]
# Conectar a la base de datos
registry = odoo.registry(db_name)
# Ejecutar cada conjunto de tests
total_tests = 0
passed_tests = 0
failed_tests = 0
for test_class, test_name in test_classes:
print(f"\n--- Ejecutando tests de {test_name} ---")
with registry.cursor() as cr:
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
# Crear instancia del test
test_instance = test_class()
test_instance.env = env
test_instance.cr = cr
test_instance.uid = odoo.SUPERUSER_ID
# Obtener todos los métodos de test
test_methods = [method for method in dir(test_instance)
if method.startswith('test_')]
for method_name in test_methods:
total_tests += 1
try:
# Ejecutar setUp
test_instance.setUp()
# Ejecutar el test
method = getattr(test_instance, method_name)
method()
print(f"{method_name}")
passed_tests += 1
except Exception as e:
print(f"{method_name}: {str(e)}")
failed_tests += 1
_logger.exception(f"Test failed: {method_name}")
finally:
# Rollback para no afectar otros tests
cr.rollback()
# Resumen final
print("\n" + "="*70)
print("RESUMEN DE TESTS")
print("="*70)
print(f"Total de tests ejecutados: {total_tests}")
print(f"✓ Tests exitosos: {passed_tests}")
print(f"✗ Tests fallidos: {failed_tests}")
if failed_tests == 0:
print("\n✅ TODOS LOS TESTS PASARON EXITOSAMENTE")
else:
print(f"\n⚠️ {failed_tests} TESTS FALLARON")
return failed_tests == 0
def run_specific_test(db_name='lims_demo', test_module=None, test_method=None):
"""Ejecuta un test específico para debugging"""
if not test_module:
print("Debe especificar el módulo de test")
return
print(f"\nEjecutando test específico: {test_module}")
if test_method:
print(f"Método: {test_method}")
# Importar el módulo de test
exec(f"from odoo.addons.lims_management.tests import {test_module}")
module = eval(test_module)
# Encontrar la clase de test
test_class = None
for item in dir(module):
obj = getattr(module, item)
if isinstance(obj, type) and issubclass(obj, TransactionCase) and obj != TransactionCase:
test_class = obj
break
if not test_class:
print("No se encontró clase de test en el módulo")
return
registry = odoo.registry(db_name)
with registry.cursor() as cr:
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
test_instance = test_class()
test_instance.env = env
test_instance.cr = cr
test_instance.uid = odoo.SUPERUSER_ID
if test_method:
# Ejecutar método específico
if hasattr(test_instance, test_method):
try:
test_instance.setUp()
method = getattr(test_instance, test_method)
method()
print(f"✓ Test {test_method} pasó exitosamente")
except Exception as e:
print(f"✗ Test {test_method} falló: {str(e)}")
import traceback
traceback.print_exc()
else:
print(f"Método {test_method} no encontrado")
else:
# Ejecutar todos los métodos del módulo
test_methods = [m for m in dir(test_instance) if m.startswith('test_')]
for method_name in test_methods:
try:
test_instance.setUp()
method = getattr(test_instance, method_name)
method()
print(f"{method_name}")
except Exception as e:
print(f"{method_name}: {str(e)}")
finally:
cr.rollback()
if __name__ == '__main__':
import sys
# Verificar argumentos
if len(sys.argv) > 1:
if sys.argv[1] == '--specific':
# Modo de test específico
module = sys.argv[2] if len(sys.argv) > 2 else None
method = sys.argv[3] if len(sys.argv) > 3 else None
run_specific_test(test_module=module, test_method=method)
else:
# Usar base de datos especificada
run_parameter_catalog_tests(db_name=sys.argv[1])
else:
# Ejecutar todos los tests con DB por defecto
success = run_parameter_catalog_tests()
sys.exit(0 if success else 1)

View File

@ -0,0 +1,221 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script simplificado para probar el catálogo de parámetros
"""
import odoo
import logging
_logger = logging.getLogger(__name__)
def test_parameter_catalog(cr):
"""Prueba el funcionamiento del catálogo de parámetros"""
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
# Limpiar parámetros de test anteriores
test_params = env['lims.analysis.parameter'].search([
('code', 'like', 'TEST_%')
])
if test_params:
print(f"Limpiando {len(test_params)} parámetros de test anteriores...")
test_params.unlink()
print("\n" + "="*60)
print("TEST: CATÁLOGO DE PARÁMETROS")
print("="*60 + "\n")
# Test 1: Crear parámetro numérico
print("1. Creando parámetro numérico...")
try:
param_numeric = env['lims.analysis.parameter'].create({
'code': 'TEST_NUM_001',
'name': 'Test Numérico',
'value_type': 'numeric',
'unit': 'mg/dL',
'description': 'Parámetro de prueba numérico'
})
print(f" ✓ Parámetro creado: {param_numeric.name} ({param_numeric.code})")
except Exception as e:
print(f" ✗ Error: {e}")
return False
# Test 2: Validación - parámetro numérico sin unidad
print("\n2. Validando requerimiento de unidad...")
try:
env['lims.analysis.parameter'].create({
'code': 'TEST_NUM_002',
'name': 'Test Sin Unidad',
'value_type': 'numeric',
# Sin unit - debe fallar
})
print(" ✗ Error: Se permitió crear parámetro numérico sin unidad")
return False
except Exception as e:
if 'unidad de medida' in str(e):
print(" ✓ Validación correcta: Se requiere unidad para parámetros numéricos")
else:
print(f" ✗ Error inesperado: {e}")
return False
# Test 3: Crear parámetro de selección
print("\n3. Creando parámetro de selección...")
try:
param_selection = env['lims.analysis.parameter'].create({
'code': 'TEST_SEL_001',
'name': 'Test Selección',
'value_type': 'selection',
'selection_values': 'Positivo,Negativo,Indeterminado'
})
print(f" ✓ Parámetro de selección creado con valores: {param_selection.selection_values}")
except Exception as e:
print(f" ✗ Error: {e}")
return False
# Test 4: Crear rango de referencia
print("\n4. Creando rangos de referencia...")
try:
range_general = env['lims.parameter.range'].create({
'parameter_id': param_numeric.id,
'name': 'Rango General',
'normal_min': 70.0,
'normal_max': 100.0,
'critical_min': 50.0,
'critical_max': 200.0
})
print(f" ✓ Rango general creado: {range_general.normal_min} - {range_general.normal_max}")
range_male = env['lims.parameter.range'].create({
'parameter_id': param_numeric.id,
'name': 'Hombre Adulto',
'gender': 'male',
'age_min': 18,
'age_max': 65,
'normal_min': 75.0,
'normal_max': 105.0
})
print(f" ✓ Rango específico creado: Hombre {range_male.age_min}-{range_male.age_max} años")
except Exception as e:
print(f" ✗ Error: {e}")
return False
# Test 5: Configurar parámetro en análisis
print("\n5. Configurando parámetros en análisis...")
try:
# Obtener un análisis existente
analysis = env['product.template'].search([
('is_analysis', '=', True)
], limit=1)
if not analysis:
print(" ⚠️ No se encontraron análisis para configurar")
else:
config = env['product.template.parameter'].create({
'product_tmpl_id': analysis.id,
'parameter_id': param_numeric.id,
'sequence': 999
})
print(f" ✓ Parámetro configurado en análisis: {analysis.name}")
except Exception as e:
print(f" ✗ Error: {e}")
return False
# Test 6: Generación automática de resultados
print("\n6. Probando generación automática de resultados...")
try:
# Buscar una prueba existente
test = env['lims.test'].search([
('state', '=', 'draft')
], limit=1)
if test and analysis:
# Cambiar el producto de la prueba para trigger la regeneración
original_product = test.product_id
test.product_id = analysis.product_variant_id.id
# Verificar que se generó el resultado
result = test.result_ids.filtered(lambda r: r.parameter_id == param_numeric)
if result:
print(f" ✓ Resultado generado automáticamente para parámetro: {param_numeric.name}")
else:
print(" ⚠️ No se generó resultado automático")
# Restaurar producto original
test.product_id = original_product.id
else:
print(" ⚠️ No se encontraron pruebas en borrador para probar")
except Exception as e:
print(f" ✗ Error: {e}")
return False
# Test 7: Verificar datos demo cargados
print("\n7. Verificando datos demo del catálogo...")
try:
param_count = env['lims.analysis.parameter'].search_count([])
range_count = env['lims.parameter.range'].search_count([])
config_count = env['product.template.parameter'].search_count([])
print(f" - Parámetros totales: {param_count}")
print(f" - Rangos de referencia: {range_count}")
print(f" - Configuraciones parámetro-análisis: {config_count}")
# Verificar algunos parámetros específicos
hemoglobin = env.ref('lims_management.param_hemoglobin', raise_if_not_found=False)
if hemoglobin:
print(f" ✓ Parámetro demo encontrado: {hemoglobin.display_name}")
print(f" - Rangos asociados: {len(hemoglobin.range_ids)}")
except Exception as e:
print(f" ✗ Error: {e}")
return False
# Test 8: Buscar rango aplicable
print("\n8. Probando búsqueda de rango aplicable...")
try:
# Crear paciente de prueba
patient = env['res.partner'].create({
'name': 'Paciente Test Rango',
'is_patient': True,
'gender': 'male',
'birthdate_date': '1990-01-01' # 34 años aprox
})
# Buscar rango aplicable
Range = env['lims.parameter.range']
applicable = Range._find_applicable_range(
param_numeric.id,
gender='male',
age=34,
is_pregnant=False
)
if applicable:
print(f" ✓ Rango aplicable encontrado: {applicable.name}")
print(f" - Valores normales: {applicable.normal_min} - {applicable.normal_max}")
else:
print(" ⚠️ No se encontró rango aplicable")
# Limpiar
patient.unlink()
except Exception as e:
print(f" ✗ Error: {e}")
return False
print("\n" + "="*60)
print("✅ TODOS LOS TESTS PASARON EXITOSAMENTE")
print("="*60)
return True
if __name__ == '__main__':
db_name = 'lims_demo'
registry = odoo.registry(db_name)
with registry.cursor() as cr:
try:
success = test_parameter_catalog(cr)
if not success:
print("\n⚠️ ALGUNOS TESTS FALLARON")
except Exception as e:
print(f"\n✗ Error crítico: {e}")
import traceback
traceback.print_exc()