mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-04 09:11:34 -07:00
feat: authoritative plugin fields
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
@@ -7,10 +7,10 @@ The Device Field Lock/Unlock feature allows users to lock specific device fields
|
|||||||
## Concepts
|
## Concepts
|
||||||
|
|
||||||
### Tracked Fields
|
### Tracked Fields
|
||||||
|
|
||||||
Only certain device fields support locking. These are the fields that can be modified by both plugins and users:
|
Only certain device fields support locking. These are the fields that can be modified by both plugins and users:
|
||||||
- `devMac` - Device MAC address
|
|
||||||
- `devName` - Device name/hostname
|
- `devName` - Device name/hostname
|
||||||
- `devLastIP` - Last known IP address
|
|
||||||
- `devVendor` - Device vendor/manufacturer
|
- `devVendor` - Device vendor/manufacturer
|
||||||
- `devFQDN` - Fully qualified domain name
|
- `devFQDN` - Fully qualified domain name
|
||||||
- `devSSID` - Network SSID
|
- `devSSID` - Network SSID
|
||||||
@@ -20,14 +20,18 @@ Only certain device fields support locking. These are the fields that can be mod
|
|||||||
- `devVlan` - VLAN identifier
|
- `devVlan` - VLAN identifier
|
||||||
|
|
||||||
### Field Source Tracking
|
### Field Source Tracking
|
||||||
|
|
||||||
Every tracked field has an associated `*Source` field that indicates where the current value originated:
|
Every tracked field has an associated `*Source` field that indicates where the current value originated:
|
||||||
|
|
||||||
- `NEWDEV` - Created via the UI as a new device
|
- `NEWDEV` - Created via the UI as a new device
|
||||||
- `USER` - Manually edited by a user
|
- `USER` - Manually edited by a user
|
||||||
- `LOCKED` - Field is locked; prevents any plugin overwrites
|
- `LOCKED` - Field is locked; prevents any plugin overwrites
|
||||||
- Plugin name (e.g., `UNIFIAPI`, `PIHOLE`) - Last updated by this plugin
|
- Plugin name (e.g., `UNIFIAPI`, `PIHOLE`) - Last updated by this plugin
|
||||||
|
|
||||||
### Locking Mechanism
|
### Locking Mechanism
|
||||||
|
|
||||||
When a field is **locked**, its source is set to `LOCKED`. This prevents plugin overwrites based on the authorization logic:
|
When a field is **locked**, its source is set to `LOCKED`. This prevents plugin overwrites based on the authorization logic:
|
||||||
|
|
||||||
1. Plugin wants to update field
|
1. Plugin wants to update field
|
||||||
2. Authoritative handler checks field's `*Source` value
|
2. Authoritative handler checks field's `*Source` value
|
||||||
3. If `*Source` == `LOCKED`, plugin update is rejected
|
3. If `*Source` == `LOCKED`, plugin update is rejected
|
||||||
@@ -38,6 +42,7 @@ When a field is **unlocked**, its source is set to `NEWDEV`, allowing plugins to
|
|||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
### Lock or Unlock a Field
|
### Lock or Unlock a Field
|
||||||
|
|
||||||
```
|
```
|
||||||
POST /device/{mac}/field/lock
|
POST /device/{mac}/field/lock
|
||||||
Authorization: Bearer {API_TOKEN}
|
Authorization: Bearer {API_TOKEN}
|
||||||
@@ -134,6 +139,7 @@ The Device Edit form displays lock/unlock buttons for all tracked fields:
|
|||||||
3. **Source Indicator**: Shows current field source (USER, LOCKED, NEWDEV, or plugin name)
|
3. **Source Indicator**: Shows current field source (USER, LOCKED, NEWDEV, or plugin name)
|
||||||
|
|
||||||
### Source Indicator Colors
|
### Source Indicator Colors
|
||||||
|
|
||||||
- Red (USER): Field was manually edited by a user
|
- Red (USER): Field was manually edited by a user
|
||||||
- Orange (LOCKED): Field is locked and protected from overwrites
|
- Orange (LOCKED): Field is locked and protected from overwrites
|
||||||
- Gray (NEWDEV/Plugin): Field value came from automatic discovery
|
- Gray (NEWDEV/Plugin): Field value came from automatic discovery
|
||||||
@@ -141,6 +147,7 @@ The Device Edit form displays lock/unlock buttons for all tracked fields:
|
|||||||
## UI Workflow
|
## UI Workflow
|
||||||
|
|
||||||
### Locking a Field via UI
|
### Locking a Field via UI
|
||||||
|
|
||||||
1. Navigate to Device Details
|
1. Navigate to Device Details
|
||||||
2. Find the field you want to protect
|
2. Find the field you want to protect
|
||||||
3. Click the lock button (🔒) next to the field
|
3. Click the lock button (🔒) next to the field
|
||||||
@@ -148,6 +155,7 @@ The Device Edit form displays lock/unlock buttons for all tracked fields:
|
|||||||
5. Field is now protected from plugin overwrites
|
5. Field is now protected from plugin overwrites
|
||||||
|
|
||||||
### Unlocking a Field via UI
|
### Unlocking a Field via UI
|
||||||
|
|
||||||
1. Find the locked field (button shows 🔓)
|
1. Find the locked field (button shows 🔓)
|
||||||
2. Click the unlock button
|
2. Click the unlock button
|
||||||
3. Button changes back to lock (🔒) and source resets to NEWDEV
|
3. Button changes back to lock (🔒) and source resets to NEWDEV
|
||||||
@@ -167,59 +175,16 @@ The lock/unlock feature is implemented in:
|
|||||||
- **Data Model**: `/server/models/device_instance.py` - Authorization checks in `setDeviceData()`
|
- **Data Model**: `/server/models/device_instance.py` - Authorization checks in `setDeviceData()`
|
||||||
- **Database**: Devices table with `*Source` columns tracking field origins
|
- **Database**: Devices table with `*Source` columns tracking field origins
|
||||||
|
|
||||||
### Frontend Logic
|
|
||||||
The lock/unlock UI is implemented in:
|
|
||||||
- **Device Edit Form**: `/front/deviceDetailsEdit.php`
|
|
||||||
- Form rendering with lock/unlock buttons
|
|
||||||
- JavaScript function `toggleFieldLock()` for API calls
|
|
||||||
- Source indicator display
|
|
||||||
- **Styling**: `/front/css/app.css` - Lock button and source indicator styles
|
|
||||||
|
|
||||||
### Authorization Handler
|
### Authorization Handler
|
||||||
|
|
||||||
The authoritative field update logic prevents plugin overwrites:
|
The authoritative field update logic prevents plugin overwrites:
|
||||||
|
|
||||||
1. Plugin provides new value for field via plugin config `SET_ALWAYS`/`SET_EMPTY`
|
1. Plugin provides new value for field via plugin config `SET_ALWAYS`/`SET_EMPTY`
|
||||||
2. Authoritative handler (in DeviceInstance) checks `{field}Source` value
|
2. Authoritative handler (in DeviceInstance) checks `{field}Source` value
|
||||||
3. If source is `LOCKED` or `USER`, plugin update is rejected
|
3. If source is `LOCKED` or `USER`, plugin update is rejected
|
||||||
4. If source is `NEWDEV` or plugin name, plugin update is accepted
|
4. If source is `NEWDEV` or plugin name, plugin update is accepted
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### When to Lock Fields
|
|
||||||
- Device names that you've customized
|
|
||||||
- Static IP addresses or important identifiers
|
|
||||||
- Device vendor information you've corrected
|
|
||||||
- Fields prone to incorrect plugin updates
|
|
||||||
|
|
||||||
### When to Keep Unlocked
|
|
||||||
- Fields that plugins actively maintain (MAC, IP address)
|
|
||||||
- Fields you want auto-updated by discovery plugins
|
|
||||||
- Fields that may change frequently in your network
|
|
||||||
|
|
||||||
### Bulk Operations
|
|
||||||
The field lock/unlock feature is currently per-device. For bulk locking:
|
|
||||||
1. Use Multi-Edit to update device fields
|
|
||||||
2. Then use individual lock operations via API script
|
|
||||||
3. Or contact support for bulk lock endpoint
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Lock Button Not Visible
|
|
||||||
- Device must be saved/created first (not "new" device)
|
|
||||||
- Field must be one of the 10 tracked fields
|
|
||||||
- Check browser console for JavaScript errors
|
|
||||||
|
|
||||||
### Lock Operation Failed
|
|
||||||
- Verify API token is valid
|
|
||||||
- Check device MAC address is correct
|
|
||||||
- Ensure device exists in database
|
|
||||||
|
|
||||||
### Field Still Updating After Lock
|
|
||||||
- Verify lock was successful (check API response)
|
|
||||||
- Reload device details page
|
|
||||||
- Check plugin logs to see if plugin is providing the field
|
|
||||||
- Look for authorization errors in NetAlertX logs
|
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
- [API Device Endpoints Documentation](API_DEVICE.md)
|
- [API Device Endpoints Documentation](./API_DEVICE.md)
|
||||||
- [Authoritative Field Updates System](../docs/PLUGINS_DEV.md#authoritative-fields)
|
- [Authoritative Field Updates System](./PLUGINS_DEV.md#authoritative-fields)
|
||||||
- [Plugin Configuration Reference](../docs/PLUGINS_DEV_CONFIG.md)
|
- [Plugin Configuration Reference](./PLUGINS_DEV_CONFIG.md)
|
||||||
|
|||||||
@@ -187,20 +187,20 @@ For tracked fields (devMac, devName, devLastIP, devVendor, devFQDN, devSSID, dev
|
|||||||
|
|
||||||
Controls whether a plugin field is enabled:
|
Controls whether a plugin field is enabled:
|
||||||
|
|
||||||
- `"1"` - Plugin can always overwrite this field when authorized (subject to source-based permissions)
|
- `["devName", "devLastIP"]` - Plugin can always overwrite this field when authorized (subject to source-based permissions)
|
||||||
- `"0"` - Plugin doesn't use this field
|
|
||||||
|
**Authorization logic:** Even with a field listed in `SET_ALWAYS`, the plugin respects source-based permissions:
|
||||||
|
|
||||||
**Authorization logic:** Even with `SET_ALWAYS: "1"`, the plugin respects source-based permissions:
|
|
||||||
- Cannot overwrite `USER` source (user manually edited)
|
- Cannot overwrite `USER` source (user manually edited)
|
||||||
- Cannot overwrite `LOCKED` source (user locked field)
|
- Cannot overwrite `LOCKED` source (user locked field)
|
||||||
- Can overwrite `NEWDEV` or plugin-owned sources (if plugin has SET_ALWAYS enabled)
|
- Can overwrite `NEWDEV` or plugin-owned sources (if plugin has SET_ALWAYS enabled)
|
||||||
|
- Will update plugin-owned sources if value the same
|
||||||
|
|
||||||
**Example in config.json:**
|
**Example in config.json:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"setKey": "NEWDEV_devName",
|
"SET_ALWAYS": ["devName", "devLastIP"]
|
||||||
"displayName": "Device Name",
|
|
||||||
"SET_ALWAYS": "1"
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -210,50 +210,18 @@ Controls whether a plugin field is enabled:
|
|||||||
|
|
||||||
Restricts when a plugin can update a field:
|
Restricts when a plugin can update a field:
|
||||||
|
|
||||||
- `"1"` - Overwrite only if current value is empty OR source is NEWDEV (conservative mode)
|
- `"SET_EMPTY": ["devName", "devLastIP"]` - Overwrite these fields only if current value is empty OR source is `NEWDEV`
|
||||||
- `"0"` - No extra restriction; respect authorization logic (default)
|
|
||||||
|
|
||||||
**Use case:** Some plugins discover optional enrichment data (like vendor/hostname) that shouldn't override user-set or existing values. Use `SET_EMPTY: "1"` to be less aggressive.
|
**Use case:** Some plugins discover optional enrichment data (like vendor/hostname) that shouldn't override user-set or existing values. Use `SET_EMPTY` to be less aggressive.
|
||||||
|
|
||||||
**Example in config.json:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"setKey": "NEWDEV_devVendor",
|
|
||||||
"displayName": "Device Vendor",
|
|
||||||
"SET_ALWAYS": "1",
|
|
||||||
"SET_EMPTY": "1"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Authorization Decision Flow
|
### Authorization Decision Flow
|
||||||
|
|
||||||
1. **Source check:** Is field LOCKED or USER? → REJECT (protected)
|
1. **Source check:** Is field LOCKED or USER? → REJECT (protected)
|
||||||
2. **SET_ALWAYS check:** Is SET_ALWAYS enabled for this plugin+field? → YES: ALLOW (can overwrite empty values, NEWDEV, plugin sources, etc.) | NO: Continue to step 3
|
2. **Field in SET_ALWAYS check:** Is SET_ALWAYS enabled for this plugin+field? → YES: ALLOW (can overwrite empty values, NEWDEV, plugin sources, etc.) | NO: Continue to step 3
|
||||||
3. **SET_EMPTY check:** Is SET_EMPTY enabled AND field non-empty+non-NEWDEV? → REJECT
|
3. **Field in SET_EMPTY check:** Is SET_EMPTY enabled AND field non-empty+non-NEWDEV? → REJECT
|
||||||
4. **Default behavior:** Allow overwrite if field empty or NEWDEV source
|
4. **Default behavior:** Allow overwrite if field empty or NEWDEV source
|
||||||
|
|
||||||
### Plugin Field Mappings Reference
|
|
||||||
|
|
||||||
This table shows all device discovery and enrichment plugins and their tracked field configuration:
|
|
||||||
|
|
||||||
| Plugin | Tracked Fields | Behavior |
|
|
||||||
|--------|---|---|
|
|
||||||
| ARPSCAN | devMac, devLastIP | SET_ALWAYS for both |
|
|
||||||
| IPNEIGH | devMac, devLastIP | SET_ALWAYS for both |
|
|
||||||
| DHCPLSS | devMac, devLastIP | SET_ALWAYS for both |
|
|
||||||
| ASUSWRT | devMac, devLastIP | SET_ALWAYS for both |
|
|
||||||
| LUCIRPC | devMac, devLastIP | SET_ALWAYS for both |
|
|
||||||
| PIHOLE | devMac, devLastIP, devName, devVendor | SET_ALWAYS for MAC/IP |
|
|
||||||
| PIHOLEAPI | devMac, devLastIP, devName, devVendor | SET_ALWAYS for MAC/IP, SET_EMPTY for name/vendor |
|
|
||||||
| NBTSCAN | devName | SET_ALWAYS |
|
|
||||||
| DIGSCAN | devName, devFQDN | SET_ALWAYS |
|
|
||||||
| NSLOOKUP | devName, devFQDN | SET_ALWAYS |
|
|
||||||
| AVAHISCAN | devName | SET_ALWAYS |
|
|
||||||
| VNDRPDT | devMac, devVendor | SET_ALWAYS for both |
|
|
||||||
| SNMPDSC | devMac, devLastIP | SET_ALWAYS for both |
|
|
||||||
| UNIFIMP | devMac, devLastIP, devName, devVendor, devSSID, devParentMAC, devParentPort | SET_ALWAYS for MAC/IP |
|
|
||||||
| UNIFIAPI | devMac, devLastIP, devName, devParentMAC | SET_ALWAYS for MAC/IP |
|
|
||||||
|
|
||||||
**Note:** Check each plugin's `config.json` manifest for its specific SET_ALWAYS/SET_EMPTY configuration.
|
**Note:** Check each plugin's `config.json` manifest for its specific SET_ALWAYS/SET_EMPTY configuration.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -10,16 +10,14 @@ The device field lock/unlock system allows you to protect specific device fields
|
|||||||
|
|
||||||
These are the ONLY fields that can be locked:
|
These are the ONLY fields that can be locked:
|
||||||
|
|
||||||
1. devMac - Device MAC address
|
- devName - Device hostname/alias
|
||||||
2. devName - Device hostname/alias
|
- devVendor - Device manufacturer
|
||||||
3. devLastIP - Last known IP address
|
- devFQDN - Fully qualified domain name
|
||||||
4. devVendor - Device manufacturer
|
- devSSID - WiFi network name
|
||||||
5. devFQDN - Fully qualified domain name
|
- devParentMAC - Parent/gateway MAC
|
||||||
6. devSSID - WiFi network name
|
- devParentPort - Parent device port
|
||||||
7. devParentMAC - Parent/gateway MAC
|
- devParentRelType - Relationship type (e.g., "gateway")
|
||||||
8. devParentPort - Parent device port
|
- devVlan - VLAN identifier
|
||||||
9. devParentRelType - Relationship type (e.g., "gateway")
|
|
||||||
10. devVlan - VLAN identifier
|
|
||||||
|
|
||||||
## Source Values Explained
|
## Source Values Explained
|
||||||
|
|
||||||
@@ -27,10 +25,10 @@ Each locked field has a "source" indicator that shows you why the value is prote
|
|||||||
|
|
||||||
| Indicator | Meaning | Can It Change? |
|
| Indicator | Meaning | Can It Change? |
|
||||||
|-----------|---------|---|
|
|-----------|---------|---|
|
||||||
| 🔒 **LOCKED** (red badge) | You locked this field | No, until you unlock it |
|
| 🔒 **LOCKED** | You locked this field | No, until you unlock it |
|
||||||
| ✏️ **USER** (orange badge) | You edited this field | No, plugins can't overwrite |
|
| ✏️ **USER** | You edited this field | No, plugins can't overwrite |
|
||||||
| 📡 **NEWDEV** (gray badge) | Default/unset value | Yes, plugins can update |
|
| 📡 **NEWDEV** | Default/unset value | Yes, plugins can update |
|
||||||
| 📡 **Plugin name** (gray badge) | Last updated by a plugin (e.g., UNIFIAPI) | Yes, plugins can update |
|
| 📡 **Plugin name** | Last updated by a plugin (e.g., UNIFIAPI) | Yes, plugins can update if field in SET_ALWAYS |
|
||||||
|
|
||||||
## How to Use
|
## How to Use
|
||||||
|
|
||||||
@@ -39,15 +37,15 @@ Each locked field has a "source" indicator that shows you why the value is prote
|
|||||||
1. Navigate to **Device Details** for the device
|
1. Navigate to **Device Details** for the device
|
||||||
2. Find the field you want to protect (e.g., device name)
|
2. Find the field you want to protect (e.g., device name)
|
||||||
3. Click the **lock button** (🔒) next to the field
|
3. Click the **lock button** (🔒) next to the field
|
||||||
4. The button changes to **unlock** (🔓) and turns red
|
4. The button changes to **unlock** (🔓)
|
||||||
5. That field is now protected
|
5. That field is now protected
|
||||||
|
|
||||||
### Unlock a Field (Allow Plugin Updates)
|
### Unlock a Field (Allow Plugin Updates)
|
||||||
|
|
||||||
1. Go to **Device Details**
|
1. Go to **Device Details**
|
||||||
2. Find the locked field (shows 🔓 in red)
|
2. Find the locked field (shows 🔓)
|
||||||
3. Click the **unlock button** (🔓)
|
3. Click the **unlock button** (🔓)
|
||||||
4. The button changes back to **lock** (🔒) and turns gray
|
4. The button changes back to **lock** (🔒)
|
||||||
5. Plugins can now update that field again
|
5. Plugins can now update that field again
|
||||||
|
|
||||||
## Common Scenarios
|
## Common Scenarios
|
||||||
@@ -77,9 +75,9 @@ Each locked field has a "source" indicator that shows you why the value is prote
|
|||||||
|
|
||||||
- ✅ Your custom value is kept
|
- ✅ Your custom value is kept
|
||||||
- ✅ Future plugin scans won't overwrite it
|
- ✅ Future plugin scans won't overwrite it
|
||||||
- ✅ You can still manually edit it anytime
|
- ✅ You can still manually edit it anytime after unlocking
|
||||||
- ✅ Lock persists across plugin runs
|
- ✅ Lock persists across plugin runs
|
||||||
- ✅ Other users can see it's locked (red indicator)
|
- ✅ Other users can see it's locked
|
||||||
|
|
||||||
## What Happens When You Unlock a Field
|
## What Happens When You Unlock a Field
|
||||||
|
|
||||||
@@ -92,7 +90,7 @@ Each locked field has a "source" indicator that shows you why the value is prote
|
|||||||
|
|
||||||
| Message | What It Means | What to Do |
|
| Message | What It Means | What to Do |
|
||||||
|---------|--------------|-----------|
|
|---------|--------------|-----------|
|
||||||
| "Field cannot be locked" | You tried to lock a field that doesn't support locking | Only lock the 10 fields listed above |
|
| "Field cannot be locked" | You tried to lock a field that doesn't support locking | Only lock the fields listed above |
|
||||||
| "Device not found" | The device MAC address doesn't exist | Verify the device hasn't been deleted |
|
| "Device not found" | The device MAC address doesn't exist | Verify the device hasn't been deleted |
|
||||||
| Lock button doesn't work | Network or permission issue | Refresh the page and try again |
|
| Lock button doesn't work | Network or permission issue | Refresh the page and try again |
|
||||||
| Unexpected field changed | Field might have been unlocked | Check if field shows unlock icon (🔓) |
|
| Unexpected field changed | Field might have been unlocked | Check if field shows unlock icon (🔓) |
|
||||||
@@ -121,7 +119,7 @@ Each locked field has a "source" indicator that shows you why the value is prote
|
|||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Lock button not appearing:**
|
**Lock button not appearing:**
|
||||||
- Confirm the field is one of the 10 tracked fields (see list above)
|
- Confirm the field is one of the tracked fields (see list above)
|
||||||
- Confirm the device is already saved (new devices don't show lock buttons)
|
- Confirm the device is already saved (new devices don't show lock buttons)
|
||||||
- Refresh the page
|
- Refresh the page
|
||||||
|
|
||||||
@@ -132,10 +130,10 @@ Each locked field has a "source" indicator that shows you why the value is prote
|
|||||||
- Try again in a few seconds
|
- Try again in a few seconds
|
||||||
|
|
||||||
**Field still changes after locking:**
|
**Field still changes after locking:**
|
||||||
- Double-check the lock icon shows (red indicator)
|
- Double-check the lock icon shows
|
||||||
- Reload the page—the change might be a display issue
|
- Reload the page—the change might be a display issue
|
||||||
- Check if you accidentally unlocked it
|
- Check if you accidentally unlocked it
|
||||||
- Contact support if it persists
|
- pen an issue if it persists
|
||||||
|
|
||||||
## For More Information
|
## For More Information
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ def process_scan(db):
|
|||||||
|
|
||||||
# Clear current scan as processed
|
# Clear current scan as processed
|
||||||
# 🐛 CurrentScan DEBUG: comment out below when debugging to keep the CurrentScan table after restarts/scan finishes
|
# 🐛 CurrentScan DEBUG: comment out below when debugging to keep the CurrentScan table after restarts/scan finishes
|
||||||
# db.sql.execute("DELETE FROM CurrentScan")
|
db.sql.execute("DELETE FROM CurrentScan")
|
||||||
|
|
||||||
# Commit changes
|
# Commit changes
|
||||||
db.commitDB()
|
db.commitDB()
|
||||||
|
|||||||
@@ -15,61 +15,61 @@ class TestCanOverwriteField:
|
|||||||
def test_user_source_prevents_overwrite(self):
|
def test_user_source_prevents_overwrite(self):
|
||||||
"""USER source should prevent any overwrite."""
|
"""USER source should prevent any overwrite."""
|
||||||
assert not can_overwrite_field(
|
assert not can_overwrite_field(
|
||||||
"devName", "USER", "UNIFIAPI", {"set_always": [], "set_empty": []}, "NewName"
|
"devName", "OldName", "USER", "UNIFIAPI", {"set_always": [], "set_empty": []}, "NewName"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_locked_source_prevents_overwrite(self):
|
def test_locked_source_prevents_overwrite(self):
|
||||||
"""LOCKED source should prevent any overwrite."""
|
"""LOCKED source should prevent any overwrite."""
|
||||||
assert not can_overwrite_field(
|
assert not can_overwrite_field(
|
||||||
"devName", "LOCKED", "ARPSCAN", {"set_always": [], "set_empty": []}, "NewName"
|
"devName", "OldName", "LOCKED", "ARPSCAN", {"set_always": [], "set_empty": []}, "NewName"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_empty_value_prevents_overwrite(self):
|
def test_empty_value_prevents_overwrite(self):
|
||||||
"""Empty/None values should prevent overwrite."""
|
"""Empty/None values should prevent overwrite."""
|
||||||
assert not can_overwrite_field(
|
assert not can_overwrite_field(
|
||||||
"devName", "", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, ""
|
"devName", "OldName", "", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, ""
|
||||||
)
|
)
|
||||||
assert not can_overwrite_field(
|
assert not can_overwrite_field(
|
||||||
"devName", "", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, None
|
"devName", "OldName", "", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, None
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_set_always_allows_overwrite(self):
|
def test_set_always_allows_overwrite(self):
|
||||||
"""SET_ALWAYS should allow overwrite regardless of current source."""
|
"""SET_ALWAYS should allow overwrite regardless of current source."""
|
||||||
assert can_overwrite_field(
|
assert can_overwrite_field(
|
||||||
"devName", "ARPSCAN", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, "NewName"
|
"devName", "OldName", "ARPSCAN", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, "NewName"
|
||||||
)
|
)
|
||||||
assert can_overwrite_field(
|
assert can_overwrite_field(
|
||||||
"devName", "NEWDEV", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, "NewName"
|
"devName", "", "NEWDEV", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, "NewName"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_set_empty_allows_overwrite_only_when_empty(self):
|
def test_set_empty_allows_overwrite_only_when_empty(self):
|
||||||
"""SET_EMPTY should allow overwrite only if field is empty or NEWDEV."""
|
"""SET_EMPTY should allow overwrite only if field is empty or NEWDEV."""
|
||||||
assert can_overwrite_field(
|
assert can_overwrite_field(
|
||||||
"devName", "", "UNIFIAPI", {"set_always": [], "set_empty": ["devName"]}, "NewName"
|
"devName", "", "", "UNIFIAPI", {"set_always": [], "set_empty": ["devName"]}, "NewName"
|
||||||
)
|
)
|
||||||
assert can_overwrite_field(
|
assert can_overwrite_field(
|
||||||
"devName", "NEWDEV", "UNIFIAPI", {"set_always": [], "set_empty": ["devName"]}, "NewName"
|
"devName", "", "NEWDEV", "UNIFIAPI", {"set_always": [], "set_empty": ["devName"]}, "NewName"
|
||||||
)
|
)
|
||||||
assert not can_overwrite_field(
|
assert not can_overwrite_field(
|
||||||
"devName", "ARPSCAN", "UNIFIAPI", {"set_always": [], "set_empty": ["devName"]}, "NewName"
|
"devName", "OldName", "ARPSCAN", "UNIFIAPI", {"set_always": [], "set_empty": ["devName"]}, "NewName"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_default_behavior_overwrites_empty_fields(self):
|
def test_default_behavior_overwrites_empty_fields(self):
|
||||||
"""Without SET_ALWAYS/SET_EMPTY, should overwrite only empty fields."""
|
"""Without SET_ALWAYS/SET_EMPTY, should overwrite only empty fields."""
|
||||||
assert can_overwrite_field(
|
assert can_overwrite_field(
|
||||||
"devName", "", "UNIFIAPI", {"set_always": [], "set_empty": []}, "NewName"
|
"devName", "", "", "UNIFIAPI", {"set_always": [], "set_empty": []}, "NewName"
|
||||||
)
|
)
|
||||||
assert can_overwrite_field(
|
assert can_overwrite_field(
|
||||||
"devName", "NEWDEV", "UNIFIAPI", {"set_always": [], "set_empty": []}, "NewName"
|
"devName", "", "NEWDEV", "UNIFIAPI", {"set_always": [], "set_empty": []}, "NewName"
|
||||||
)
|
)
|
||||||
assert not can_overwrite_field(
|
assert not can_overwrite_field(
|
||||||
"devName", "ARPSCAN", "UNIFIAPI", {"set_always": [], "set_empty": []}, "NewName"
|
"devName", "OldName", "ARPSCAN", "UNIFIAPI", {"set_always": [], "set_empty": []}, "NewName"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_whitespace_value_treated_as_empty(self):
|
def test_whitespace_value_treated_as_empty(self):
|
||||||
"""Whitespace-only values should be treated as empty."""
|
"""Whitespace-only values should be treated as empty."""
|
||||||
assert not can_overwrite_field(
|
assert not can_overwrite_field(
|
||||||
"devName", "", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, " "
|
"devName", "OldName", "", "UNIFIAPI", {"set_always": ["devName"], "set_empty": []}, " "
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -355,6 +355,7 @@ class TestFieldLockIntegration:
|
|||||||
proposed_value = "Plugin Name"
|
proposed_value = "Plugin Name"
|
||||||
can_overwrite = can_overwrite_field(
|
can_overwrite = can_overwrite_field(
|
||||||
"devName",
|
"devName",
|
||||||
|
device_data.get("devName"),
|
||||||
device_data.get("devNameSource"),
|
device_data.get("devNameSource"),
|
||||||
plugin_prefix,
|
plugin_prefix,
|
||||||
plugin_settings,
|
plugin_settings,
|
||||||
|
|||||||
Reference in New Issue
Block a user