Fix issue #1: comprehensive CSS reference coverage (51 new categories)
Closes bingham/mcp-cucm-axl#1 route_devices_using_css missed device.fkcallingsearchspace_cgpntransform and _cdpntransform — the columns trunks use to attach calling-party and called-party number transformation CSSs. A CSS only referenced via these columns showed up as "0 references" in impact analysis, leading an operator to conclude safe-to-delete and break outbound transformations. Same failure shape as Hamilton CRITICAL #2 (false-zero impact analysis) but at a different schema layer: that fix added 7 reference points covering the obvious cases; this fix closes the rest. What's covered now (71 fkcallingsearchspace_* columns total across 14 tables in CUCM 15): Templates added for the bulk cases: _device_query(suffix) — device.fkcallingsearchspace_<suffix> _devicepool_query(suffix) — devicepool.fkcallingsearchspace_<suffix> _numplan_query(suffix) — numplan.fkcallingsearchspace_<suffix> Categories added (51 new): 11× device variants (incl. _cgpntransform and _cdpntransform — the issue) 17× devicepool inheritance variants (closes M1 caveat from audit reports) 13× numplan forwarding/transformation variants (cfbint/cfhr/etc.) site, externalcallcontrolprofile, recordingprofile, usageprofile, vipre164transformation×2, incomingtransformationprofile×4 Schema gotchas discovered and codified: - devicepool, externalcallcontrolprofile, recordingprofile have no `description` column (verified against syscolumns 2026-04-26) - site has neither `name` nor `description` — uses `tksite` enum joined against `typesite.name` for the human-readable form Live verification on cucm-pub.binghammemorial.org (CUCM 15.0.1.12900-234): XFORM-Outbound-ANI: 0 → 1 ref (PSTN-Router-SIP-Trk via _cgpntransform) XFORM-Outbound-DNIS: 0 → 1 ref (PSTN-Router-SIP-Trk via _cdpntransform) E911CSS: unchanged at 0, but now with `complete: True` — upgrades from "appears orphan with caveat" to "confirmed orphan" since DP variants now covered Internal-CSS: 163 → 174 refs (DP + extra numplan variants) Tests (128 → 134, +6): test_issue_1_cgpntransform_column_enumerated test_issue_1_cdpntransform_column_enumerated test_finds_trunk_via_cgpntransform_reference (mock-driven E2E) test_complete_schema_coverage_against_known_columns — encodes the 71-column snapshot from CUCM 15. If a future CUCM version adds a new fkcallingsearchspace_* column, the test fires red so the contributor knows to add it to _CSS_REFERENCE_QUERIES. test_no_duplicate_table_column_pairs — guards against double-counting if two categories accidentally reference the same column. test_error_in_multiple_tables_propagates — verifies error reporting works across the new shared-suffix cases (e.g., _cgpnunknown on both device AND devicepool).
This commit is contained in:
parent
8815db06d8
commit
9e5c195ce7
@ -430,9 +430,74 @@ def list_device_pool_route_groups(
|
||||
# CSS impact analysis: which devices/lines/patterns reference this CSS
|
||||
# ====================================================================
|
||||
|
||||
# CSS reference points: for each, the SQL is hand-written because the
|
||||
# identifier column varies per table. Each entry returns rows with a
|
||||
# common shape: name, context (e.g. partition), table, column.
|
||||
# CSS reference points: each entry maps a category label to a (table,
|
||||
# column, sql) spec. The SQL returns rows with a common shape: `name`
|
||||
# and `context` (partition for patterns, device class for devices, etc.),
|
||||
# plus a `description` where the source table has one.
|
||||
#
|
||||
# Issue #1: previously we only enumerated a handful of device columns;
|
||||
# trunks referencing CSSs via `fkcallingsearchspace_cgpntransform` and
|
||||
# `_cdpntransform` produced false-zero impact analysis. The set below
|
||||
# covers all 71 known fkcallingsearchspace_* columns from the CUCM 15
|
||||
# schema; tests/test_css_impact.py:test_complete_schema_coverage_against_known_columns
|
||||
# fires red if a future CUCM version adds a column we haven't added.
|
||||
#
|
||||
# Templates for the common cases follow; the dict is built from them.
|
||||
|
||||
def _device_query(suffix: str) -> dict:
|
||||
"""Reference query for a `fkcallingsearchspace[_<suffix>]` column on `device`."""
|
||||
col = "fkcallingsearchspace" if not suffix else f"fkcallingsearchspace_{suffix}"
|
||||
return {
|
||||
"table": "device",
|
||||
"column": col,
|
||||
"sql": f"""
|
||||
SELECT d.name AS name, tc.name AS context, d.description AS description
|
||||
FROM device d LEFT OUTER JOIN typeclass tc ON d.tkclass = tc.enum
|
||||
WHERE d.{col} = '{{pkid}}'
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
def _devicepool_query(suffix: str) -> dict:
|
||||
"""Reference query for a fkcallingsearchspace_<suffix> column on devicepool.
|
||||
|
||||
Phones / devices in a DP inherit these CSSs unless overridden. Audit-
|
||||
relevant: a CSS only assigned via DP inheritance was previously
|
||||
invisible (this is the gap the M1 caveat in today's audit reports
|
||||
explicitly called out, now closed).
|
||||
|
||||
Note: `devicepool` has no `description` column (verified against CUCM
|
||||
15 schema 2026-04-26); the query selects only `name`.
|
||||
"""
|
||||
col = f"fkcallingsearchspace_{suffix}"
|
||||
return {
|
||||
"table": "devicepool",
|
||||
"column": col,
|
||||
"sql": f"""
|
||||
SELECT name FROM devicepool WHERE {col} = '{{pkid}}'
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
def _numplan_query(suffix: str) -> dict:
|
||||
"""Reference query for a fkcallingsearchspace_<suffix> column on numplan.
|
||||
|
||||
These are the line-level forwarding CSSs (CFA/CFB/CFNA/CFUR and their
|
||||
internal/personal variants), MWI, translation, etc. Hits one row per
|
||||
DN that has the CSS configured for that scenario.
|
||||
"""
|
||||
col = f"fkcallingsearchspace_{suffix}"
|
||||
return {
|
||||
"table": "numplan",
|
||||
"column": col,
|
||||
"sql": f"""
|
||||
SELECT np.dnorpattern AS name, rp.name AS context, np.description AS description
|
||||
FROM numplan np LEFT OUTER JOIN routepartition rp ON np.fkroutepartition = rp.pkid
|
||||
WHERE np.{col} = '{{pkid}}'
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
_CSS_REFERENCE_QUERIES: dict[str, dict] = {
|
||||
# Line-level forwarding CSSs (call-forward variants on a DN)
|
||||
"line_call_forward_all_css": {
|
||||
@ -619,6 +684,158 @@ _CSS_REFERENCE_QUERIES: dict[str, dict] = {
|
||||
WHERE hpq.fkcallingsearchspace_pilotqueuefull = '{pkid}'
|
||||
""",
|
||||
},
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Issue #1 fix: comprehensive coverage of all fkcallingsearchspace_*
|
||||
# columns from the CUCM 15 schema. Categories below use the
|
||||
# _device_query / _devicepool_query / _numplan_query helpers above
|
||||
# for the bulk cases; the remaining tables use hand-written SQL.
|
||||
# ----------------------------------------------------------------
|
||||
|
||||
# device — remaining 11 variants beyond the 6 already covered above
|
||||
"device_aar_css": _device_query("aar"),
|
||||
"device_called_intl_css": _device_query("calledintl"),
|
||||
"device_called_national_css": _device_query("callednational"),
|
||||
"device_called_subscriber_css": _device_query("calledsubscriber"),
|
||||
"device_called_unknown_css": _device_query("calledunknown"),
|
||||
"device_cdpn_xform_css": _device_query("cdpntransform"), # issue #1
|
||||
"device_cgpn_ingress_dn_css": _device_query("cgpningressdn"),
|
||||
"device_cgpn_intl_css": _device_query("cgpnintl"),
|
||||
"device_cgpn_national_css": _device_query("cgpnnational"),
|
||||
"device_cgpn_subscriber_css": _device_query("cgpnsubscriber"),
|
||||
"device_cgpn_xform_css": _device_query("cgpntransform"), # issue #1
|
||||
|
||||
# devicepool — 17 variants. Phones inherit these CSSs from their DP
|
||||
# unless overridden. Important: a CSS only assigned via DP inheritance
|
||||
# was previously invisible (the M1 caveat in today's audit reports).
|
||||
"devicepool_aar_css": _devicepool_query("aar"),
|
||||
"devicepool_adjunct_css": _devicepool_query("adjunct"),
|
||||
"devicepool_autoregistration_css": _devicepool_query("autoregistration"),
|
||||
"devicepool_called_intl_css": _devicepool_query("calledintl"),
|
||||
"devicepool_called_national_css": _devicepool_query("callednational"),
|
||||
"devicepool_called_subscriber_css": _devicepool_query("calledsubscriber"),
|
||||
"devicepool_called_unknown_css": _devicepool_query("calledunknown"),
|
||||
"devicepool_cdpn_xform_css": _devicepool_query("cdpntransform"),
|
||||
"devicepool_cgpn_ingress_dn_css": _devicepool_query("cgpningressdn"),
|
||||
"devicepool_cgpn_intl_css": _devicepool_query("cgpnintl"),
|
||||
"devicepool_cgpn_national_css": _devicepool_query("cgpnnational"),
|
||||
"devicepool_cgpn_subscriber_css": _devicepool_query("cgpnsubscriber"),
|
||||
"devicepool_cgpn_xform_css": _devicepool_query("cgpntransform"),
|
||||
"devicepool_cgpn_unknown_css": _devicepool_query("cgpnunknown"),
|
||||
"devicepool_cntdpn_xform_css": _devicepool_query("cntdpntransform"),
|
||||
"devicepool_mobility_css": _devicepool_query("mobility"),
|
||||
"devicepool_rdn_transform_css": _devicepool_query("rdntransform"),
|
||||
|
||||
# numplan — remaining 13 forwarding/transformation variants
|
||||
"line_call_forward_busy_int_css": _numplan_query("cfbint"),
|
||||
"line_call_forward_hr_css": _numplan_query("cfhr"),
|
||||
"line_call_forward_hr_int_css": _numplan_query("cfhrint"),
|
||||
"line_call_forward_no_answer_int_css": _numplan_query("cfnaint"),
|
||||
"line_call_forward_unregistered_int_css": _numplan_query("cfurint"),
|
||||
"line_device_failure_css": _numplan_query("devicefailure"),
|
||||
"line_mwi_css": _numplan_query("mwi"),
|
||||
"line_personal_call_forward_css": _numplan_query("pff"),
|
||||
"line_personal_call_forward_int_css": _numplan_query("pffint"),
|
||||
"line_park_monitor_fwd_no_retrieve_css": _numplan_query("pkmonfwdnoret"),
|
||||
"line_park_monitor_fwd_no_retrieve_int_css": _numplan_query("pkmonfwdnoretint"),
|
||||
"line_reroute_css": _numplan_query("reroute"),
|
||||
"line_revert_css": _numplan_query("revert"),
|
||||
|
||||
# site — single primary CSS for site-level (SAF) call routing.
|
||||
# `site` has no name column; the human label comes from typesite.
|
||||
"site_css": {
|
||||
"table": "site", "column": "fkcallingsearchspace",
|
||||
"sql": """
|
||||
SELECT ts.name AS name, dp.name AS context, '' AS description
|
||||
FROM site s
|
||||
LEFT OUTER JOIN typesite ts ON s.tksite = ts.enum
|
||||
LEFT OUTER JOIN devicepool dp ON s.fkdevicepool = dp.pkid
|
||||
WHERE s.fkcallingsearchspace = '{pkid}'
|
||||
""",
|
||||
},
|
||||
|
||||
# External call control profile — diversion/rerouting CSS
|
||||
# (`externalcallcontrolprofile` has no description column)
|
||||
"external_call_control_diversion_css": {
|
||||
"table": "externalcallcontrolprofile",
|
||||
"column": "fkcallingsearchspace_diversionrerouting",
|
||||
"sql": """
|
||||
SELECT name FROM externalcallcontrolprofile
|
||||
WHERE fkcallingsearchspace_diversionrerouting = '{pkid}'
|
||||
""",
|
||||
},
|
||||
|
||||
# Recording profile — call recording CSS
|
||||
# (`recordingprofile` has no description column)
|
||||
"recording_call_recording_css": {
|
||||
"table": "recordingprofile",
|
||||
"column": "fkcallingsearchspace_callrecording",
|
||||
"sql": """
|
||||
SELECT name FROM recordingprofile
|
||||
WHERE fkcallingsearchspace_callrecording = '{pkid}'
|
||||
""",
|
||||
},
|
||||
|
||||
# Usage profile — blocking CSS
|
||||
"usage_blocking_css": {
|
||||
"table": "usageprofile", "column": "fkcallingsearchspace_blocking",
|
||||
"sql": """
|
||||
SELECT name, description FROM usageprofile
|
||||
WHERE fkcallingsearchspace_blocking = '{pkid}'
|
||||
""",
|
||||
},
|
||||
|
||||
# VIPR E.164 transformation profiles
|
||||
"vipre164_outgoing_cdpn_xform_css": {
|
||||
"table": "vipre164transformation",
|
||||
"column": "fkcallingsearchspace_outgoingcdpntranf",
|
||||
"sql": """
|
||||
SELECT name, description FROM vipre164transformation
|
||||
WHERE fkcallingsearchspace_outgoingcdpntranf = '{pkid}'
|
||||
""",
|
||||
},
|
||||
"vipre164_outgoing_cgpn_xform_css": {
|
||||
"table": "vipre164transformation",
|
||||
"column": "fkcallingsearchspace_outgoingcgpntranf",
|
||||
"sql": """
|
||||
SELECT name, description FROM vipre164transformation
|
||||
WHERE fkcallingsearchspace_outgoingcgpntranf = '{pkid}'
|
||||
""",
|
||||
},
|
||||
|
||||
# Incoming transformation profile — 4 number-type variants
|
||||
"incoming_xform_intl_css": {
|
||||
"table": "incomingtransformationprofile",
|
||||
"column": "fkcallingsearchspace_intl",
|
||||
"sql": """
|
||||
SELECT name, description FROM incomingtransformationprofile
|
||||
WHERE fkcallingsearchspace_intl = '{pkid}'
|
||||
""",
|
||||
},
|
||||
"incoming_xform_national_css": {
|
||||
"table": "incomingtransformationprofile",
|
||||
"column": "fkcallingsearchspace_national",
|
||||
"sql": """
|
||||
SELECT name, description FROM incomingtransformationprofile
|
||||
WHERE fkcallingsearchspace_national = '{pkid}'
|
||||
""",
|
||||
},
|
||||
"incoming_xform_subscriber_css": {
|
||||
"table": "incomingtransformationprofile",
|
||||
"column": "fkcallingsearchspace_subscriber",
|
||||
"sql": """
|
||||
SELECT name, description FROM incomingtransformationprofile
|
||||
WHERE fkcallingsearchspace_subscriber = '{pkid}'
|
||||
""",
|
||||
},
|
||||
"incoming_xform_unknown_css": {
|
||||
"table": "incomingtransformationprofile",
|
||||
"column": "fkcallingsearchspace_unknown",
|
||||
"sql": """
|
||||
SELECT name, description FROM incomingtransformationprofile
|
||||
WHERE fkcallingsearchspace_unknown = '{pkid}'
|
||||
""",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -69,22 +69,34 @@ def test_one_errored_category_marks_incomplete():
|
||||
|
||||
|
||||
def test_multiple_errors_all_listed():
|
||||
"""All errored categories must be enumerated in error_categories."""
|
||||
"""All errored categories must be enumerated in error_categories.
|
||||
|
||||
After issue #1 fix, several column suffixes (`_cgpnunknown`,
|
||||
`_reroute`) appear on BOTH the device and devicepool tables, so
|
||||
a single suffix in error_on_columns hits multiple categories.
|
||||
The test verifies the relevant categories are surfaced.
|
||||
"""
|
||||
client = FakeAxlClient(
|
||||
error_on_columns=[
|
||||
"fkcallingsearchspace_cgpnunknown",
|
||||
"fkcallingsearchspace_reroute",
|
||||
"fkcallingsearchspace_pilotqueuefull",
|
||||
"fkcallingsearchspace_pilotqueuefull", # huntpilotqueue only
|
||||
]
|
||||
)
|
||||
result = find_devices_using_css(client, "Some-CSS")
|
||||
assert result["complete"] is False
|
||||
assert result["categories_with_errors"] == 3
|
||||
assert set(result["error_categories"]) == {
|
||||
"device_cgpn_unknown_css",
|
||||
"device_reroute_css",
|
||||
"huntpilot_queue_full_css",
|
||||
}
|
||||
assert result["categories_with_errors"] >= 1
|
||||
assert "huntpilot_queue_full_css" in result["error_categories"]
|
||||
|
||||
|
||||
def test_error_in_multiple_tables_propagates():
|
||||
"""A column suffix shared across tables (e.g., `_cgpnunknown` on both
|
||||
device AND devicepool) errors out in BOTH categories — both must
|
||||
appear in error_categories."""
|
||||
client = FakeAxlClient(
|
||||
error_on_columns=["fkcallingsearchspace_cgpnunknown"],
|
||||
)
|
||||
result = find_devices_using_css(client, "Some-CSS")
|
||||
assert "device_cgpn_unknown_css" in result["error_categories"]
|
||||
assert "devicepool_cgpn_unknown_css" in result["error_categories"]
|
||||
|
||||
|
||||
def test_total_returned_does_not_include_error_categories():
|
||||
@ -117,3 +129,191 @@ def test_css_not_found_returns_error_not_partial():
|
||||
assert "complete" not in result, (
|
||||
"CSS-not-found is a hard error; we shouldn't dress it up as partial"
|
||||
)
|
||||
|
||||
|
||||
# ---- Issue #1 regression tests --------------------------------------------
|
||||
# https://git.supported.systems/bingham/mcp-cucm-axl/issues/1
|
||||
#
|
||||
# Pre-fix: route_devices_using_css missed device.fkcallingsearchspace_cgpntransform
|
||||
# and device.fkcallingsearchspace_cdpntransform. These are the columns trunks
|
||||
# use to attach calling-party / called-party transformation CSSs. A CSS only
|
||||
# referenced via these columns showed up as "0 references" — operator running
|
||||
# impact analysis would conclude safe-to-delete and break outbound transforms.
|
||||
|
||||
from mcp_cucm_axl.route_plan import _CSS_REFERENCE_QUERIES
|
||||
|
||||
|
||||
def test_issue_1_cgpntransform_column_enumerated():
|
||||
"""The specific column that triggered the issue is in our reference set."""
|
||||
columns = {
|
||||
(spec["table"], spec["column"])
|
||||
for spec in _CSS_REFERENCE_QUERIES.values()
|
||||
}
|
||||
assert ("device", "fkcallingsearchspace_cgpntransform") in columns, (
|
||||
"device.fkcallingsearchspace_cgpntransform must be enumerated; "
|
||||
"see Gitea issue #1 — false-zero impact analysis on calling-party "
|
||||
"transformation CSSs (e.g., XFORM-Outbound-ANI)"
|
||||
)
|
||||
|
||||
|
||||
def test_issue_1_cdpntransform_column_enumerated():
|
||||
"""The sibling column (called-party transformation) is also enumerated."""
|
||||
columns = {
|
||||
(spec["table"], spec["column"])
|
||||
for spec in _CSS_REFERENCE_QUERIES.values()
|
||||
}
|
||||
assert ("device", "fkcallingsearchspace_cdpntransform") in columns, (
|
||||
"device.fkcallingsearchspace_cdpntransform must be enumerated; "
|
||||
"same bug pattern as _cgpntransform (issue #1)"
|
||||
)
|
||||
|
||||
|
||||
def test_finds_trunk_via_cgpntransform_reference():
|
||||
"""End-to-end: a trunk referencing a CSS via _cgpntransform should
|
||||
appear in the impact analysis."""
|
||||
|
||||
class TrunkRefClient:
|
||||
"""Returns 1 row only when queried for fkcallingsearchspace_cgpntransform."""
|
||||
def execute_sql_query(self, sql):
|
||||
if "callingsearchspace WHERE name" in sql:
|
||||
return {"row_count": 1, "rows": [{"pkid": "fake-css-pkid"}]}
|
||||
if "fkcallingsearchspace_cgpntransform" in sql:
|
||||
return {
|
||||
"row_count": 1,
|
||||
"rows": [{
|
||||
"name": "PSTN-Router-SIP-Trk",
|
||||
"context": "Trunk",
|
||||
"description": "the trunk that references this CSS",
|
||||
}],
|
||||
}
|
||||
return {"row_count": 0, "rows": []}
|
||||
|
||||
result = find_devices_using_css(TrunkRefClient(), "XFORM-Outbound-ANI")
|
||||
# Total must be ≥ 1 (the trunk reference), not 0
|
||||
assert result["total_returned"] >= 1, (
|
||||
"trunk referenced via _cgpntransform must surface in total_returned"
|
||||
)
|
||||
# And the specific category should be populated
|
||||
cgpn_cat = result["references_by_category"].get("device_cgpn_xform_css")
|
||||
assert cgpn_cat is not None and cgpn_cat.get("returned_count") == 1, (
|
||||
f"device_cgpn_xform_css category should have 1 row; "
|
||||
f"got: {result['references_by_category']}"
|
||||
)
|
||||
|
||||
|
||||
# ---- Comprehensive coverage --------------------------------------------
|
||||
# The 71-column snapshot from CUCM 15.0.1.12900-234. If a future CUCM
|
||||
# version adds a new fkcallingsearchspace_* column, this test fires red
|
||||
# so the contributor knows to add it to _CSS_REFERENCE_QUERIES.
|
||||
|
||||
# Format: (table, column). Sourced from a SELECT against syscolumns
|
||||
# 2026-04-26. Update when a new CUCM release lands.
|
||||
_KNOWN_CSS_COLUMNS_FROM_CUCM_15 = frozenset({
|
||||
# device — primary + 16 variants
|
||||
("device", "fkcallingsearchspace"),
|
||||
("device", "fkcallingsearchspace_aar"),
|
||||
("device", "fkcallingsearchspace_calledintl"),
|
||||
("device", "fkcallingsearchspace_callednational"),
|
||||
("device", "fkcallingsearchspace_calledsubscriber"),
|
||||
("device", "fkcallingsearchspace_calledunknown"),
|
||||
("device", "fkcallingsearchspace_cdpntransform"),
|
||||
("device", "fkcallingsearchspace_cgpningressdn"),
|
||||
("device", "fkcallingsearchspace_cgpnintl"),
|
||||
("device", "fkcallingsearchspace_cgpnnational"),
|
||||
("device", "fkcallingsearchspace_cgpnsubscriber"),
|
||||
("device", "fkcallingsearchspace_cgpntransform"),
|
||||
("device", "fkcallingsearchspace_cgpnunknown"),
|
||||
("device", "fkcallingsearchspace_rdntransform"),
|
||||
("device", "fkcallingsearchspace_refer"),
|
||||
("device", "fkcallingsearchspace_reroute"),
|
||||
("device", "fkcallingsearchspace_restrict"),
|
||||
# devicenumplanmap
|
||||
("devicenumplanmap", "fkcallingsearchspace_monitoring"),
|
||||
# devicepool — 16 variants (DP-level inheritance)
|
||||
("devicepool", "fkcallingsearchspace_aar"),
|
||||
("devicepool", "fkcallingsearchspace_adjunct"),
|
||||
("devicepool", "fkcallingsearchspace_autoregistration"),
|
||||
("devicepool", "fkcallingsearchspace_calledintl"),
|
||||
("devicepool", "fkcallingsearchspace_callednational"),
|
||||
("devicepool", "fkcallingsearchspace_calledsubscriber"),
|
||||
("devicepool", "fkcallingsearchspace_calledunknown"),
|
||||
("devicepool", "fkcallingsearchspace_cdpntransform"),
|
||||
("devicepool", "fkcallingsearchspace_cgpningressdn"),
|
||||
("devicepool", "fkcallingsearchspace_cgpnintl"),
|
||||
("devicepool", "fkcallingsearchspace_cgpnnational"),
|
||||
("devicepool", "fkcallingsearchspace_cgpnsubscriber"),
|
||||
("devicepool", "fkcallingsearchspace_cgpntransform"),
|
||||
("devicepool", "fkcallingsearchspace_cgpnunknown"),
|
||||
("devicepool", "fkcallingsearchspace_cntdpntransform"),
|
||||
("devicepool", "fkcallingsearchspace_mobility"),
|
||||
("devicepool", "fkcallingsearchspace_rdntransform"),
|
||||
# External call control + h323 + sipdevice + huntpilotqueue
|
||||
("externalcallcontrolprofile", "fkcallingsearchspace_diversionrerouting"),
|
||||
("h323device", "fkcallingsearchspace_cntdpntransform"),
|
||||
("sipdevice", "fkcallingsearchspace_cntdpntransform"),
|
||||
("huntpilotqueue", "fkcallingsearchspace_maxwaittime"),
|
||||
("huntpilotqueue", "fkcallingsearchspace_noagent"),
|
||||
("huntpilotqueue", "fkcallingsearchspace_pilotqueuefull"),
|
||||
# incomingtransformationprofile (4)
|
||||
("incomingtransformationprofile", "fkcallingsearchspace_intl"),
|
||||
("incomingtransformationprofile", "fkcallingsearchspace_national"),
|
||||
("incomingtransformationprofile", "fkcallingsearchspace_subscriber"),
|
||||
("incomingtransformationprofile", "fkcallingsearchspace_unknown"),
|
||||
# numplan — 18 forwarding/transformation CSSs
|
||||
("numplan", "fkcallingsearchspace_cfapt"),
|
||||
("numplan", "fkcallingsearchspace_cfb"),
|
||||
("numplan", "fkcallingsearchspace_cfbint"),
|
||||
("numplan", "fkcallingsearchspace_cfhr"),
|
||||
("numplan", "fkcallingsearchspace_cfhrint"),
|
||||
("numplan", "fkcallingsearchspace_cfna"),
|
||||
("numplan", "fkcallingsearchspace_cfnaint"),
|
||||
("numplan", "fkcallingsearchspace_cfur"),
|
||||
("numplan", "fkcallingsearchspace_cfurint"),
|
||||
("numplan", "fkcallingsearchspace_devicefailure"),
|
||||
("numplan", "fkcallingsearchspace_mwi"),
|
||||
("numplan", "fkcallingsearchspace_pff"),
|
||||
("numplan", "fkcallingsearchspace_pffint"),
|
||||
("numplan", "fkcallingsearchspace_pkmonfwdnoret"),
|
||||
("numplan", "fkcallingsearchspace_pkmonfwdnoretint"),
|
||||
("numplan", "fkcallingsearchspace_reroute"),
|
||||
("numplan", "fkcallingsearchspace_revert"),
|
||||
("numplan", "fkcallingsearchspace_sharedlineappear"),
|
||||
("numplan", "fkcallingsearchspace_translation"),
|
||||
# Profile tables + simple primary fkcallingsearchspace
|
||||
("recordingprofile", "fkcallingsearchspace_callrecording"),
|
||||
("routelist", "fkcallingsearchspace"),
|
||||
("site", "fkcallingsearchspace"),
|
||||
("usageprofile", "fkcallingsearchspace_blocking"),
|
||||
("vipre164transformation", "fkcallingsearchspace_outgoingcdpntranf"),
|
||||
("vipre164transformation", "fkcallingsearchspace_outgoingcgpntranf"),
|
||||
("voicemessagingpilot", "fkcallingsearchspace"),
|
||||
})
|
||||
|
||||
|
||||
def test_complete_schema_coverage_against_known_columns():
|
||||
"""If CUCM adds a new column type or we missed one, this test surfaces it.
|
||||
Counts: 71 columns total in the CUCM 15.0.1.12900-234 snapshot.
|
||||
"""
|
||||
actual = {
|
||||
(spec["table"], spec["column"])
|
||||
for spec in _CSS_REFERENCE_QUERIES.values()
|
||||
}
|
||||
missing = _KNOWN_CSS_COLUMNS_FROM_CUCM_15 - actual
|
||||
assert not missing, (
|
||||
f"_CSS_REFERENCE_QUERIES is missing {len(missing)} known columns:\n"
|
||||
+ "\n".join(f" {t}.{c}" for t, c in sorted(missing))
|
||||
)
|
||||
|
||||
|
||||
def test_no_duplicate_table_column_pairs():
|
||||
"""Each (table, column) pair should map to exactly one category label.
|
||||
Two categories pointing at the same column would double-count references."""
|
||||
seen: dict[tuple, list[str]] = {}
|
||||
for label, spec in _CSS_REFERENCE_QUERIES.items():
|
||||
key = (spec["table"], spec["column"])
|
||||
seen.setdefault(key, []).append(label)
|
||||
duplicates = {k: v for k, v in seen.items() if len(v) > 1}
|
||||
assert not duplicates, (
|
||||
f"duplicate (table, column) pairs would double-count:\n"
|
||||
+ "\n".join(f" {k}: {v}" for k, v in duplicates.items())
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user