Skip to content

Commit d49abd9

Browse files
committed
Enhance code standards, update contributing guidelines, and add tests for SYNC plugin functionality
1 parent abf024d commit d49abd9

8 files changed

Lines changed: 709 additions & 26 deletions

File tree

.github/skills/code-standards/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ description: NetAlertX coding standards and conventions. Use this when writing c
55

66
# Code Standards
77

8-
- ask me to review before going to each next step (mention n step out of x)
9-
- before starting, prepare implementation plan
8+
- ask me to review before going to each next step (mention n step out of x) (AI only)
9+
- before starting, prepare implementation plan (AI only)
1010
- ask me to review it and ask any clarifying questions first
1111
- add test creation as last step - follow repo architecture patterns - do not place in the root of /test
1212
- code has to be maintainable, no duplicate code
13-
- follow DRY principle
13+
- follow DRY principle - maintainability of code is more important than speed of implementation
1414
- code files should be less than 500 LOC for better maintainability
1515

1616
## File Length

.github/workflows/run-all-tests.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ name: 🧪 Manual Test Suite Selector
33
on:
44
workflow_dispatch:
55
inputs:
6+
run_all:
7+
description: '✅ Run ALL tests (overrides individual selectors)'
8+
type: boolean
9+
default: false
610
run_scan:
711
description: '📂 scan/ (Scan, Logic, Locks, IPs)'
812
type: boolean
@@ -23,6 +27,10 @@ on:
2327
description: '📂 ui/ (Selenium & Dashboard)'
2428
type: boolean
2529
default: false
30+
run_plugins:
31+
description: '📂 plugins/ (Sync insert schema-aware logic)'
32+
type: boolean
33+
default: false
2634
run_root_files:
2735
description: '📄 Root Test Files (WOL, Atomicity, etc.)'
2836
type: boolean
@@ -42,12 +50,20 @@ jobs:
4250
id: builder
4351
run: |
4452
PATHS=""
53+
54+
# run_all overrides everything
55+
if [ "${{ github.event.inputs.run_all }}" == "true" ]; then
56+
echo "final_paths=test/" >> $GITHUB_OUTPUT
57+
exit 0
58+
fi
59+
4560
# Folder Mapping with 'test/' prefix
4661
if [ "${{ github.event.inputs.run_scan }}" == "true" ]; then PATHS="$PATHS test/scan/"; fi
4762
if [ "${{ github.event.inputs.run_api }}" == "true" ]; then PATHS="$PATHS test/api_endpoints/ test/server/"; fi
4863
if [ "${{ github.event.inputs.run_backend }}" == "true" ]; then PATHS="$PATHS test/backend/ test/db/"; fi
4964
if [ "${{ github.event.inputs.run_docker_env }}" == "true" ]; then PATHS="$PATHS test/docker_tests/"; fi
5065
if [ "${{ github.event.inputs.run_ui }}" == "true" ]; then PATHS="$PATHS test/ui/"; fi
66+
if [ "${{ github.event.inputs.run_plugins }}" == "true" ]; then PATHS="$PATHS test/plugins/"; fi
5167
5268
# Root Files Mapping (files sitting directly in /test/)
5369
if [ "${{ github.event.inputs.run_root_files }}" == "true" ]; then

CONTRIBUTING.md

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
# 🤝 Contributing to NetAlertX
1+
# Contributing to NetAlertX
22

33
First off, **thank you** for taking the time to contribute! NetAlertX is built and improved with the help of passionate people like you.
44

55
---
66

7-
## 📂 Issues, Bugs, and Feature Requests
7+
## Issues, Bugs, and Feature Requests
88

