deb86b24ec
- test_group_visual_inheritance.py (4 tests): homogeneo->Url heredado, heterogeneo->generico Group, vacio->generico, subgrupos anidados ignorados. - test_manifest_threshold_override.py (4 tests): override 100 con 80 unicas no agrupa; override bajo (20) si agrupa cuando se supera; threshold=0 cae al default 50; mirror Python del parser de manifest C++ confirma el campo se extrae como int. - test_schema_migration_group_id.py (3 tests): mirror Python de project_migrate_schema, verifica idempotencia (1a y 2a apertura no duplican columna), no-op sobre BD ya migrada, datos previos sobreviven la migracion.
122 lines
5.2 KiB
Python
122 lines
5.2 KiB
Python
"""Tests del visual heredado del Group (issue 0035e).
|
|
|
|
El binario C++ implementa `apply_group_inherited_visuals` en data.cpp:
|
|
para cada nodo Group del grafo, consulta `SELECT DISTINCT type_ref
|
|
FROM entities WHERE group_id = ? AND type_ref != 'Group'`. Si la
|
|
familia es homogenea (un solo tipo), reasigna el `type_id` del nodo
|
|
Group al de ese tipo y fija `shape_override = SHAPE_SQUARE`. Si es
|
|
heterogenea o vacia, conserva el visual generico.
|
|
|
|
El subcomando `gx-cli group visual <id>` espejea exactamente esa SQL,
|
|
asi estos tests verifican el contrato (homogeneo vs heterogeneo,
|
|
type heredado y shape=square preservado) sin depender del binario.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sqlite3
|
|
|
|
from test_gx_cli import OPS_SCHEMA, APP_SCHEMA, env_dirs, run_gx # noqa: F401
|
|
|
|
|
|
def _seed_group_with_children(ops_db, group_id: str,
|
|
child_specs: list[tuple[str, str]]):
|
|
"""Inserta el contenedor Group + cada hijo (id, type_ref).
|
|
|
|
`child_specs` = [(child_id, type_ref), ...]. Se anaden con group_id
|
|
apuntando al contenedor.
|
|
"""
|
|
cn = sqlite3.connect(ops_db)
|
|
try:
|
|
cn.execute(
|
|
"INSERT INTO entities(id, name, type_ref, status, source, "
|
|
" metadata, created_at, updated_at) "
|
|
"VALUES (?, ?, 'Group', 'active', 'manual', '{}', "
|
|
" '2026-05-04T10:00:00.000Z', '2026-05-04T10:00:00.000Z')",
|
|
(group_id, "test-group"),
|
|
)
|
|
for i, (cid, type_ref) in enumerate(child_specs):
|
|
cn.execute(
|
|
"INSERT INTO entities(id, name, type_ref, status, source, "
|
|
" metadata, group_id, "
|
|
" created_at, updated_at) "
|
|
"VALUES (?, ?, ?, 'active', 'manual', '{}', ?, ?, ?)",
|
|
(cid, f"name-{i}", type_ref, group_id,
|
|
f"2026-05-04T11:{i:02d}:00.000Z",
|
|
f"2026-05-04T11:{i:02d}:00.000Z"),
|
|
)
|
|
cn.commit()
|
|
finally:
|
|
cn.close()
|
|
|
|
|
|
def test_group_inherits_visual_from_homogeneous_children(env_dirs):
|
|
"""5 Urls como hijos -> visual heredado a 'Url' (homogeneo)."""
|
|
children = [(f"u_{i:02d}", "Url") for i in range(5)]
|
|
_seed_group_with_children(env_dirs["ops"], "G_homogeneous", children)
|
|
out = run_gx(env_dirs, "group", "visual", "G_homogeneous")
|
|
assert out["homogeneous"] is True, out
|
|
assert out["inherited"] == "Url", out
|
|
assert out["child_types"] == ["Url"], out
|
|
# La forma siempre se queda como square — distintivo de contenedor.
|
|
assert out["shape"] == "square", out
|
|
|
|
|
|
def test_group_falls_back_to_generic_for_heterogeneous(env_dirs):
|
|
"""Url + Email en el mismo Group -> visual generico Group."""
|
|
children = [
|
|
("u_00", "Url"), ("u_01", "Url"), ("u_02", "Url"),
|
|
("e_00", "Email"), ("e_01", "Email"),
|
|
]
|
|
_seed_group_with_children(env_dirs["ops"], "G_heterogeneous", children)
|
|
out = run_gx(env_dirs, "group", "visual", "G_heterogeneous")
|
|
assert out["homogeneous"] is False, out
|
|
assert out["inherited"] == "Group", out
|
|
# child_types ordenado alfabeticamente — verifica ambos presentes.
|
|
assert out["child_types"] == ["Email", "Url"], out
|
|
assert out["shape"] == "square", out
|
|
|
|
|
|
def test_group_with_no_children_falls_back_to_generic(env_dirs):
|
|
"""Group vacio (sin hijos con group_id apuntando a el) -> generico."""
|
|
_seed_group_with_children(env_dirs["ops"], "G_empty", [])
|
|
out = run_gx(env_dirs, "group", "visual", "G_empty")
|
|
assert out["homogeneous"] is False, out
|
|
assert out["inherited"] == "Group", out
|
|
assert out["child_types"] == [], out
|
|
|
|
|
|
def test_group_visual_ignores_nested_subgroups(env_dirs):
|
|
"""Subgrupos anidados (type_ref='Group') no cuentan — siguen scope fase 1."""
|
|
cn = sqlite3.connect(env_dirs["ops"])
|
|
try:
|
|
cn.execute(
|
|
"INSERT INTO entities(id, name, type_ref, status, source, "
|
|
" metadata, created_at, updated_at) "
|
|
"VALUES ('G_outer', 'outer', 'Group', 'active', 'manual', '{}', "
|
|
" '2026-05-04T10:00:00.000Z', '2026-05-04T10:00:00.000Z')"
|
|
)
|
|
for i in range(3):
|
|
cn.execute(
|
|
"INSERT INTO entities(id, name, type_ref, status, source, "
|
|
" metadata, group_id, "
|
|
" created_at, updated_at) "
|
|
"VALUES (?, ?, 'Url', 'active', 'manual', '{}', 'G_outer', "
|
|
" '2026-05-04T11:00:00.000Z', '2026-05-04T11:00:00.000Z')",
|
|
(f"u_{i}", f"url-{i}"),
|
|
)
|
|
# Subgrupo anidado — el resolver lo excluye via type_ref != 'Group'.
|
|
cn.execute(
|
|
"INSERT INTO entities(id, name, type_ref, status, source, "
|
|
" metadata, group_id, "
|
|
" created_at, updated_at) "
|
|
"VALUES ('G_nested', 'nested', 'Group', 'active', 'manual', "
|
|
" '{}', 'G_outer', "
|
|
" '2026-05-04T11:00:00.000Z', '2026-05-04T11:00:00.000Z')"
|
|
)
|
|
cn.commit()
|
|
finally:
|
|
cn.close()
|
|
out = run_gx(env_dirs, "group", "visual", "G_outer")
|
|
assert out["homogeneous"] is True, out
|
|
assert out["inherited"] == "Url", out
|