mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-09-13 06:36:58 -04:00
Merge pull request #1739 from netalertx/copilot/fix-nic-child-relationship-issue
Fix: NIC child presence no longer forces a directly-detected parent offline (#1736)
This commit is contained in:
9 files changed
+432
-2
No files matched your search
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: logging-standards
|
||||
description: Logging conventions for NetAlertX backend Python code. Use this when adding, modifying, or reviewing log statements.
|
||||
---
|
||||
|
||||
# Logging Standards
|
||||
|
||||
## Import
|
||||
|
||||
```python
|
||||
from logger import mylog
|
||||
```
|
||||
|
||||
Never import `logging` directly in application code. Use `mylog` exclusively.
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
mylog(level, message_or_list)
|
||||
```
|
||||
|
||||
`message_or_list` can be a plain string or a list of values — the logger joins them with spaces.
|
||||
|
||||
## Log Levels
|
||||
|
||||
Levels from least to most verbose (higher number = more output):
|
||||
|
||||
| Level | Numeric | When to use |
|
||||
|-------|---------|-------------|
|
||||
| `"none"` | 0 | Always printed regardless of user setting. Reserve for startup, fatal errors, and one-time permission checks. |
|
||||
| `"minimal"` | 1 | Important state transitions visible by default (scan start/end, plugin finish, restart). |
|
||||
| `"verbose"` | 2 | Informational progress — what the system is doing without clutter (e.g. "No changes to report"). |
|
||||
| `"debug"` | 3 | Developer-level detail — loop decisions, branch taken, counts. |
|
||||
| `"trace"` | 4 | Granular per-item tracing — individual device rows, SQL queries, raw values. |
|
||||
|
||||
## Message Format
|
||||
|
||||
Prefix every message with a `[Module]` tag matching the file/function context:
|
||||
|
||||
```python
|
||||
mylog("debug", [f"[device_handling] Processing MAC: {mac}"])
|
||||
mylog("verbose", ["[Scan] Scan complete — devices updated:", count])
|
||||
```
|
||||
|
||||
Use `f-strings` inside a list element, not string concatenation:
|
||||
|
||||
```python
|
||||
# Correct
|
||||
mylog("debug", [f"[NIC] parent={parent_mac} nic_online={nic_online}"])
|
||||
|
||||
# Avoid
|
||||
mylog("debug", "[NIC] parent=" + parent_mac + " nic_online=" + str(nic_online))
|
||||
```
|
||||
|
||||
## Timestamp
|
||||
|
||||
`mylog` / `file_print` prepend the current local-timezone time automatically via `timeNowTZ`. Do **not** add a timestamp manually inside the message.
|
||||
|
||||
## What NOT to Log
|
||||
|
||||
- Do not log raw user input without sanitization.
|
||||
- Do not log full SQL query strings at `"none"` or `"minimal"` — use `"trace"` at most.
|
||||
- Do not use `print()` in server code — use `mylog`. `file_print` is an internal helper; do not call it directly.
|
||||
|
||||
## Log File Location
|
||||
|
||||
Written to `{logPath}/app.log` (`logPath` from `const.py` → `/tmp/logs` at runtime). Do not hardcode this path.
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
name: pr-analysis
|
||||
description: How to analyze and respond to GitHub PR review comments in NetAlertX. Use this whenever you are addressing PR feedback, review threads, or inline code comments.
|
||||
---
|
||||
|
||||
# PR Analysis
|
||||
|
||||
## Before Writing Any Test Code — Non-Negotiable Checklist
|
||||
|
||||
Run through this before creating or editing any file under `test/`:
|
||||
|
||||
1. **Helpers first:** Check `test/db_test_helpers.py` for existing factories (`make_db`, `make_device_dict`, `insert_device_from_dict`, `DummyDB`). Use them. If what you need doesn't exist, add it there — never define it locally in the test file.
|
||||
2. **MAC literals must be lowercase:** Every MAC string in fixtures, parametrize, assertions, docstrings, and comments must be lowercase hex (e.g. `aa:bb:cc:dd:ee:01`). No exceptions.
|
||||
3. **Test file location:** Place tests under a subdirectory of `test/` that mirrors the source path (e.g. `test/scan/` for `server/scan/`). Never put test files directly in `test/`.
|
||||
4. **No inline imports:** All imports at the top of the file.
|
||||
|
||||
## Before Acting on Any PR Comment
|
||||
|
||||
1. Load `code-standards` skill — all code changes must comply with it before replying.
|
||||
2. Load `testing-workflow` skill — any test additions or changes must follow it.
|
||||
3. Load any domain-specific skill relevant to the files being changed (e.g. `database-patterns` for DB writes, `settings` for config).
|
||||
|
||||
## Comment Classification
|
||||
|
||||
For each comment, determine:
|
||||
|
||||
| Type | Action |
|
||||
|------|--------|
|
||||
| Request for code change | Make the change, validate it, then reply with the short commit hash |
|
||||
| Question about code | Reply with a concise answer (no restatement of the question) |
|
||||
| Suggestion / feedback | Decide if it is actionable. If yes, act and reply. If not, do not reply. |
|
||||
| General / praise | Do not reply. |
|
||||
|
||||
## Acting on Comments — Step by Step
|
||||
|
||||
1. **Identify all actionable comments** before touching any file.
|
||||
2. **Load relevant skills** to understand conventions that apply.
|
||||
3. **Prepare a plan** — list each file and the exact change required.
|
||||
4. **Make changes one comment at a time** — keep commits focused.
|
||||
5. **Run targeted tests** after each change (`testing-workflow` skill).
|
||||
6. **Reply** only after the commit is pushed. Include the short SHA.
|
||||
|
||||
## Reply Guidelines
|
||||
|
||||
- Be concise. Do not summarize or restate the original comment.
|
||||
- State what was done and (optionally) why.
|
||||
- Include the short commit hash when relevant.
|
||||
- Do not thank or compliment the reviewer.
|
||||
|
||||
## What to Check After Every Batch of Changes
|
||||
|
||||
- **MAC literals lowercase** — grep for uppercase hex in every changed test file: `grep -Pn '[0-9A-F]{2}:[0-9A-F]' test/` must be empty.
|
||||
- **No local DB helpers** — no `DummyDB`, `make_db`, or inline DDL defined outside `test/db_test_helpers.py`.
|
||||
- No inline imports — all imports at the top of the file.
|
||||
- Tests live under a subdirectory of `test/` matching the source path, not in `test/` root.
|
||||
|
||||
## Stacked / Base-Branch Issues
|
||||
|
||||
When a PR targets a non-default branch (e.g. `next_release`):
|
||||
- Do **not** retarget the branch yourself; note it in a reply so the author can do it from the GitHub UI.
|
||||
- Check CI failures on the **base branch** first before checking your branch.
|
||||
@@ -24,6 +24,8 @@ Skills with the same purpose exist in both, sometimes under different names and
|
||||
| Project navigation | `project-navigation` | `project-navigation` | Copilot version has full path tables and env vars; Gemini version is a brief reference |
|
||||
| Plugin dev | `plugin-development` | `plugin-run-development` | Copilot version is comprehensive (data contract, phases, formats); Gemini version is a brief checklist pointing to `docs/PLUGINS_DEV.md` |
|
||||
| Devcontainer | `devcontainer-management` | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | Gemini combines into one (uses `docker exec`); Copilot splits into 3 focused skills |
|
||||
| PR review | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist |
|
||||
| Logging | `logging-standards` | `logging-standards` | `mylog` levels, message format, what not to log |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -97,6 +97,21 @@ from db_test_helpers import make_db, DummyDB, insert_device, minutes_ago
|
||||
|
||||
If a helper you need doesn't exist yet, add it to `db_test_helpers.py` — not locally in the test file.
|
||||
|
||||
## MAC Literals in Tests — ALWAYS Lowercase
|
||||
|
||||
**MANDATORY:** Every MAC address literal used in test fixtures, parametrize decorators, assertions, or comments must be lowercase hex:
|
||||
|
||||
```python
|
||||
# Correct
|
||||
make_device_dict("aa:bb:cc:dd:ee:01", ...)
|
||||
|
||||
# Wrong — will be rejected in review
|
||||
make_device_dict("AA:BB:CC:DD:EE:01", ...)
|
||||
make_device_dict("Aa:Bb:Cc:Dd:Ee:01", ...)
|
||||
```
|
||||
|
||||
This applies to hardcoded strings in `assert`, `pytest.mark.parametrize`, docstrings, and comments too. There are no exceptions.
|
||||
|
||||
## Path Hygiene
|
||||
|
||||
- Use environment variables for runtime paths
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: netalertx-logging-standards
|
||||
description: Logging conventions for NetAlertX backend Python code. Use this when adding, modifying, or reviewing log statements.
|
||||
---
|
||||
|
||||
# Logging Standards
|
||||
|
||||
## Import
|
||||
|
||||
```python
|
||||
from logger import mylog
|
||||
```
|
||||
|
||||
Never import `logging` directly in application code. Use `mylog` exclusively.
|
||||
|
||||
## Function Signature
|
||||
|
||||
```python
|
||||
mylog(level, message_or_list)
|
||||
```
|
||||
|
||||
`message_or_list` can be a plain string or a list of values — the logger joins them with spaces.
|
||||
|
||||
## Log Levels
|
||||
|
||||
Levels from least to most verbose (higher number = more output):
|
||||
|
||||
| Level | Numeric | When to use |
|
||||
|-------|---------|-------------|
|
||||
| `"none"` | 0 | Always printed regardless of user setting. Reserve for startup, fatal errors, and one-time permission checks. |
|
||||
| `"minimal"` | 1 | Important state transitions visible by default (scan start/end, plugin finish, restart). |
|
||||
| `"verbose"` | 2 | Informational progress — what the system is doing without clutter (e.g. "No changes to report"). |
|
||||
| `"debug"` | 3 | Developer-level detail — loop decisions, branch taken, counts. |
|
||||
| `"trace"` | 4 | Granular per-item tracing — individual device rows, SQL queries, raw values. |
|
||||
|
||||
## Message Format
|
||||
|
||||
Prefix every message with a `[Module]` tag matching the file/function context:
|
||||
|
||||
```python
|
||||
mylog("debug", [f"[device_handling] Processing MAC: {mac}"])
|
||||
mylog("verbose", ["[Scan] Scan complete — devices updated:", count])
|
||||
```
|
||||
|
||||
Use `f-strings` inside a list element, not string concatenation:
|
||||
|
||||
```python
|
||||
# Correct
|
||||
mylog("debug", [f"[NIC] parent={parent_mac} nic_online={nic_online}"])
|
||||
|
||||
# Avoid
|
||||
mylog("debug", "[NIC] parent=" + parent_mac + " nic_online=" + str(nic_online))
|
||||
```
|
||||
|
||||
## Timestamp
|
||||
|
||||
`mylog` / `file_print` prepend the current local-timezone time automatically via `timeNowTZ`. Do **not** add a timestamp manually inside the message.
|
||||
|
||||
## What NOT to Log
|
||||
|
||||
- Do not log raw user input without sanitization.
|
||||
- Do not log full SQL query strings at `"none"` or `"minimal"` — use `"trace"` at most.
|
||||
- Do not use `print()` in server code — use `mylog`. `file_print` is an internal helper; do not call it directly.
|
||||
|
||||
## Log File Location
|
||||
|
||||
Written to `{logPath}/app.log` (`logPath` from `const.py` → `/tmp/logs` at runtime). Do not hardcode this path.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: netalertx-pr-analysis
|
||||
description: How to analyze and respond to GitHub PR review comments in NetAlertX. Use this whenever you are addressing PR feedback, review threads, or inline code comments.
|
||||
---
|
||||
|
||||
# PR Analysis
|
||||
|
||||
## Before Writing Any Test Code — Non-Negotiable Checklist
|
||||
|
||||
Run through this before creating or editing any file under `test/`:
|
||||
|
||||
1. **Helpers first:** Check `test/db_test_helpers.py` for existing factories (`make_db`, `make_device_dict`, `insert_device_from_dict`, `DummyDB`). Use them. If what you need doesn't exist, add it there — never define it locally in the test file.
|
||||
2. **MAC literals must be lowercase:** Every MAC string in fixtures, `parametrize`, assertions, docstrings, and comments must be lowercase hex (e.g. `aa:bb:cc:dd:ee:01`). No exceptions.
|
||||
3. **Test file location:** Place tests under a subdirectory of `test/` that mirrors the source path (e.g. `test/scan/` for `server/scan/`). Never put test files directly in `test/`.
|
||||
4. **No inline imports:** All imports at the top of the file.
|
||||
|
||||
## Before Acting on Any PR Comment
|
||||
|
||||
1. Load `code-standards` skill — all code changes must comply with it before replying.
|
||||
2. Load `testing-workflow` skill — any test additions or changes must follow it.
|
||||
3. Load any domain-specific skill relevant to the files being changed (e.g. `database-patterns` for DB writes, `settings-management` for config).
|
||||
|
||||
## Comment Classification
|
||||
|
||||
For each comment, determine:
|
||||
|
||||
| Type | Action |
|
||||
|------|--------|
|
||||
| Request for code change | Make the change, validate it, then reply with the short commit hash |
|
||||
| Question about code | Reply with a concise answer (no restatement of the question) |
|
||||
| Suggestion / feedback | Decide if it is actionable. If yes, act and reply. If not, do not reply. |
|
||||
| General / praise | Do not reply. |
|
||||
|
||||
## Acting on Comments — Step by Step
|
||||
|
||||
1. **Identify all actionable comments** before touching any file.
|
||||
2. **Load relevant skills** to understand conventions that apply.
|
||||
3. **Prepare a plan** — list each file and the exact change required.
|
||||
4. **Make changes one comment at a time** — keep commits focused.
|
||||
5. **Run targeted tests** after each change (`testing-workflow` skill).
|
||||
6. **Reply** only after the commit is pushed via `report_progress`. Include the short SHA.
|
||||
|
||||
## Reply Guidelines
|
||||
|
||||
- Be concise. Do not summarize or restate the original comment.
|
||||
- State what was done and (optionally) why.
|
||||
- Include the short commit hash when relevant.
|
||||
- Do not thank or compliment the reviewer.
|
||||
|
||||
## What to Check After Every Batch of Changes
|
||||
|
||||
- **MAC literals lowercase** — grep for uppercase hex in every changed test file: `grep -Pn '[0-9A-F]{2}:[0-9A-F]' test/` must be empty.
|
||||
- **No local DB helpers** — no `DummyDB`, `make_db`, or inline DDL defined outside `test/db_test_helpers.py`.
|
||||
- No inline imports — all imports at the top of the file.
|
||||
- Tests live under a subdirectory of `test/` matching the source path, not in `test/` root.
|
||||
- Secret scan (`runtime-tools-secret_scanning`) before committing.
|
||||
|
||||
## Stacked / Base-Branch Issues
|
||||
|
||||
When a PR targets a non-default branch (e.g. `next_release`):
|
||||
- Do **not** retarget the branch yourself; note it in a reply so the author can do it from the GitHub UI.
|
||||
- Check CI failures on the **base branch** first before checking your branch.
|
||||
@@ -24,6 +24,8 @@ Skills with the same purpose exist in both, sometimes under different names and
|
||||
| Project navigation | `project-navigation` | `project-navigation` | Copilot version has full path tables and env vars; Gemini version is a brief reference |
|
||||
| Plugin dev | `plugin-run-development` | `plugin-development` | Copilot version is comprehensive (data contract, phases, formats); Gemini version is a brief checklist pointing to `docs/PLUGINS_DEV.md` |
|
||||
| Devcontainer | `devcontainer-services` + `devcontainer-setup` + `devcontainer-configs` | `devcontainer-management` | Copilot splits into 3 focused skills; Gemini combines into one (uses `docker exec`) |
|
||||
| PR review | `pr-analysis` | `pr-analysis` | How to classify and respond to PR comments; pre-flight skill loading checklist |
|
||||
| Logging | `logging-standards` | `logging-standards` | `mylog` levels, message format, what not to log |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1270,9 +1270,17 @@ def update_devPresentLastScan_based_on_nics(db):
|
||||
if nics:
|
||||
nic_statuses = [nic.get("devPresentLastScan") == 1 for nic in nics]
|
||||
if req_all:
|
||||
new_present = int(all(nic_statuses))
|
||||
nic_online = all(nic_statuses)
|
||||
else:
|
||||
new_present = int(any(nic_statuses))
|
||||
nic_online = any(nic_statuses)
|
||||
|
||||
if original == 1:
|
||||
# Parent was directly detected this scan — NIC children cannot
|
||||
# force it offline. Leave new_present = original (no change).
|
||||
pass
|
||||
else:
|
||||
# Parent was not directly detected — NICs determine presence.
|
||||
new_present = 1 if nic_online else 0
|
||||
|
||||
# Only add update if changed
|
||||
if original != new_present:
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
"""Tests for update_devPresentLastScan_based_on_nics.
|
||||
|
||||
Regression coverage for the bug where a parent device with a 'nic' child
|
||||
relationship had its own directly-detected presence overwritten by the NIC
|
||||
child's absence, producing an endless one-directional Connected event stream.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||||
|
||||
from db_test_helpers import make_db, make_device_dict, insert_device_from_dict, DummyDB
|
||||
|
||||
from server.scan import device_handling
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _setup(devices: list[dict]):
|
||||
"""Return a DummyDB seeded with the given device dicts."""
|
||||
conn = make_db()
|
||||
for dev in devices:
|
||||
insert_device_from_dict(conn, dev)
|
||||
return DummyDB(conn)
|
||||
|
||||
|
||||
def _present(db: DummyDB, mac: str) -> int:
|
||||
row = db._conn.execute(
|
||||
"SELECT devPresentLastScan FROM Devices WHERE devMac = ?", (mac,)
|
||||
).fetchone()
|
||||
return row["devPresentLastScan"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Core bug regression: parent directly detected (present=1) + absent NIC child
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestNicChildDoesNotForcePresentParentDown:
|
||||
"""Parent was directly detected this scan; an absent NIC must not override that."""
|
||||
|
||||
def test_any_mode_absent_nic_does_not_clear_present_parent(self):
|
||||
"""Bug: req_all=0, parent present=1, nic present=0 → parent must stay 1."""
|
||||
db = _setup([
|
||||
make_device_dict("aa:aa:aa:aa:aa:01", devPresentLastScan=1,
|
||||
devParentMAC="", devParentRelType="", devReqNicsOnline=0),
|
||||
make_device_dict("bb:bb:bb:bb:bb:01", devPresentLastScan=0,
|
||||
devParentMAC="aa:aa:aa:aa:aa:01",
|
||||
devParentRelType="nic", devReqNicsOnline=0),
|
||||
])
|
||||
device_handling.update_devPresentLastScan_based_on_nics(db)
|
||||
assert _present(db, "aa:aa:aa:aa:aa:01") == 1, (
|
||||
"Parent directly detected as present must not be forced offline "
|
||||
"by an absent NIC child."
|
||||
)
|
||||
|
||||
def test_req_all_mode_absent_nic_does_not_clear_present_parent(self):
|
||||
"""Bug: req_all=1, parent present=1, nic present=0 → parent must stay 1."""
|
||||
db = _setup([
|
||||
make_device_dict("aa:aa:aa:aa:aa:02", devPresentLastScan=1,
|
||||
devParentMAC="", devParentRelType="", devReqNicsOnline=1),
|
||||
make_device_dict("bb:bb:bb:bb:bb:02", devPresentLastScan=0,
|
||||
devParentMAC="aa:aa:aa:aa:aa:02",
|
||||
devParentRelType="nic", devReqNicsOnline=0),
|
||||
])
|
||||
device_handling.update_devPresentLastScan_based_on_nics(db)
|
||||
assert _present(db, "aa:aa:aa:aa:aa:02") == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# NIC can still raise an undetected parent (original=0)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestNicRaisesAbsentParent:
|
||||
"""NIC children should be able to mark a parent present when it was not seen directly."""
|
||||
|
||||
def test_any_mode_online_nic_raises_absent_parent(self):
|
||||
db = _setup([
|
||||
make_device_dict("aa:aa:aa:aa:aa:03", devPresentLastScan=0,
|
||||
devParentMAC="", devParentRelType="", devReqNicsOnline=0),
|
||||
make_device_dict("bb:bb:bb:bb:bb:03", devPresentLastScan=1,
|
||||
devParentMAC="aa:aa:aa:aa:aa:03",
|
||||
devParentRelType="nic", devReqNicsOnline=0),
|
||||
])
|
||||
device_handling.update_devPresentLastScan_based_on_nics(db)
|
||||
assert _present(db, "aa:aa:aa:aa:aa:03") == 1
|
||||
|
||||
def test_req_all_mode_all_nics_online_raises_absent_parent(self):
|
||||
db = _setup([
|
||||
make_device_dict("aa:aa:aa:aa:aa:04", devPresentLastScan=0,
|
||||
devParentMAC="", devParentRelType="", devReqNicsOnline=1),
|
||||
make_device_dict("bb:bb:bb:bb:bb:04", devPresentLastScan=1,
|
||||
devParentMAC="aa:aa:aa:aa:aa:04",
|
||||
devParentRelType="nic", devReqNicsOnline=0),
|
||||
make_device_dict("cc:cc:cc:cc:cc:04", devPresentLastScan=1,
|
||||
devParentMAC="aa:aa:aa:aa:aa:04",
|
||||
devParentRelType="nic", devReqNicsOnline=0),
|
||||
])
|
||||
device_handling.update_devPresentLastScan_based_on_nics(db)
|
||||
assert _present(db, "aa:aa:aa:aa:aa:04") == 1
|
||||
|
||||
def test_req_all_mode_partial_nics_does_not_raise_absent_parent(self):
|
||||
"""req_all=1: if not all NICs are online, an absent parent stays absent."""
|
||||
db = _setup([
|
||||
make_device_dict("aa:aa:aa:aa:aa:05", devPresentLastScan=0,
|
||||
devParentMAC="", devParentRelType="", devReqNicsOnline=1),
|
||||
make_device_dict("bb:bb:bb:bb:bb:05", devPresentLastScan=1,
|
||||
devParentMAC="aa:aa:aa:aa:aa:05",
|
||||
devParentRelType="nic", devReqNicsOnline=0),
|
||||
make_device_dict("cc:cc:cc:cc:cc:05", devPresentLastScan=0,
|
||||
devParentMAC="aa:aa:aa:aa:aa:05",
|
||||
devParentRelType="nic", devReqNicsOnline=0),
|
||||
])
|
||||
device_handling.update_devPresentLastScan_based_on_nics(db)
|
||||
assert _present(db, "aa:aa:aa:aa:aa:05") == 0
|
||||
|
||||
def test_any_mode_all_nics_absent_leaves_parent_absent(self):
|
||||
db = _setup([
|
||||
make_device_dict("aa:aa:aa:aa:aa:06", devPresentLastScan=0,
|
||||
devParentMAC="", devParentRelType="", devReqNicsOnline=0),
|
||||
make_device_dict("bb:bb:bb:bb:bb:06", devPresentLastScan=0,
|
||||
devParentMAC="aa:aa:aa:aa:aa:06",
|
||||
devParentRelType="nic", devReqNicsOnline=0),
|
||||
])
|
||||
device_handling.update_devPresentLastScan_based_on_nics(db)
|
||||
assert _present(db, "aa:aa:aa:aa:aa:06") == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# No NIC children → no change regardless of presence
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestNoNicChildren:
|
||||
def test_parent_with_no_nics_unchanged(self):
|
||||
db = _setup([
|
||||
make_device_dict("aa:aa:aa:aa:aa:07", devPresentLastScan=1,
|
||||
devParentMAC="", devParentRelType="", devReqNicsOnline=0),
|
||||
make_device_dict("aa:aa:aa:aa:aa:08", devPresentLastScan=0,
|
||||
devParentMAC="", devParentRelType="", devReqNicsOnline=0),
|
||||
])
|
||||
updated = device_handling.update_devPresentLastScan_based_on_nics(db)
|
||||
assert updated == 0
|
||||
assert _present(db, "aa:aa:aa:aa:aa:07") == 1
|
||||
assert _present(db, "aa:aa:aa:aa:aa:08") == 0
|
||||
Reference in new issue
Block a user