99
Please use the [GitHub Issue Tracker](https://github.com/netalertx/NetAlertX/issues) for:
10-
- Bug reports 🐞
11-
- Feature requests 💡
12-
- Documentation feedback 📖
10+
- Bug reports
11+
- Feature requests
12+
- Documentation feedback
1313

1414
Before opening a new issue:
15-
- 🛑 [Check Common Issues & Debug Tips](https://docs.netalertx.com/DEBUG_TIPS#common-issues)
16-
- 🔍 [Search Closed Issues](https://github.com/netalertx/NetAlertX/issues?q=is%3Aissue+is%3Aclosed)
15+
- [Check Common Issues & Debug Tips](https://docs.netalertx.com/DEBUG_TIPS#common-issues)
16+
- [Search Closed Issues](https://github.com/netalertx/NetAlertX/issues?q=is%3Aissue+is%3Aclosed)
1717

1818
---
1919

20-
## 🚀 Submitting Pull Requests (PRs)
20+
## Submitting Pull Requests (PRs)
2121

2222
We welcome PRs to improve the code, docs, or UI!
2323

@@ -29,25 +29,30 @@ Please:
2929
- If relevant, add or update tests and documentation
3030
- For plugins, refer to the [Plugin Dev Guide](https://docs.netalertx.com/PLUGINS_DEV)
3131

32+
33+
## Code quality
34+
35+
- read and follow the [code-standards](/.github/skills/code-standards/SKILL.md)
36+
3237
---
3338

34-
## 🌟 First-Time Contributors
39+
## First-Time Contributors
3540

3641
New to open source? Check out these resources:
3742
- [How to Fork and Submit a PR](https://opensource.guide/how-to-contribute/)
3843
- Ask questions or get support in our [Discord](https://discord.gg/NczTUTWyRr)
3944

4045
---
4146

42-
## 🔐 Code of Conduct
47+
## Code of Conduct
4348

4449
By participating, you agree to follow our [Code of Conduct](./CODE_OF_CONDUCT.md), which ensures a respectful and welcoming community.
4550

4651
---
4752

48-
## 📬 Contact
53+
## Contact
4954

5055
If you have more in-depth questions or want to discuss contributing in other ways, feel free to reach out at:
51-
📧 [jokob@duck.com](mailto:jokob@duck.com?subject=NetAlertX%20Contribution)
56+
[jokob.sk@gmail.com](mailto:jokob.sk@gmail.com?subject=NetAlertX%20Contribution)
5257

5358
We appreciate every contribution, big or small! 💙

front/plugins/sync/sync.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -222,27 +222,30 @@ def main():
222222
extra = '',
223223
foreignKey = device['devGUID'])
224224

225+
# Resolve the actual columns that exist in the Devices table once.
226+
# This automatically excludes computed/virtual fields (e.g. devStatus,
227+
# devIsSleeping) and 'rowid' without needing a maintained exclusion list.
228+
cursor.execute("PRAGMA table_info(Devices)")
229+
db_columns = {row[1] for row in cursor.fetchall()}
230+
225231
# Filter out existing devices
226232
new_devices = [device for device in device_data if device['devMac'] not in existing_mac_addresses]
227233

228-
# Remove 'rowid' key if it exists
229-
for device in new_devices:
230-
device.pop('rowid', None)
231-
device.pop('devStatus', None)
232-
233234
mylog('verbose', [f'[{pluginName}] All devices: "{len(device_data)}"'])
234235
mylog('verbose', [f'[{pluginName}] New devices: "{len(new_devices)}"'])
235236

236237
# Prepare the insert statement
237238
if new_devices:
238239

239-
# creating insert statement, removing 'rowid', 'devStatus' as handled on the target and devStatus is resolved on the fly
240-
columns = ', '.join(k for k in new_devices[0].keys() if k not in ['rowid', 'devStatus'])
241-
placeholders = ', '.join('?' for k in new_devices[0] if k not in ['rowid', 'devStatus'])
240+
# Only keep keys that are real columns in the target DB; computed
241+
# or unknown fields are silently dropped regardless of source schema.
242+
insert_cols = [k for k in new_devices[0].keys() if k in db_columns]
243+
columns = ', '.join(insert_cols)
244+
placeholders = ', '.join('?' for _ in insert_cols)
242245
sql = f'INSERT INTO Devices ({columns}) VALUES ({placeholders})'
243246

244-
# Extract values for the new devices
245-
values = [tuple(device.values()) for device in new_devices]
247+
# Extract only the whitelisted column values for each device
248+
values = [tuple(device.get(col) for col in insert_cols) for device in new_devices]
246249

247250
mylog('verbose', [f'[{pluginName}] Inserting Devices SQL : "{sql}"'])
248251
mylog('verbose', [f'[{pluginName}] Inserting Devices VALUES: "{values}"'])

test/db_test_helpers.py

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
import sys, os
77
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
8-
from db_test_helpers import make_db, insert_device, minutes_ago, DummyDB, down_event_macs
8+
from db_test_helpers import make_db, insert_device, minutes_ago, DummyDB, down_event_macs, make_device_dict, sync_insert_devices
99
"""
1010

1111
import sqlite3
@@ -202,6 +202,125 @@ def insert_device(
202202
)
203203

204204

205+
def make_device_dict(mac: str = "aa:bb:cc:dd:ee:ff", **overrides) -> dict:
206+
"""
207+
Return a fully-populated Devices row dict with safe defaults.
208+
209+
Mirrors every column in CREATE_DEVICES so callers can be inserted
210+
directly via sync_insert_devices() or similar helpers. Pass keyword
211+
arguments to override any individual field.
212+
213+
Computed/view-only columns (devStatus, devIsSleeping, devFlapping,
214+
rowid, …) are intentionally absent — tests that need to verify they are
215+
dropped should add them after calling this function.
216+
"""
217+
base = {
218+
"devMac": mac,
219+
"devName": "Test Device",
220+
"devOwner": "",
221+
"devType": "",
222+
"devVendor": "Acme",
223+
"devFavorite": 0,
224+
"devGroup": "",
225+
"devComments": "",
226+
"devFirstConnection": "2024-01-01 00:00:00",
227+
"devLastConnection": "2024-01-02 00:00:00",
228+
"devLastIP": "192.168.1.10",
229+
"devPrimaryIPv4": "192.168.1.10",
230+
"devPrimaryIPv6": "",
231+
"devVlan": "",
232+
"devForceStatus": "",
233+
"devStaticIP": "",
234+
"devScan": 1,
235+
"devLogEvents": 1,
236+
"devAlertEvents": 1,
237+
"devAlertDown": 1,
238+
"devCanSleep": 0,
239+
"devSkipRepeated": 0,
240+
"devLastNotification": "",
241+
"devPresentLastScan": 1,
242+
"devIsNew": 0,
243+
"devLocation": "",
244+
"devIsArchived": 0,
245+
"devParentMAC": "",
246+
"devParentPort": "",
247+
"devIcon": "",
248+
"devGUID": "test-guid-1",
249+
"devSite": "",
250+
"devSSID": "",
251+
"devSyncHubNode": "node1",
252+
"devSourcePlugin": "",
253+
"devCustomProps": "",
254+
"devFQDN": "",
255+
"devParentRelType": "",
256+
"devReqNicsOnline": 0,
257+
"devMacSource": "",
258+
"devNameSource": "",
259+
"devFQDNSource": "",
260+
"devLastIPSource": "",
261+
"devVendorSource": "",
262+
"devSSIDSource": "",
263+
"devParentMACSource": "",
264+
"devParentPortSource": "",
265+
"devParentRelTypeSource": "",
266+
"devVlanSource": "",
267+
}
268+
base.update(overrides)
269+
return base
270+
271+
272+
# ---------------------------------------------------------------------------
273+
# Sync insert helper (shared by test/plugins/test_sync_insert.py and
274+
# test/plugins/test_sync_protocol.py — mirrors sync.py's insert block)
275+
# ---------------------------------------------------------------------------
276+
277+
def sync_insert_devices(
278+
conn: sqlite3.Connection,
279+
device_data: list,
280+
existing_macs: set | None = None,
281+
) -> int:
282+
"""
283+
Schema-aware device INSERT mirroring sync.py's Mode-3 insert block.
284+
285+
Parameters
286+
----------
287+
conn:
288+
In-memory (or real) SQLite connection with a Devices table.
289+
device_data:
290+
List of device dicts as received from table_devices.json or a node log.
291+
existing_macs:
292+
Set of MAC addresses already present in Devices. Rows whose devMac is
293+
in this set are skipped. Pass ``None`` (default) to insert everything.
294+
295+
Returns the number of rows actually inserted.
296+
"""
297+
if not device_data:
298+
return 0
299+
300+
cursor = conn.cursor()
301+
302+
candidates = (
303+
[d for d in device_data if d["devMac"] not in existing_macs]
304+
if existing_macs is not None
305+
else list(device_data)
306+
)
307+
308+
if not candidates:
309+
return 0
310+
311+
cursor.execute("PRAGMA table_info(Devices)")
312+
db_columns = {row[1] for row in cursor.fetchall()}
313+
314+
insert_cols = [k for k in candidates[0].keys() if k in db_columns]
315+
columns = ", ".join(insert_cols)
316+
placeholders = ", ".join("?" for _ in insert_cols)
317+
sql = f"INSERT INTO Devices ({columns}) VALUES ({placeholders})"
318+
values = [tuple(d.get(col) for col in insert_cols) for d in candidates]
319+
cursor.executemany(sql, values)
320+
conn.commit()
321+
return len(values)
322+
323+
205324
# ---------------------------------------------------------------------------
206325
# Assertion helpers
207326
# ---------------------------------------------------------------------------

test/plugins/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)