request = $this->createMock(IRequest::class); $this->db = $this->createMock(IDBConnection::class); $this->session = $this->createMock(ISession::class); $this->userSession = $this->createMock(IUserSession::class); $this->tokenProvider = $this->createMock(IProvider::class); $this->hostValidator = $this->createMock(IRemoteHostValidator::class); $this->identityProof = $this->createMock(Manager::class); } protected function getController(array $methods = []): PushController|MockObject { if (empty($methods)) { return new PushController( 'notifications', $this->request, $this->db, $this->session, $this->userSession, $this->tokenProvider, $this->hostValidator, $this->identityProof, ); } return $this->getMockBuilder(PushController::class) ->setConstructorArgs([ 'notifications', $this->request, $this->db, $this->session, $this->userSession, $this->tokenProvider, $this->hostValidator, $this->identityProof, ]) ->onlyMethods($methods) ->getMock(); } public static function dataRegisterDevice(): array { return [ 'not authenticated' => [ '', '', '', false, 0, false, null, [], Http::STATUS_UNAUTHORIZED ], 'too short token hash' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e47', '', '', true, 0, false, null, ['message' => 'INVALID_PUSHTOKEN_HASH'], Http::STATUS_BAD_REQUEST, ], 'too long token hash' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e4722', '', '', true, 0, false, null, ['message' => 'INVALID_PUSHTOKEN_HASH'], Http::STATUS_BAD_REQUEST, ], 'invalid char in token hash' => [ 'rb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', '', '', true, 0, false, null, ['message' => 'INVALID_PUSHTOKEN_HASH'], Http::STATUS_BAD_REQUEST, ], 'device key invalid start' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', substr(self::$devicePublicKey, 1), '', true, 0, false, null, ['message' => 'INVALID_DEVICE_KEY'], Http::STATUS_BAD_REQUEST, ], 'device key invalid end' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', substr(self::$devicePublicKey, 0, -1), '', true, 0, false, null, ['message' => 'INVALID_DEVICE_KEY'], Http::STATUS_BAD_REQUEST, ], 'device key too much end' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey . "\n\n", '', true, 0, false, null, ['message' => 'INVALID_DEVICE_KEY'], Http::STATUS_BAD_REQUEST, ], 'device key without trailing new line' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey, '', true, 0, false, null, ['message' => 'INVALID_PROXY_SERVER'], Http::STATUS_BAD_REQUEST, ], 'device key with trailing new line' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey . "\n", '', true, 0, false, null, ['message' => 'INVALID_PROXY_SERVER'], Http::STATUS_BAD_REQUEST, ], 'invalid push proxy' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey, 'localhost', true, 0, false, null, ['message' => 'INVALID_PROXY_SERVER'], Http::STATUS_BAD_REQUEST, ], 'using localhost' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey, 'http://localhost/', true, 23, false, null, ['message' => 'INVALID_SESSION_TOKEN'], Http::STATUS_BAD_REQUEST, ], 'using localhost with port' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey, 'http://localhost:8088/', true, 23, false, null, ['message' => 'INVALID_SESSION_TOKEN'], Http::STATUS_BAD_REQUEST, ], 'using production' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey, 'https://push-notifications.nextcloud.com/', true, 23, false, null, ['message' => 'INVALID_SESSION_TOKEN'], Http::STATUS_BAD_REQUEST, ], 'created or updated' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey, 'https://push-notifications.nextcloud.com/', true, 23, true, true, [ 'publicKey' => self::$userPublicKey, 'deviceIdentifier' => 'XUCEZ1EHvTUcVhIvrQQQ1XcP0ZD2BFdFqw4EYbOhBfiEgXgirurR4x/ve4GSSyfivvbQOdOkZUM+g4m+tSb0Ew==', 'signature' => 'LRhbXO71WYX9qqDbQX7C+87YaaFfWoT/vG0DlaXdBz6+lhyOA0dw/1Ggz3fd7RerCQ0MfgnnTyxO+cSeRpUaPdA2yPjfoiPpfYA5SOJQGF3comS/HYna3fHiFDbOoM3BJOnjvqiSZdxA/ICdyl2mEEC5wO7AZ4OZKBTa5XfL7eSCXZLEv1YldqcLOStbXrI7voDQocTMJxoQZI/j8BVcf2i3D6F454aXIFDrYYzC2PQY+CKJoXZW0m0RMWaTM2B8tBmFFwrmaGLDqcjjpd33TsTtsV5DB7WimffLBPpOuGV4Z1Kiagp/mxpPLz2NImNV79mDX9gY3ZppCZTwChP5qQ==', ], Http::STATUS_CREATED, ], 'not updated' => [ 'bb9b52140661ee4f2c31e02ea50a8f67ba353bffc58aa981718f90bd2aa2bd8fc08cad4c0b3ed8f7eb9d79d6a577be75d084bbeb963da1ad74d9279e0014e472', self::$devicePublicKey, 'https://push-notifications.nextcloud.com/', true, 42, true, false, [ 'publicKey' => self::$userPublicKey, 'deviceIdentifier' => 'x9vSImcGjhzR9BfZ/XbbUqqCCNC4bHKsX7vkQWNZRd1/MiY+OuF02fx8K08My0RpkNnwj/rQ/gVSU1oEdFwkww==', 'signature' => 'J9AcdJt5youJmMnBhS+Cc9ytArynIKtCRoNf/m0oOFO/e0hWHqs1NRdQBe81qzYIjf0+bj0Q97X9Xv1rnVJesPkQUbGaa4nAPt+viGSfvzTptjX4LKgqm8B3UkduBA262IcaWgM5P84gUqelkQIC1nIqq/MJTuC6oQ5lUwIV1a92ZurDjhwH4b3f7/ZLTTOTRD0DWN9W/yOyF1qECivgePR3eu+mkcBzXVU/TDZDJic9G7xhqcTnWV6qk+aKyzdNo1tu5W7mF+v5vF6rrGZrq55vPLWAHApTD7P+NFV01BnaCuN7/qGJNVs7m7EH03jpOw7y3jqNMmcmonYrJSMVqg==', ], Http::STATUS_OK, ], ]; } #[DataProvider(methodName: 'dataRegisterDevice')] public function testRegisterDevice(string $pushTokenHash, string $devicePublicKey, string $proxyServer, bool $userIsValid, int $tokenId, bool $tokenIsValid, ?bool $deviceCreated, array $payload, int $status): void { $controller = $this->getController([ 'savePushToken', ]); $user = $this->createMock(IUser::class); if ($userIsValid) { $this->userSession->expects($this->any()) ->method('getUser') ->willReturn($user); } else { $this->userSession->expects($this->any()) ->method('getUser') ->willReturn(null); } $this->session->expects($tokenId > 0 ? $this->once() : $this->never()) ->method('get') ->with('token-id') ->willReturn($tokenId); if ($tokenIsValid) { $token = $this->createMock(IToken::class); $token->expects($this->once()) ->method('getId') ->willReturn($tokenId); $this->tokenProvider->expects($this->any()) ->method('getTokenById') ->with($tokenId) ->willReturn($token); $key = $this->createMock(Key::class); $key->expects($this->once()) ->method('getPrivate') ->willReturn(self::$userPrivateKey); $key->expects($this->once()) ->method('getPublic') ->willReturn(self::$userPublicKey); $this->identityProof->expects($this->once()) ->method('getKey') ->with($user) ->willReturn($key); $controller->expects($this->once()) ->method('savePushToken') ->with($user, $token, $this->anything(), $devicePublicKey, $pushTokenHash, $proxyServer) ->willReturn($deviceCreated); } else { $controller->expects($this->never()) ->method('savePushToken'); $this->tokenProvider->expects($this->any()) ->method('getTokenById') ->with($tokenId) ->willThrowException(new InvalidTokenException()); } $this->hostValidator->expects($this->any()) ->method('isValid') ->willReturn(true); $response = $controller->registerDevice($pushTokenHash, $devicePublicKey, $proxyServer); $this->assertInstanceOf(DataResponse::class, $response); $this->assertSame($status, $response->getStatus()); $this->assertSame($payload, $response->getData()); } public static function dataRemoveDevice(): array { return [ 'not authenticated' => [ false, 0, false, null, [], Http::STATUS_UNAUTHORIZED ], 'invalid token' => [ true, 23, false, null, ['message' => 'INVALID_SESSION_TOKEN'], Http::STATUS_BAD_REQUEST, ], 'using production' => [ true, 23, false, null, ['message' => 'INVALID_SESSION_TOKEN'], Http::STATUS_BAD_REQUEST, ], 'created or updated' => [ true, 23, true, true, [], Http::STATUS_ACCEPTED, ], 'not updated' => [ true, 42, true, false, [], Http::STATUS_OK, ], ]; } #[DataProvider(methodName: 'dataRemoveDevice')] public function testRemoveDevice(bool $userIsValid, int $tokenId, bool $tokenIsValid, ?bool $deviceDeleted, array $payload, int $status): void { $controller = $this->getController([ 'deletePushToken', ]); $user = $this->createMock(IUser::class); if ($userIsValid) { $this->userSession->expects($this->any()) ->method('getUser') ->willReturn($user); } else { $this->userSession->expects($this->any()) ->method('getUser') ->willReturn(null); } $this->session->expects($tokenId > 0 ? $this->once() : $this->never()) ->method('get') ->with('token-id') ->willReturn($tokenId); if ($tokenIsValid) { $token = $this->createMock(IToken::class); $this->tokenProvider->expects($this->any()) ->method('getTokenById') ->with($tokenId) ->willReturn($token); $controller->expects($this->once()) ->method('deletePushToken') ->with($user, $token) ->willReturn($deviceDeleted); } else { $controller->expects($this->never()) ->method('deletePushToken'); $this->tokenProvider->expects($this->any()) ->method('getTokenById') ->with($tokenId) ->willThrowException(new InvalidTokenException()); } $response = $controller->removeDevice(); $this->assertInstanceOf(DataResponse::class, $response); $this->assertSame($status, $response->getStatus()); $this->assertSame($payload, $response->getData()); } }