from fastapi import status from handler.database import db_device_handler from models.device import Device from models.user import User class TestDeviceEndpoints: def test_register_device(self, client, access_token: str): response = client.post( "/api/devices", json={ "name": "Test Device", "platform": "android", "client": "argosy", "client_version": "0.16.0", }, headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_201_CREATED data = response.json() assert data["name"] == "Test Device" assert "device_id" in data assert "created_at" in data def test_register_device_minimal(self, client, access_token: str): response = client.post( "/api/devices", json={}, headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_201_CREATED data = response.json() assert data["name"] is None assert "device_id" in data def test_list_devices(self, client, access_token: str, admin_user: User): db_device_handler.add_device( Device( id="test-device-1", user_id=admin_user.id, name="Device 1", ) ) db_device_handler.add_device( Device( id="test-device-2", user_id=admin_user.id, name="Device 2", ) ) response = client.get( "/api/devices", headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_200_OK data = response.json() assert len(data) == 2 names = [d["name"] for d in data] assert "Device 1" in names assert "Device 2" in names def test_get_device(self, client, access_token: str, admin_user: User): device = db_device_handler.add_device( Device( id="test-device-get", user_id=admin_user.id, name="Get Test Device", platform="linux", ) ) response = client.get( f"/api/devices/{device.id}", headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_200_OK data = response.json() assert data["id"] == "test-device-get" assert data["name"] == "Get Test Device" assert data["platform"] == "linux" def test_get_device_not_found(self, client, access_token: str): response = client.get( "/api/devices/nonexistent-device", headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_404_NOT_FOUND def test_update_device(self, client, access_token: str, admin_user: User): device = db_device_handler.add_device( Device( id="test-device-update", user_id=admin_user.id, name="Original Name", ) ) response = client.put( f"/api/devices/{device.id}", json={ "name": "Updated Name", "platform": "android", "client": "daijishou", "client_version": "4.0.0", "ip_address": "192.168.1.100", "mac_address": "AA:BB:CC:DD:EE:FF", "hostname": "my-odin3", "sync_enabled": False, }, headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_200_OK data = response.json() assert data["name"] == "Updated Name" assert data["platform"] == "android" assert data["client"] == "daijishou" assert data["client_version"] == "4.0.0" assert data["ip_address"] == "192.168.1.100" assert data["mac_address"] == "AA:BB:CC:DD:EE:FF" assert data["hostname"] == "my-odin3" assert data["sync_enabled"] is False def test_delete_device(self, client, access_token: str, admin_user: User): device = db_device_handler.add_device( Device( id="test-device-delete", user_id=admin_user.id, name="To Delete", ) ) response = client.delete( f"/api/devices/{device.id}", headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_204_NO_CONTENT get_response = client.get( f"/api/devices/{device.id}", headers={"Authorization": f"Bearer {access_token}"}, ) assert get_response.status_code == status.HTTP_404_NOT_FOUND class TestDeviceUserIsolation: def test_list_devices_only_returns_own_devices( self, client, access_token: str, editor_access_token: str, admin_user: User, editor_user: User, ): db_device_handler.add_device( Device(id="admin-device", user_id=admin_user.id, name="Admin Device") ) db_device_handler.add_device( Device(id="editor-device", user_id=editor_user.id, name="Editor Device") ) admin_response = client.get( "/api/devices", headers={"Authorization": f"Bearer {access_token}"}, ) assert admin_response.status_code == status.HTTP_200_OK admin_devices = admin_response.json() assert len(admin_devices) == 1 assert admin_devices[0]["name"] == "Admin Device" editor_response = client.get( "/api/devices", headers={"Authorization": f"Bearer {editor_access_token}"}, ) assert editor_response.status_code == status.HTTP_200_OK editor_devices = editor_response.json() assert len(editor_devices) == 1 assert editor_devices[0]["name"] == "Editor Device" def test_cannot_get_other_users_device( self, client, editor_access_token: str, admin_user: User, ): device = db_device_handler.add_device( Device(id="admin-only-device", user_id=admin_user.id, name="Admin Only") ) response = client.get( f"/api/devices/{device.id}", headers={"Authorization": f"Bearer {editor_access_token}"}, ) assert response.status_code == status.HTTP_404_NOT_FOUND def test_cannot_update_other_users_device( self, client, editor_access_token: str, admin_user: User, ): device = db_device_handler.add_device( Device(id="admin-protected-device", user_id=admin_user.id, name="Protected") ) response = client.put( f"/api/devices/{device.id}", json={"name": "Hacked Name"}, headers={"Authorization": f"Bearer {editor_access_token}"}, ) assert response.status_code == status.HTTP_404_NOT_FOUND original = db_device_handler.get_device( device_id=device.id, user_id=admin_user.id ) assert original is not None assert original.name == "Protected" def test_cannot_delete_other_users_device( self, client, editor_access_token: str, admin_user: User, ): device = db_device_handler.add_device( Device(id="admin-nodelete-device", user_id=admin_user.id, name="No Delete") ) response = client.delete( f"/api/devices/{device.id}", headers={"Authorization": f"Bearer {editor_access_token}"}, ) assert response.status_code == status.HTTP_404_NOT_FOUND still_exists = db_device_handler.get_device( device_id=device.id, user_id=admin_user.id ) assert still_exists is not None class TestDeviceDuplicateHandling: def test_duplicate_mac_address_returns_existing( self, client, access_token: str, admin_user: User ): db_device_handler.add_device( Device( id="existing-mac-device", user_id=admin_user.id, name="Existing Device", mac_address="AA:BB:CC:DD:EE:FF", ) ) response = client.post( "/api/devices", json={ "name": "New Device", "mac_address": "AA:BB:CC:DD:EE:FF", }, headers={"Authorization": f"Bearer {access_token}"}, ) 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_existing( self, client, access_token: str, admin_user: User ): db_device_handler.add_device( Device( id="existing-hostname-device", user_id=admin_user.id, name="Existing Device", hostname="my-device", platform="android", ) ) response = client.post( "/api/devices", json={ "name": "New Device", "hostname": "my-device", "platform": "android", }, 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"] == "reject-duplicate-device" def test_allow_existing_returns_existing_device( self, client, access_token: str, admin_user: User ): existing = db_device_handler.add_device( Device( id="allow-existing-device", user_id=admin_user.id, name="Existing Device", mac_address="11:22:33:44:55:66", ) ) response = client.post( "/api/devices", json={ "name": "New Device Name", "mac_address": "11:22:33:44:55:66", "allow_existing": True, }, headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_200_OK data = response.json() assert data["device_id"] == existing.id assert data["name"] == "Existing Device" def test_allow_existing_with_reset_syncs( self, client, access_token: str, admin_user: User, rom ): from handler.database import db_device_save_sync_handler, db_save_handler from models.assets import Save existing = db_device_handler.add_device( Device( id="reset-syncs-device", user_id=admin_user.id, name="Device With Syncs", mac_address="77:88:99:AA:BB:CC", ) ) save = db_save_handler.add_save( Save( file_name="test.sav", file_name_no_tags="test", file_name_no_ext="test", file_extension="sav", file_path="/saves", file_size_bytes=100, rom_id=rom.id, user_id=admin_user.id, ) ) db_device_save_sync_handler.upsert_sync(device_id=existing.id, save_id=save.id) sync_before = db_device_save_sync_handler.get_sync( device_id=existing.id, save_id=save.id ) assert sync_before is not None response = client.post( "/api/devices", json={ "mac_address": "77:88:99:AA:BB:CC", "allow_existing": True, "reset_syncs": True, }, headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_200_OK assert response.json()["device_id"] == existing.id sync_after = db_device_save_sync_handler.get_sync( device_id=existing.id, save_id=save.id ) assert sync_after is None def test_allow_duplicate_creates_new_device( self, client, access_token: str, admin_user: User ): existing = db_device_handler.add_device( Device( id="original-device", user_id=admin_user.id, name="Original Device", mac_address="DD:EE:FF:00:11:22", ) ) response = client.post( "/api/devices", json={ "name": "Duplicate Install", "mac_address": "DD:EE:FF:00:11:22", "allow_duplicate": True, }, headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_201_CREATED data = response.json() assert data["device_id"] != existing.id assert data["name"] == "Duplicate Install" def test_no_conflict_without_fingerprint(self, client, access_token: str): response1 = client.post( "/api/devices", json={"name": "Device 1"}, headers={"Authorization": f"Bearer {access_token}"}, ) assert response1.status_code == status.HTTP_201_CREATED response2 = client.post( "/api/devices", json={"name": "Device 2"}, headers={"Authorization": f"Bearer {access_token}"}, ) assert response2.status_code == status.HTTP_201_CREATED assert response1.json()["device_id"] != response2.json()["device_id"] def test_hostname_only_no_conflict_without_platform( self, client, access_token: str, admin_user: User ): db_device_handler.add_device( Device( id="hostname-only-device", user_id=admin_user.id, name="Existing", hostname="my-device", ) ) response = client.post( "/api/devices", json={ "name": "New Device", "hostname": "my-device", }, headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_201_CREATED class TestSyncConfigMasking: def test_ssh_credentials_masked_in_response( self, client, access_token: str, admin_user: User ): db_device_handler.add_device( Device( id="mask-dev-1", user_id=admin_user.id, name="SSH Device", sync_config={ "ssh_host": "192.168.1.100", "ssh_port": 22, "ssh_username": "retro", "ssh_password": "supersecret123", "ssh_key_path": "/home/retro/.ssh/id_rsa", "save_directories": [ {"platform_slug": "gba", "path": "/saves/gba"} ], }, ) ) response = client.get( "/api/devices/mask-dev-1", headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_200_OK config = response.json()["sync_config"] assert config["ssh_host"] == "192.168.1.100" assert config["ssh_port"] == 22 assert config["ssh_username"] == "retro" assert config["ssh_password"] == "********" assert config["ssh_key_path"] == "********" assert config["save_directories"] == [ {"platform_slug": "gba", "path": "/saves/gba"} ] def test_null_sync_config_passes_through( self, client, access_token: str, admin_user: User ): db_device_handler.add_device( Device( id="mask-dev-2", user_id=admin_user.id, name="No Config Device", ) ) response = client.get( "/api/devices/mask-dev-2", headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_200_OK assert response.json()["sync_config"] is None def test_sync_config_without_sensitive_fields( self, client, access_token: str, admin_user: User ): db_device_handler.add_device( Device( id="mask-dev-3", user_id=admin_user.id, sync_config={"ssh_host": "10.0.0.1", "ssh_port": 2222}, ) ) response = client.get( "/api/devices/mask-dev-3", headers={"Authorization": f"Bearer {access_token}"}, ) assert response.status_code == status.HTTP_200_OK config = response.json()["sync_config"] assert config["ssh_host"] == "10.0.0.1" assert config["ssh_port"] == 2222