feat(devices): default to returning existing device on duplicate registration

Change allow_existing default to True so duplicate fingerprint matches
return the existing device (200) instead of 409 Conflict. Add model
validator to force allow_existing=False when allow_duplicate is set.
This commit is contained in:
nendo
2026-02-04 11:10:40 +09:00
parent e3642523f9
commit 34f48e58a1
2 changed files with 41 additions and 8 deletions

View File

@@ -2,7 +2,7 @@ import uuid
from datetime import datetime, timezone
from fastapi import HTTPException, Request, Response, status
from pydantic import BaseModel
from pydantic import BaseModel, model_validator
from decorators.auth import protected_route
from endpoints.responses.device import DeviceCreateResponse, DeviceSchema
@@ -26,10 +26,16 @@ class DeviceCreatePayload(BaseModel):
ip_address: str | None = None
mac_address: str | None = None
hostname: str | None = None
allow_existing: bool = False
allow_existing: bool = True
allow_duplicate: bool = False
reset_syncs: bool = False
@model_validator(mode="after")
def _duplicate_disables_existing(self) -> "DeviceCreatePayload":
if self.allow_duplicate:
self.allow_existing = False
return self
class DeviceUpdatePayload(BaseModel):
name: str | None = None

View File

@@ -282,7 +282,7 @@ class TestDeviceUserIsolation:
class TestDeviceDuplicateHandling:
def test_duplicate_mac_address_returns_409(
def test_duplicate_mac_address_returns_existing(
self, client, access_token: str, admin_user: User
):
db_device_handler.add_device(
@@ -303,12 +303,12 @@ class TestDeviceDuplicateHandling:
headers={"Authorization": f"Bearer {access_token}"},
)
assert response.status_code == status.HTTP_409_CONFLICT
data = response.json()["detail"]
assert data["error"] == "device_exists"
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["device_id"] == "existing-mac-device"
assert data["name"] == "Existing Device"
def test_duplicate_hostname_platform_returns_409(
def test_duplicate_hostname_platform_returns_existing(
self, client, access_token: str, admin_user: User
):
db_device_handler.add_device(
@@ -331,10 +331,37 @@ class TestDeviceDuplicateHandling:
headers={"Authorization": f"Bearer {access_token}"},
)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert data["device_id"] == "existing-hostname-device"
assert data["name"] == "Existing Device"
def test_duplicate_with_allow_existing_false_returns_409(
self, client, access_token: str, admin_user: User
):
db_device_handler.add_device(
Device(
id="reject-duplicate-device",
user_id=admin_user.id,
name="Existing Device",
mac_address="FF:EE:DD:CC:BB:AA",
)
)
response = client.post(
"/api/devices",
json={
"name": "New Device",
"mac_address": "FF:EE:DD:CC:BB:AA",
"allow_existing": False,
},
headers={"Authorization": f"Bearer {access_token}"},
)
assert response.status_code == status.HTTP_409_CONFLICT
data = response.json()["detail"]
assert data["error"] == "device_exists"
assert data["device_id"] == "existing-hostname-device"
assert data["device_id"] == "reject-duplicate-device"
def test_allow_existing_returns_existing_device(
self, client, access_token: str, admin_user: User