setMac($mac); $device->setName('Frame ' . $mac); $device->setUser($user); $device->setModel(DeviceModel::V1); $device->setOrientation(Orientation::Landscape); $this->em()->persist($device); $this->em()->flush(); return $device; } private function makeImage($user): Image { $image = (new Image())->setUser($user)->setOriginalFilename('x.jpg')->setStoragePath('x'); $this->em()->persist($image); $this->em()->flush(); return $image; } public function test_list_returns_own_devices(): void { $user = $this->createUser('list@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:A1', $user); $client = $this->loginAs($user); $client->request('GET', '/api/devices'); $this->assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); $this->assertCount(1, $data); $this->assertSame($device->getMac(), $data[0]['mac']); } public function test_list_unauthenticated_returns_redirect(): void { $this->client->request('GET', '/api/devices'); // form_login firewall redirects unauthenticated requests to /login $this->assertResponseRedirects('/login'); } public function test_patch_updates_name_orientation_and_interval(): void { $user = $this->createUser('patch@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:A2', $user); $client = $this->loginAs($user); $client->request('PATCH', '/api/devices/' . $device->getId(), [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode([ 'name' => 'New Name', 'orientation' => 'portrait', 'rotationIntervalMinutes' => 30, ])); $this->assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); $this->assertSame('New Name', $data['name']); $this->assertSame('portrait', $data['orientation']); $this->assertSame(30, $data['rotationIntervalMinutes']); } public function test_patch_wrong_users_device_returns_404(): void { $owner = $this->createUser('owner@example.com'); $other = $this->createUser('other@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:A3', $owner); $client = $this->loginAs($other); $client->request('PATCH', '/api/devices/' . $device->getId(), [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['name' => 'Hack'])); $this->assertResponseStatusCodeSame(404); } public function test_put_lock_sets_locked_image_id(): void { $user = $this->createUser('lock@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:A4', $user); $image = $this->makeImage($user); $image->approveForDevice($device); $this->em()->flush(); $client = $this->loginAs($user); $client->request('PUT', '/api/devices/' . $device->getId() . '/lock', [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['imageId' => $image->getId()])); $this->assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); $this->assertSame($image->getId(), $data['lockedImageId']); } // A-06: PUT /lock with image not approved for the device → 422 public function test_put_lock_unapproved_image_returns_422(): void { $user = $this->createUser('lock422@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:A8', $user); $image = $this->makeImage($user); // Image is owned by user but NOT approved for this device $client = $this->loginAs($user); $client->request('PUT', '/api/devices/' . $device->getId() . '/lock', [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['imageId' => $image->getId()])); $this->assertResponseStatusCodeSame(422); } public function test_put_lock_image_not_owned_by_user_returns_404(): void { $user1 = $this->createUser('lockown1@example.com'); $user2 = $this->createUser('lockown2@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:A5', $user1); $image = $this->makeImage($user2); $client = $this->loginAs($user1); $client->request('PUT', '/api/devices/' . $device->getId() . '/lock', [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['imageId' => $image->getId()])); $this->assertResponseStatusCodeSame(404); } public function test_delete_lock_clears_locked_image_id(): void { $user = $this->createUser('unlock@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:A6', $user); $image = $this->makeImage($user); $device->setLockedImage($image); $this->em()->flush(); $client = $this->loginAs($user); $client->request('DELETE', '/api/devices/' . $device->getId() . '/lock'); $this->assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); $this->assertNull($data['lockedImageId']); } public function test_put_lock_wrong_users_device_returns_404(): void { $owner = $this->createUser('lockwrong1@example.com'); $other = $this->createUser('lockwrong2@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:A7', $owner); $image = $this->makeImage($other); $client = $this->loginAs($other); $client->request('PUT', '/api/devices/' . $device->getId() . '/lock', [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['imageId' => $image->getId()])); $this->assertResponseStatusCodeSame(404); } public function test_patch_empty_name_returns_422(): void { $user = $this->createUser('patchname@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:B1', $user); $client = $this->loginAs($user); $client->request('PATCH', '/api/devices/' . $device->getId(), [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['name' => ''])); $this->assertResponseStatusCodeSame(422); } public function test_patch_invalid_orientation_returns_422(): void { $user = $this->createUser('patchorient@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:B2', $user); $client = $this->loginAs($user); $client->request('PATCH', '/api/devices/' . $device->getId(), [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['orientation' => 'diagonal'])); $this->assertResponseStatusCodeSame(422); } public function test_patch_invalid_timezone_returns_422(): void { $user = $this->createUser('patchtzone@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:B3', $user); $client = $this->loginAs($user); $client->request('PATCH', '/api/devices/' . $device->getId(), [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['timezone' => 'Not/Real'])); $this->assertResponseStatusCodeSame(422); } public function test_patch_sets_wake_hour(): void { $user = $this->createUser('patchwake@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:B4', $user); $client = $this->loginAs($user); $client->request('PATCH', '/api/devices/' . $device->getId(), [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['wakeHour' => 8])); $this->assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); $this->assertSame(8, $data['wakeHour']); } public function test_patch_sets_uniqueness_window(): void { $user = $this->createUser('patchuniq@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:B5', $user); $client = $this->loginAs($user); $client->request('PATCH', '/api/devices/' . $device->getId(), [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['uniquenessWindow' => 14])); $this->assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); $this->assertSame(14, $data['uniquenessWindow']); } public function test_patch_sets_valid_timezone(): void { $user = $this->createUser('patchtzvld@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:C1', $user); $client = $this->loginAs($user); $client->request('PATCH', '/api/devices/' . $device->getId(), [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode(['timezone' => 'America/New_York'])); $this->assertResponseIsSuccessful(); $data = json_decode($client->getResponse()->getContent(), true); $this->assertSame('America/New_York', $data['timezone']); } public function test_lock_with_no_image_id_returns_422(): void { $user = $this->createUser('locknoimgid@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:C2', $user); $client = $this->loginAs($user); $client->request('PUT', '/api/devices/' . $device->getId() . '/lock', [], [], [ 'CONTENT_TYPE' => 'application/json', ], json_encode([])); $this->assertResponseStatusCodeSame(422); } public function test_unlock_wrong_users_device_returns_404(): void { $owner = $this->createUser('unlockown@example.com'); $other = $this->createUser('unlockoth@example.com'); $device = $this->makeDevice('AA:BB:CC:DD:EE:C3', $owner); $client = $this->loginAs($other); $client->request('DELETE', '/api/devices/' . $device->getId() . '/lock'); $this->assertResponseStatusCodeSame(404); } }