django-rule-engine 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_rule_engine/__init__.py +5 -0
- django_rule_engine/api/__init__.py +5 -0
- django_rule_engine/api/urls.py +8 -0
- django_rule_engine/api/views.py +85 -0
- django_rule_engine/apps.py +11 -0
- django_rule_engine/fields/__init__.py +6 -0
- django_rule_engine/fields/rule_field.py +89 -0
- django_rule_engine/fields/rule_widget.py +85 -0
- django_rule_engine/fields/test_rule_field.py +297 -0
- django_rule_engine/static/rule_widget/codemirror-rule-engine.js +156 -0
- django_rule_engine/static/rule_widget/rule_widget.css +236 -0
- django_rule_engine/static/rule_widget/rule_widget.js +246 -0
- django_rule_engine/static/rule_widget/rule_widget_highlighting.css +54 -0
- django_rule_engine/templates/widgets/rule_widget.html +48 -0
- django_rule_engine-1.0.0.dist-info/METADATA +122 -0
- django_rule_engine-1.0.0.dist-info/RECORD +19 -0
- django_rule_engine-1.0.0.dist-info/WHEEL +5 -0
- django_rule_engine-1.0.0.dist-info/licenses/LICENSE +28 -0
- django_rule_engine-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""API views for rule validation."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from django.http import JsonResponse
|
|
5
|
+
from django.views.decorators.http import require_http_methods
|
|
6
|
+
from django.views.decorators.csrf import ensure_csrf_cookie
|
|
7
|
+
import rule_engine
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@require_http_methods(["POST"])
|
|
11
|
+
@ensure_csrf_cookie
|
|
12
|
+
def validate_rule(request):
|
|
13
|
+
"""
|
|
14
|
+
Valida uma regra do rule-engine com dados de exemplo.
|
|
15
|
+
|
|
16
|
+
POST /api/validate-rule/
|
|
17
|
+
Body: {
|
|
18
|
+
"rule": "age >= 18 and status == 'active'",
|
|
19
|
+
"data": {"age": 25, "status": "active"}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Response: {
|
|
23
|
+
"valid": true,
|
|
24
|
+
"result": true,
|
|
25
|
+
"matches": true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Ou em caso de erro:
|
|
29
|
+
{
|
|
30
|
+
"valid": false,
|
|
31
|
+
"error": "mensagem de erro"
|
|
32
|
+
}
|
|
33
|
+
"""
|
|
34
|
+
print(f"DEBUG: validate_rule chamado - Method: {request.method}")
|
|
35
|
+
print(f"DEBUG: Path: {request.path}")
|
|
36
|
+
print(f"DEBUG: Body: {request.body}")
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
# Parse request body
|
|
40
|
+
body = json.loads(request.body)
|
|
41
|
+
rule_text = body.get('rule', '')
|
|
42
|
+
data = body.get('data', {})
|
|
43
|
+
|
|
44
|
+
if not rule_text:
|
|
45
|
+
return JsonResponse({
|
|
46
|
+
'valid': False,
|
|
47
|
+
'error': 'Regra não pode ser vazia'
|
|
48
|
+
}, status=400)
|
|
49
|
+
|
|
50
|
+
# Compila a regra
|
|
51
|
+
try:
|
|
52
|
+
rule = rule_engine.Rule(rule_text)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
return JsonResponse({
|
|
55
|
+
'valid': False,
|
|
56
|
+
'error': f'Erro ao compilar regra: {str(e)}'
|
|
57
|
+
}, status=400)
|
|
58
|
+
|
|
59
|
+
# Avalia a regra com os dados fornecidos
|
|
60
|
+
try:
|
|
61
|
+
result = rule.matches(data)
|
|
62
|
+
|
|
63
|
+
return JsonResponse({
|
|
64
|
+
'valid': True,
|
|
65
|
+
'result': result,
|
|
66
|
+
'matches': result,
|
|
67
|
+
'rule': rule_text,
|
|
68
|
+
'data': data
|
|
69
|
+
})
|
|
70
|
+
except Exception as e:
|
|
71
|
+
return JsonResponse({
|
|
72
|
+
'valid': False,
|
|
73
|
+
'error': f'Erro ao avaliar regra: {str(e)}'
|
|
74
|
+
}, status=400)
|
|
75
|
+
|
|
76
|
+
except json.JSONDecodeError as e:
|
|
77
|
+
return JsonResponse({
|
|
78
|
+
'valid': False,
|
|
79
|
+
'error': f'JSON inválido: {str(e)}'
|
|
80
|
+
}, status=400)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
return JsonResponse({
|
|
83
|
+
'valid': False,
|
|
84
|
+
'error': f'Erro interno: {str(e)}'
|
|
85
|
+
}, status=500)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Django app configuration for django_rule_engine."""
|
|
2
|
+
|
|
3
|
+
from django.apps import AppConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DjangoRuleEngineConfig(AppConfig):
|
|
7
|
+
"""Configuration for Django rule-engine app."""
|
|
8
|
+
|
|
9
|
+
default_auto_field = 'django.db.models.BigAutoField'
|
|
10
|
+
name = 'django_rule_engine'
|
|
11
|
+
verbose_name = 'Django Rule Engine'
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Django field for rule-engine rules."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from django.db import models
|
|
5
|
+
from django.core.exceptions import ValidationError
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
import rule_engine
|
|
8
|
+
|
|
9
|
+
from .rule_widget import RuleWidget
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RuleField(models.TextField):
|
|
13
|
+
"""
|
|
14
|
+
Campo Django para armazenar e validar regras do rule-engine.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
example_data: Dicionário ou string JSON com dados de exemplo para validação.
|
|
18
|
+
Pode ser sobrescrito pelo usuário no admin.
|
|
19
|
+
resolver: Opcional. Resolver customizado para rule-engine.
|
|
20
|
+
**kwargs: Argumentos adicionais do TextField.
|
|
21
|
+
|
|
22
|
+
Example:
|
|
23
|
+
class MyModel(models.Model):
|
|
24
|
+
rule = RuleField(
|
|
25
|
+
verbose_name="Regra de validação",
|
|
26
|
+
example_data={"age": 25, "status": "active"},
|
|
27
|
+
help_text="Digite uma regra válida do rule-engine"
|
|
28
|
+
)
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
description = _("Campo para regras do rule-engine")
|
|
32
|
+
|
|
33
|
+
def __init__(self, *args, example_data=None, resolver=None, **kwargs):
|
|
34
|
+
self.example_data = example_data
|
|
35
|
+
self.resolver = resolver
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
def deconstruct(self):
|
|
39
|
+
name, path, args, kwargs = super().deconstruct()
|
|
40
|
+
if self.example_data is not None:
|
|
41
|
+
kwargs['example_data'] = self.example_data
|
|
42
|
+
if self.resolver is not None:
|
|
43
|
+
kwargs['resolver'] = self.resolver
|
|
44
|
+
return name, path, args, kwargs
|
|
45
|
+
|
|
46
|
+
def formfield(self, **kwargs):
|
|
47
|
+
"""Retorna o formfield com o widget customizado."""
|
|
48
|
+
# Prepara example_data para o widget
|
|
49
|
+
example_data_json = None
|
|
50
|
+
if self.example_data is not None:
|
|
51
|
+
if isinstance(self.example_data, str):
|
|
52
|
+
example_data_json = self.example_data
|
|
53
|
+
else:
|
|
54
|
+
example_data_json = json.dumps(self.example_data, indent=2)
|
|
55
|
+
|
|
56
|
+
# Define o widget como padrão
|
|
57
|
+
kwargs['widget'] = RuleWidget(example_data=example_data_json)
|
|
58
|
+
|
|
59
|
+
return super().formfield(**kwargs)
|
|
60
|
+
|
|
61
|
+
def validate(self, value, model_instance):
|
|
62
|
+
"""Valida se a regra é válida."""
|
|
63
|
+
super().validate(value, model_instance)
|
|
64
|
+
|
|
65
|
+
if not value:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
# Tenta compilar a regra
|
|
70
|
+
# O resolver deve ser passado no contexto, não no init do Rule
|
|
71
|
+
rule_engine.Rule(value)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
raise ValidationError(
|
|
74
|
+
_('Regra inválida: %(error)s'),
|
|
75
|
+
code='invalid_rule',
|
|
76
|
+
params={'error': str(e)},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def to_python(self, value):
|
|
80
|
+
"""Converte o valor do banco para Python."""
|
|
81
|
+
if value is None:
|
|
82
|
+
return value
|
|
83
|
+
return str(value)
|
|
84
|
+
|
|
85
|
+
def get_prep_value(self, value):
|
|
86
|
+
"""Prepara o valor para salvar no banco."""
|
|
87
|
+
if value is None:
|
|
88
|
+
return value
|
|
89
|
+
return str(value)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Widget Django para edição de regras do rule-engine."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from django import forms
|
|
5
|
+
from django.utils.safestring import mark_safe
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django_json_widget.widgets import JSONEditorWidget
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RuleWidget(forms.Textarea):
|
|
11
|
+
"""
|
|
12
|
+
Widget customizado para editar regras do rule-engine.
|
|
13
|
+
|
|
14
|
+
Fornece:
|
|
15
|
+
- Editor de código com syntax highlighting (CodeMirror)
|
|
16
|
+
- Campo para JSON de exemplo (editável pelo usuário)
|
|
17
|
+
- Validação dinâmica no frontend
|
|
18
|
+
- Feedback visual do resultado da regra
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
template_name = 'widgets/rule_widget.html'
|
|
22
|
+
|
|
23
|
+
class Media:
|
|
24
|
+
css = {
|
|
25
|
+
'all': (
|
|
26
|
+
'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css',
|
|
27
|
+
'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/material.min.css',
|
|
28
|
+
'rule_widget/rule_widget.css',
|
|
29
|
+
'rule_widget/rule_widget_highlighting.css', # Estilos customizados de highlighting
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
js = (
|
|
33
|
+
'https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js',
|
|
34
|
+
'rule_widget/codemirror-rule-engine.js', # Modo customizado para rule-engine
|
|
35
|
+
'rule_widget/rule_widget.js',
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def __init__(self, example_data=None, attrs=None):
|
|
39
|
+
"""
|
|
40
|
+
Inicializa o widget.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
example_data: JSON string com dados de exemplo
|
|
44
|
+
attrs: Atributos HTML adicionais
|
|
45
|
+
"""
|
|
46
|
+
self.example_data = example_data or '{}'
|
|
47
|
+
super().__init__(attrs)
|
|
48
|
+
|
|
49
|
+
def get_context(self, name, value, attrs):
|
|
50
|
+
"""Adiciona contexto extra para o template."""
|
|
51
|
+
context = super().get_context(name, value, attrs)
|
|
52
|
+
|
|
53
|
+
# Formata o example_data para exibição
|
|
54
|
+
try:
|
|
55
|
+
if isinstance(self.example_data, str):
|
|
56
|
+
example_obj = json.loads(self.example_data)
|
|
57
|
+
else:
|
|
58
|
+
example_obj = self.example_data
|
|
59
|
+
formatted_example = json.dumps(example_obj, indent=2)
|
|
60
|
+
except (json.JSONDecodeError, TypeError):
|
|
61
|
+
formatted_example = self.example_data
|
|
62
|
+
|
|
63
|
+
# Cria um JSONEditorWidget para o campo de exemplo
|
|
64
|
+
json_widget = JSONEditorWidget(height='120px') # ~5 linhas
|
|
65
|
+
json_widget_name = f"{name}_example_data"
|
|
66
|
+
json_widget_attrs = {
|
|
67
|
+
'id': f"{attrs.get('id', name)}_example_data",
|
|
68
|
+
'class': 'example-data-json-editor',
|
|
69
|
+
}
|
|
70
|
+
json_widget_html = json_widget.render(json_widget_name, formatted_example, json_widget_attrs)
|
|
71
|
+
|
|
72
|
+
context['widget'].update({
|
|
73
|
+
'example_data': formatted_example,
|
|
74
|
+
'field_id': attrs.get('id', name),
|
|
75
|
+
'json_widget_html': mark_safe(json_widget_html),
|
|
76
|
+
'json_widget_media': json_widget.media,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
return context
|
|
80
|
+
|
|
81
|
+
def format_value(self, value):
|
|
82
|
+
"""Formata o valor para exibição no widget."""
|
|
83
|
+
if value is None:
|
|
84
|
+
return ''
|
|
85
|
+
return str(value)
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Script de teste do RuleField.
|
|
4
|
+
|
|
5
|
+
Execute com:
|
|
6
|
+
python manage.py shell < base/fields/test_rule_field.py
|
|
7
|
+
|
|
8
|
+
Ou no shell do Django:
|
|
9
|
+
from django_rule_engine.fields.test_rule_field import test_all
|
|
10
|
+
test_all()
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_rule_compilation():
|
|
17
|
+
"""Testa compilação básica de regras."""
|
|
18
|
+
import rule_engine
|
|
19
|
+
|
|
20
|
+
print("=" * 60)
|
|
21
|
+
print("TESTE 1: Compilação de Regras")
|
|
22
|
+
print("=" * 60)
|
|
23
|
+
|
|
24
|
+
regras = [
|
|
25
|
+
"age >= 18",
|
|
26
|
+
'status == "active"',
|
|
27
|
+
"age >= 18 and status == 'active'",
|
|
28
|
+
'(price > 100 or quantity >= 5) and customer_type == "premium"',
|
|
29
|
+
'"@ifrn.edu.br" in email',
|
|
30
|
+
"len(name) > 3",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
for regra in regras:
|
|
34
|
+
try:
|
|
35
|
+
rule = rule_engine.Rule(regra)
|
|
36
|
+
print(f"✓ OK: {regra}")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"✗ ERRO: {regra}")
|
|
39
|
+
print(f" Mensagem: {e}")
|
|
40
|
+
|
|
41
|
+
print()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_rule_evaluation():
|
|
45
|
+
"""Testa avaliação de regras com dados."""
|
|
46
|
+
import rule_engine
|
|
47
|
+
|
|
48
|
+
print("=" * 60)
|
|
49
|
+
print("TESTE 2: Avaliação de Regras")
|
|
50
|
+
print("=" * 60)
|
|
51
|
+
|
|
52
|
+
casos = [
|
|
53
|
+
{
|
|
54
|
+
"regra": "age >= 18",
|
|
55
|
+
"dados": {"age": 25},
|
|
56
|
+
"esperado": True
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"regra": "age >= 18",
|
|
60
|
+
"dados": {"age": 15},
|
|
61
|
+
"esperado": False
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"regra": 'status == "active"',
|
|
65
|
+
"dados": {"status": "active"},
|
|
66
|
+
"esperado": True
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"regra": 'age >= 18 and status == "active"',
|
|
70
|
+
"dados": {"age": 25, "status": "active"},
|
|
71
|
+
"esperado": True
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"regra": 'age >= 18 and status == "active"',
|
|
75
|
+
"dados": {"age": 15, "status": "active"},
|
|
76
|
+
"esperado": False
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"regra": '"@ifrn.edu.br" in email',
|
|
80
|
+
"dados": {"email": "usuario@ifrn.edu.br"},
|
|
81
|
+
"esperado": True
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"regra": "len(name) > 3",
|
|
85
|
+
"dados": {"name": "João Silva"},
|
|
86
|
+
"esperado": True
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"regra": "(price * quantity) > 1000",
|
|
90
|
+
"dados": {"price": 100, "quantity": 12},
|
|
91
|
+
"esperado": True
|
|
92
|
+
},
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
for i, caso in enumerate(casos, 1):
|
|
96
|
+
regra = rule_engine.Rule(caso["regra"])
|
|
97
|
+
resultado = regra.matches(caso["dados"])
|
|
98
|
+
|
|
99
|
+
status = "✓" if resultado == caso["esperado"] else "✗"
|
|
100
|
+
print(f"{status} Caso {i}:")
|
|
101
|
+
print(f" Regra: {caso['regra']}")
|
|
102
|
+
print(f" Dados: {caso['dados']}")
|
|
103
|
+
print(f" Resultado: {resultado} (esperado: {caso['esperado']})")
|
|
104
|
+
print()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def test_field_validation():
|
|
108
|
+
"""Testa validação do campo Django."""
|
|
109
|
+
from django_rule_engine.fields import RuleField
|
|
110
|
+
from django.core.exceptions import ValidationError
|
|
111
|
+
|
|
112
|
+
print("=" * 60)
|
|
113
|
+
print("TESTE 3: Validação do Campo Django")
|
|
114
|
+
print("=" * 60)
|
|
115
|
+
|
|
116
|
+
field = RuleField()
|
|
117
|
+
|
|
118
|
+
# Regras válidas
|
|
119
|
+
regras_validas = [
|
|
120
|
+
"age >= 18",
|
|
121
|
+
'status == "active"',
|
|
122
|
+
"age >= 18 and status == 'active'",
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
print("Testando regras VÁLIDAS:")
|
|
126
|
+
for regra in regras_validas:
|
|
127
|
+
try:
|
|
128
|
+
field.validate(regra, None)
|
|
129
|
+
print(f"✓ OK: {regra}")
|
|
130
|
+
except ValidationError as e:
|
|
131
|
+
print(f"✗ ERRO (inesperado): {regra}")
|
|
132
|
+
print(f" Mensagem: {e}")
|
|
133
|
+
|
|
134
|
+
print()
|
|
135
|
+
|
|
136
|
+
# Regras inválidas
|
|
137
|
+
regras_invalidas = [
|
|
138
|
+
"age >>= 18", # Operador inválido
|
|
139
|
+
"age == ", # Incompleta
|
|
140
|
+
"status = 'active'", # Usa = ao invés de ==
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
print("Testando regras INVÁLIDAS:")
|
|
144
|
+
for regra in regras_invalidas:
|
|
145
|
+
try:
|
|
146
|
+
field.validate(regra, None)
|
|
147
|
+
print(f"✗ ERRO (deveria falhar): {regra}")
|
|
148
|
+
except ValidationError as e:
|
|
149
|
+
print(f"✓ OK (erro esperado): {regra}")
|
|
150
|
+
print(f" Mensagem: {e}")
|
|
151
|
+
|
|
152
|
+
print()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_api_simulation():
|
|
156
|
+
"""Simula chamada à API de validação."""
|
|
157
|
+
import rule_engine
|
|
158
|
+
|
|
159
|
+
print("=" * 60)
|
|
160
|
+
print("TESTE 4: Simulação da API")
|
|
161
|
+
print("=" * 60)
|
|
162
|
+
|
|
163
|
+
request_body = {
|
|
164
|
+
"rule": "age >= 18 and status == 'active'",
|
|
165
|
+
"data": {
|
|
166
|
+
"age": 25,
|
|
167
|
+
"status": "active"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
print(f"Request Body:")
|
|
172
|
+
print(json.dumps(request_body, indent=2))
|
|
173
|
+
print()
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
rule = rule_engine.Rule(request_body["rule"])
|
|
177
|
+
result = rule.matches(request_body["data"])
|
|
178
|
+
|
|
179
|
+
response = {
|
|
180
|
+
"valid": True,
|
|
181
|
+
"result": result,
|
|
182
|
+
"matches": result,
|
|
183
|
+
"rule": request_body["rule"],
|
|
184
|
+
"data": request_body["data"]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
print("Response:")
|
|
188
|
+
print(json.dumps(response, indent=2))
|
|
189
|
+
print()
|
|
190
|
+
print("✓ API simulada com sucesso!")
|
|
191
|
+
|
|
192
|
+
except Exception as e:
|
|
193
|
+
response = {
|
|
194
|
+
"valid": False,
|
|
195
|
+
"error": str(e)
|
|
196
|
+
}
|
|
197
|
+
print("Response (ERRO):")
|
|
198
|
+
print(json.dumps(response, indent=2))
|
|
199
|
+
print()
|
|
200
|
+
print("✗ Erro na simulação da API")
|
|
201
|
+
|
|
202
|
+
print()
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def test_real_world_examples():
|
|
206
|
+
"""Testa exemplos do mundo real."""
|
|
207
|
+
import rule_engine
|
|
208
|
+
|
|
209
|
+
print("=" * 60)
|
|
210
|
+
print("TESTE 5: Exemplos do Mundo Real")
|
|
211
|
+
print("=" * 60)
|
|
212
|
+
|
|
213
|
+
exemplos = [
|
|
214
|
+
{
|
|
215
|
+
"nome": "Validação de Matrícula",
|
|
216
|
+
"regra": 'idade >= 16 and nivel_ensino == "medio" and aprovado == true',
|
|
217
|
+
"dados": {
|
|
218
|
+
"idade": 18,
|
|
219
|
+
"nivel_ensino": "medio",
|
|
220
|
+
"aprovado": True
|
|
221
|
+
},
|
|
222
|
+
"esperado": True
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"nome": "Desconto para Cliente Premium",
|
|
226
|
+
"regra": '(quantidade >= 10 or valor_total > 1000) and tipo_cliente == "premium"',
|
|
227
|
+
"dados": {
|
|
228
|
+
"quantidade": 12,
|
|
229
|
+
"valor_total": 500,
|
|
230
|
+
"tipo_cliente": "premium"
|
|
231
|
+
},
|
|
232
|
+
"esperado": True
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"nome": "Email Institucional",
|
|
236
|
+
"regra": '"@ifrn.edu.br" in email and ativo == true',
|
|
237
|
+
"dados": {
|
|
238
|
+
"email": "joao.silva@ifrn.edu.br",
|
|
239
|
+
"ativo": True
|
|
240
|
+
},
|
|
241
|
+
"esperado": True
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"nome": "Acesso de Administrador",
|
|
245
|
+
"regra": 'is_admin == true or (departamento == "TI" and nivel_acesso >= 5)',
|
|
246
|
+
"dados": {
|
|
247
|
+
"is_admin": False,
|
|
248
|
+
"departamento": "TI",
|
|
249
|
+
"nivel_acesso": 7
|
|
250
|
+
},
|
|
251
|
+
"esperado": True
|
|
252
|
+
},
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
for exemplo in exemplos:
|
|
256
|
+
print(f"\n{exemplo['nome']}:")
|
|
257
|
+
print(f" Regra: {exemplo['regra']}")
|
|
258
|
+
print(f" Dados: {json.dumps(exemplo['dados'], indent=4)}")
|
|
259
|
+
|
|
260
|
+
try:
|
|
261
|
+
rule = rule_engine.Rule(exemplo["regra"])
|
|
262
|
+
resultado = rule.matches(exemplo["dados"])
|
|
263
|
+
|
|
264
|
+
status = "✓" if resultado == exemplo["esperado"] else "✗"
|
|
265
|
+
print(f" {status} Resultado: {resultado} (esperado: {exemplo['esperado']})")
|
|
266
|
+
|
|
267
|
+
except Exception as e:
|
|
268
|
+
print(f" ✗ ERRO: {e}")
|
|
269
|
+
|
|
270
|
+
print()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def test_all():
|
|
274
|
+
"""Executa todos os testes."""
|
|
275
|
+
print("\n" + "=" * 60)
|
|
276
|
+
print("INICIANDO TESTES DO RULEFIELD")
|
|
277
|
+
print("=" * 60 + "\n")
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
test_rule_compilation()
|
|
281
|
+
test_rule_evaluation()
|
|
282
|
+
test_field_validation()
|
|
283
|
+
test_api_simulation()
|
|
284
|
+
test_real_world_examples()
|
|
285
|
+
|
|
286
|
+
print("=" * 60)
|
|
287
|
+
print("TODOS OS TESTES CONCLUÍDOS!")
|
|
288
|
+
print("=" * 60)
|
|
289
|
+
|
|
290
|
+
except Exception as e:
|
|
291
|
+
print(f"\n✗ ERRO DURANTE OS TESTES: {e}")
|
|
292
|
+
import traceback
|
|
293
|
+
traceback.print_exc()
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
if __name__ == "__main__":
|
|
297
|
+
test_all()
|